trodo-python 2.10.4__tar.gz → 2.10.6__tar.gz

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.
Files changed (47) hide show
  1. {trodo_python-2.10.4 → trodo_python-2.10.6}/PKG-INFO +1 -1
  2. {trodo_python-2.10.4 → trodo_python-2.10.6}/pyproject.toml +1 -1
  3. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/__init__.py +1 -1
  4. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/api/http_client.py +18 -0
  5. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/otel/auto_instrument.py +40 -3
  6. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo_python.egg-info/PKG-INFO +1 -1
  7. {trodo_python-2.10.4 → trodo_python-2.10.6}/README.md +0 -0
  8. {trodo_python-2.10.4 → trodo_python-2.10.6}/setup.cfg +0 -0
  9. {trodo_python-2.10.4 → trodo_python-2.10.6}/tests/test_anon_distinct_id 2.py +0 -0
  10. {trodo_python-2.10.4 → trodo_python-2.10.6}/tests/test_anon_distinct_id.py +0 -0
  11. {trodo_python-2.10.4 → trodo_python-2.10.6}/tests/test_auto_instrument_fixes.py +0 -0
  12. {trodo_python-2.10.4 → trodo_python-2.10.6}/tests/test_cross_process_session.py +0 -0
  13. {trodo_python-2.10.4 → trodo_python-2.10.6}/tests/test_end_run.py +0 -0
  14. {trodo_python-2.10.4 → trodo_python-2.10.6}/tests/test_error_enrichment.py +0 -0
  15. {trodo_python-2.10.4 → trodo_python-2.10.6}/tests/test_llm_usage_cost.py +0 -0
  16. {trodo_python-2.10.4 → trodo_python-2.10.6}/tests/test_processor_methods.py +0 -0
  17. {trodo_python-2.10.4 → trodo_python-2.10.6}/tests/test_register_otel.py +0 -0
  18. {trodo_python-2.10.4 → trodo_python-2.10.6}/tests/test_start_run.py +0 -0
  19. {trodo_python-2.10.4 → trodo_python-2.10.6}/tests/test_wrap_agent_unchanged.py +0 -0
  20. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/api/__init__.py +0 -0
  21. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/api/async_client.py +0 -0
  22. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/api/endpoints.py +0 -0
  23. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/auto/__init__.py +0 -0
  24. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/auto/auto_event_manager.py +0 -0
  25. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/client.py +0 -0
  26. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/managers/__init__.py +0 -0
  27. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/managers/group_manager.py +0 -0
  28. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/managers/people_manager.py +0 -0
  29. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/otel/__init__.py +0 -0
  30. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/otel/context.py +0 -0
  31. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/otel/helpers.py +0 -0
  32. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/otel/processor.py +0 -0
  33. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/otel/register.py +0 -0
  34. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/otel/transport.py +0 -0
  35. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/otel/wrap_agent.py +0 -0
  36. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/queue/__init__.py +0 -0
  37. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/queue/batch_flusher.py +0 -0
  38. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/queue/event_queue.py +0 -0
  39. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/session/__init__.py +0 -0
  40. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/session/server_session.py +0 -0
  41. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/session/session_manager.py +0 -0
  42. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/types.py +0 -0
  43. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo/user_context.py +0 -0
  44. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo_python.egg-info/SOURCES.txt +0 -0
  45. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo_python.egg-info/dependency_links.txt +0 -0
  46. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo_python.egg-info/requires.txt +0 -0
  47. {trodo_python-2.10.4 → trodo_python-2.10.6}/trodo_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trodo-python
3
- Version: 2.10.4
3
+ Version: 2.10.6
4
4
  Summary: Trodo Analytics SDK for Python — server-side event tracking
5
5
  License: ISC
6
6
  Keywords: analytics,tracking,trodo,server-side
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "trodo-python"
7
- version = "2.10.4"
7
+ version = "2.10.6"
8
8
  description = "Trodo Analytics SDK for Python — server-side event tracking"
9
9
  readme = "README.md"
10
10
  license = { text = "ISC" }
@@ -40,7 +40,7 @@ Downstream microservice (join the caller's run instead of making a new one):
40
40
 
41
41
  from __future__ import annotations
42
42
 
43
- __version__ = "2.10.4"
43
+ __version__ = "2.10.6"
44
44
 
45
45
  from typing import Any, Callable, Dict, List, Optional, Union
46
46
 
@@ -47,6 +47,24 @@ class HttpClient:
47
47
  self._log(f"Retry {attempt + 1} after {delay}s (status {resp.status_code})")
48
48
  time.sleep(delay)
49
49
  return self._request(path, body, attempt + 1)
50
+ if resp.status_code >= 400:
51
+ # Surface non-2xx responses to on_error — a 4xx means the
52
+ # payload was REJECTED and the data is gone; before this it
53
+ # was fully silent (on_error only saw network errors), which
54
+ # made ingest-schema bugs invisible to callers.
55
+ detail = ""
56
+ try:
57
+ detail = resp.text[:300]
58
+ except Exception:
59
+ pass
60
+ self._log(f"HTTP {resp.status_code} on {path}: {detail}")
61
+ if self.on_error:
62
+ try:
63
+ self.on_error(
64
+ RuntimeError(f"trodo-python: POST {path} -> HTTP {resp.status_code}: {detail}")
65
+ )
66
+ except Exception:
67
+ pass
50
68
  try:
51
69
  return resp.json()
52
70
  except Exception:
@@ -58,6 +58,24 @@ _ATTR_TRODO_RUN_ID = "trodo.run_id"
58
58
  _ATTR_TRODO_PARENT_SPAN_ID = "trodo.parent_span_id"
59
59
 
60
60
 
61
+ def _span_id_to_uuid(span_id: Optional[str]) -> Optional[str]:
62
+ """Convert an OTel hex span id into UUID format. Mirrors trodo-node's
63
+ spanIdToUuid: 16-hex OTel ids are left-zero-padded into the same UUID
64
+ address space as our uuid4-minted ids. Without this the bridge sent raw
65
+ 16-hex into agent_spans' Postgres uuid columns -> the ingest 400'd and
66
+ the WHOLE run payload (run row + every span) was silently dropped, so
67
+ Python auto-instrumented runs never landed."""
68
+ if not span_id:
69
+ return span_id
70
+ h = str(span_id).replace("-", "").lower()
71
+ if len(h) == 32:
72
+ return f"{h[0:8]}-{h[8:12]}-{h[12:16]}-{h[16:20]}-{h[20:32]}"
73
+ if len(h) == 16:
74
+ p = "0000000000000000" + h
75
+ return f"{p[0:8]}-{p[8:12]}-{p[12:16]}-{p[16:20]}-{p[20:32]}"
76
+ return span_id
77
+
78
+
61
79
  def otel_span_to_trodo_span(otel_span: Any) -> Optional[TrodoSpan]:
62
80
  """Translate an OTel ReadableSpan to our TrodoSpan using GenAI semconv.
63
81
 
@@ -77,7 +95,8 @@ def otel_span_to_trodo_span(otel_span: Any) -> Optional[TrodoSpan]:
77
95
  span_id_int = getattr(span_ctx, "span_id", None)
78
96
  if not span_id_int:
79
97
  return None
80
- span_id = f"{span_id_int:016x}" if isinstance(span_id_int, int) else str(span_id_int)
98
+ span_id_hex = f"{span_id_int:016x}" if isinstance(span_id_int, int) else str(span_id_int)
99
+ span_id = _span_id_to_uuid(span_id_hex)
81
100
 
82
101
  attrs = dict(getattr(otel_span, "attributes", {}) or {})
83
102
 
@@ -95,7 +114,7 @@ def otel_span_to_trodo_span(otel_span: Any) -> Optional[TrodoSpan]:
95
114
  otel_parent_span_id: Optional[str] = None
96
115
  if parent is not None:
97
116
  pid = getattr(parent, "span_id", None)
98
- otel_parent_span_id = (
117
+ otel_parent_span_id = _span_id_to_uuid(
99
118
  f"{pid:016x}" if isinstance(pid, int) else str(pid) if pid else None
100
119
  )
101
120
 
@@ -201,12 +220,30 @@ def otel_span_to_trodo_span(otel_span: Any) -> Optional[TrodoSpan]:
201
220
  )
202
221
 
203
222
 
204
- class _OtelAdapter:
223
+ # Subclass the REAL SpanProcessor when the sdk is importable (it always is on
224
+ # the path that registers the adapter — enable_auto_instrument requires it).
225
+ # Duck-typing broke on opentelemetry-sdk >= 1.43: the provider calls
226
+ # `sp._on_ending(span)` on every span end, so a processor without the base
227
+ # class raises AttributeError INSIDE USER CODE at span.end().
228
+ try: # pragma: no cover — exercised implicitly by every auto-instrument test
229
+ from opentelemetry.sdk.trace import SpanProcessor as _SpanProcessorBase
230
+ except Exception:
231
+ _SpanProcessorBase = object # type: ignore[assignment,misc]
232
+
233
+
234
+ class _OtelAdapter(_SpanProcessorBase): # type: ignore[valid-type,misc]
205
235
  """Adapter matching opentelemetry.sdk.trace.SpanProcessor interface."""
206
236
 
207
237
  def __init__(self, processor: TrodoSpanProcessor) -> None:
238
+ try:
239
+ super().__init__()
240
+ except TypeError:
241
+ pass
208
242
  self._processor = processor
209
243
 
244
+ def on_ending(self, span: Any) -> None: # sdk >= 1.43 hook — no mutation needed
245
+ return None
246
+
210
247
  def on_start(self, span: Any, parent_context: Any = None) -> None:
211
248
  # Stamp active run/parent ids while contextvars are still alive.
212
249
  # See otel_span_to_trodo_span() docstring for the failure mode this
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trodo-python
3
- Version: 2.10.4
3
+ Version: 2.10.6
4
4
  Summary: Trodo Analytics SDK for Python — server-side event tracking
5
5
  License: ISC
6
6
  Keywords: analytics,tracking,trodo,server-side
File without changes
File without changes