telemetry-dev 0.2.8__tar.gz → 0.2.9__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: telemetry-dev
3
- Version: 0.2.8
3
+ Version: 0.2.9
4
4
  Summary: telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and metrics
5
5
  Keywords: telemetry,opentelemetry,llm,genai,tracing,observability
6
6
  Author: telemetry.dev
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "telemetry-dev"
3
- version = "0.2.8"
3
+ version = "0.2.9"
4
4
  description = "telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and metrics"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "telemetry-dev"
3
- version = "0.2.8"
3
+ version = "0.2.9"
4
4
  description = "telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and metrics"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import re
5
6
  from collections.abc import Iterable, Mapping, Sequence
6
7
  from itertools import chain
7
8
  from typing import Final, cast
@@ -11,6 +12,36 @@ _DEFAULT_MAX_ITEMS: Final = 1024
11
12
  _MAX_DEPTH: Final = 32
12
13
  _BASE_ITEM_BYTES: Final = 16
13
14
 
15
+ # Code points whose JSON form is longer than their UTF-8 form, mapped to the extra bytes.
16
+ _SHORT_ESCAPE_EXTRA: Final = 1 # '"', '\\', \b \t \n \f \r: 1 byte raw, 2 escaped
17
+ _CONTROL_ESCAPE_EXTRA: Final = 5 # other code points below 0x20: 1 byte raw, 6 as \u00XX
18
+ _SURROGATE_ESCAPE_EXTRA: Final = 3 # a surrogate code point: 3 bytes raw, 6 as \uXXXX
19
+ _JSON_ESCAPED: Final = re.compile('["\\\\\x00-\x1f\ud800-\udfff]')
20
+ _JSON_STRING_CHUNK: Final = 4096
21
+
22
+
23
+ def _escape_extra(character: str) -> int:
24
+ code = ord(character)
25
+ if character in '"\\\b\t\n\f\r':
26
+ return _SHORT_ESCAPE_EXTRA
27
+ if code < 0x20:
28
+ return _CONTROL_ESCAPE_EXTRA
29
+ return _SURROGATE_ESCAPE_EXTRA
30
+
31
+
32
+ def _json_string_bytes(value: str, limit: int | None) -> int:
33
+ # Every code point costs at least one byte, so a string longer than the limit needs no scan.
34
+ if limit is not None and len(value) > limit:
35
+ return len(value)
36
+ size = 0
37
+ for start in range(0, len(value), _JSON_STRING_CHUNK):
38
+ chunk = value[start : start + _JSON_STRING_CHUNK]
39
+ size += len(chunk.encode("utf-8", "surrogatepass"))
40
+ size += sum(_escape_extra(match.group()) for match in _JSON_ESCAPED.finditer(chunk))
41
+ if limit is not None and size > limit:
42
+ return size
43
+ return size
44
+
14
45
 
15
46
  class CaptureBudget:
16
47
  """Track a hard byte and item budget before retaining streamed provider data."""
@@ -32,6 +63,18 @@ class CaptureBudget:
32
63
  max_bytes=client.max_attribute_length if client is not None else _DEFAULT_MAX_BYTES
33
64
  )
34
65
 
66
+ @staticmethod
67
+ def json_string_bytes(value: str, limit: int | None = None) -> int:
68
+ """Return the UTF-8 bytes ``value`` occupies inside a JSON string, without the quotes.
69
+
70
+ Matches the TypeScript SDK's measure: ``"``, ``\\``, and ``\\b \\t \\n \\f \\r`` cost 2
71
+ bytes, other control characters cost 6 (``\\u00XX``), and every surrogate code point
72
+ costs 6 (``\\uXXXX``); anything else costs its UTF-8 width. The budget charges a string
73
+ this plus its per-item cost, which covers the quotes and separators. Counting stops
74
+ once the total passes ``limit``, so a result above ``limit`` is only a lower bound.
75
+ """
76
+ return _json_string_bytes(value, limit)
77
+
35
78
  @property
36
79
  def remaining_bytes(self) -> int:
37
80
  """Return bytes still available for retained capture state."""
@@ -98,20 +141,9 @@ class CaptureBudget:
98
141
  if value is None or isinstance(value, bool | int | float):
99
142
  return byte_count, item_count
100
143
  if isinstance(value, str):
101
- for character in value:
102
- codepoint = ord(character)
103
- byte_count += (
104
- 1
105
- if codepoint <= 0x7F
106
- else 2
107
- if codepoint <= 0x7FF
108
- else 3
109
- if codepoint <= 0xFFFF
110
- else 4
111
- )
112
- if byte_count > remaining_bytes:
113
- return None
114
- return byte_count, item_count
144
+ # Mapping keys come through here too, so they are charged at their escaped size.
145
+ byte_count += _json_string_bytes(value, remaining_bytes - byte_count)
146
+ return (byte_count, item_count) if byte_count <= remaining_bytes else None
115
147
  if isinstance(value, bytes | bytearray | memoryview):
116
148
  length = value.nbytes if isinstance(value, memoryview) else len(value)
117
149
  # Structured attributes serialize binary values as base64 text.
File without changes
File without changes