trodo-python 2.4.1__py3-none-any.whl → 2.5.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.
trodo/otel/wrap_agent.py CHANGED
@@ -114,12 +114,37 @@ def _aggregate(spans: list[TrodoSpan]) -> Dict[str, Any]:
114
114
  }
115
115
 
116
116
 
117
+ def _mint_anon_distinct_id() -> str:
118
+ """Mint a server-side anonymous distinct_id for an agent run.
119
+
120
+ Mirrors UserIdentity.generateAnonymousDistinctId on the backend
121
+ (``anon_<ts>_<scope>_<uuid>_<rand>``) so server-SDK anon runs and
122
+ browser-SDK anon sessions share one prefix the dashboard already
123
+ filters on.
124
+
125
+ Minted client-side (not just server-side) so:
126
+ * the RunHandle / OTLP span attribute carries the id immediately and
127
+ downstream ``trodo.feedback(distinct_id=...)`` calls have something
128
+ to bind to,
129
+ * users running against an older backend still get a real attributable
130
+ distinct_id on every ``agent_runs`` row, instead of NULL.
131
+ """
132
+ import random
133
+ import string
134
+ ts = int(time.time() * 1000)
135
+ rand = "".join(random.choices(string.ascii_lowercase + string.digits, k=16))
136
+ return f"anon_{ts}_python_{uuid.uuid4()}_{rand}"
137
+
138
+
117
139
  class RunHandle:
118
140
  """Handle returned by wrap_agent for setting input/output and getting run_id."""
119
141
 
120
- def __init__(self, run_id: str, agent_name: str) -> None:
142
+ def __init__(self, run_id: str, agent_name: str, distinct_id: str) -> None:
121
143
  self.run_id = run_id
122
144
  self.agent_name = agent_name
145
+ # Always populated — wrap_agent mints anon if caller didn't pass one
146
+ # so downstream ``trodo.feedback(distinct_id=...)`` always has a target.
147
+ self.distinct_id = distinct_id
123
148
  self.input: Optional[str] = None
124
149
  self.output: Optional[str] = None
125
150
  self.metadata: Dict[str, Any] = {}
@@ -209,10 +234,16 @@ def start_run(
209
234
  to add spans — they flush incrementally via ``append_spans``.
210
235
  """
211
236
  rid = run_id or str(uuid.uuid4())
237
+ # Mint anon when missing so the run row is attributable. The minted id
238
+ # is stamped onto the TrodoRun payload but is not surfaced to the caller —
239
+ # start_run's signature returns just run_id for backward compat. Callers
240
+ # who need the distinct_id should pass their own (or use wrap_agent,
241
+ # whose RunHandle exposes it via handle.distinct_id).
242
+ effective_distinct_id = distinct_id or _mint_anon_distinct_id()
212
243
  run = TrodoRun(
213
244
  run_id=rid,
214
245
  agent_name=agent_name,
215
- distinct_id=distinct_id,
246
+ distinct_id=effective_distinct_id,
216
247
  conversation_id=conversation_id,
217
248
  parent_run_id=parent_run_id,
218
249
  status="running",
@@ -278,7 +309,11 @@ class wrap_agent:
278
309
  self._processor = processor
279
310
  self._team_site_id = team_site_id
280
311
  self._agent_name = agent_name
281
- self._distinct_id = distinct_id
312
+ # Mint anon when caller didn't pass one. From this point on every
313
+ # internal path uses self._distinct_id, never the raw constructor
314
+ # argument, so the run row, OTLP span attribute, and RunHandle all
315
+ # agree on a single non-null value.
316
+ self._distinct_id = distinct_id or _mint_anon_distinct_id()
282
317
  self._conversation_id = conversation_id
283
318
  self._parent_run_id = parent_run_id
284
319
  self._metadata = metadata
@@ -312,7 +347,7 @@ class wrap_agent:
312
347
  self._started_iso = _now_iso()
313
348
  self._started_ms = time.time() * 1000.0
314
349
 
315
- self.handle = RunHandle(run_id, self._agent_name)
350
+ self.handle = RunHandle(run_id, self._agent_name, self._distinct_id)
316
351
  ctx = ActiveSpanContext(
317
352
  run_id=run_id,
318
353
  span_id=root_span_id,
@@ -387,8 +422,9 @@ class wrap_agent:
387
422
  run_id = _hex_to_uuid(trace_id_hex)
388
423
 
389
424
  otel_span.set_attribute("trodo.agent_name", self._agent_name)
390
- if self._distinct_id:
391
- otel_span.set_attribute("trodo.distinct_id", str(self._distinct_id))
425
+ # self._distinct_id is always set (anon-minted in __init__ when caller
426
+ # didn't pass one), so the attribute always lands on the OTLP span.
427
+ otel_span.set_attribute("trodo.distinct_id", str(self._distinct_id))
392
428
  if self._conversation_id:
393
429
  otel_span.set_attribute("trodo.conversation_id", str(self._conversation_id))
394
430
  if self._parent_run_id:
@@ -398,7 +434,7 @@ class wrap_agent:
398
434
  otel_span.set_attribute(f"trodo.metadata.{k}", _serialize_attr(v))
399
435
 
400
436
  self._otel_span = otel_span
401
- self.handle = RunHandle(run_id, self._agent_name)
437
+ self.handle = RunHandle(run_id, self._agent_name, self._distinct_id)
402
438
  return self.handle
403
439
 
404
440
  def _exit_otel(self, exc_type, exc, tb) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trodo-python
3
- Version: 2.4.1
3
+ Version: 2.5.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
@@ -18,14 +18,14 @@ trodo/otel/helpers.py,sha256=IEAHxAEN-Bvv_ZODrmRzC6PCGGhGTXU7IPcp6iO2nbA,16405
18
18
  trodo/otel/processor.py,sha256=jVZkslZlw50G5uRAa7-GMRgn_yvae58EmlWTZL8tMkQ,6285
19
19
  trodo/otel/register.py,sha256=YV2EnkUoa-_54YAuChOe-Mg28UUKg8JO7-qhVP9G6u4,7644
20
20
  trodo/otel/transport.py,sha256=hzZz8gwSMGJ8CxdijmLn1Ljt18owr9XTWy13DLbwYbw,2441
21
- trodo/otel/wrap_agent.py,sha256=J_ho4O8oOa5ECX4Cft0mJ9e5pzAe3sWOla5awikOVK8,22542
21
+ trodo/otel/wrap_agent.py,sha256=mXEQqqNSiHJY3M0T0QPRkozOONB0dbLBcX5WYT0ddXI,24560
22
22
  trodo/queue/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  trodo/queue/batch_flusher.py,sha256=4Lg6T3Urwi9U0Q4FpFGPmjDYKg4ZliCTR-ND8BJvWaY,1298
24
24
  trodo/queue/event_queue.py,sha256=EVFZrhlq_kwC3jJ2GK0wMhHISf9UzLCZNDnT_aZ2I2A,872
25
25
  trodo/session/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  trodo/session/server_session.py,sha256=4bQZc_Zxktmu8RVoyh0qI7tvr8AKsHI5xkGf3jEpWVE,2005
27
27
  trodo/session/session_manager.py,sha256=JrgH1VeicmtlxPR4dXEuJbxhi23OelkgwW3-9Slv80o,2525
28
- trodo_python-2.4.1.dist-info/METADATA,sha256=_V5plcgVHmz0vb9S9fQXgivuVvmsYs2Aict8OViy5FU,17882
29
- trodo_python-2.4.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
30
- trodo_python-2.4.1.dist-info/top_level.txt,sha256=VCQu1CJWFmNsqTs1YxMcw4Mq35Tc3z3uI9RwHEXAayQ,6
31
- trodo_python-2.4.1.dist-info/RECORD,,
28
+ trodo_python-2.5.0.dist-info/METADATA,sha256=JOZlK8iGa5MyS9B8sVklhM2mVwvI9bKu5UUTFg5c8eI,17882
29
+ trodo_python-2.5.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
30
+ trodo_python-2.5.0.dist-info/top_level.txt,sha256=VCQu1CJWFmNsqTs1YxMcw4Mq35Tc3z3uI9RwHEXAayQ,6
31
+ trodo_python-2.5.0.dist-info/RECORD,,