trodo-python 2.5.0__tar.gz → 2.7.0__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 (44) hide show
  1. {trodo_python-2.5.0 → trodo_python-2.7.0}/PKG-INFO +1 -1
  2. {trodo_python-2.5.0 → trodo_python-2.7.0}/pyproject.toml +1 -1
  3. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/__init__.py +1 -1
  4. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/otel/processor.py +5 -5
  5. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/otel/wrap_agent.py +38 -14
  6. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/session/server_session.py +21 -32
  7. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo_python.egg-info/PKG-INFO +1 -1
  8. {trodo_python-2.5.0 → trodo_python-2.7.0}/README.md +0 -0
  9. {trodo_python-2.5.0 → trodo_python-2.7.0}/setup.cfg +0 -0
  10. {trodo_python-2.5.0 → trodo_python-2.7.0}/tests/test_anon_distinct_id.py +0 -0
  11. {trodo_python-2.5.0 → trodo_python-2.7.0}/tests/test_auto_instrument_fixes.py +0 -0
  12. {trodo_python-2.5.0 → trodo_python-2.7.0}/tests/test_cross_process_session.py +0 -0
  13. {trodo_python-2.5.0 → trodo_python-2.7.0}/tests/test_end_run.py +0 -0
  14. {trodo_python-2.5.0 → trodo_python-2.7.0}/tests/test_processor_methods.py +0 -0
  15. {trodo_python-2.5.0 → trodo_python-2.7.0}/tests/test_register_otel.py +0 -0
  16. {trodo_python-2.5.0 → trodo_python-2.7.0}/tests/test_start_run.py +0 -0
  17. {trodo_python-2.5.0 → trodo_python-2.7.0}/tests/test_wrap_agent_unchanged.py +0 -0
  18. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/api/__init__.py +0 -0
  19. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/api/async_client.py +0 -0
  20. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/api/endpoints.py +0 -0
  21. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/api/http_client.py +0 -0
  22. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/auto/__init__.py +0 -0
  23. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/auto/auto_event_manager.py +0 -0
  24. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/client.py +0 -0
  25. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/managers/__init__.py +0 -0
  26. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/managers/group_manager.py +0 -0
  27. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/managers/people_manager.py +0 -0
  28. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/otel/__init__.py +0 -0
  29. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/otel/auto_instrument.py +0 -0
  30. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/otel/context.py +0 -0
  31. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/otel/helpers.py +0 -0
  32. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/otel/register.py +0 -0
  33. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/otel/transport.py +0 -0
  34. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/queue/__init__.py +0 -0
  35. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/queue/batch_flusher.py +0 -0
  36. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/queue/event_queue.py +0 -0
  37. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/session/__init__.py +0 -0
  38. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/session/session_manager.py +0 -0
  39. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/types.py +0 -0
  40. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo/user_context.py +0 -0
  41. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo_python.egg-info/SOURCES.txt +0 -0
  42. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo_python.egg-info/dependency_links.txt +0 -0
  43. {trodo_python-2.5.0 → trodo_python-2.7.0}/trodo_python.egg-info/requires.txt +0 -0
  44. {trodo_python-2.5.0 → trodo_python-2.7.0}/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.5.0
3
+ Version: 2.7.0
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.5.0"
7
+ version = "2.7.0"
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.4.0"
43
+ __version__ = "2.7.0"
44
44
 
45
45
  from typing import Any, Callable, Dict, List, Optional, Union
46
46
 
@@ -9,7 +9,7 @@ from __future__ import annotations
9
9
 
10
10
  import threading
11
11
  from dataclasses import dataclass, asdict
12
- from typing import Any, Dict, List, Optional
12
+ from typing import Any, Dict, List, Optional, Union
13
13
 
14
14
 
15
15
  @dataclass
@@ -20,8 +20,8 @@ class TrodoRun:
20
20
  conversation_id: Optional[str] = None
21
21
  parent_run_id: Optional[str] = None
22
22
  status: str = "ok" # 'running' | 'ok' | 'error'
23
- input: Optional[str] = None
24
- output: Optional[str] = None
23
+ input: Optional[Union[str, Dict[str, Any]]] = None
24
+ output: Optional[Union[str, Dict[str, Any]]] = None
25
25
  started_at: Optional[str] = None
26
26
  ended_at: Optional[str] = None
27
27
  duration_ms: Optional[int] = None
@@ -50,8 +50,8 @@ class TrodoSpan:
50
50
  started_at: Optional[str] = None
51
51
  ended_at: Optional[str] = None
52
52
  duration_ms: Optional[int] = None
53
- input: Optional[str] = None
54
- output: Optional[str] = None
53
+ input: Optional[Union[str, Dict[str, Any]]] = None
54
+ output: Optional[Union[str, Dict[str, Any]]] = None
55
55
  error_type: Optional[str] = None
56
56
  error_message: Optional[str] = None
57
57
  model: Optional[str] = None
@@ -28,7 +28,7 @@ import json
28
28
  import time
29
29
  import uuid
30
30
  from datetime import datetime, timezone
31
- from typing import Any, Callable, Dict, Optional
31
+ from typing import Any, Callable, Dict, Optional, Union
32
32
 
33
33
  from .context import ActiveSpanContext, get_active_context, run_with_context
34
34
  from .processor import TrodoSpanProcessor, TrodoRun, TrodoSpan
@@ -62,7 +62,11 @@ def _now_iso() -> str:
62
62
  return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
63
63
 
64
64
 
65
- def _truncate(value: Any, max_len: int = 64_000) -> Optional[str]:
65
+ _MAX_VALUE_LEN = 1_000_000 # 1 MB
66
+
67
+
68
+ def _truncate(value: Any, max_len: int = _MAX_VALUE_LEN) -> Optional[str]:
69
+ """Coerce to string and truncate. Use for string-only contexts (OTel attrs, error summaries)."""
66
70
  if value is None:
67
71
  return None
68
72
  if isinstance(value, str):
@@ -75,6 +79,26 @@ def _truncate(value: Any, max_len: int = 64_000) -> Optional[str]:
75
79
  return s[:max_len] if len(s) > max_len else s
76
80
 
77
81
 
82
+ def _prepare_value(value: Any, max_len: int = _MAX_VALUE_LEN) -> Optional[Union[str, Dict[str, Any]]]:
83
+ """Prepare a value for storage in the JSONB input/output column.
84
+
85
+ Dicts/lists pass through as-is (stored as JSONB objects/arrays).
86
+ Strings are truncated at max_len.
87
+ Everything else is JSON-serialised then truncated.
88
+ """
89
+ if value is None:
90
+ return None
91
+ if isinstance(value, dict):
92
+ return value
93
+ if isinstance(value, str):
94
+ return value[:max_len] if len(value) > max_len else value
95
+ try:
96
+ s = json.dumps(value, default=str)
97
+ except Exception:
98
+ s = str(value)
99
+ return s[:max_len] if len(s) > max_len else s
100
+
101
+
78
102
  def current_run_id() -> Optional[str]:
79
103
  """Return the run_id of the currently active agent run, if any."""
80
104
  ctx = get_active_context()
@@ -145,15 +169,15 @@ class RunHandle:
145
169
  # Always populated — wrap_agent mints anon if caller didn't pass one
146
170
  # so downstream ``trodo.feedback(distinct_id=...)`` always has a target.
147
171
  self.distinct_id = distinct_id
148
- self.input: Optional[str] = None
149
- self.output: Optional[str] = None
172
+ self.input: Optional[Union[str, Dict[str, Any]]] = None
173
+ self.output: Optional[Union[str, Dict[str, Any]]] = None
150
174
  self.metadata: Dict[str, Any] = {}
151
175
 
152
176
  def set_input(self, value: Any) -> None:
153
- self.input = _truncate(value)
177
+ self.input = _prepare_value(value)
154
178
 
155
179
  def set_output(self, value: Any) -> None:
156
- self.output = _truncate(value)
180
+ self.output = _prepare_value(value)
157
181
 
158
182
  def set_metadata(self, **kwargs: Any) -> None:
159
183
  self.metadata.update(kwargs)
@@ -165,8 +189,8 @@ class SpanHandle:
165
189
  def __init__(self, span_id: str, name: str) -> None:
166
190
  self.span_id = span_id
167
191
  self.name = name
168
- self.input: Optional[str] = None
169
- self.output: Optional[str] = None
192
+ self.input: Optional[Union[str, Dict[str, Any]]] = None
193
+ self.output: Optional[Union[str, Dict[str, Any]]] = None
170
194
  self.attributes: Dict[str, Any] = {}
171
195
  self.model: Optional[str] = None
172
196
  self.provider: Optional[str] = None
@@ -177,10 +201,10 @@ class SpanHandle:
177
201
  self.tool_name: Optional[str] = None
178
202
 
179
203
  def set_input(self, value: Any) -> None:
180
- self.input = _truncate(value)
204
+ self.input = _prepare_value(value)
181
205
 
182
206
  def set_output(self, value: Any) -> None:
183
- self.output = _truncate(value)
207
+ self.output = _prepare_value(value)
184
208
 
185
209
  def set_attribute(self, key: str, value: Any) -> None:
186
210
  self.attributes[key] = value
@@ -247,7 +271,7 @@ def start_run(
247
271
  conversation_id=conversation_id,
248
272
  parent_run_id=parent_run_id,
249
273
  status="running",
250
- input=_truncate(input),
274
+ input=_prepare_value(input),
251
275
  started_at=_now_iso(),
252
276
  metadata=metadata,
253
277
  )
@@ -279,7 +303,7 @@ def end_run(
279
303
  **agg,
280
304
  }
281
305
  if output is not None:
282
- payload["output"] = _truncate(output)
306
+ payload["output"] = _prepare_value(output)
283
307
  if error_summary is not None:
284
308
  payload["error_summary"] = _truncate(error_summary, 4_000)
285
309
  if metadata is not None:
@@ -493,7 +517,7 @@ class join_run:
493
517
  self._parent_span_id = parent_span_id
494
518
  self._name = name
495
519
  self._kind = kind
496
- self._input = _truncate(input) if input is not None else None
520
+ self._input = _prepare_value(input) if input is not None else None
497
521
  self._attributes = attributes
498
522
  self._ctx_mgr: Optional[run_with_context] = None
499
523
  self._started_ms: float = 0.0
@@ -580,7 +604,7 @@ class span:
580
604
  ) -> None:
581
605
  self._name = name
582
606
  self._kind = kind
583
- self._input = _truncate(input) if input is not None else None
607
+ self._input = _prepare_value(input) if input is not None else None
584
608
  self._attributes = attributes
585
609
  self._ctx_mgr: Optional[run_with_context] = None
586
610
  self._started_ms: float = 0.0
@@ -3,7 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import time
6
- import uuid
7
6
  from datetime import datetime, timezone
8
7
  from typing import Any, Dict, Optional
9
8
 
@@ -14,13 +13,24 @@ def now_iso() -> str:
14
13
  return datetime.now(timezone.utc).isoformat()
15
14
 
16
15
 
16
+ def server_session_id(distinct_id: str) -> str:
17
+ """Deterministic, "backend consistent" session id for a backend user.
18
+
19
+ Backend SDKs are stateless: the same distinct_id must resolve to the SAME
20
+ session across processes and restarts. Using ``server:{distinct_id}`` instead
21
+ of a per-process ``uuid4()`` produces exactly one session row per backend
22
+ user (no per-process bloat) and is idempotent server-side.
23
+ """
24
+ return f"server:{distinct_id}"
25
+
26
+
17
27
  def create_server_session(
18
28
  site_id: str,
19
29
  distinct_id: str,
20
30
  session_id: Optional[str] = None,
21
31
  ) -> ServerSession:
22
32
  return ServerSession(
23
- session_id=session_id or str(uuid.uuid4()),
33
+ session_id=session_id or server_session_id(distinct_id),
24
34
  site_id=site_id,
25
35
  distinct_id=distinct_id,
26
36
  start_time=now_iso(),
@@ -30,6 +40,15 @@ def create_server_session(
30
40
 
31
41
 
32
42
  def build_session_payload(session: ServerSession) -> Dict[str, Any]:
43
+ """Minimal server-session payload.
44
+
45
+ Backend SDKs cannot know browser-only signals (geo, device, browser, UTM,
46
+ referrer, wallet), so those fields are OMITTED rather than sent as ~30
47
+ explicit nulls — ingestion defaults missing fields to null. This saves
48
+ ingestion bandwidth and is more accurate. Only the markers ingestion keys
49
+ on are retained: ``is_server_session`` (drives identity-level browser-field
50
+ guards) and ``device_type='server'`` (server-origin fallback detector).
51
+ """
33
52
  return {
34
53
  "session_id": session.session_id,
35
54
  "site_id": session.site_id,
@@ -37,39 +56,9 @@ def build_session_payload(session: ServerSession) -> Dict[str, Any]:
37
56
  "distinct_id": session.distinct_id,
38
57
  "team_id": None,
39
58
  "start_time": session.start_time,
40
- "end_time": None,
41
59
  "last_activity": int(session.last_activity * 1000),
42
- "duration": 0,
43
- "pages_viewed": 0,
44
60
  "is_bounce": False,
45
- "previous_session_id": None,
46
- "time_since_last_session": None,
47
- "entry_page": None,
48
- "exit_page": None,
49
61
  "referrer": "server",
50
- "ip_address": None,
51
- "city": None,
52
- "region": None,
53
- "country": None,
54
- "browser_name": None,
55
- "browser_version": None,
56
62
  "device_type": "server",
57
- "os": None,
58
- "resolution": None,
59
- "user_agent": None,
60
- "language": None,
61
- "wallet_address": None,
62
- "wallet_type": None,
63
- "chain_name": None,
64
- "is_web3_user": False,
65
- "wallet_connected": False,
66
- "utm_source": None,
67
- "utm_medium": None,
68
- "utm_campaign": None,
69
- "utm_term": None,
70
- "utm_content": None,
71
- "utm_id": None,
72
- "visited_pages": [],
73
- "active_time_ms": 0,
74
63
  "is_server_session": True,
75
64
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trodo-python
3
- Version: 2.5.0
3
+ Version: 2.7.0
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