uipath 2.0.9__tar.gz → 2.0.11__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.

Potentially problematic release.


This version of uipath might be problematic. Click here for more details.

Files changed (123) hide show
  1. {uipath-2.0.9 → uipath-2.0.11}/.vscode/settings.json +1 -1
  2. {uipath-2.0.9 → uipath-2.0.11}/PKG-INFO +3 -1
  3. {uipath-2.0.9 → uipath-2.0.11}/pyproject.toml +3 -1
  4. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/cli_init.py +2 -1
  5. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/cli_pack.py +1 -1
  6. uipath-2.0.11/src/uipath/tracing/__init__.py +3 -0
  7. uipath-2.0.11/src/uipath/tracing/_otel_exporters.py +67 -0
  8. uipath-2.0.11/src/uipath/tracing/_traced.py +165 -0
  9. uipath-2.0.11/src/uipath/tracing/_utils.py +267 -0
  10. uipath-2.0.11/tests/tracing/test_span_utils.py +204 -0
  11. uipath-2.0.11/tests/tracing/test_traced.py +145 -0
  12. {uipath-2.0.9 → uipath-2.0.11}/uv.lock +151 -1
  13. {uipath-2.0.9 → uipath-2.0.11}/.cursorrules +0 -0
  14. {uipath-2.0.9 → uipath-2.0.11}/.editorconfig +0 -0
  15. {uipath-2.0.9 → uipath-2.0.11}/.gitattributes +0 -0
  16. {uipath-2.0.9 → uipath-2.0.11}/.github/workflows/build.yml +0 -0
  17. {uipath-2.0.9 → uipath-2.0.11}/.github/workflows/cd.yml +0 -0
  18. {uipath-2.0.9 → uipath-2.0.11}/.github/workflows/ci.yml +0 -0
  19. {uipath-2.0.9 → uipath-2.0.11}/.github/workflows/commitlint.yml +0 -0
  20. {uipath-2.0.9 → uipath-2.0.11}/.github/workflows/lint.yml +0 -0
  21. {uipath-2.0.9 → uipath-2.0.11}/.github/workflows/test.yml +0 -0
  22. {uipath-2.0.9 → uipath-2.0.11}/.gitignore +0 -0
  23. {uipath-2.0.9 → uipath-2.0.11}/.pre-commit-config.yaml +0 -0
  24. {uipath-2.0.9 → uipath-2.0.11}/.python-version +0 -0
  25. {uipath-2.0.9 → uipath-2.0.11}/.vscode/extensions.json +0 -0
  26. {uipath-2.0.9 → uipath-2.0.11}/CONTRIBUTING.md +0 -0
  27. {uipath-2.0.9 → uipath-2.0.11}/LICENSE +0 -0
  28. {uipath-2.0.9 → uipath-2.0.11}/README.md +0 -0
  29. {uipath-2.0.9 → uipath-2.0.11}/docs/CONTRIBUTING.md +0 -0
  30. {uipath-2.0.9 → uipath-2.0.11}/docs/actions.md +0 -0
  31. {uipath-2.0.9 → uipath-2.0.11}/docs/assets/uipath-logo.svg +0 -0
  32. {uipath-2.0.9 → uipath-2.0.11}/docs/assets.md +0 -0
  33. {uipath-2.0.9 → uipath-2.0.11}/docs/buckets.md +0 -0
  34. {uipath-2.0.9 → uipath-2.0.11}/docs/connections.md +0 -0
  35. {uipath-2.0.9 → uipath-2.0.11}/docs/context_grounding.md +0 -0
  36. {uipath-2.0.9 → uipath-2.0.11}/docs/getting_started_agent.md +0 -0
  37. {uipath-2.0.9 → uipath-2.0.11}/docs/getting_started_cli.md +0 -0
  38. {uipath-2.0.9 → uipath-2.0.11}/docs/getting_started_cloud.md +0 -0
  39. {uipath-2.0.9 → uipath-2.0.11}/docs/getting_started_sdk.md +0 -0
  40. {uipath-2.0.9 → uipath-2.0.11}/docs/index.md +0 -0
  41. {uipath-2.0.9 → uipath-2.0.11}/docs/jobs.md +0 -0
  42. {uipath-2.0.9 → uipath-2.0.11}/docs/processes.md +0 -0
  43. {uipath-2.0.9 → uipath-2.0.11}/docs/queues.md +0 -0
  44. {uipath-2.0.9 → uipath-2.0.11}/docs/sdk.md +0 -0
  45. {uipath-2.0.9 → uipath-2.0.11}/justfile +0 -0
  46. {uipath-2.0.9 → uipath-2.0.11}/mkdocs.yml +0 -0
  47. {uipath-2.0.9 → uipath-2.0.11}/py.typed +0 -0
  48. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/__init__.py +0 -0
  49. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/README.md +0 -0
  50. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/__init__.py +0 -0
  51. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_auth/_auth_server.py +0 -0
  52. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_auth/_models.py +0 -0
  53. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_auth/_oidc_utils.py +0 -0
  54. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_auth/_portal_service.py +0 -0
  55. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_auth/_utils.py +0 -0
  56. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_auth/auth_config.json +0 -0
  57. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_auth/index.html +0 -0
  58. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_auth/localhost.crt +0 -0
  59. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_auth/localhost.key +0 -0
  60. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_runtime/_contracts.py +0 -0
  61. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_runtime/_logging.py +0 -0
  62. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_runtime/_runtime.py +0 -0
  63. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_templates/.psmdcp.template +0 -0
  64. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_templates/.rels.template +0 -0
  65. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_templates/[Content_Types].xml.template +0 -0
  66. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_templates/main.py.template +0 -0
  67. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_templates/package.nuspec.template +0 -0
  68. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_utils/_common.py +0 -0
  69. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_utils/_input_args.py +0 -0
  70. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/_utils/_parse_ast.py +0 -0
  71. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/cli_auth.py +0 -0
  72. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/cli_deploy.py +0 -0
  73. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/cli_new.py +0 -0
  74. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/cli_publish.py +0 -0
  75. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/cli_run.py +0 -0
  76. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_cli/middlewares.py +0 -0
  77. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_config.py +0 -0
  78. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_execution_context.py +0 -0
  79. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_folder_context.py +0 -0
  80. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_services/__init__.py +0 -0
  81. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_services/_base_service.py +0 -0
  82. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_services/actions_service.py +0 -0
  83. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_services/api_client.py +0 -0
  84. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_services/assets_service.py +0 -0
  85. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_services/buckets_service.py +0 -0
  86. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_services/connections_service.py +0 -0
  87. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_services/connections_service.pyi +0 -0
  88. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_services/context_grounding_service.py +0 -0
  89. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_services/folder_service.py +0 -0
  90. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_services/jobs_service.py +0 -0
  91. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_services/llm_gateway_service.py +0 -0
  92. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_services/processes_service.py +0 -0
  93. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_services/queues_service.py +0 -0
  94. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_uipath.py +0 -0
  95. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_utils/__init__.py +0 -0
  96. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_utils/_endpoint.py +0 -0
  97. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_utils/_infer_bindings.py +0 -0
  98. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_utils/_logs.py +0 -0
  99. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_utils/_request_override.py +0 -0
  100. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_utils/_request_spec.py +0 -0
  101. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_utils/_user_agent.py +0 -0
  102. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/_utils/constants.py +0 -0
  103. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/models/__init__.py +0 -0
  104. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/models/action_schema.py +0 -0
  105. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/models/actions.py +0 -0
  106. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/models/assets.py +0 -0
  107. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/models/connections.py +0 -0
  108. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/models/context_grounding.py +0 -0
  109. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/models/context_grounding_index.py +0 -0
  110. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/models/exceptions.py +0 -0
  111. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/models/interrupt_models.py +0 -0
  112. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/models/job.py +0 -0
  113. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/models/llm_gateway.py +0 -0
  114. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/models/processes.py +0 -0
  115. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/models/queues.py +0 -0
  116. {uipath-2.0.9 → uipath-2.0.11}/src/uipath/py.typed +0 -0
  117. {uipath-2.0.9 → uipath-2.0.11}/tests/__init__.py +0 -0
  118. {uipath-2.0.9 → uipath-2.0.11}/tests/cli/test_init.py +0 -0
  119. {uipath-2.0.9 → uipath-2.0.11}/tests/conftest.py +0 -0
  120. {uipath-2.0.9 → uipath-2.0.11}/tests/sdk/services/test_llm_integration.py +0 -0
  121. {uipath-2.0.9 → uipath-2.0.11}/tests/sdk/services/test_llm_service.py +0 -0
  122. {uipath-2.0.9 → uipath-2.0.11}/tests/sdk/services/test_uipath_llm_integration.py +0 -0
  123. {uipath-2.0.9 → uipath-2.0.11}/tests/sdk/test_config.py +0 -0
@@ -21,7 +21,7 @@
21
21
  "titleBar.inactiveBackground": "#0099cc"
22
22
  },
23
23
  "python.testing.pytestArgs": [
24
- "sdk"
24
+ "tests"
25
25
  ],
26
26
  "python.testing.unittestEnabled": false,
27
27
  "python.testing.pytestEnabled": true
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: uipath
3
- Version: 2.0.9
3
+ Version: 2.0.11
4
4
  Summary: Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools.
5
5
  Project-URL: Homepage, https://uipath.com
6
6
  Project-URL: Repository, https://github.com/UiPath/uipath-python
@@ -15,6 +15,8 @@ Classifier: Topic :: Software Development :: Build Tools
15
15
  Requires-Python: >=3.10
16
16
  Requires-Dist: click>=8.1.8
17
17
  Requires-Dist: httpx>=0.28.1
18
+ Requires-Dist: opentelemetry-sdk>=1.32.1
19
+ Requires-Dist: pathlib>=1.0.1
18
20
  Requires-Dist: pydantic>=2.11.1
19
21
  Requires-Dist: pytest-asyncio>=0.25.3
20
22
  Requires-Dist: python-dotenv>=1.0.1
@@ -1,12 +1,13 @@
1
1
  [project]
2
2
  name = "uipath"
3
- version = "2.0.9"
3
+ version = "2.0.11"
4
4
  description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
5
5
  readme = { file = "README.md", content-type = "text/markdown" }
6
6
  requires-python = ">=3.10"
7
7
  dependencies = [
8
8
  "click>=8.1.8",
9
9
  "httpx>=0.28.1",
10
+ "opentelemetry-sdk>=1.32.1",
10
11
  "pydantic>=2.11.1",
11
12
  "pytest-asyncio>=0.25.3",
12
13
  "python-dotenv>=1.0.1",
@@ -14,6 +15,7 @@ dependencies = [
14
15
  "tenacity>=9.0.0",
15
16
  "tomli>=2.2.1",
16
17
  "types-requests>=2.32.0.20250306",
18
+ "pathlib>=1.0.1"
17
19
  ]
18
20
  classifiers = [
19
21
  "Development Status :: 3 - Alpha",
@@ -3,6 +3,7 @@ import json
3
3
  import os
4
4
  import traceback
5
5
  import uuid
6
+ from pathlib import Path
6
7
  from typing import Optional
7
8
 
8
9
  import click
@@ -75,7 +76,7 @@ def init(entrypoint: str) -> None:
75
76
  try:
76
77
  args = generate_args(script_path)
77
78
 
78
- relative_path = os.path.relpath(script_path, current_directory)
79
+ relative_path = Path(script_path).relative_to(current_directory).as_posix()
79
80
 
80
81
  config_data = {
81
82
  "entryPoints": [
@@ -232,7 +232,7 @@ def pack_fn(projectName, description, entryPoints, version, authors, directory):
232
232
  os.makedirs(".uipath", exist_ok=True)
233
233
 
234
234
  # Define the allowlist of file extensions to include
235
- file_extensions_allowlist = [".py", ".mermaid", ".json"]
235
+ file_extensions_allowlist = [".py", ".mermaid", ".json", ".yaml", ".yml"]
236
236
 
237
237
  with zipfile.ZipFile(
238
238
  f".uipath/{projectName}.{version}.nupkg", "w", zipfile.ZIP_DEFLATED
@@ -0,0 +1,3 @@
1
+ from ._traced import TracedDecoratorRegistry, traced, wait_for_tracers # noqa: D104
2
+
3
+ __all__ = ["TracedDecoratorRegistry", "traced", "wait_for_tracers"]
@@ -0,0 +1,67 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ from typing import Sequence
5
+
6
+ from httpx import Client
7
+ from opentelemetry.sdk.trace import ReadableSpan
8
+ from opentelemetry.sdk.trace.export import (
9
+ SpanExporter,
10
+ SpanExportResult,
11
+ )
12
+
13
+ from ._utils import _SpanUtils
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class LlmOpsHttpExporter(SpanExporter):
19
+ """An OpenTelemetry span exporter that sends spans to UiPath LLM Ops."""
20
+
21
+ def __init__(self, **kwargs):
22
+ """Initialize the exporter with the base URL and authentication token."""
23
+ super().__init__(**kwargs)
24
+ self.base_url = self._get_base_url()
25
+ self.auth_token = os.environ.get("UIPATH_ACCESS_TOKEN")
26
+ self.headers = {
27
+ "Content-Type": "application/json",
28
+ "Authorization": f"Bearer {self.auth_token}",
29
+ }
30
+
31
+ self.http_client = Client(headers=self.headers)
32
+
33
+ def export(self, spans: Sequence[ReadableSpan]):
34
+ """Export spans to UiPath LLM Ops."""
35
+ logger.debug(
36
+ f"Exporting {len(spans)} spans to {self.base_url}/llmopstenant_/api/Traces/spans"
37
+ )
38
+
39
+ span_list = [
40
+ _SpanUtils.otel_span_to_uipath_span(span).to_dict() for span in spans
41
+ ]
42
+
43
+ trace_id = str(span_list[0]["TraceId"])
44
+ url = f"{self.base_url}/llmopstenant_/api/Traces/spans?traceId={trace_id}&source=Robots"
45
+
46
+ logger.debug("payload: ", json.dumps(span_list))
47
+
48
+ res = self.http_client.post(url, json=span_list)
49
+
50
+ if res.status_code == 200:
51
+ return SpanExportResult.SUCCESS
52
+ else:
53
+ return SpanExportResult.FAILURE
54
+
55
+ def force_flush(self, timeout_millis: int = 30000) -> bool:
56
+ """Force flush the exporter."""
57
+ return True
58
+
59
+ def _get_base_url(self) -> str:
60
+ uipath_url = (
61
+ os.environ.get("UIPATH_URL")
62
+ or "https://cloud.uipath.com/dummyOrg/dummyTennant/"
63
+ )
64
+
65
+ uipath_url = uipath_url.rstrip("/")
66
+
67
+ return uipath_url
@@ -0,0 +1,165 @@
1
+ import inspect
2
+ import json
3
+ import logging
4
+ from functools import wraps
5
+ from typing import Any
6
+
7
+ from opentelemetry import trace
8
+ from opentelemetry.sdk.trace import TracerProvider
9
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
10
+
11
+ from ._otel_exporters import LlmOpsHttpExporter
12
+ from ._utils import _SpanUtils
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ trace.set_tracer_provider(TracerProvider())
17
+ trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(LlmOpsHttpExporter())) # type: ignore
18
+ tracer = trace.get_tracer(__name__)
19
+
20
+
21
+ def wait_for_tracers():
22
+ """Wait for all tracers to finish."""
23
+ trace.get_tracer_provider().shutdown() # type: ignore
24
+
25
+
26
+ class TracedDecoratorRegistry:
27
+ """Registry for tracing decorators."""
28
+
29
+ _decorators: dict[str, Any] = {}
30
+ _active_decorator = "opentelemetry"
31
+
32
+ @classmethod
33
+ def register_decorator(cls, name, decorator_factory):
34
+ """Register a decorator factory function with a name."""
35
+ cls._decorators[name] = decorator_factory
36
+ cls._active_decorator = name
37
+ return cls
38
+
39
+ @classmethod
40
+ def get_decorator(cls):
41
+ """Get the currently active decorator factory."""
42
+ return cls._decorators.get(cls._active_decorator)
43
+
44
+
45
+ def _opentelemetry_traced():
46
+ def decorator(func):
47
+ @wraps(func)
48
+ def sync_wrapper(*args, **kwargs):
49
+ with tracer.start_as_current_span(func.__name__) as span:
50
+ span.set_attribute("span_type", "function_call_sync")
51
+ span.set_attribute(
52
+ "inputs",
53
+ _SpanUtils.format_args_for_trace_json(
54
+ inspect.signature(func), *args, **kwargs
55
+ ),
56
+ )
57
+ try:
58
+ result = func(*args, **kwargs)
59
+ span.set_attribute(
60
+ "output", json.dumps(result, default=str)
61
+ ) # Record output
62
+ return result
63
+ except Exception as e:
64
+ span.record_exception(e)
65
+ span.set_status(
66
+ trace.status.Status(trace.status.StatusCode.ERROR, str(e))
67
+ )
68
+ raise
69
+
70
+ @wraps(func)
71
+ async def async_wrapper(*args, **kwargs):
72
+ with tracer.start_as_current_span(func.__name__) as span:
73
+ span.set_attribute("span_type", "function_call_async")
74
+ span.set_attribute(
75
+ "inputs",
76
+ _SpanUtils.format_args_for_trace_json(
77
+ inspect.signature(func), *args, **kwargs
78
+ ),
79
+ )
80
+ try:
81
+ result = await func(*args, **kwargs)
82
+ span.set_attribute(
83
+ "output", json.dumps(result, default=str)
84
+ ) # Record output
85
+ return result
86
+ except Exception as e:
87
+ span.record_exception(e)
88
+ span.set_status(
89
+ trace.status.Status(trace.status.StatusCode.ERROR, str(e))
90
+ )
91
+ raise
92
+
93
+ @wraps(func)
94
+ def generator_wrapper(*args, **kwargs):
95
+ with tracer.start_as_current_span(func.__name__) as span:
96
+ span.set_attribute("span_type", "function_call_generator_sync")
97
+ span.set_attribute(
98
+ "inputs",
99
+ _SpanUtils.format_args_for_trace_json(
100
+ inspect.signature(func), *args, **kwargs
101
+ ),
102
+ )
103
+ outputs = []
104
+ try:
105
+ for item in func(*args, **kwargs):
106
+ outputs.append(item)
107
+ span.add_event(f"Yielded: {item}") # Add event for each yield
108
+ yield item
109
+ span.set_attribute(
110
+ "output", json.dumps(outputs, default=str)
111
+ ) # Record aggregated outputs
112
+ except Exception as e:
113
+ span.record_exception(e)
114
+ span.set_status(
115
+ trace.status.Status(trace.status.StatusCode.ERROR, str(e))
116
+ )
117
+ raise
118
+
119
+ @wraps(func)
120
+ async def async_generator_wrapper(*args, **kwargs):
121
+ with tracer.start_as_current_span(func.__name__) as span:
122
+ span.set_attribute("span_type", "function_call_generator_async")
123
+ span.set_attribute(
124
+ "inputs",
125
+ _SpanUtils.format_args_for_trace_json(
126
+ inspect.signature(func), *args, **kwargs
127
+ ),
128
+ )
129
+ outputs = []
130
+ try:
131
+ async for item in func(*args, **kwargs):
132
+ outputs.append(item)
133
+ span.add_event(f"Yielded: {item}") # Add event for each yield
134
+ yield item
135
+ span.set_attribute(
136
+ "output", json.dumps(outputs, default=str)
137
+ ) # Record aggregated outputs
138
+ except Exception as e:
139
+ span.record_exception(e)
140
+ span.set_status(
141
+ trace.status.Status(trace.status.StatusCode.ERROR, str(e))
142
+ )
143
+ raise
144
+
145
+ if inspect.iscoroutinefunction(func):
146
+ return async_wrapper
147
+ elif inspect.isgeneratorfunction(func):
148
+ return generator_wrapper
149
+ elif inspect.isasyncgenfunction(func):
150
+ return async_generator_wrapper
151
+ else:
152
+ return sync_wrapper
153
+
154
+ return decorator
155
+
156
+
157
+ def traced():
158
+ """Decorator that will trace function invocations."""
159
+ decorator_factory = TracedDecoratorRegistry.get_decorator()
160
+
161
+ if decorator_factory:
162
+ return decorator_factory()
163
+ else:
164
+ # Fallback to original implementation if no active decorator
165
+ return _opentelemetry_traced()
@@ -0,0 +1,267 @@
1
+ import inspect
2
+ import json
3
+ import logging
4
+ import os
5
+ import random
6
+ import uuid
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime
9
+ from os import environ as env
10
+ from typing import Any, Dict, Optional
11
+
12
+ from opentelemetry.sdk.trace import ReadableSpan
13
+ from opentelemetry.trace import StatusCode
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass
19
+ class UiPathSpan:
20
+ """Represents a span in the UiPath tracing system."""
21
+
22
+ id: uuid.UUID
23
+ trace_id: uuid.UUID
24
+ name: str
25
+ attributes: str
26
+ parent_id: Optional[uuid.UUID] = None
27
+ start_time: str = field(default_factory=lambda: datetime.now().isoformat())
28
+ end_time: str = field(default_factory=lambda: datetime.now().isoformat())
29
+ status: int = 1
30
+ created_at: str = field(default_factory=lambda: datetime.now().isoformat() + "Z")
31
+ updated_at: str = field(default_factory=lambda: datetime.now().isoformat() + "Z")
32
+ organization_id: Optional[str] = field(
33
+ default_factory=lambda: env.get("UIPATH_ORGANIZATION_ID", "")
34
+ )
35
+ tenant_id: Optional[str] = field(
36
+ default_factory=lambda: env.get("UIPATH_TENANT_ID", "")
37
+ )
38
+ expiry_time_utc: Optional[str] = None
39
+ folder_key: Optional[str] = field(
40
+ default_factory=lambda: env.get("UIPATH_FOLDER_KEY_XYZ", "")
41
+ )
42
+ source: Optional[str] = None
43
+ span_type: str = "Coded Agents"
44
+ process_key: Optional[str] = field(
45
+ default_factory=lambda: env.get("UIPATH_PROCESS_UUID")
46
+ )
47
+ job_key: Optional[str] = field(default_factory=lambda: env.get("UIPATH_JOB_KEY"))
48
+
49
+ def to_dict(self) -> Dict[str, Any]:
50
+ """Convert the Span to a dictionary suitable for JSON serialization."""
51
+ return {
52
+ "Id": str(self.id),
53
+ "TraceId": str(self.trace_id),
54
+ "ParentId": str(self.parent_id) if self.parent_id else None,
55
+ "Name": self.name,
56
+ "StartTime": self.start_time,
57
+ "EndTime": self.end_time,
58
+ "Attributes": self.attributes,
59
+ "Status": self.status,
60
+ "CreatedAt": self.created_at,
61
+ "UpdatedAt": self.updated_at,
62
+ "OrganizationId": self.organization_id,
63
+ "TenantId": self.tenant_id,
64
+ "ExpiryTimeUtc": self.expiry_time_utc,
65
+ "FolderKey": self.folder_key,
66
+ "Source": self.source,
67
+ "SpanType": self.span_type,
68
+ "ProcessKey": self.process_key,
69
+ "JobKey": self.job_key,
70
+ }
71
+
72
+
73
+ class _SpanUtils:
74
+ @staticmethod
75
+ def span_id_to_uuid4(span_id: int) -> uuid.UUID:
76
+ """Convert a 64-bit span ID to a valid UUID4 format.
77
+
78
+ Creates a UUID where:
79
+ - The 64 least significant bits contain the span ID
80
+ - The UUID version (bits 48-51) is set to 4
81
+ - The UUID variant (bits 64-65) is set to binary 10
82
+ """
83
+ # Generate deterministic high bits using the span_id as seed
84
+ temp_random = random.Random(span_id)
85
+ high_bits = temp_random.getrandbits(64)
86
+
87
+ # Combine high bits and span ID into a 128-bit integer
88
+ combined = (high_bits << 64) | span_id
89
+
90
+ # Set version to 4 (UUID4)
91
+ combined = (combined & ~(0xF << 76)) | (0x4 << 76)
92
+
93
+ # Set variant to binary 10
94
+ combined = (combined & ~(0x3 << 62)) | (2 << 62)
95
+
96
+ # Convert to hex string in UUID format
97
+ hex_str = format(combined, "032x")
98
+ return uuid.UUID(hex_str)
99
+
100
+ @staticmethod
101
+ def trace_id_to_uuid4(trace_id: int) -> uuid.UUID:
102
+ """Convert a 128-bit trace ID to a valid UUID4 format.
103
+
104
+ Modifies the trace ID to conform to UUID4 requirements:
105
+ - The UUID version (bits 48-51) is set to 4
106
+ - The UUID variant (bits 64-65) is set to binary 10
107
+ """
108
+ # Set version to 4 (UUID4)
109
+ uuid_int = (trace_id & ~(0xF << 76)) | (0x4 << 76)
110
+
111
+ # Set variant to binary 10
112
+ uuid_int = (uuid_int & ~(0x3 << 62)) | (2 << 62)
113
+
114
+ # Convert to hex string in UUID format
115
+ hex_str = format(uuid_int, "032x")
116
+ return uuid.UUID(hex_str)
117
+
118
+ @staticmethod
119
+ def otel_span_to_uipath_span(otel_span: ReadableSpan) -> UiPathSpan:
120
+ """Convert an OpenTelemetry span to a UiPathSpan."""
121
+ # Extract the context information from the OTel span
122
+ span_context = otel_span.get_span_context()
123
+
124
+ # OTel uses hexadecimal strings, we need to convert to UUID
125
+ trace_id = _SpanUtils.trace_id_to_uuid4(span_context.trace_id)
126
+ span_id = _SpanUtils.span_id_to_uuid4(span_context.span_id)
127
+
128
+ trace_id_str = os.environ.get("UIPATH_TRACE_ID")
129
+ if trace_id_str:
130
+ trace_id = uuid.UUID(trace_id_str)
131
+
132
+ # Get parent span ID if it exists
133
+ parent_id = None
134
+ if otel_span.parent is not None:
135
+ parent_id = _SpanUtils.span_id_to_uuid4(otel_span.parent.span_id)
136
+
137
+ # Map status
138
+ status = 1 # Default to OK
139
+ if otel_span.status.status_code == StatusCode.ERROR:
140
+ status = 2 # Error
141
+
142
+ # Convert attributes to a format compatible with UiPathSpan
143
+ attributes_dict: dict[str, Any] = (
144
+ dict(otel_span.attributes) if otel_span.attributes else {}
145
+ )
146
+
147
+ original_inputs = attributes_dict.get("inputs", None)
148
+ original_outputs = attributes_dict.get("outputs", None)
149
+
150
+ if original_inputs:
151
+ try:
152
+ if isinstance(original_inputs, str):
153
+ json_inputs = json.loads(original_inputs)
154
+ attributes_dict["inputs"] = json_inputs
155
+ else:
156
+ attributes_dict["inputs"] = original_inputs
157
+ except Exception as e:
158
+ print(f"Error parsing inputs: {e}")
159
+ attributes_dict["inputs"] = str(original_inputs)
160
+
161
+ if original_outputs:
162
+ try:
163
+ if isinstance(original_outputs, str):
164
+ json_outputs = json.loads(original_outputs)
165
+ attributes_dict["outputs"] = json_outputs
166
+ else:
167
+ attributes_dict["outputs"] = original_outputs
168
+ except Exception as e:
169
+ print(f"Error parsing outputs: {e}")
170
+ attributes_dict["outputs"] = str(original_outputs)
171
+
172
+ # Add events as additional attributes if they exist
173
+ if otel_span.events:
174
+ events_list = [
175
+ {
176
+ "name": event.name,
177
+ "timestamp": event.timestamp,
178
+ "attributes": dict(event.attributes) if event.attributes else {},
179
+ }
180
+ for event in otel_span.events
181
+ ]
182
+ attributes_dict["events"] = events_list
183
+
184
+ # Add links as additional attributes if they exist
185
+ if hasattr(otel_span, "links") and otel_span.links:
186
+ links_list = [
187
+ {
188
+ "trace_id": link.context.trace_id,
189
+ "span_id": link.context.span_id,
190
+ "attributes": dict(link.attributes) if link.attributes else {},
191
+ }
192
+ for link in otel_span.links
193
+ ]
194
+ attributes_dict["links"] = links_list
195
+
196
+ span_type_value = attributes_dict.get("span_type", "OpenTelemetry")
197
+ span_type = str(span_type_value)
198
+
199
+ # Create UiPathSpan from OpenTelemetry span
200
+ start_time = datetime.fromtimestamp(
201
+ (otel_span.start_time or 0) / 1e9
202
+ ).isoformat()
203
+
204
+ end_time_str = None
205
+ if otel_span.end_time is not None:
206
+ end_time_str = datetime.fromtimestamp(
207
+ (otel_span.end_time or 0) / 1e9
208
+ ).isoformat()
209
+ else:
210
+ end_time_str = datetime.now().isoformat()
211
+
212
+ return UiPathSpan(
213
+ id=span_id,
214
+ trace_id=trace_id,
215
+ parent_id=parent_id,
216
+ name=otel_span.name,
217
+ attributes=json.dumps(attributes_dict),
218
+ start_time=start_time,
219
+ end_time=end_time_str,
220
+ status=status,
221
+ span_type=span_type,
222
+ )
223
+
224
+ @staticmethod
225
+ def format_args_for_trace_json(
226
+ signature: inspect.Signature, *args: Any, **kwargs: Any
227
+ ) -> str:
228
+ """Return a JSON string of inputs from the function signature."""
229
+ result = _SpanUtils.format_args_for_trace(signature, *args, **kwargs)
230
+ return json.dumps(result, default=str)
231
+
232
+ @staticmethod
233
+ def format_args_for_trace(
234
+ signature: inspect.Signature, *args: Any, **kwargs: Any
235
+ ) -> Dict[str, Any]:
236
+ try:
237
+ """Return a dictionary of inputs from the function signature."""
238
+ # Create a parameter mapping by partially binding the arguments
239
+
240
+ parameter_binding = signature.bind_partial(*args, **kwargs)
241
+
242
+ # Fill in default values for any unspecified parameters
243
+ parameter_binding.apply_defaults()
244
+
245
+ # Extract the input parameters, skipping special Python parameters
246
+ result = {}
247
+ for name, value in parameter_binding.arguments.items():
248
+ # Skip class and instance references
249
+ if name in ("self", "cls"):
250
+ continue
251
+
252
+ # Handle **kwargs parameters specially
253
+ param_info = signature.parameters.get(name)
254
+ if param_info and param_info.kind == inspect.Parameter.VAR_KEYWORD:
255
+ # Flatten nested kwargs directly into the result
256
+ if isinstance(value, dict):
257
+ result.update(value)
258
+ else:
259
+ # Regular parameter
260
+ result[name] = value
261
+
262
+ return result
263
+ except Exception as e:
264
+ logger.warning(
265
+ f"Error formatting arguments for trace: {e}. Using args and kwargs directly."
266
+ )
267
+ return {"args": args, "kwargs": kwargs}