braintrust 0.3.15__py3-none-any.whl → 0.4.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.
Files changed (78) hide show
  1. braintrust/_generated_types.py +737 -672
  2. braintrust/audit.py +2 -2
  3. braintrust/cli/eval.py +6 -7
  4. braintrust/cli/push.py +11 -11
  5. braintrust/context.py +12 -17
  6. braintrust/contrib/temporal/__init__.py +16 -27
  7. braintrust/contrib/temporal/test_temporal.py +8 -3
  8. braintrust/devserver/auth.py +8 -8
  9. braintrust/devserver/cache.py +3 -4
  10. braintrust/devserver/cors.py +8 -7
  11. braintrust/devserver/dataset.py +3 -5
  12. braintrust/devserver/eval_hooks.py +7 -6
  13. braintrust/devserver/schemas.py +22 -19
  14. braintrust/devserver/server.py +19 -12
  15. braintrust/devserver/test_cached_login.py +4 -4
  16. braintrust/framework.py +128 -140
  17. braintrust/framework2.py +88 -87
  18. braintrust/functions/invoke.py +66 -59
  19. braintrust/functions/stream.py +3 -2
  20. braintrust/generated_types.py +3 -1
  21. braintrust/git_fields.py +11 -11
  22. braintrust/gitutil.py +2 -3
  23. braintrust/graph_util.py +10 -10
  24. braintrust/id_gen.py +2 -2
  25. braintrust/logger.py +346 -357
  26. braintrust/merge_row_batch.py +10 -9
  27. braintrust/oai.py +21 -20
  28. braintrust/otel/__init__.py +49 -49
  29. braintrust/otel/context.py +16 -30
  30. braintrust/otel/test_distributed_tracing.py +14 -11
  31. braintrust/otel/test_otel_bt_integration.py +32 -31
  32. braintrust/parameters.py +8 -8
  33. braintrust/prompt.py +14 -14
  34. braintrust/prompt_cache/disk_cache.py +5 -4
  35. braintrust/prompt_cache/lru_cache.py +3 -2
  36. braintrust/prompt_cache/prompt_cache.py +13 -14
  37. braintrust/queue.py +4 -4
  38. braintrust/score.py +4 -4
  39. braintrust/serializable_data_class.py +4 -4
  40. braintrust/span_identifier_v1.py +1 -2
  41. braintrust/span_identifier_v2.py +3 -4
  42. braintrust/span_identifier_v3.py +23 -20
  43. braintrust/span_identifier_v4.py +34 -25
  44. braintrust/test_framework.py +16 -6
  45. braintrust/test_helpers.py +5 -5
  46. braintrust/test_id_gen.py +2 -3
  47. braintrust/test_otel.py +61 -53
  48. braintrust/test_queue.py +0 -1
  49. braintrust/test_score.py +1 -3
  50. braintrust/test_span_components.py +29 -44
  51. braintrust/util.py +9 -8
  52. braintrust/version.py +2 -2
  53. braintrust/wrappers/_anthropic_utils.py +4 -4
  54. braintrust/wrappers/agno/__init__.py +3 -4
  55. braintrust/wrappers/agno/agent.py +1 -2
  56. braintrust/wrappers/agno/function_call.py +1 -2
  57. braintrust/wrappers/agno/model.py +1 -2
  58. braintrust/wrappers/agno/team.py +1 -2
  59. braintrust/wrappers/agno/utils.py +12 -12
  60. braintrust/wrappers/anthropic.py +7 -8
  61. braintrust/wrappers/claude_agent_sdk/__init__.py +3 -4
  62. braintrust/wrappers/claude_agent_sdk/_wrapper.py +29 -27
  63. braintrust/wrappers/dspy.py +15 -17
  64. braintrust/wrappers/google_genai/__init__.py +16 -16
  65. braintrust/wrappers/langchain.py +22 -24
  66. braintrust/wrappers/litellm.py +4 -3
  67. braintrust/wrappers/openai.py +15 -15
  68. braintrust/wrappers/pydantic_ai.py +21 -20
  69. braintrust/wrappers/test_agno.py +0 -1
  70. braintrust/wrappers/test_dspy.py +0 -1
  71. braintrust/wrappers/test_google_genai.py +2 -3
  72. braintrust/wrappers/test_litellm.py +0 -1
  73. {braintrust-0.3.15.dist-info → braintrust-0.4.0.dist-info}/METADATA +3 -2
  74. braintrust-0.4.0.dist-info/RECORD +120 -0
  75. braintrust-0.3.15.dist-info/RECORD +0 -120
  76. {braintrust-0.3.15.dist-info → braintrust-0.4.0.dist-info}/WHEEL +0 -0
  77. {braintrust-0.3.15.dist-info → braintrust-0.4.0.dist-info}/entry_points.txt +0 -0
  78. {braintrust-0.3.15.dist-info → braintrust-0.4.0.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,6 @@ when created in mixed contexts.
8
8
  import os
9
9
 
10
10
  import pytest
11
-
12
11
  from braintrust import current_span
13
12
  from braintrust.logger import _internal_with_memory_background_logger
14
13
  from braintrust.otel import BraintrustSpanProcessor
@@ -20,6 +19,7 @@ try:
20
19
  from opentelemetry.sdk.trace.export import SimpleSpanProcessor
21
20
  from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
22
21
  except ImportError:
22
+
23
23
  class InMemorySpanExporter:
24
24
  def __init__(self):
25
25
  pass
@@ -44,17 +44,17 @@ class OtelFixture:
44
44
 
45
45
  @pytest.fixture
46
46
  def otel_fixture():
47
- """ otel fixture configures everything we need to run mixed otel/bt tracing tests
48
- that export to memory.
47
+ """otel fixture configures everything we need to run mixed otel/bt tracing tests
48
+ that export to memory.
49
49
  """
50
50
  if not OTEL_AVAILABLE:
51
51
  pytest.skip("OpenTelemetry not installed")
52
52
 
53
- with preserve_env_vars('BRAINTRUST_OTEL_COMPAT', 'BRAINTRUST_API_KEY'):
53
+ with preserve_env_vars("BRAINTRUST_OTEL_COMPAT", "BRAINTRUST_API_KEY"):
54
54
  # 1. Set environment variable first
55
- os.environ['BRAINTRUST_OTEL_COMPAT'] = 'true'
55
+ os.environ["BRAINTRUST_OTEL_COMPAT"] = "true"
56
56
  # Set dummy API key for tests
57
- os.environ['BRAINTRUST_API_KEY'] = 'test-api-key-for-fixture'
57
+ os.environ["BRAINTRUST_API_KEY"] = "test-api-key-for-fixture"
58
58
 
59
59
  # 2. Set up memory logger with proper context manager
60
60
  with _internal_with_memory_background_logger() as memory_logger:
@@ -109,8 +109,8 @@ def test_mixed_otel_bt_tracing_with_bt_logger_first(otel_fixture):
109
109
  s2 = otel_spans_by_name["2"]
110
110
 
111
111
  # Verify unified trace IDs - convert OTEL trace to hex string for comparison
112
- s2_trace_id = format(s2.context.trace_id, '032x')
113
- s2_span_id = format(s2.context.span_id, '016x')
112
+ s2_trace_id = format(s2.context.trace_id, "032x")
113
+ s2_span_id = format(s2.context.span_id, "016x")
114
114
 
115
115
  assert s1["root_span_id"] == s2_trace_id
116
116
  assert s1["root_span_id"] == s3["root_span_id"]
@@ -151,8 +151,8 @@ def test_mixed_otel_bt_tracing_with_experiment_parent(otel_fixture):
151
151
  s1, s2, s3 = spans_by_name["1"], spans_by_name["2"], spans_by_name["3"]
152
152
 
153
153
  # Verify unified trace IDs - convert OTEL trace to hex string for comparison
154
- s2_trace_id = format(s2.context.trace_id, '032x')
155
- s2_span_id = format(s2.context.span_id, '016x')
154
+ s2_trace_id = format(s2.context.trace_id, "032x")
155
+ s2_span_id = format(s2.context.span_id, "016x")
156
156
 
157
157
  assert s1["root_span_id"] == s2_trace_id
158
158
  assert s1["root_span_id"] == s3["root_span_id"]
@@ -193,10 +193,10 @@ def test_mixed_otel_bt_tracing_with_otel_first(otel_fixture):
193
193
  s1, s2, s3 = spans_by_name["1"], spans_by_name["2"], spans_by_name["3"]
194
194
 
195
195
  # Verify unified trace IDs - convert OTEL traces to hex string for comparison
196
- s1_trace_id = format(s1.context.trace_id, '032x')
197
- s1_span_id = format(s1.context.span_id, '016x')
198
- s3_trace_id = format(s3.context.trace_id, '032x')
199
- s3_span_id = format(s3.context.span_id, '016x')
196
+ s1_trace_id = format(s1.context.trace_id, "032x")
197
+ s1_span_id = format(s1.context.span_id, "016x")
198
+ s3_trace_id = format(s3.context.trace_id, "032x")
199
+ s3_span_id = format(s3.context.span_id, "016x")
200
200
 
201
201
  assert s1_trace_id == s2["root_span_id"]
202
202
  assert s1_trace_id == s3_trace_id
@@ -222,14 +222,14 @@ def test_separate_traces_should_not_be_unified(otel_fixture):
222
222
  # Second trace: OTEL only
223
223
  trace2_spans = []
224
224
  with tracer.start_as_current_span("otel_trace2") as otel_span2:
225
- trace2_id = format(otel_span2.context.trace_id, '032x')
225
+ trace2_id = format(otel_span2.context.trace_id, "032x")
226
226
  trace2_spans.append(trace2_id)
227
227
  otel_span2.set_attribute("test", "second_trace")
228
228
 
229
229
  # Third trace: OTEL root with BT child
230
230
  trace3_spans = []
231
231
  with tracer.start_as_current_span("otel_trace3_root") as otel_span3:
232
- otel3_trace_id = format(otel_span3.context.trace_id, '032x')
232
+ otel3_trace_id = format(otel_span3.context.trace_id, "032x")
233
233
  trace3_spans.append(otel3_trace_id)
234
234
 
235
235
  # BT span inside OTEL - should inherit OTEL trace ID, not previous BT trace
@@ -286,28 +286,28 @@ def test_otel_spans_inherit_parent_attribute(otel_fixture):
286
286
  assert len(bt_spans) == 1
287
287
 
288
288
 
289
-
290
289
  def test_uses_braintrust_context_manager_when_otel_disabled():
291
290
  """Test that BraintrustContextManager is used when OTEL is not enabled."""
292
291
  # Ensure OTEL is disabled
293
- os.environ.pop('BRAINTRUST_OTEL_COMPAT', None)
292
+ os.environ.pop("BRAINTRUST_OTEL_COMPAT", None)
294
293
 
295
294
  try:
296
295
  from braintrust.context import get_context_manager
296
+
297
297
  cm = get_context_manager()
298
298
 
299
299
  # Should be BraintrustContextManager, not OTEL ContextManager
300
300
  assert type(cm).__name__ == "BraintrustContextManager"
301
301
 
302
302
  # Verify it has the expected interface
303
- assert hasattr(cm, 'get_current_span_info')
304
- assert hasattr(cm, 'get_parent_span_ids')
305
- assert hasattr(cm, 'set_current_span')
306
- assert hasattr(cm, 'unset_current_span')
303
+ assert hasattr(cm, "get_current_span_info")
304
+ assert hasattr(cm, "get_parent_span_ids")
305
+ assert hasattr(cm, "set_current_span")
306
+ assert hasattr(cm, "unset_current_span")
307
307
 
308
308
  finally:
309
309
  # Clean up - remove any environment variable we might have set
310
- os.environ.pop('BRAINTRUST_OTEL_COMPAT', None)
310
+ os.environ.pop("BRAINTRUST_OTEL_COMPAT", None)
311
311
 
312
312
 
313
313
  def test_uses_otel_context_manager_when_enabled():
@@ -315,21 +315,22 @@ def test_uses_otel_context_manager_when_enabled():
315
315
  if not OTEL_AVAILABLE:
316
316
  pytest.skip("OpenTelemetry not installed")
317
317
 
318
- with preserve_env_vars('BRAINTRUST_OTEL_COMPAT'):
318
+ with preserve_env_vars("BRAINTRUST_OTEL_COMPAT"):
319
319
  # Enable OTEL
320
- os.environ['BRAINTRUST_OTEL_COMPAT'] = 'true'
320
+ os.environ["BRAINTRUST_OTEL_COMPAT"] = "true"
321
321
 
322
322
  from braintrust.context import get_context_manager
323
+
323
324
  cm = get_context_manager()
324
325
 
325
326
  # Should be OTEL ContextManager, not BraintrustContextManager
326
327
  assert type(cm).__name__ == "ContextManager"
327
328
 
328
329
  # Verify it has the expected interface
329
- assert hasattr(cm, 'get_current_span_info')
330
- assert hasattr(cm, 'get_parent_span_ids')
331
- assert hasattr(cm, 'set_current_span')
332
- assert hasattr(cm, 'unset_current_span')
330
+ assert hasattr(cm, "get_current_span_info")
331
+ assert hasattr(cm, "get_parent_span_ids")
332
+ assert hasattr(cm, "set_current_span")
333
+ assert hasattr(cm, "unset_current_span")
333
334
 
334
335
 
335
336
  def test_bt_span_without_explicit_parent_inherits_from_otel(otel_fixture):
@@ -359,8 +360,8 @@ def test_bt_span_without_explicit_parent_inherits_from_otel(otel_fixture):
359
360
  otel_parent = otel_spans[0]
360
361
 
361
362
  # Convert OTEL IDs to hex for comparison
362
- otel_trace_id = format(otel_parent.context.trace_id, '032x')
363
- otel_span_id = format(otel_parent.context.span_id, '016x')
363
+ otel_trace_id = format(otel_parent.context.trace_id, "032x")
364
+ otel_span_id = format(otel_parent.context.span_id, "016x")
364
365
 
365
366
  # BT span should have inherited OTEL parent's trace ID as root_span_id
366
367
  assert bt_child["root_span_id"] == otel_trace_id
braintrust/parameters.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Evaluation parameters support for Python SDK."""
2
2
 
3
- from typing import Any, Dict, Optional, TypedDict, Union
3
+ from typing import Any, TypedDict
4
4
 
5
5
  from typing_extensions import NotRequired
6
6
 
@@ -12,17 +12,17 @@ class PromptParameter(TypedDict):
12
12
  """A prompt parameter specification."""
13
13
 
14
14
  type: str # Literal["prompt"] but using str for flexibility
15
- default: NotRequired[Optional[PromptData]]
16
- description: NotRequired[Optional[str]]
15
+ default: NotRequired[PromptData | None]
16
+ description: NotRequired[str | None]
17
17
 
18
18
 
19
19
  # EvalParameters is a dict where values can be either:
20
20
  # - A PromptParameter (dict with type="prompt")
21
21
  # - A pydantic model class (typed as Any for now)
22
- EvalParameters = Dict[str, Union[PromptParameter, Any]]
22
+ EvalParameters = dict[str, PromptParameter | Any]
23
23
 
24
24
 
25
- def _pydantic_to_json_schema(model: Any) -> Dict[str, Any]:
25
+ def _pydantic_to_json_schema(model: Any) -> dict[str, Any]:
26
26
  """Convert a pydantic model to JSON schema."""
27
27
  if hasattr(model, "model_json_schema"):
28
28
  # pydantic 2
@@ -35,9 +35,9 @@ def _pydantic_to_json_schema(model: Any) -> Dict[str, Any]:
35
35
 
36
36
 
37
37
  def validate_parameters(
38
- parameters: Dict[str, Any],
38
+ parameters: dict[str, Any],
39
39
  parameter_schema: EvalParameters,
40
- ) -> Dict[str, Any]:
40
+ ) -> dict[str, Any]:
41
41
  """
42
42
  Validate parameters against the schema.
43
43
 
@@ -120,7 +120,7 @@ def validate_parameters(
120
120
  return result
121
121
 
122
122
 
123
- def parameters_to_json_schema(parameters: EvalParameters) -> Dict[str, Any]:
123
+ def parameters_to_json_schema(parameters: EvalParameters) -> dict[str, Any]:
124
124
  """
125
125
  Convert EvalParameters to JSON schema format for serialization.
126
126
 
braintrust/prompt.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import List, Literal, Optional, Union
2
+ from typing import Literal, Union
3
3
 
4
4
  from .generated_types import PromptOptions
5
5
  from .serializable_data_class import SerializableDataClass
@@ -46,17 +46,17 @@ class ImagePart(SerializableDataClass):
46
46
 
47
47
  @dataclass
48
48
  class PromptMessage(SerializableDataClass):
49
- content: Union[str, List[Union[TextPart, ImagePart]]]
49
+ content: str | list[TextPart | ImagePart]
50
50
  role: Literal["system", "user", "assistant", "function", "tool", "model"]
51
- name: Optional[str] = None
52
- function_call: Union[str, FunctionCall, None] = None
53
- tool_calls: Optional[List[ToolCall]] = None
51
+ name: str | None = None
52
+ function_call: str | FunctionCall | None = None
53
+ tool_calls: list[ToolCall] | None = None
54
54
 
55
55
 
56
56
  @dataclass
57
57
  class PromptChatBlock(SerializableDataClass):
58
- messages: List[PromptMessage]
59
- tools: Optional[str] = None
58
+ messages: list[PromptMessage]
59
+ tools: str | None = None
60
60
  type: Literal["chat"] = "chat"
61
61
 
62
62
 
@@ -65,20 +65,20 @@ PromptBlockData = Union[PromptCompletionBlock, PromptChatBlock]
65
65
 
66
66
  @dataclass
67
67
  class PromptData(SerializableDataClass):
68
- prompt: Optional[PromptBlockData] = None
69
- options: Optional[PromptOptions] = None
68
+ prompt: PromptBlockData | None = None
69
+ options: PromptOptions | None = None
70
70
 
71
71
 
72
72
  @dataclass
73
73
  class PromptSchema(SerializableDataClass):
74
- id: Optional[str]
75
- project_id: Optional[str]
76
- _xact_id: Optional[str]
74
+ id: str | None
75
+ project_id: str | None
76
+ _xact_id: str | None
77
77
  name: str
78
78
  slug: str
79
- description: Optional[str]
79
+ description: str | None
80
80
  prompt_data: PromptData
81
- tags: Optional[List[str]]
81
+ tags: list[str] | None
82
82
 
83
83
 
84
84
  BRAINTRUST_PARAMS = ["use_cache"]
@@ -12,7 +12,8 @@ import hashlib
12
12
  import json
13
13
  import logging
14
14
  import os
15
- from typing import Any, Callable, Generic, Optional, TypeVar
15
+ from collections.abc import Callable
16
+ from typing import Any, Generic, TypeVar
16
17
 
17
18
  T = TypeVar("T")
18
19
 
@@ -36,9 +37,9 @@ class DiskCache(Generic[T]):
36
37
  def __init__(
37
38
  self,
38
39
  cache_dir: str,
39
- max_size: Optional[int] = None,
40
- serializer: Optional[Callable[[T], Any]] = None,
41
- deserializer: Optional[Callable[[Any], T]] = None,
40
+ max_size: int | None = None,
41
+ serializer: Callable[[T], Any] | None = None,
42
+ deserializer: Callable[[Any], T] | None = None,
42
43
  log_warnings: bool = True,
43
44
  mkdirs: bool = True,
44
45
  ):
@@ -7,7 +7,8 @@ used items when it reaches a maximum size. The implementation uses an OrderedDic
7
7
  for O(1) access and update operations.
8
8
  """
9
9
 
10
- from typing import Generic, Optional, OrderedDict, TypeVar
10
+ from collections import OrderedDict
11
+ from typing import Generic, TypeVar
11
12
 
12
13
  K = TypeVar("K")
13
14
  V = TypeVar("V")
@@ -28,7 +29,7 @@ class LRUCache(Generic[K, V]):
28
29
  If not specified, the cache will grow unbounded.
29
30
  """
30
31
 
31
- def __init__(self, max_size: Optional[int] = None):
32
+ def __init__(self, max_size: int | None = None):
32
33
  self._cache: OrderedDict[K, V] = OrderedDict()
33
34
  self._max_size = max_size
34
35
 
@@ -9,18 +9,17 @@ This allows for efficient prompt retrieval while maintaining persistence across
9
9
  The cache is keyed by project identifier (ID or name), prompt slug, and version.
10
10
  """
11
11
 
12
- from typing import Optional
13
12
 
14
13
  from braintrust import prompt
15
14
  from braintrust.prompt_cache import disk_cache, lru_cache
16
15
 
17
16
 
18
17
  def _create_cache_key(
19
- project_id: Optional[str],
20
- project_name: Optional[str],
21
- slug: Optional[str],
18
+ project_id: str | None,
19
+ project_name: str | None,
20
+ slug: str | None,
22
21
  version: str = "latest",
23
- id: Optional[str] = None,
22
+ id: str | None = None,
24
23
  ) -> str:
25
24
  """Creates a unique cache key from project identifier, slug and version, or from ID."""
26
25
  if id:
@@ -47,7 +46,7 @@ class PromptCache:
47
46
  def __init__(
48
47
  self,
49
48
  memory_cache: lru_cache.LRUCache[str, prompt.PromptSchema],
50
- disk_cache: Optional[disk_cache.DiskCache[prompt.PromptSchema]] = None,
49
+ disk_cache: disk_cache.DiskCache[prompt.PromptSchema] | None = None,
51
50
  ):
52
51
  """
53
52
  Initialize the prompt cache.
@@ -61,11 +60,11 @@ class PromptCache:
61
60
 
62
61
  def get(
63
62
  self,
64
- slug: Optional[str] = None,
63
+ slug: str | None = None,
65
64
  version: str = "latest",
66
- project_id: Optional[str] = None,
67
- project_name: Optional[str] = None,
68
- id: Optional[str] = None,
65
+ project_id: str | None = None,
66
+ project_name: str | None = None,
67
+ id: str | None = None,
69
68
  ) -> prompt.PromptSchema:
70
69
  """
71
70
  Retrieve a prompt from the cache.
@@ -107,11 +106,11 @@ class PromptCache:
107
106
  def set(
108
107
  self,
109
108
  value: prompt.PromptSchema,
110
- slug: Optional[str] = None,
109
+ slug: str | None = None,
111
110
  version: str = "latest",
112
- project_id: Optional[str] = None,
113
- project_name: Optional[str] = None,
114
- id: Optional[str] = None,
111
+ project_id: str | None = None,
112
+ project_name: str | None = None,
113
+ id: str | None = None,
115
114
  ) -> None:
116
115
  """
117
116
  Store a prompt in the cache.
braintrust/queue.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import threading
2
2
  from collections import deque
3
- from typing import List, Optional, TypeVar
3
+ from typing import TypeVar
4
4
 
5
5
  from .util import eprint
6
6
 
@@ -46,7 +46,7 @@ class LogQueue:
46
46
  with self._mutex:
47
47
  self._enforce_size_limit = enforce
48
48
 
49
- def put(self, item: T) -> List[T]:
49
+ def put(self, item: T) -> list[T]:
50
50
  """
51
51
  Put an item in the queue.
52
52
 
@@ -76,7 +76,7 @@ class LogQueue:
76
76
 
77
77
  return dropped
78
78
 
79
- def drain_all(self) -> List[T]:
79
+ def drain_all(self) -> list[T]:
80
80
  """
81
81
  Drain all items from the queue.
82
82
 
@@ -105,7 +105,7 @@ class LogQueue:
105
105
  """
106
106
  return len(self._queue)
107
107
 
108
- def wait_for_items(self, timeout: Optional[float] = None) -> bool:
108
+ def wait_for_items(self, timeout: float | None = None) -> bool:
109
109
  """
110
110
  Will block until the queue has at least one item in it. Might be empty by the time
111
111
  you read though.
braintrust/score.py CHANGED
@@ -2,7 +2,7 @@ import dataclasses
2
2
  import inspect
3
3
  import warnings
4
4
  from abc import ABC, abstractmethod
5
- from typing import Any, Dict, Optional
5
+ from typing import Any
6
6
 
7
7
  from .serializable_data_class import SerializableDataClass
8
8
 
@@ -22,14 +22,14 @@ class Score(SerializableDataClass):
22
22
  name: str
23
23
  """The name of the score. This should be a unique name for the scorer."""
24
24
 
25
- score: Optional[float] = None
25
+ score: float | None = None
26
26
  """The score for the evaluation. This should be a float between 0 and 1. If the score is None, the evaluation is considered to be skipped."""
27
27
 
28
- metadata: Dict[str, Any] = dataclasses.field(default_factory=dict)
28
+ metadata: dict[str, Any] = dataclasses.field(default_factory=dict)
29
29
  """Metadata for the score. This can be used to store additional information about the score."""
30
30
 
31
31
  # DEPRECATION_NOTICE: this field is deprecated, as errors are propagated up to the caller.
32
- error: Optional[Exception] = None
32
+ error: Exception | None = None
33
33
  """Deprecated: The error field is deprecated, as errors are now propagated to the caller. The field will be removed in a future version of the library."""
34
34
 
35
35
  def as_dict(self):
@@ -1,6 +1,6 @@
1
1
  import dataclasses
2
2
  import json
3
- from typing import Dict, Union, get_origin
3
+ from typing import Union, get_origin
4
4
 
5
5
 
6
6
  class SerializableDataClass:
@@ -16,15 +16,15 @@ class SerializableDataClass:
16
16
  return getattr(self, item)
17
17
 
18
18
  @classmethod
19
- def from_dict(cls, d: Dict):
19
+ def from_dict(cls, d: dict):
20
20
  """Deserialize the object from a dictionary. This method
21
21
  is shallow and will not call from_dict() on nested objects."""
22
- fields = set(f.name for f in dataclasses.fields(cls))
22
+ fields = {f.name for f in dataclasses.fields(cls)}
23
23
  filtered = {k: v for k, v in d.items() if k in fields}
24
24
  return cls(**filtered)
25
25
 
26
26
  @classmethod
27
- def from_dict_deep(cls, d: Dict):
27
+ def from_dict_deep(cls, d: dict):
28
28
  """Deserialize the object from a dictionary. This method
29
29
  is deep and will call from_dict_deep() on nested objects."""
30
30
  fields = {f.name: f for f in dataclasses.fields(cls)}
@@ -5,7 +5,6 @@
5
5
  import base64
6
6
  import dataclasses
7
7
  from enum import Enum, auto
8
- from typing import Optional
9
8
  from uuid import UUID
10
9
 
11
10
 
@@ -50,7 +49,7 @@ class SpanRowIdsV1:
50
49
  class SpanComponentsV1:
51
50
  object_type: SpanObjectTypeV1
52
51
  object_id: str
53
- row_ids: Optional[SpanRowIdsV1] = None
52
+ row_ids: SpanRowIdsV1 | None = None
54
53
 
55
54
  def __post_init__(self):
56
55
  assert isinstance(self.object_type, SpanObjectTypeV1)
@@ -6,7 +6,6 @@ import base64
6
6
  import dataclasses
7
7
  import json
8
8
  from enum import Enum
9
- from typing import Dict, Optional
10
9
  from uuid import UUID
11
10
 
12
11
  from .span_identifier_v1 import SpanComponentsV1
@@ -54,9 +53,9 @@ class SpanRowIdsV2:
54
53
  @dataclasses.dataclass
55
54
  class SpanComponentsV2:
56
55
  object_type: SpanObjectTypeV2
57
- object_id: Optional[str] = None
58
- compute_object_metadata_args: Optional[Dict] = None
59
- row_ids: Optional[SpanRowIdsV2] = None
56
+ object_id: str | None = None
57
+ compute_object_metadata_args: dict | None = None
58
+ row_ids: SpanRowIdsV2 | None = None
60
59
 
61
60
  def __post_init__(self):
62
61
  assert isinstance(self.object_type, SpanObjectTypeV2)
@@ -6,7 +6,6 @@ import base64
6
6
  import dataclasses
7
7
  import json
8
8
  from enum import Enum
9
- from typing import Dict, Optional, Union
10
9
  from uuid import UUID
11
10
 
12
11
  from .span_identifier_v2 import SpanComponentsV2
@@ -59,16 +58,16 @@ class SpanComponentsV3:
59
58
  object_type: SpanObjectTypeV3
60
59
 
61
60
  # Must provide one or the other.
62
- object_id: Optional[str] = None
63
- compute_object_metadata_args: Optional[Dict] = None
61
+ object_id: str | None = None
62
+ compute_object_metadata_args: dict | None = None
64
63
 
65
64
  # Either all of these must be provided or none.
66
- row_id: Optional[str] = None
67
- span_id: Optional[str] = None
68
- root_span_id: Optional[str] = None
65
+ row_id: str | None = None
66
+ span_id: str | None = None
67
+ root_span_id: str | None = None
69
68
 
70
69
  # Additional span properties.
71
- propagated_event: Optional[Dict] = None
70
+ propagated_event: dict | None = None
72
71
 
73
72
  def __post_init__(self):
74
73
  assert isinstance(self.object_type, SpanObjectTypeV3)
@@ -173,7 +172,7 @@ class SpanComponentsV3:
173
172
  except Exception:
174
173
  raise Exception(INVALID_ENCODING_ERRMSG)
175
174
 
176
- def object_id_fields(self) -> Dict[str, str]:
175
+ def object_id_fields(self) -> dict[str, str]:
177
176
  if not self.object_id:
178
177
  raise Exception(
179
178
  "Impossible: cannot invoke `object_id_fields` unless SpanComponentsV3 is initialized with an `object_id`"
@@ -192,7 +191,7 @@ class SpanComponentsV3:
192
191
  return self.to_str()
193
192
 
194
193
  @staticmethod
195
- def _from_json_obj(json_obj: Dict) -> "SpanComponentsV3":
194
+ def _from_json_obj(json_obj: dict) -> "SpanComponentsV3":
196
195
  kwargs = {
197
196
  **json_obj,
198
197
  "object_type": SpanObjectTypeV3(json_obj["object_type"]),
@@ -200,7 +199,7 @@ class SpanComponentsV3:
200
199
  return SpanComponentsV3(**kwargs)
201
200
 
202
201
 
203
- def parse_parent(parent: Union[str, Dict, None]) -> Optional[str]:
202
+ def parse_parent(parent: str | dict | None) -> str | None:
204
203
  """
205
204
  Parse a parent object into a string representation.
206
205
 
@@ -235,17 +234,21 @@ def parse_parent(parent: Union[str, Dict, None]) -> Optional[str]:
235
234
  # Handle row_ids if present
236
235
  row_ids = parent.get("row_ids")
237
236
  if row_ids:
238
- kwargs.update({
239
- "row_id": row_ids.get("id"),
240
- "span_id": row_ids.get("span_id"),
241
- "root_span_id": row_ids.get("root_span_id"),
242
- })
237
+ kwargs.update(
238
+ {
239
+ "row_id": row_ids.get("id"),
240
+ "span_id": row_ids.get("span_id"),
241
+ "root_span_id": row_ids.get("root_span_id"),
242
+ }
243
+ )
243
244
  else:
244
- kwargs.update({
245
- "row_id": None,
246
- "span_id": None,
247
- "root_span_id": None,
248
- })
245
+ kwargs.update(
246
+ {
247
+ "row_id": None,
248
+ "span_id": None,
249
+ "root_span_id": None,
250
+ }
251
+ )
249
252
 
250
253
  # Include propagated_event if present
251
254
  if "propagated_event" in parent: