telemetry-dev 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
telemetry_dev/otel.py ADDED
@@ -0,0 +1,186 @@
1
+ """Bring-your-own-OpenTelemetry surface for telemetry.dev.
2
+
3
+ Attach ``TelemetrySpanProcessor`` to your own ``TracerProvider`` (OTel SDK,
4
+ opentelemetry-distro, ...) to ship its spans to telemetry.dev without calling
5
+ ``telemetry_dev.init()``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Callable, Sequence
11
+
12
+ from opentelemetry.context import Context
13
+ from opentelemetry.exporter.otlp.proto.http import Compression
14
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
15
+ from opentelemetry.sdk.metrics import Histogram, MeterProvider
16
+ from opentelemetry.sdk.metrics.export import (
17
+ AggregationTemporality,
18
+ PeriodicExportingMetricReader,
19
+ )
20
+ from opentelemetry.sdk.resources import Resource
21
+ from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
22
+ from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
23
+
24
+ from ._config import SDK_VERSION, logger, report_error, resolve_config
25
+ from ._metrics import GuardedOTLPMetricExporter, MetricsRecorder
26
+ from ._processor import ExportMode, StampingSpanProcessor
27
+ from ._semconv import SCOPE_NAME
28
+
29
+ __all__ = ["ExportMode", "TelemetrySpanProcessor", "create_telemetry_span_exporter"]
30
+
31
+ _EXPORTER_TIMEOUT_S = 10.0
32
+ _BATCHED_METRIC_INTERVAL_MILLIS = 60_000
33
+ _DORMANT_METRIC_INTERVAL_MILLIS = 2**31 - 1
34
+
35
+
36
+ class _NoOpSpanExporter(SpanExporter):
37
+ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
38
+ return SpanExportResult.SUCCESS
39
+
40
+ def shutdown(self) -> None:
41
+ pass
42
+
43
+ def force_flush(self, timeout_millis: int = 30000) -> bool:
44
+ return True
45
+
46
+
47
+ def create_telemetry_span_exporter(
48
+ *,
49
+ api_key: str | None = None,
50
+ base_url: str | None = None,
51
+ timeout: float = _EXPORTER_TIMEOUT_S,
52
+ ) -> SpanExporter:
53
+ """Build a telemetry.dev OTLP span exporter, or a no-op exporter without an API key."""
54
+ config = resolve_config(api_key=api_key, base_url=base_url)
55
+ if config.api_key is None:
56
+ logger.debug(
57
+ "telemetry-dev: no API key (api_key option or TELEMETRY_DEV_API_KEY); "
58
+ "span exporter is a no-op"
59
+ )
60
+ return _NoOpSpanExporter()
61
+ return OTLPSpanExporter(
62
+ endpoint=f"{config.base_url}/v1/traces",
63
+ headers={"Authorization": f"Bearer {config.api_key}"},
64
+ compression=Compression.Gzip,
65
+ timeout=timeout,
66
+ )
67
+
68
+
69
+ class TelemetrySpanProcessor(SpanProcessor):
70
+ """Span processor for BYO OpenTelemetry SDK setups.
71
+
72
+ Builds telemetry.dev exporters when an API key is configured; otherwise no-ops unless
73
+ ``span_exporter`` is supplied.
74
+ """
75
+
76
+ def __init__(
77
+ self,
78
+ span_exporter: SpanExporter | None = None,
79
+ *,
80
+ api_key: str | None = None,
81
+ base_url: str | None = None,
82
+ export_mode: ExportMode = "batched",
83
+ max_export_batch_size: int = 64,
84
+ schedule_delay_millis: float = 1000,
85
+ max_queue_size: int = 2048,
86
+ export_timeout_millis: float = 30000,
87
+ span_filter: Callable[[ReadableSpan], bool] | None = None,
88
+ metrics: bool = True,
89
+ service_name: str | None = None,
90
+ environment: str | None = None,
91
+ metrics_recorder: MetricsRecorder | None = None,
92
+ on_error: Callable[[BaseException], None] | None = None,
93
+ ) -> None:
94
+ self._inner: StampingSpanProcessor | None = None
95
+ self._meter_provider: MeterProvider | None = None
96
+ self._on_error = on_error
97
+ config = resolve_config(
98
+ api_key=api_key,
99
+ base_url=base_url,
100
+ environment=environment,
101
+ service_name=service_name,
102
+ )
103
+
104
+ if span_exporter is None:
105
+ span_exporter = create_telemetry_span_exporter(
106
+ api_key=config.api_key,
107
+ base_url=config.base_url,
108
+ )
109
+ if isinstance(span_exporter, _NoOpSpanExporter):
110
+ return
111
+
112
+ if metrics_recorder is None and metrics and config.api_key is not None:
113
+ resource = Resource.create(
114
+ {
115
+ "service.name": config.service_name,
116
+ "deployment.environment.name": config.environment,
117
+ }
118
+ )
119
+ metric_exporter = GuardedOTLPMetricExporter(
120
+ endpoint=f"{config.base_url}/v1/metrics",
121
+ headers={"Authorization": f"Bearer {config.api_key}"},
122
+ compression=Compression.Gzip,
123
+ timeout=_EXPORTER_TIMEOUT_S,
124
+ preferred_temporality={Histogram: AggregationTemporality.DELTA},
125
+ )
126
+ reader = PeriodicExportingMetricReader(
127
+ metric_exporter,
128
+ export_interval_millis=(
129
+ _BATCHED_METRIC_INTERVAL_MILLIS
130
+ if export_mode == "batched"
131
+ else _DORMANT_METRIC_INTERVAL_MILLIS
132
+ ),
133
+ )
134
+ self._meter_provider = MeterProvider(
135
+ metric_readers=[reader], resource=resource, shutdown_on_exit=False
136
+ )
137
+ meter = self._meter_provider.get_meter(SCOPE_NAME, SDK_VERSION)
138
+ metrics_recorder = MetricsRecorder(meter, on_error=on_error)
139
+
140
+ self._inner = StampingSpanProcessor(
141
+ span_exporter,
142
+ export_mode=export_mode,
143
+ max_export_batch_size=max_export_batch_size,
144
+ schedule_delay_millis=schedule_delay_millis,
145
+ max_queue_size=max_queue_size,
146
+ export_timeout_millis=export_timeout_millis,
147
+ span_filter=span_filter,
148
+ metrics_recorder=metrics_recorder,
149
+ on_error=on_error,
150
+ )
151
+
152
+ def on_start(self, span: Span, parent_context: Context | None = None) -> None:
153
+ if self._inner is not None:
154
+ self._inner.on_start(span, parent_context)
155
+
156
+ def on_end(self, span: ReadableSpan) -> None:
157
+ if self._inner is not None:
158
+ self._inner.on_end(span)
159
+
160
+ def shutdown(self) -> None:
161
+ if self._inner is not None:
162
+ try:
163
+ self._inner.shutdown()
164
+ except BaseException as exc:
165
+ report_error(self._on_error, "span processor shutdown failed", exc)
166
+ if self._meter_provider is not None:
167
+ try:
168
+ self._meter_provider.shutdown(timeout_millis=30_000)
169
+ except BaseException as exc:
170
+ report_error(self._on_error, "metric provider shutdown failed", exc)
171
+
172
+ def force_flush(self, timeout_millis: int = 30000) -> bool:
173
+ ok = True
174
+ if self._inner is not None:
175
+ try:
176
+ ok = self._inner.force_flush(timeout_millis)
177
+ except BaseException as exc:
178
+ report_error(self._on_error, "span processor force_flush failed", exc)
179
+ ok = False
180
+ if self._meter_provider is not None:
181
+ try:
182
+ ok = self._meter_provider.force_flush(timeout_millis=timeout_millis) and ok
183
+ except BaseException as exc:
184
+ report_error(self._on_error, "metric provider force_flush failed", exc)
185
+ ok = False
186
+ return ok
telemetry_dev/py.typed ADDED
File without changes
@@ -0,0 +1,235 @@
1
+ Metadata-Version: 2.4
2
+ Name: telemetry-dev
3
+ Version: 0.1.0
4
+ Summary: telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and metrics
5
+ Keywords: telemetry,opentelemetry,llm,genai,tracing,observability
6
+ Author: telemetry.dev
7
+ License-Expression: MIT
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Typing :: Typed
16
+ Requires-Dist: opentelemetry-api>=1.35.0,<2
17
+ Requires-Dist: opentelemetry-sdk>=1.35.0,<2
18
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.35.0,<2
19
+ Requires-Python: >=3.10
20
+ Project-URL: Homepage, https://telemetry.dev
21
+ Project-URL: Repository, https://github.com/telemetry-dev/telemetry.dev
22
+ Description-Content-Type: text/markdown
23
+
24
+ # telemetry-dev
25
+
26
+ telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and metrics. Thin
27
+ ergonomic functions over OTel spans (`gen_ai.*` semantic conventions), exported as OTLP
28
+ protobuf to the telemetry.dev ingest.
29
+
30
+ ## Install
31
+
32
+ ```sh
33
+ pip install telemetry-dev
34
+ # or
35
+ uv add telemetry-dev
36
+ ```
37
+
38
+ Requires Python >= 3.10.
39
+
40
+ ## Quickstart
41
+
42
+ ```python
43
+ import telemetry_dev
44
+ from telemetry_dev import log, observe, propagate_attributes, start_span, update_current_span
45
+
46
+ telemetry_dev.init() # reads TELEMETRY_DEV_API_KEY from the environment
47
+
48
+ @observe # arguments -> input, return value -> output, errors captured + re-raised
49
+ def lookup_weather(city: str) -> dict:
50
+ return {"forecast": "sunny"}
51
+
52
+ with propagate_attributes(user_id="user_123", session_id="session_456"):
53
+ with start_span(
54
+ "chat gpt-4o",
55
+ type="generation",
56
+ model="gpt-4o",
57
+ provider="openai",
58
+ input=[{"role": "user", "content": "Plan a day trip"}],
59
+ ):
60
+ log("calling the model")
61
+ update_current_span(
62
+ output=[{"role": "assistant", "content": "Here you go..."}],
63
+ usage={"input_tokens": 11, "output_tokens": 7},
64
+ finish_reason="stop",
65
+ )
66
+
67
+ lookup_weather("Kyoto")
68
+
69
+ telemetry_dev.flush()
70
+ ```
71
+
72
+ The SDK fails open: without an API key every call is a silent no-op, and internal errors are
73
+ routed to the `on_error` hook / `telemetry_dev` logger — never raised into your code.
74
+
75
+ ## Environment variables
76
+
77
+ | Variable | Default | Purpose |
78
+ | --- | --- | --- |
79
+ | `TELEMETRY_DEV_API_KEY` | — | Ingest key (`td_live_...`). Absent = SDK is a no-op. |
80
+ | `TELEMETRY_DEV_BASE_URL` | `https://ingest.telemetry.dev` | Ingest base URL (trailing slashes stripped). |
81
+ | `TELEMETRY_DEV_ENVIRONMENT` | `production` | Deployment environment label. |
82
+ | `OTEL_SERVICE_NAME` | `unknown_service` | Service name on every trace. |
83
+
84
+ Explicit `init()` arguments take precedence over environment variables.
85
+
86
+ ## API reference
87
+
88
+ | Name | Description |
89
+ | --- | --- |
90
+ | `init(**options) -> Client` | Initialize the SDK (see options below). Calling again replaces the previous client. |
91
+ | `@observe` / `@observe(name=, type=, capture_input=, capture_output=, attributes=)` | Wrap a sync/async function (or generator) in a span. Arguments become `input` (param-name dict, `self`/`cls` dropped), the return value becomes `output`, exceptions are captured and re-raised. |
92
+ | `start_span(name, *, type="span", ...) -> SpanHandle` | Start a span. `with` activates it in the current context; without `with` it is a detached handle you must `.end()`. |
93
+ | `SpanHandle.update(**fields)` / `.end(**fields, end_time=)` / `.traceparent()` | Update attributes, end (accepts the full update field set), or read the W3C traceparent. |
94
+ | `update_current_span(**fields)` | Apply the update field set to the currently active span (no-op without one). |
95
+ | `propagate_attributes(*, user_id=, session_id=, metadata=)` | Context manager stamping `user.id` / `gen_ai.conversation.id` / `td.metadata.*` on every span and log record started inside (threads/asyncio included via contextvars). |
96
+ | `log(message, *, level="info", event_name=None, attributes=None)` | Emit an OTLP log record to `/v1/logs`, correlated with the current trace. Levels: `debug`/`info`/`warn`/`error` (`"warning"` is accepted as an alias of `warn`). |
97
+ | `get_traceparent() -> str \| None` | W3C traceparent of the current context. |
98
+ | `flush(timeout_s=10.0)` / `shutdown(timeout_s=10.0)` | Force-flush / tear down traces + logs + metrics. Shutdown also runs atexit unless `disable_atexit=True`. |
99
+ | `TelemetrySpanProcessor` / `telemetry_dev.otel.create_telemetry_span_exporter` | Bring-your-own-OTel helpers (below). |
100
+ | `MaskContext`, `Usage`, `SpanHandle`, `Client`, `NOT_GIVEN` | Supporting types. |
101
+
102
+ ### Span types
103
+
104
+ `type=` maps to `gen_ai.operation.name`:
105
+
106
+ | `type` | operation | input / output attributes |
107
+ | --- | --- | --- |
108
+ | `"span"` (default) | `function` | `gen_ai.input.messages` / `gen_ai.output.messages` |
109
+ | `"generation"` | `chat` | `gen_ai.input.messages` / `gen_ai.output.messages` |
110
+ | `"tool"` | `execute_tool` | `gen_ai.tool.call.arguments` / `gen_ai.tool.call.result` |
111
+ | `"agent"` | `invoke_agent` | `gen_ai.input.messages` / `gen_ai.output.messages` |
112
+ | `"embedding"` | `embeddings` | `gen_ai.input.messages` / `gen_ai.output.messages` |
113
+
114
+ ### Span fields (start/update/end)
115
+
116
+ `input`, `output`, `model`, `provider`, `system_instructions`, `response_model`, `response_id`,
117
+ `output_type`, `finish_reason`, `usage` (dict with exactly `input_tokens`, `output_tokens`,
118
+ `total_tokens`, `cache_read_input_tokens`, `cache_creation_input_tokens`,
119
+ `reasoning_output_tokens`), `cost_usd`, `temperature`, `top_p`, `top_k`, `max_tokens`,
120
+ `stop_sequences`, `seed`, `frequency_penalty`, `presence_penalty`, `time_to_first_chunk_ms`,
121
+ `tool_name`, `tool_call_id`, `tool_description`, `agent_name`, `agent_id`, `metadata`
122
+ (→ `td.metadata.*`, this span only), `attributes` (raw escape hatch, merged last), `error`.
123
+ `start_span` additionally accepts `parent` (traceparent string, OTel `Context`, or
124
+ `SpanContext`), `start_time`, and per-call `capture_input` / `capture_output` overrides;
125
+ `end()` additionally accepts `end_time`.
126
+
127
+ ### init() options
128
+
129
+ | Option | Default | Purpose |
130
+ | --- | --- | --- |
131
+ | `api_key`, `base_url`, `environment`, `service_name` | env vars | Connection + resource settings. |
132
+ | `enabled` | `True` | `False` = hard kill switch (tests). |
133
+ | `register_global` | `False` | Also register the tracer provider globally. Applies a default export filter (only `telemetry_dev`-scoped spans are exported); pass `span_filter=lambda s: True` to export everything. |
134
+ | `export_mode` | `"batched"` | `"immediate"` exports synchronously per span/log (serverless). |
135
+ | `log_level` | `"warn"` | SDK diagnostics level: `debug`/`info`/`warn`/`error`/`silent`. |
136
+ | `capture_input`, `capture_output` | `True` | Global content-capture defaults. |
137
+ | `mask` | `None` | `Callable[[Any, MaskContext], Any]` redaction hook, runs before JSON serialization on input/output/log messages (`MaskContext.key` is the attribute being written). Not applied to correlation identifiers. |
138
+ | `max_attribute_length` | `65536` | Per-content-attribute cap; truncated values get an ASCII `...[truncated]` marker appended. |
139
+ | `span_filter` | `None` | Export predicate `Callable[[ReadableSpan], bool]`. |
140
+ | `on_error` | `None` | Receives every internal SDK error; the SDK never raises. |
141
+ | `disable_atexit` | `False` | Skip the automatic atexit shutdown. |
142
+ | `timeout` | `10.0` | OTLP HTTP timeout in seconds. |
143
+ | `span_exporter`, `log_exporter`, `metric_reader` | `None` | Test seams / offline mode; any of them enables the client without an API key. |
144
+
145
+ ## Auto-metrics
146
+
147
+ Ended spans automatically record two histograms (DELTA temporality, exported every 60s):
148
+
149
+ - `gen_ai.client.operation.duration` (unit `s`) for `chat`, `invoke_agent`, `embeddings`,
150
+ `execute_tool`
151
+ - `gen_ai.client.token.usage` (unit `{token}`, attribute `gen_ai.token.type=input|output`) for
152
+ `chat`, `invoke_agent`, `embeddings`
153
+
154
+ Plain spans (`function`) record no metrics. Quiet intervals produce zero metric requests.
155
+
156
+ ## Serverless
157
+
158
+ Use `export_mode="immediate"` and/or call `telemetry_dev.flush()` before the runtime freezes:
159
+
160
+ ```python
161
+ telemetry_dev.init(export_mode="immediate")
162
+ ...
163
+ telemetry_dev.flush() # force-flush traces + logs + metrics
164
+ ```
165
+
166
+ ## Bring your own OTel (`telemetry_dev.otel`)
167
+
168
+ If you already run an OpenTelemetry SDK, attach the telemetry.dev processor to your provider
169
+ instead of calling `init()`:
170
+
171
+ ```python
172
+ from telemetry_dev.otel import TelemetrySpanProcessor
173
+
174
+ your_tracer_provider.add_span_processor(TelemetrySpanProcessor()) # reads TELEMETRY_DEV_API_KEY
175
+ ```
176
+
177
+ If you need to wire your own span processor stack, create the telemetry.dev exporter directly:
178
+
179
+ ```python
180
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
181
+ from telemetry_dev.otel import create_telemetry_span_exporter
182
+
183
+ span_exporter = create_telemetry_span_exporter()
184
+ your_tracer_provider.add_span_processor(BatchSpanProcessor(span_exporter))
185
+ ```
186
+
187
+ `TelemetrySpanProcessor` remains available from the package root for compatibility:
188
+
189
+ ```python
190
+ from telemetry_dev import TelemetrySpanProcessor
191
+ ```
192
+
193
+ | Option | Default | Purpose |
194
+ | --- | --- | --- |
195
+ | `api_key` | `TELEMETRY_DEV_API_KEY` | Ingest key. Absent with no `span_exporter` = inert no-op. |
196
+ | `base_url` | `TELEMETRY_DEV_BASE_URL` or `https://ingest.telemetry.dev` | Ingest base URL; trailing slashes are stripped. |
197
+ | `export_mode` | `"batched"` | `"batched"` or `"immediate"` span export. |
198
+ | `max_export_batch_size` | `64` | BatchSpanProcessor export batch size. |
199
+ | `schedule_delay_millis` | `1000` | BatchSpanProcessor schedule delay. |
200
+ | `max_queue_size` | `2048` | BatchSpanProcessor queue size. |
201
+ | `export_timeout_millis` | `30000` | BatchSpanProcessor export timeout. |
202
+ | `span_filter` | `None` | Export predicate `Callable[[ReadableSpan], bool]`; failures export the span. |
203
+ | `metrics` | `True` | Auto-record GenAI duration/token histograms for exported spans when an API key is available. |
204
+ | `service_name` | `OTEL_SERVICE_NAME` or `unknown_service` | `service.name` on the metrics resource. |
205
+ | `environment` | `TELEMETRY_DEV_ENVIRONMENT` or `production` | `deployment.environment.name` on the metrics resource. |
206
+ | `on_error` | `None` | Receives internal processor errors; errors are never raised into your code. |
207
+ | `span_exporter` | `None` | Advanced/test seam replacing the telemetry.dev OTLP trace exporter. |
208
+
209
+ Without an API key and without `span_exporter`, `TelemetrySpanProcessor()` is an inert no-op:
210
+ safe to attach unconditionally, with only a debug log. Auto-metrics are on by default for exported
211
+ GenAI spans, use the `service_name` / `environment` resource settings above, and are skipped
212
+ without an API key.
213
+
214
+ ## Limitations
215
+
216
+ - `register_global=True` cannot be undone on `shutdown()`: OpenTelemetry Python has no public
217
+ API to unregister a global `TracerProvider`, so a later `init(register_global=True)` in the
218
+ same process cannot reclaim the global slot. Prefer the isolated default (or the BYO
219
+ processor) for processes that re-initialize.
220
+
221
+ ## Development
222
+
223
+ Uses [uv](https://docs.astral.sh/uv/):
224
+
225
+ ```sh
226
+ uv sync # install (writes uv.lock)
227
+ uv run pytest # tests
228
+ uv run ruff format . # format
229
+ uv run ruff check . # lint
230
+ uv run pyright # type-check
231
+ uv build # build wheel + sdist
232
+ ```
233
+
234
+ `examples/quickstart.py` is runnable against a real ingest:
235
+ `TELEMETRY_DEV_API_KEY=td_live_... uv run examples/quickstart.py`.
@@ -0,0 +1,16 @@
1
+ telemetry_dev/__init__.py,sha256=JGFuWI0jUkefVN3rK9e3WP_NxvpcZnXkCTlqsmgfPls,1376
2
+ telemetry_dev/_client.py,sha256=Af--G2mutbHlyiWnH_WuXmawlCVwm8E1AJzDuOAjJ3w,14317
3
+ telemetry_dev/_config.py,sha256=sRpZ_teq80TjNtkwFrgipGGnyPFHZg6hrb634PZaLlw,2341
4
+ telemetry_dev/_context.py,sha256=pqbWF9khWZMLc3UnEe1fl2O4I6TThAl_HoWgx1d0M4E,3595
5
+ telemetry_dev/_logs.py,sha256=ZHGZ155l7iMEQwVziBFbp4Mu8k_dTte2sl5f8IQvQmY,2437
6
+ telemetry_dev/_metrics.py,sha256=IBicRcLB4hUi0RGRaoNX88CHgBx-Dr6FCVK2reiVGwo,4713
7
+ telemetry_dev/_observe.py,sha256=gN1P3EgcUeXvG-xZfE6BL78o42106CwqCHq2Ot9B6TM,4824
8
+ telemetry_dev/_processor.py,sha256=yZpF7tONVLU7jDLOI-CFi8TGGo8y3A9oOeh2FPufDFU,2893
9
+ telemetry_dev/_semconv.py,sha256=nEav6No-jkJbqKSmSvnoidzU47EIBH_nK28ZnBbuQbs,3576
10
+ telemetry_dev/_serialize.py,sha256=wIUdzeqKrTcKv6c1nw86b9s_UNdJHOf7MmKJfIFP028,3504
11
+ telemetry_dev/_spans.py,sha256=z2__yb9mZmz3ypPq1hUjariZGuC8oeJHuKC0mKrP2Fw,23079
12
+ telemetry_dev/otel.py,sha256=nxFyDqboIYogwYxKSR_JVPrGfyr_6RfVXOfaWws6X6Q,7109
13
+ telemetry_dev/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ telemetry_dev-0.1.0.dist-info/WHEEL,sha256=bu7Cckf7DKpj8ztfOQHBiWtXM4xigujiTkrhvV6U2aU,81
15
+ telemetry_dev-0.1.0.dist-info/METADATA,sha256=oJ8jurkZjRClG0gnRJiclNp_1nmEYgDnH2Kzxy_aZcA,11585
16
+ telemetry_dev-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.11.27
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any