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 packages/uipath-platform/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath-platform"
version = "0.2.43"
version = "0.3.0"
description = "HTTP client library for programmatic access to UiPath Platform"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from ..common._execution_context import UiPathExecutionContext
from ..common._folder_context import FolderContext, header_folder
from ..common._models import Endpoint, RequestSpec
from ..common._span_utils import _SpanUtils
from .task_schema import TaskSchema
from .tasks import Task, TaskRecipient, TaskRecipientType

Expand Down Expand Up @@ -210,6 +211,7 @@ def _apply_task_source(
trace_id = UiPathConfig.trace_id
if not (project_id and trace_id):
return
parent_span_id = _SpanUtils.current_span_id()
payload["taskSource"] = {
"sourceName": source_name,
"sourceId": project_id,
Expand All @@ -218,6 +220,7 @@ def _apply_task_source(
"FolderKey": UiPathConfig.folder_key,
"JobKey": UiPathConfig.job_key,
"ProcessKey": UiPathConfig.process_uuid,
**({"ParentSpanId": parent_span_id} if parent_span_id else {}),
Comment thread
Sahil-Wakode marked this conversation as resolved.
Resolved
**(custom_metadata or {}),
},
"jobId": UiPathConfig.job_key,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,10 @@ def _inject_trace_context(headers: dict[str, str]) -> None:
if HEADER_TRACEPARENT_ID in headers:
return

from uipath.core.tracing.span_utils import UiPathSpanUtils

from ._config import UiPathConfig
from ._span_utils import _SpanUtils

llmops_span = UiPathSpanUtils.get_external_current_span()
span = llmops_span or trace.get_current_span()
ctx = span.get_span_context()
ctx = _SpanUtils.current_span().get_span_context()
if not (ctx.trace_id and ctx.span_id):
return

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
from typing import Any, Dict, List, Optional, TypeVar

from opentelemetry import context as context_api
from opentelemetry import trace
from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
from opentelemetry.trace import StatusCode
from opentelemetry.trace import StatusCode, format_span_id
from pydantic import BaseModel, ConfigDict, Field
from uipath.core.serialization import serialize_json

Expand Down Expand Up @@ -371,6 +372,23 @@ def normalize_span_id(value: str) -> str:
else:
raise ValueError(f"Invalid span ID format: {value}")

@staticmethod
def current_span() -> trace.Span:
from uipath.core.tracing.span_utils import UiPathSpanUtils

return UiPathSpanUtils.get_external_current_span() or trace.get_current_span()

@staticmethod
def current_span_id() -> Optional[str]:
try:
span_context = _SpanUtils.current_span().get_span_context()
span_id = span_context.span_id
if span_context.is_valid and isinstance(span_id, int) and span_id:
return format_span_id(span_id)
except Exception:
logger.debug("Could not read the current span id", exc_info=True)
return None

@staticmethod
def otel_span_to_uipath_span(
otel_span: ReadableSpan,
Expand Down
62 changes: 61 additions & 1 deletion packages/uipath-platform/tests/services/test_actions_service.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import json
from typing import Any
from unittest.mock import MagicMock

import pytest
from opentelemetry.trace import INVALID_SPAN, NonRecordingSpan, SpanContext
from pytest_httpx import HTTPXMock
from uipath.core.tracing.span_utils import UiPathSpanUtils

from uipath.platform import UiPathApiConfig, UiPathExecutionContext
from uipath.platform.action_center import Task
from uipath.platform.action_center._tasks_service import TasksService
from uipath.platform.action_center._tasks_service import (
TasksService,
_apply_task_source,
)
from uipath.platform.action_center.tasks import TaskRecipient, TaskRecipientType
from uipath.platform.constants import HEADER_USER_AGENT

Expand Down Expand Up @@ -1071,6 +1077,60 @@ async def test_create_async_merges_task_source_metadata(
assert task_source_metadata["InstanceId"] == "trace-1"


async def test_create_quickform_async_sends_the_current_span_as_parent_span_id(
qf_runner_async: Any, jit_debug_env: None, monkeypatch: pytest.MonkeyPatch
) -> None:
span = NonRecordingSpan(
SpanContext(trace_id=1, span_id=0xA3C27222815E80CA, is_remote=False)
)
monkeypatch.setattr(UiPathSpanUtils, "get_external_current_span", lambda: span)
monkeypatch.setenv("UIPATH_TRACE_ID", "4bf92f3577b34da6a3ce929d0e0e4736")

_, body = await qf_runner_async()

task_source_metadata = body["taskSource"]["taskSourceMetadata"]
assert task_source_metadata["ParentSpanId"] == "a3c27222815e80ca"


class _BrokenSpan:
def get_span_context(self) -> Any:
raise RuntimeError("span broke")


def _span_with_id(span_id: Any) -> Any:
span = MagicMock()
span.get_span_context.return_value.span_id = span_id
span.get_span_context.return_value.is_valid = True
return span


@pytest.mark.parametrize(
"span",
[
INVALID_SPAN,
_BrokenSpan(),
_span_with_id(0),
_span_with_id(None),
_span_with_id(""),
_span_with_id("abc"),
_span_with_id(1.5),
NonRecordingSpan(SpanContext(trace_id=1, span_id=-1, is_remote=False)),
NonRecordingSpan(SpanContext(trace_id=1, span_id=2**64, is_remote=False)),
],
)
def test_apply_task_source_without_a_valid_span_omits_parent_span_id(
jit_debug_env: None, monkeypatch: pytest.MonkeyPatch, span: Any
) -> None:
monkeypatch.setattr(UiPathSpanUtils, "get_external_current_span", lambda: span)
payload: dict[str, Any] = {}

_apply_task_source(payload, "Agent")

task_source_metadata = payload["taskSource"]["taskSourceMetadata"]
assert task_source_metadata["InstanceId"] == "trace-1"
assert "ParentSpanId" not in task_source_metadata


def test_create_omits_extra_task_source_metadata_when_unset(
httpx_mock: HTTPXMock,
service: TasksService,
Expand Down
2 changes: 1 addition & 1 deletion packages/uipath-platform/uv.lock

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

4 changes: 2 additions & 2 deletions packages/uipath/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[project]
name = "uipath"
version = "2.14.49"
version = "2.15.0"
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"
dependencies = [
"uipath-core>=0.5.33, <0.6.0",
"uipath-runtime>=0.13.5, <0.14.0",
"uipath-platform>=0.2.37, <0.3.0",
"uipath-platform>=0.3.0, <0.4.0",
"uipath-ipc>=2.5.1, <2.6.0",
"click>=8.3.3, <9.0.0",
"httpx>=0.28.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/uipath/src/uipath/_cli/cli_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
# installed release itself: a Robot refuses a project on an older `uipath`
# than it supports, so a scaffold must never ask for less than the CLI that
# wrote it.
UIPATH_SCAFFOLD_MINOR = "2.14"
UIPATH_SCAFFOLD_MINOR = "2.15"


def generate_script(target_directory):
Expand Down
4 changes: 2 additions & 2 deletions packages/uipath/uv.lock

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

Loading