Skip to content

Commit 4de530c

Browse files
feat(platform): promote verbosityLevel attribute to top-level VerbosityLevel field (#1627)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7901128 commit 4de530c

8 files changed

Lines changed: 117 additions & 7 deletions

File tree

packages/uipath-platform/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-platform"
3-
version = "0.1.52"
3+
version = "0.1.53"
44
description = "HTTP client library for programmatic access to UiPath Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath-platform/src/uipath/platform/common/_span_utils.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ class AttachmentDirection(IntEnum):
2929
OUT = 2
3030

3131

32+
class VerbosityLevel(IntEnum):
33+
VERBOSE = 0
34+
TRACE = 1
35+
INFORMATION = 2
36+
WARNING = 3
37+
ERROR = 4
38+
CRITICAL = 5
39+
OFF = 6
40+
41+
3242
class SpanAttachment(BaseModel):
3343
"""Represents an attachment in the UiPath tracing system."""
3444

@@ -87,6 +97,7 @@ class UiPathSpan:
8797
# Top-level fields for internal tracing schema
8898
execution_type: Optional[int] = None
8999
agent_version: Optional[str] = None
100+
verbosity_level: Optional[int] = None
90101
attachments: Optional[List[SpanAttachment]] = None
91102

92103
def to_dict(self, serialize_attributes: bool = True) -> Dict[str, Any]:
@@ -114,7 +125,7 @@ def to_dict(self, serialize_attributes: bool = True) -> Dict[str, Any]:
114125
for att in self.attachments
115126
]
116127

117-
return {
128+
result: Dict[str, Any] = {
118129
"Id": self.id,
119130
"TraceId": self.trace_id,
120131
"ParentId": self.parent_id,
@@ -138,6 +149,9 @@ def to_dict(self, serialize_attributes: bool = True) -> Dict[str, Any]:
138149
"AgentVersion": self.agent_version,
139150
"Attachments": attachments_out,
140151
}
152+
if self.verbosity_level is not None:
153+
result["VerbosityLevel"] = self.verbosity_level
154+
return result
141155

142156

143157
class _SpanUtils:
@@ -284,6 +298,7 @@ def otel_span_to_uipath_span(
284298
execution_type = attributes_dict.get("executionType")
285299
agent_version = attributes_dict.get("agentVersion")
286300
reference_id = env.get("UIPATH_AGENT_ID") or attributes_dict.get("agentId")
301+
verbosity_level = attributes_dict.get("verbosityLevel")
287302

288303
# Source: override via uipath.source attribute, else DEFAULT_SOURCE
289304
uipath_source = attributes_dict.get("uipath.source")
@@ -334,6 +349,7 @@ def otel_span_to_uipath_span(
334349
span_type=span_type,
335350
execution_type=execution_type,
336351
agent_version=agent_version,
352+
verbosity_level=verbosity_level,
337353
reference_id=reference_id,
338354
source=source,
339355
attachments=attachments,

packages/uipath-platform/tests/services/test_span_utils.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,85 @@
1010
from uipath.platform.common import UiPathSpan, _SpanUtils
1111

1212

13+
class TestOTelToUiPathSpan:
14+
"""OTEL attribute -> top-level UiPathSpan field mapping.
15+
16+
`_SpanUtils.otel_span_to_uipath_span` lifts a small set of OTEL
17+
span attributes onto dedicated `UiPathSpan` fields surfaced under
18+
`to_dict()`. This test documents that mapping — adding a new row
19+
means the attribute is newly mapped, removing one breaks
20+
downstream consumers.
21+
"""
22+
23+
ATTRIBUTE_FIELD_MAP = [
24+
("executionType", "execution_type", "ExecutionType", 1),
25+
("agentVersion", "agent_version", "AgentVersion", "1.2.3"),
26+
("agentId", "reference_id", "ReferenceId", "ref-abc"),
27+
("verbosityLevel", "verbosity_level", "VerbosityLevel", 6),
28+
]
29+
30+
@patch.dict(os.environ, {"UIPATH_ORGANIZATION_ID": "test-org"})
31+
def test_attributes_map_to_top_level_fields(self) -> None:
32+
attrs = {
33+
otel_attr: value for otel_attr, _, _, value in self.ATTRIBUTE_FIELD_MAP
34+
}
35+
36+
mock_span = Mock(spec=OTelSpan)
37+
mock_context = SpanContext(
38+
trace_id=0x123456789ABCDEF0123456789ABCDEF0,
39+
span_id=0x0123456789ABCDEF,
40+
is_remote=False,
41+
)
42+
mock_span.get_span_context.return_value = mock_context
43+
mock_span.name = "test-span"
44+
mock_span.parent = None
45+
mock_span.status.status_code = StatusCode.OK
46+
mock_span.attributes = attrs
47+
mock_span.events = []
48+
mock_span.links = []
49+
now_ns = int(datetime.now().timestamp() * 1e9)
50+
mock_span.start_time = now_ns
51+
mock_span.end_time = now_ns + 1_000_000
52+
53+
uipath_span = _SpanUtils.otel_span_to_uipath_span(mock_span)
54+
span_dict = uipath_span.to_dict()
55+
56+
for _, span_field, top_level_key, value in self.ATTRIBUTE_FIELD_MAP:
57+
assert getattr(uipath_span, span_field) == value, span_field
58+
assert span_dict[top_level_key] == value, top_level_key
59+
60+
@patch.dict(os.environ, {"UIPATH_ORGANIZATION_ID": "test-org"})
61+
def test_verbosity_level_omitted_when_unset(self) -> None:
62+
"""Spans that don't set verbosityLevel must not carry the key on the wire.
63+
64+
Backwards compat: pre-existing spans never emitted VerbosityLevel; the
65+
LLMOps backend applies its own default. Adding `"VerbosityLevel": null`
66+
unconditionally would change the wire format for every existing span.
67+
"""
68+
mock_span = Mock(spec=OTelSpan)
69+
mock_context = SpanContext(
70+
trace_id=0x123456789ABCDEF0123456789ABCDEF0,
71+
span_id=0x0123456789ABCDEF,
72+
is_remote=False,
73+
)
74+
mock_span.get_span_context.return_value = mock_context
75+
mock_span.name = "legacy-span"
76+
mock_span.parent = None
77+
mock_span.status.status_code = StatusCode.OK
78+
mock_span.attributes = {"someOtherAttr": "value"}
79+
mock_span.events = []
80+
mock_span.links = []
81+
now_ns = int(datetime.now().timestamp() * 1e9)
82+
mock_span.start_time = now_ns
83+
mock_span.end_time = now_ns + 1_000_000
84+
85+
uipath_span = _SpanUtils.otel_span_to_uipath_span(mock_span)
86+
span_dict = uipath_span.to_dict()
87+
88+
assert uipath_span.verbosity_level is None
89+
assert "VerbosityLevel" not in span_dict
90+
91+
1392
class TestNormalizeIds:
1493
"""Tests for OTEL ID normalization functions."""
1594

packages/uipath-platform/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/uipath/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
[project]
22
name = "uipath"
3-
version = "2.10.66"
3+
version = "2.10.67"
44
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"
77
dependencies = [
88
"uipath-core>=0.5.8, <0.6.0",
99
"uipath-runtime>=0.10.1, <0.11.0",
10-
"uipath-platform>=0.1.47, <0.2.0",
10+
"uipath-platform>=0.1.53, <0.2.0",
1111
"click>=8.3.1",
1212
"httpx>=0.28.1",
1313
"pyjwt>=2.10.1",

packages/uipath/src/uipath/tracing/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
AttachmentDirection,
66
AttachmentProvider,
77
SpanAttachment,
8+
VerbosityLevel,
89
)
910

1011
from ._live_tracking_processor import LiveTrackingSpanProcessor
@@ -23,4 +24,5 @@
2324
"AttachmentDirection",
2425
"AttachmentProvider",
2526
"SpanAttachment",
27+
"VerbosityLevel",
2628
]

packages/uipath/tests/tracing/test_otel_exporters.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,5 +810,18 @@ def test_none_stays_none(self, mock_env_vars, mock_span):
810810
assert payload["ProcessKey"] is None
811811

812812

813+
class TestVerbosityLevelReexport:
814+
"""VerbosityLevel from uipath-platform is re-exported via uipath.tracing."""
815+
816+
def test_uipath_tracing_reexports_verbosity_level(self) -> None:
817+
from uipath.platform.common._span_utils import (
818+
VerbosityLevel as _CommonVerbosity,
819+
)
820+
from uipath.tracing import VerbosityLevel as _TracingVerbosity
821+
822+
assert _TracingVerbosity is _CommonVerbosity
823+
assert _TracingVerbosity.OFF == 6
824+
825+
813826
if __name__ == "__main__":
814827
unittest.main()

packages/uipath/uv.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)