trodo-python 2.18.1__tar.gz → 2.19.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 (64) hide show
  1. {trodo_python-2.18.1 → trodo_python-2.19.0}/PKG-INFO +1 -1
  2. {trodo_python-2.18.1 → trodo_python-2.19.0}/pyproject.toml +1 -1
  3. trodo_python-2.19.0/tests/test_get_prompt_version_types.py +54 -0
  4. {trodo_python-2.18.1 → trodo_python-2.19.0}/tests/test_prompts.py +32 -3
  5. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/__init__.py +5 -3
  6. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/prompts/compile.py +21 -4
  7. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/prompts/types.py +10 -0
  8. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo_python.egg-info/PKG-INFO +1 -1
  9. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo_python.egg-info/SOURCES.txt +1 -0
  10. {trodo_python-2.18.1 → trodo_python-2.19.0}/README.md +0 -0
  11. {trodo_python-2.18.1 → trodo_python-2.19.0}/setup.cfg +0 -0
  12. {trodo_python-2.18.1 → trodo_python-2.19.0}/tests/test_anon_distinct_id.py +0 -0
  13. {trodo_python-2.18.1 → trodo_python-2.19.0}/tests/test_auto_instrument_fixes.py +0 -0
  14. {trodo_python-2.18.1 → trodo_python-2.19.0}/tests/test_cross_process_session.py +0 -0
  15. {trodo_python-2.18.1 → trodo_python-2.19.0}/tests/test_datasets.py +0 -0
  16. {trodo_python-2.18.1 → trodo_python-2.19.0}/tests/test_end_run.py +0 -0
  17. {trodo_python-2.18.1 → trodo_python-2.19.0}/tests/test_error_enrichment.py +0 -0
  18. {trodo_python-2.18.1 → trodo_python-2.19.0}/tests/test_experiments.py +0 -0
  19. {trodo_python-2.18.1 → trodo_python-2.19.0}/tests/test_llm_usage_cost.py +0 -0
  20. {trodo_python-2.18.1 → trodo_python-2.19.0}/tests/test_multi_agent_shape.py +0 -0
  21. {trodo_python-2.18.1 → trodo_python-2.19.0}/tests/test_processor_methods.py +0 -0
  22. {trodo_python-2.18.1 → trodo_python-2.19.0}/tests/test_prompt_trace.py +0 -0
  23. {trodo_python-2.18.1 → trodo_python-2.19.0}/tests/test_register_otel.py +0 -0
  24. {trodo_python-2.18.1 → trodo_python-2.19.0}/tests/test_start_run.py +0 -0
  25. {trodo_python-2.18.1 → trodo_python-2.19.0}/tests/test_user_traits.py +0 -0
  26. {trodo_python-2.18.1 → trodo_python-2.19.0}/tests/test_wrap_agent_unchanged.py +0 -0
  27. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/api/__init__.py +0 -0
  28. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/api/async_client.py +0 -0
  29. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/api/endpoints.py +0 -0
  30. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/api/http_client.py +0 -0
  31. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/auto/__init__.py +0 -0
  32. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/auto/auto_event_manager.py +0 -0
  33. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/client.py +0 -0
  34. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/managers/__init__.py +0 -0
  35. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/managers/dataset_manager.py +0 -0
  36. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/managers/experiment_manager.py +0 -0
  37. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/managers/group_manager.py +0 -0
  38. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/managers/people_manager.py +0 -0
  39. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/managers/prompt_manager.py +0 -0
  40. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/managers/user_manager.py +0 -0
  41. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/otel/__init__.py +0 -0
  42. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/otel/auto_instrument.py +0 -0
  43. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/otel/context.py +0 -0
  44. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/otel/helpers.py +0 -0
  45. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/otel/processor.py +0 -0
  46. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/otel/prompt_trace.py +0 -0
  47. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/otel/register.py +0 -0
  48. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/otel/transport.py +0 -0
  49. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/otel/wrap_agent.py +0 -0
  50. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/prompts/__init__.py +0 -0
  51. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/prompts/template.py +0 -0
  52. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/queue/__init__.py +0 -0
  53. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/queue/batch_flusher.py +0 -0
  54. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/queue/event_queue.py +0 -0
  55. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/session/__init__.py +0 -0
  56. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/session/server_session.py +0 -0
  57. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/session/session_manager.py +0 -0
  58. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/types.py +0 -0
  59. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/user_context.py +0 -0
  60. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/util/__init__.py +0 -0
  61. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo/util/lru.py +0 -0
  62. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo_python.egg-info/dependency_links.txt +0 -0
  63. {trodo_python-2.18.1 → trodo_python-2.19.0}/trodo_python.egg-info/requires.txt +0 -0
  64. {trodo_python-2.18.1 → trodo_python-2.19.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.18.1
3
+ Version: 2.19.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.18.1"
7
+ version = "2.19.0"
8
8
  description = "Trodo Analytics SDK for Python — server-side event tracking"
9
9
  readme = "README.md"
10
10
  license = { text = "ISC" }
@@ -0,0 +1,54 @@
1
+ """The public get_prompt must accept a version_hash, not just an int.
2
+
3
+ The manager has always taken ``Union[int, str]`` and documents hash support —
4
+ the hash is the stable identity the dashboard shows and the SDK returns. But
5
+ the top-level ``trodo.get_prompt`` wrapper narrowed the hint to ``int``, so a
6
+ type checker flagged the documented usage, and the dashboard's own "Use this
7
+ prompt" snippet emits ``version="<hash>"`` for Python.
8
+
9
+ Checked by signature rather than by a network call: the wrapper only forwards,
10
+ so the contract IS the signature.
11
+ """
12
+ import inspect
13
+ import typing
14
+
15
+ import trodo
16
+ from trodo.managers.prompt_manager import PromptManager
17
+
18
+
19
+ def _version_annotation(fn):
20
+ return typing.get_type_hints(fn)["version"]
21
+
22
+
23
+ def test_public_get_prompt_accepts_int_or_str():
24
+ ann = _version_annotation(trodo.get_prompt)
25
+ args = typing.get_args(ann)
26
+ assert int in args, f"version should accept int, got {ann}"
27
+ assert str in args, f"version should accept a version_hash (str), got {ann}"
28
+
29
+
30
+ def test_matches_the_manager_it_forwards_to():
31
+ assert _version_annotation(trodo.get_prompt) == _version_annotation(PromptManager.get)
32
+
33
+
34
+ def test_forwards_version_through_untouched():
35
+ """A hash must reach the manager as-is — not coerced, not dropped."""
36
+ seen = {}
37
+
38
+ class _Stub:
39
+ def get(self, name, **kwargs):
40
+ seen.update(kwargs, name=name)
41
+ return "sentinel"
42
+
43
+ class _Client:
44
+ prompts = _Stub()
45
+
46
+ original = trodo._get_client
47
+ trodo._get_client = lambda: _Client()
48
+ try:
49
+ assert trodo.get_prompt("p", version="a3f9c2") == "sentinel"
50
+ finally:
51
+ trodo._get_client = original
52
+
53
+ assert seen["version"] == "a3f9c2"
54
+ assert seen["label"] is None
@@ -106,12 +106,41 @@ def test_compile_carries_model_and_tools():
106
106
  assert out.response_format is None
107
107
 
108
108
 
109
- def test_compile_renders_missing_variable_empty():
110
- # `who` has no value and no default -> empty, not a raise. Nothing is required.
111
- out = compile_prompt(PROMPT, {})
109
+ def test_compile_raises_for_required_variable_with_no_value():
110
+ # `who` is required with no default. Catching it here is the point: the
111
+ # alternative is paying for a call to a prompt that says "Hello ".
112
+ with pytest.raises(CompileError, match="'who': required"):
113
+ compile_prompt(PROMPT, {})
114
+
115
+
116
+ def test_compile_treats_none_and_empty_string_as_no_value():
117
+ # Both render as nothing, the exact outcome `required` exists to stop.
118
+ with pytest.raises(CompileError, match="'who': required"):
119
+ compile_prompt(PROMPT, {"who": None})
120
+ with pytest.raises(CompileError, match="'who': required"):
121
+ compile_prompt(PROMPT, {"who": ""})
122
+
123
+
124
+ def test_compile_renders_missing_optional_variable_empty():
125
+ # `required` defaults to False, so a declaration that doesn't ask for it
126
+ # behaves exactly as before -- no stored prompt changes meaning.
127
+ optional = {**PROMPT, "variables": [
128
+ {"name": "persona", "type": "string", "default": "a greeter"},
129
+ {"name": "who", "type": "string"},
130
+ ]}
131
+ out = compile_prompt(optional, {})
112
132
  assert out.messages[1]["content"][0] == {"type": "text", "text": "Hello "}
113
133
 
114
134
 
135
+ def test_compile_lets_a_default_satisfy_required():
136
+ with_default = {**PROMPT, "variables": [
137
+ {"name": "persona", "type": "string", "default": "a greeter"},
138
+ {"name": "who", "type": "string", "required": True, "default": "world"},
139
+ ]}
140
+ out = compile_prompt(with_default, {})
141
+ assert out.messages[1]["content"][0] == {"type": "text", "text": "Hello world"}
142
+
143
+
115
144
  def test_compile_rejects_undeclared_variable():
116
145
  # Passing a variable the prompt doesn't declare is the one variable mistake
117
146
  # compile still catches (it's almost always a typo/rename).
@@ -41,7 +41,7 @@ Downstream microservice (join the caller's run instead of making a new one):
41
41
 
42
42
  from __future__ import annotations
43
43
 
44
- __version__ = "2.18.0"
44
+ __version__ = "2.19.0"
45
45
 
46
46
  from typing import Any, Callable, Dict, List, Optional, Union
47
47
 
@@ -322,7 +322,7 @@ def reset(distinct_id: str) -> ResetResult:
322
322
  def get_prompt(
323
323
  name: str,
324
324
  label: Optional[str] = None,
325
- version: Optional[int] = None,
325
+ version: Optional[Union[int, str]] = None,
326
326
  cache_ttl_seconds: Optional[float] = None,
327
327
  fallback: Optional[Dict[str, Any]] = None,
328
328
  max_retries: int = 2,
@@ -330,7 +330,9 @@ def get_prompt(
330
330
  """Fetch a managed prompt by name.
331
331
 
332
332
  Follows the ``production`` label by default; pass ``label`` for a different
333
- one or ``version`` to pin exactly.
333
+ one or ``version`` to pin exactly. ``version`` may be the integer
334
+ ``version_no`` or a ``version_hash`` (full, or an unambiguous short prefix
335
+ like ``a3f9c2``) — the hash is the stable identity, so prefer it.
334
336
 
335
337
  Cached for 60s with stale-while-revalidate, so a Trodo outage degrades
336
338
  instead of taking your app down. Pass ``fallback`` to cover cold start::
@@ -74,8 +74,12 @@ def build_scope(
74
74
  ) -> Dict[str, Any]:
75
75
  """Build the render scope from declarations + caller values.
76
76
 
77
- Resolution order: caller value declared default required-and-absent
78
- raises optional-and-absent renders empty.
77
+ Resolution order: caller value -> declared default -> required-and-absent
78
+ raises -> optional-and-absent renders empty.
79
+
80
+ `required` is enforced HERE, in your process, before the model call -- Trodo
81
+ only stores the declaration. A variable with a default can never fail this
82
+ check, because the default is itself a value.
79
83
  """
80
84
  errors: List[str] = []
81
85
  scope: Dict[str, Any] = {}
@@ -100,11 +104,24 @@ def build_scope(
100
104
  has = name in values
101
105
  value = values.get(name) if has else None
102
106
 
103
- # Missing value -> the declared default, or empty. Nothing is
104
- # "required": compile always succeeds with whatever the caller provides.
107
+ # `required` asks a narrower question than default-substitution does:
108
+ # did the caller supply something that will actually render? None and ""
109
+ # both render as nothing, the very outcome the flag exists to prevent,
110
+ # so neither counts. (0 and False do -- they render.)
111
+ supplied = has and value is not None and value != ""
112
+
113
+ # Missing value -> the declared default, or empty.
105
114
  if not has or value is None:
106
115
  value = default if has_default else ([] if type_ == "messages" else "")
107
116
 
117
+ # A default always satisfies the requirement, so the two never both
118
+ # apply; the editor won't let you author both, but a hand-written
119
+ # declaration can.
120
+ required = v.get("required") if isinstance(v, dict) else getattr(v, "required", None)
121
+ if not supplied and required is True and not has_default:
122
+ errors.append(f"variable '{name}': required, but no value was provided")
123
+ continue
124
+
108
125
  scope[name] = _coerce(value, type_, name, errors)
109
126
 
110
127
  # Passing something the prompt doesn't declare is nearly always a rename
@@ -29,6 +29,16 @@ class PromptVariable:
29
29
  regex, with no defaults and no required-ness. The declaration is what lets
30
30
  ``compile()`` fail before the model call rather than shipping a literal
31
31
  ``{{typo}}`` and finding out from the bill.
32
+
33
+ ``required`` is enforced by ``compile()``, which runs in YOUR process —
34
+ Trodo only stores the declaration. Nothing server-side rejects a call for a
35
+ missing variable, and nothing rejects a version for declaring one.
36
+
37
+ Marking a variable required means the caller must pass a value that will
38
+ actually render: an omitted key, ``None`` and ``""`` all fail, since all
39
+ three render as nothing (``0`` and ``False`` pass). A variable with
40
+ a ``default`` can never fail the check — the default is a value — so the two
41
+ are mutually exclusive in practice.
32
42
  """
33
43
 
34
44
  name: str
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trodo-python
3
- Version: 2.18.1
3
+ Version: 2.19.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
@@ -7,6 +7,7 @@ tests/test_datasets.py
7
7
  tests/test_end_run.py
8
8
  tests/test_error_enrichment.py
9
9
  tests/test_experiments.py
10
+ tests/test_get_prompt_version_types.py
10
11
  tests/test_llm_usage_cost.py
11
12
  tests/test_multi_agent_shape.py
12
13
  tests/test_processor_methods.py
File without changes
File without changes