dt-extensions-models
Helper library implementing best practices for strongly typed models and reporting metrics.
Installation
pip install dt-extensions-models
Usage
Use just like you would use Pydantic models in general. However, there are a couple of new additions.
Example
Raw data
Imagine a response from an JSON REST API coming in like this:
{
"name": "my_tunnel",
"outgoing_bytes": 5,
"proxyid": [
{
"p2name": "my_proxy",
"status": "UP",
"incoming_bytes": 3,
"outgoing_bytes": 2
}
]
}
Parsing requirements
and we want to parse it and report 5 metrics and 2 events:
Metrics:
- Counter
fortigate.tunnel.bytes.in.countfor incoming traffic to the tunnel, if any. - Counter
fortigate.tunnel.bytes.out.countfor outgoing traffic from the tunnel, if any. - Counter
fortigate.tunnel.proxy.bytes.in.countfor incoming traffic for each of the proxies within the tunnel. - Counter
fortigate.tunnel.proxy.bytes.out.countfor outgoing traffic for each of the proxies within the tunnel. - Gauge
fortigate.tunnel.proxy.statusto reflect the overall status of each proxy.
Additionally, we want each proxy metric to have 3 dimensions:
status- status of this proxy. "UP" if it's "up" or "UP" and "DOWN" for everything else.proxy- value of thep2namefield.tunnel- name of the tunnel this proxy belongs to.
And for each tunnel we also want a tunnel dimension with the tunnel's name.
Events:
- Info event
Small outgoing traffic!if outgoing traffic on tunnel is less than 80 bytes. - Custom alert event
Proxy my_proxy is slow!if the proxy is up but its outgoing traffic is less than 10 bytes.
AND we want our code to complain or throw exceptions if the incoming data is invalid!
Imagine the sheer amount of code and validation required to implement all of the requirements above!
Implementation
Here is how you could define a nested model that can be evaluated both for metrics and events in just about 80 lines. The example intentionally uses as much variation as possible to demonstrate the flexibility of the library.
from dynatrace_extension_models import Field, MetricInfo, EventInfo, IngestBase, MetricType, DtEventType
class TunnelProxy(IngestBase):
p2name: str = Field(...)
status: str = Field("unknown")
incoming_bytes: int | None = Field(None, title="incoming_bytes")
outgoing_bytes: int | None = Field(None, title="outgoing_bytes")
def properties(self) -> dict:
return {
"proxy": self.p2name,
"status": self.status.upper(),
"tunnel": getattr(self._parent, "name"),
}
def status_to_metric(self) -> float:
return 1 if self.status.upper() == "UP" else 0
def proxy_is_slow_title(self) -> str:
return f"Proxy {self.p2name} is slow!"
_metrics = [
MetricInfo(
key="fortigate.tunnel.proxy.status",
properties=properties,
value=status_to_metric,
),
MetricInfo(
key="fortigate.tunnel.proxy.bytes.in.count",
type=MetricType.COUNT,
properties=properties,
value=incoming_bytes,
),
MetricInfo(
key="fortigate.tunnel.proxy.bytes.out.count",
type=MetricType.COUNT,
properties=properties,
value=outgoing_bytes,
)
]
_events = [
EventInfo(
title=proxy_is_slow_title,
properties=properties,
type=DtEventType.CUSTOM_ALERT,
when=lambda v: v.status.upper() == "UP" and v.outgoing_bytes < 10,
)
]
class Tunnel(IngestBase):
name: str = Field(...)
incoming_bytes: int | None = Field(None, title="incoming_bytes")
outgoing_bytes: int | None = Field(None, title="outgoing_bytes")
proxyid: list[TunnelProxy] | None = Field(None)
def not_enough_traffic(self) -> bool:
return self.outgoing_bytes < 80
def current_incoming_bytes(self) -> int | None:
return self.incoming_bytes
_metrics = [
MetricInfo(
key="fortigate.tunnel.bytes.in.count",
type=MetricType.COUNT,
properties={"tunnel": "{name}"},
value=current_incoming_bytes,
),
MetricInfo(
key="fortigate.tunnel.bytes.out.count",
type=MetricType.COUNT,
properties={"tunnel": "{name}"},
value=outgoing_bytes,
)
]
_events = [
EventInfo(
title="Small outgoing traffic!",
type=DtEventType.CUSTOM_INFO,
when=not_enough_traffic,
)
]
Results
If you initialize the Tunnel model instance with the JSON above and convert it to metrics and events, here is what you would get.
import requests
data = requests.get("https://some-api.com").json()
tunnel = Tunnel(**data)
print(tunnel.mint_lines)
print(tunnel.event_dicts)
output:
fortigate.tunnel.bytes.out.count,tunnel="my_tunnel" count,5 1719449431472
fortigate.tunnel.proxy.bytes.in.count,proxy="my_proxy",status="UP",tunnel="my_tunnel" count,3 1719449431472
fortigate.tunnel.proxy.bytes.out.count,proxy="my_proxy",status="UP",tunnel="my_tunnel" count,2 1719449431472
fortigate.tunnel.proxy.status,proxy="my_proxy",status="UP",tunnel="my_tunnel" gauge,1 1719449431472
{'title': 'Proxy my_proxy is slow!', 'event_type': <DtEventType.CUSTOM_ALERT: 'CUSTOM_ALERT'>, 'timeout': 15, 'properties': {'proxy': 'my_proxy', 'status': 'UP', 'tunnel': 'my_tunnel'}}
{'title': 'Small outgoing traffic!', 'event_type': <DtEventType.CUSTOM_INFO: 'CUSTOM_INFO'>, 'timeout': 15}
Ingesting metrics and sending events in Python EF2 framework
Continuing the code above, inside your Python EF2 Dynatrace extension you would do the following:
import requests
from dynatrace_extension import Extension, Status, StatusValue
class ExtensionImpl(Extension):
def query(self) -> None:
data = requests.get("https://some-api.com").json()
tunnel = Tunnel(**data)
# Ingest metrics
self.report_mint_lines(tunnel.mint_lines)
# Ingest events
for event in tunnel.event_dicts:
self.report_dt_event_dict(event)
🤩 Beautiful!
Metadata
Release files for dt-extensions-models 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| dt_extensions_models-0.2.0.tar.gz | 14.9 kB | Without provenance | Details for dt_extensions_models-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| dt_extensions_models-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for dt_extensions_models-0.2.0-py3-none-any.whl |
Total release size: 29.2 kB
Release files / dt_extensions_models-0.2.0.tar.gz
| Download URL | dt_extensions_models-0.2.0.tar.gz |
|---|---|
| Size | 14.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fbdd25f8d033ec5c191c11c2d6496ed2bfbdd92bb9570f33810a866d77fe8290
|
|
BLAKE2b-256 checksum How to use checksums |
41de865a8c0417ba879f0859d16df2265ea67f1730297c67664c97afd2de0d9a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/1.8.2 CPython/3.12.3 Darwin/23.5.0
|
Release files / dt_extensions_models-0.2.0-py3-none-any.whl
| Download URL | dt_extensions_models-0.2.0-py3-none-any.whl |
|---|---|
| Size | 14.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
88e1251018a4df62d50437288210534b807439c9daaa4d5e22092d0229498526
|
|
BLAKE2b-256 checksum How to use checksums |
15b2595de68981a3872c94ea9454f2acdd24f1e74a422733a5d085bca941e894
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/1.8.2 CPython/3.12.3 Darwin/23.5.0
|
Security for dt-extensions-models 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.