otlp-json 0.9.1__py3-none-any.whl → 0.9.2__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.
@@ -1,12 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
- from typing import Any, Sequence, TYPE_CHECKING
4
+ from collections.abc import Mapping, Sequence
5
+ from typing import Any, TYPE_CHECKING
5
6
 
6
7
  if TYPE_CHECKING:
7
8
  from typing_extensions import TypeAlias
8
9
 
9
- from opentelemetry.sdk.trace import ReadableSpan
10
+ from opentelemetry.sdk.trace import ReadableSpan, Event
10
11
  from opentelemetry.sdk.resources import Resource
11
12
  from opentelemetry.sdk.util.instrumentation import InstrumentationScope
12
13
  from opentelemetry.trace.status import Status
@@ -18,6 +19,24 @@ if TYPE_CHECKING:
18
19
  CONTENT_TYPE = "application/json"
19
20
 
20
21
 
22
+ _VALUE_TYPES = {
23
+ # NOTE: order matters, for isinstance(True, int).
24
+ bool: ("boolValue", bool),
25
+ int: ("intValue", str),
26
+ float: ("doubleValue", float),
27
+ bytes: ("bytesValue", bytes),
28
+ str: ("stringValue", str),
29
+ Sequence: (
30
+ "arrayValue",
31
+ lambda value: {"values": [_value(e) for e in _homogeneous_array(value)]},
32
+ ),
33
+ Mapping: (
34
+ "kvlistValue",
35
+ lambda value: {"values": [{k: _value(v) for k, v in value.items()}]},
36
+ ),
37
+ }
38
+
39
+
21
40
  def encode_spans(spans: Sequence[ReadableSpan]) -> bytes:
22
41
  spans = sorted(spans, key=lambda s: (id(s.resource), id(s.instrumentation_scope)))
23
42
  rv = {"resourceSpans": []}
@@ -47,36 +66,46 @@ def encode_spans(spans: Sequence[ReadableSpan]) -> bytes:
47
66
 
48
67
 
49
68
  def _resource(resource: Resource):
50
- return {
51
- "attributes": [
52
- {"key": k, "value": _value(v)} for k, v in resource.attributes.items()
53
- ]
54
- }
69
+ rv = {"attributes": []}
70
+ for k, v in resource.attributes.items():
71
+ try:
72
+ rv["attributes"].append({"key": k, "value": _value(v)})
73
+ except ValueError:
74
+ pass
75
+
76
+ # NOTE: blocks that contain droppedAttributesCount:
77
+ # - Event
78
+ # - Link
79
+ # - InstrumentationScope
80
+ # - LogRecord (out of scope for this library)
81
+ # - Resource
82
+ rv["dropped_attributes_count"] = len(resource.attributes) - len(rv["attributes"]) # type: ignore
83
+
84
+ if not rv["attributes"]:
85
+ del rv["attributes"]
86
+
87
+ if not rv["dropped_attributes_count"]:
88
+ del rv["dropped_attributes_count"]
89
+
90
+ return rv
91
+
92
+
93
+ def _homogeneous_array(value: list[_LEAF_VALUE]) -> list[_LEAF_VALUE]:
94
+ # TODO: empty lists are allowed, aren't they?
95
+ if len(types := {type(v) for v in value}) > 1:
96
+ raise ValueError(f"Attribute value arrays must be homogeneous, got {types=}")
97
+ return value
55
98
 
56
99
 
57
100
  def _value(value: _VALUE) -> dict[str, Any]:
58
101
  # Attribute value can be a primitive type, excluging None...
59
102
  # TODO: protobuf allows bytes, but I think OTLP doesn't.
60
103
  # TODO: protobuf allows k:v pairs, but I think OTLP doesn't.
61
- if isinstance(value, (str, int, float, bool)):
62
- k = {
63
- # TODO: move these to module level
64
- str: "stringValue",
65
- int: "intValue",
66
- float: "floatValue",
67
- bool: "boolValue",
68
- }[type(value)]
69
- return {k: value}
70
-
71
- # Or a homogenous array of a primitive type, excluding None.
72
- value = list(value)
73
-
74
- # TODO: empty lists are allowed, aren't they?
75
- if len({type(v) for v in value}) > 1:
76
- raise ValueError(f"Attribute value arrays must be homogenous, got {value}")
104
+ for klass, (key, post) in _VALUE_TYPES.items():
105
+ if isinstance(value, klass):
106
+ return {key: post(value)}
77
107
 
78
- # TODO: maybe prevent recursion, OTEL doesn't allow lists of lists
79
- return {"arrayValue": [_value(e) for e in value]}
108
+ raise ValueError(f"Cannot convert attribute of {type(value)=}")
80
109
 
81
110
 
82
111
  def _scope(scope: InstrumentationScope):
@@ -93,13 +122,33 @@ def _span(span: ReadableSpan):
93
122
  "kind": span.kind.value or 1, # unspecified -> internal
94
123
  "traceId": _trace_id(span.context.trace_id),
95
124
  "spanId": _span_id(span.context.span_id),
96
- "flags": 0x100 | ([0, 0x200][bool(span.parent)]),
125
+ "flags": 0x100 | ([0, 0x200][bool(span.parent and span.parent.is_remote)]),
97
126
  "startTimeUnixNano": str(span.start_time), # TODO: is it ever optional?
98
127
  "endTimeUnixNano": str(span.end_time), # -"-
99
128
  "status": _status(span.status),
129
+ "attributes": [],
100
130
  }
131
+
101
132
  if span.parent:
102
133
  rv["parentSpanId"] = _span_id(span.parent.span_id)
134
+
135
+ for k, v in span.attributes.items(): # type: ignore
136
+ try:
137
+ rv["attributes"].append({"key": k, "value": _value(v)})
138
+ except ValueError:
139
+ pass
140
+
141
+ rv["dropped_attributes_count"] = len(span.attributes) - len(rv["attributes"]) # type: ignore
142
+
143
+ if not rv["attributes"]:
144
+ del rv["attributes"]
145
+
146
+ if not rv["dropped_attributes_count"]:
147
+ del rv["dropped_attributes_count"]
148
+
149
+ if span.events:
150
+ rv["events"] = [_event(e) for e in span.events]
151
+
103
152
  return rv
104
153
 
105
154
 
@@ -118,3 +167,27 @@ def _span_id(span_id: int) -> str:
118
167
  def _status(status: Status) -> dict[str, Any]:
119
168
  # FIXME
120
169
  return {}
170
+
171
+
172
+ def _event(event: Event) -> dict[str, Any]:
173
+ rv = {
174
+ "name": event.name,
175
+ "timeUnixNano": str(event.timestamp),
176
+ "attributes": [],
177
+ }
178
+
179
+ for k, v in event.attributes.items(): # type: ignore
180
+ try:
181
+ rv["attributes"].append({"key": k, "value": _value(v)})
182
+ except ValueError:
183
+ pass
184
+
185
+ rv["dropped_attributes_count"] = len(event.attributes) - len(rv["attributes"]) # type: ignore
186
+
187
+ if not rv["attributes"]:
188
+ del rv["attributes"]
189
+
190
+ if not rv["dropped_attributes_count"]:
191
+ del rv["dropped_attributes_count"]
192
+
193
+ return rv
@@ -1,14 +1,19 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: otlp-json
3
- Version: 0.9.1
3
+ Version: 0.9.2
4
4
  Summary: 🐍Lightweight OTEL span to JSON converter, no dependencies, pure Python🐍
5
+ Project-URL: Repository, https://github.com/dimaqq/otlp-json
6
+ Project-URL: Issues, https://github.com/dimaqq/otlp-json/issues
5
7
  Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Framework :: OpenTelemetry
9
+ Classifier: Framework :: OpenTelemetry :: Exporters
6
10
  Classifier: Intended Audience :: Developers
7
11
  Classifier: License :: OSI Approved :: MIT License
8
12
  Classifier: Natural Language :: English
9
13
  Classifier: Operating System :: OS Independent
10
14
  Classifier: Programming Language :: Python
11
15
  Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
12
17
  Classifier: Programming Language :: Python :: 3.8
13
18
  Classifier: Programming Language :: Python :: 3.9
14
19
  Classifier: Programming Language :: Python :: 3.10
@@ -16,12 +21,9 @@ Classifier: Programming Language :: Python :: 3.11
16
21
  Classifier: Programming Language :: Python :: 3.12
17
22
  Classifier: Programming Language :: Python :: 3.13
18
23
  Classifier: Programming Language :: Python :: 3.14
19
- Classifier: Programming Language :: Python :: 3 :: Only
20
24
  Classifier: Programming Language :: Python :: Implementation :: CPython
21
25
  Classifier: Programming Language :: Python :: Implementation :: PyPy
22
26
  Classifier: Topic :: Software Development :: Libraries
23
- Classifier: Framework :: OpenTelemetry
24
- Classifier: Framework :: OpenTelemetry :: Exporters
25
27
  Requires-Python: >=3.8
26
28
  Description-Content-Type: text/markdown
27
29
 
@@ -0,0 +1,4 @@
1
+ otlp_json/__init__.py,sha256=KFI4PCFiyTOw3wTUClS1hIeUyAMCCZp1F9F6K6Qi_IQ,5862
2
+ otlp_json-0.9.2.dist-info/METADATA,sha256=1CqS5onFoYgi8n4ft4DuQnsHwV_kp4aHC5zoQVxLIZA,2810
3
+ otlp_json-0.9.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4
+ otlp_json-0.9.2.dist-info/RECORD,,
@@ -1,5 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
-
@@ -1,5 +0,0 @@
1
- otlp_json.py,sha256=GCHauwquLROkov7v4WfmevNlKzC45aYDbvqGYCb66lg,3869
2
- otlp_json-0.9.1.dist-info/METADATA,sha256=3OlCQjfep4nN69aMkIVHxTOawNDCnEqOLIu5cDCKHVQ,2685
3
- otlp_json-0.9.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
4
- otlp_json-0.9.1.dist-info/top_level.txt,sha256=7jOohtnyFAP4ixxnF_1gQz1NKEuVS8ELe3ymA4I5FtU,10
5
- otlp_json-0.9.1.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- otlp_json