Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath"
version = "2.6.27"
version = "2.6.28"
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
Expand Down
10 changes: 8 additions & 2 deletions src/uipath/tracing/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

logger = logging.getLogger(__name__)

# SourceEnum.Robots = 4 (default for Python SDK / coded agents)
DEFAULT_SOURCE = 4


@dataclass
class UiPathSpan:
Expand Down Expand Up @@ -43,7 +46,7 @@ class UiPathSpan:
folder_key: Optional[str] = field(
default_factory=lambda: env.get("UIPATH_FOLDER_KEY", "")
)
source: Optional[int] = None
source: int = DEFAULT_SOURCE
span_type: str = "Coded Agents"
process_key: Optional[str] = field(
default_factory=lambda: env.get("UIPATH_PROCESS_UUID")
Expand Down Expand Up @@ -248,7 +251,10 @@ def otel_span_to_uipath_span(
execution_type = attributes_dict.get("executionType")
agent_version = attributes_dict.get("agentVersion")
reference_id = attributes_dict.get("referenceId")
source = attributes_dict.get("source")

# Source: override via uipath.source attribute, else DEFAULT_SOURCE
uipath_source = attributes_dict.get("uipath.source")
source = uipath_source if isinstance(uipath_source, int) else DEFAULT_SOURCE

# Create UiPathSpan from OpenTelemetry span
start_time = datetime.fromtimestamp(
Expand Down
66 changes: 66 additions & 0 deletions tests/tracing/test_span_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,69 @@ def test_uipath_span_missing_execution_type_and_agent_version(self):

assert span_dict["ExecutionType"] is None
assert span_dict["AgentVersion"] is None

@patch.dict(os.environ, {"UIPATH_ORGANIZATION_ID": "test-org"})
def test_uipath_span_source_defaults_to_robots(self):
"""Test that Source defaults to 4 (Robots) and ignores attributes.source."""
mock_span = Mock(spec=OTelSpan)

trace_id = 0x123456789ABCDEF0123456789ABCDEF0
span_id = 0x0123456789ABCDEF
mock_context = SpanContext(trace_id=trace_id, span_id=span_id, is_remote=False)
mock_span.get_span_context.return_value = mock_context

mock_span.name = "test-span"
mock_span.parent = None
mock_span.status.status_code = StatusCode.OK
# source in attributes should NOT override top-level Source
mock_span.attributes = {"source": "runtime"}
mock_span.events = []
mock_span.links = []

current_time_ns = int(datetime.now().timestamp() * 1e9)
mock_span.start_time = current_time_ns
mock_span.end_time = current_time_ns + 1000000

uipath_span = _SpanUtils.otel_span_to_uipath_span(mock_span)
span_dict = uipath_span.to_dict()

# Top-level Source should be 4 (Robots), string "runtime" is ignored
assert uipath_span.source == 4
assert span_dict["Source"] == 4

# attributes.source string should still be in Attributes JSON
attrs = json.loads(span_dict["Attributes"])
assert attrs["source"] == "runtime"

@patch.dict(os.environ, {"UIPATH_ORGANIZATION_ID": "test-org"})
def test_uipath_span_source_override_with_uipath_source(self):
"""Test that uipath.source attribute overrides default (for low-code agents)."""
mock_span = Mock(spec=OTelSpan)

trace_id = 0x123456789ABCDEF0123456789ABCDEF0
span_id = 0x0123456789ABCDEF
mock_context = SpanContext(trace_id=trace_id, span_id=span_id, is_remote=False)
mock_span.get_span_context.return_value = mock_context

mock_span.name = "test-span"
mock_span.parent = None
mock_span.status.status_code = StatusCode.OK
# uipath.source=1 (Agents) overrides default of 4 (Robots)
mock_span.attributes = {"uipath.source": 1, "source": "runtime"}
mock_span.events = []
mock_span.links = []

current_time_ns = int(datetime.now().timestamp() * 1e9)
mock_span.start_time = current_time_ns
mock_span.end_time = current_time_ns + 1000000

uipath_span = _SpanUtils.otel_span_to_uipath_span(mock_span)
span_dict = uipath_span.to_dict()

# uipath.source overrides - low-code agents use 1 (Agents)
assert uipath_span.source == 1
assert span_dict["Source"] == 1

# String source still in Attributes JSON
attrs = json.loads(span_dict["Attributes"])
assert attrs["source"] == "runtime"
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.