uipath 2.0.12__tar.gz → 2.0.13__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.
- {uipath-2.0.12 → uipath-2.0.13}/PKG-INFO +1 -1
- {uipath-2.0.12 → uipath-2.0.13}/pyproject.toml +1 -1
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/tracing/_otel_exporters.py +32 -12
- uipath-2.0.13/tests/tracing/test_otel_exporters.py +190 -0
- {uipath-2.0.12 → uipath-2.0.13}/uv.lock +1 -1
- {uipath-2.0.12 → uipath-2.0.13}/.cursorrules +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/.editorconfig +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/.gitattributes +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/.github/workflows/build.yml +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/.github/workflows/cd.yml +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/.github/workflows/ci.yml +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/.github/workflows/commitlint.yml +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/.github/workflows/lint.yml +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/.github/workflows/test.yml +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/.gitignore +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/.pre-commit-config.yaml +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/.python-version +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/.vscode/extensions.json +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/.vscode/settings.json +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/CONTRIBUTING.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/LICENSE +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/README.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/docs/CONTRIBUTING.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/docs/actions.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/docs/assets/uipath-logo.svg +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/docs/assets.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/docs/buckets.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/docs/connections.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/docs/context_grounding.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/docs/getting_started_agent.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/docs/getting_started_cli.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/docs/getting_started_cloud.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/docs/getting_started_sdk.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/docs/index.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/docs/jobs.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/docs/processes.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/docs/queues.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/docs/sdk.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/justfile +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/mkdocs.yml +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/py.typed +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/__init__.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/README.md +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/__init__.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_auth/_auth_server.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_auth/_models.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_auth/_oidc_utils.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_auth/_portal_service.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_auth/_utils.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_auth/auth_config.json +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_auth/index.html +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_auth/localhost.crt +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_auth/localhost.key +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_runtime/_contracts.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_runtime/_logging.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_runtime/_runtime.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_templates/.psmdcp.template +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_templates/.rels.template +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_templates/[Content_Types].xml.template +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_templates/main.py.template +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_templates/package.nuspec.template +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_utils/_common.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_utils/_input_args.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/_utils/_parse_ast.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/cli_auth.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/cli_deploy.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/cli_init.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/cli_new.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/cli_pack.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/cli_publish.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/cli_run.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_cli/middlewares.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_config.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_execution_context.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_folder_context.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_services/__init__.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_services/_base_service.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_services/actions_service.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_services/api_client.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_services/assets_service.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_services/buckets_service.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_services/connections_service.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_services/connections_service.pyi +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_services/context_grounding_service.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_services/folder_service.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_services/jobs_service.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_services/llm_gateway_service.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_services/processes_service.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_services/queues_service.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_uipath.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_utils/__init__.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_utils/_endpoint.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_utils/_infer_bindings.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_utils/_logs.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_utils/_request_override.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_utils/_request_spec.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_utils/_user_agent.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/_utils/constants.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/models/__init__.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/models/action_schema.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/models/actions.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/models/assets.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/models/connections.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/models/context_grounding.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/models/context_grounding_index.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/models/exceptions.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/models/interrupt_models.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/models/job.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/models/llm_gateway.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/models/processes.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/models/queues.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/py.typed +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/tracing/__init__.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/tracing/_traced.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/src/uipath/tracing/_utils.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/tests/__init__.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/tests/cli/test_init.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/tests/conftest.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/tests/sdk/services/test_llm_integration.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/tests/sdk/services/test_llm_service.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/tests/sdk/services/test_uipath_llm_integration.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/tests/sdk/test_config.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/tests/tracing/test_span_utils.py +0 -0
- {uipath-2.0.12 → uipath-2.0.13}/tests/tracing/test_traced.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: uipath
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.13
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "uipath"
|
|
3
|
-
version = "2.0.
|
|
3
|
+
version = "2.0.13"
|
|
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"
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
-
|
|
4
|
+
import time
|
|
5
|
+
from typing import Any, Dict, Sequence
|
|
5
6
|
|
|
6
7
|
from httpx import Client
|
|
7
8
|
from opentelemetry.sdk.trace import ReadableSpan
|
|
@@ -30,7 +31,7 @@ class LlmOpsHttpExporter(SpanExporter):
|
|
|
30
31
|
|
|
31
32
|
self.http_client = Client(headers=self.headers)
|
|
32
33
|
|
|
33
|
-
def export(self, spans: Sequence[ReadableSpan]):
|
|
34
|
+
def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
|
|
34
35
|
"""Export spans to UiPath LLM Ops."""
|
|
35
36
|
logger.debug(
|
|
36
37
|
f"Exporting {len(spans)} spans to {self.base_url}/llmopstenant_/api/Traces/spans"
|
|
@@ -39,23 +40,42 @@ class LlmOpsHttpExporter(SpanExporter):
|
|
|
39
40
|
span_list = [
|
|
40
41
|
_SpanUtils.otel_span_to_uipath_span(span).to_dict() for span in spans
|
|
41
42
|
]
|
|
43
|
+
url = self._build_url(span_list)
|
|
42
44
|
|
|
43
|
-
|
|
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)
|
|
45
|
+
logger.debug("Payload: %s", json.dumps(span_list))
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
return SpanExportResult.SUCCESS
|
|
52
|
-
else:
|
|
53
|
-
return SpanExportResult.FAILURE
|
|
47
|
+
return self._send_with_retries(url, span_list)
|
|
54
48
|
|
|
55
49
|
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
|
56
50
|
"""Force flush the exporter."""
|
|
57
51
|
return True
|
|
58
52
|
|
|
53
|
+
def _build_url(self, span_list: list[Dict[str, Any]]) -> str:
|
|
54
|
+
"""Construct the URL for the API request."""
|
|
55
|
+
trace_id = str(span_list[0]["TraceId"])
|
|
56
|
+
return f"{self.base_url}/llmopstenant_/api/Traces/spans?traceId={trace_id}&source=Robots"
|
|
57
|
+
|
|
58
|
+
def _send_with_retries(
|
|
59
|
+
self, url: str, payload: list[Dict[str, Any]], max_retries: int = 4
|
|
60
|
+
) -> SpanExportResult:
|
|
61
|
+
"""Send the HTTP request with retry logic."""
|
|
62
|
+
for attempt in range(max_retries):
|
|
63
|
+
try:
|
|
64
|
+
response = self.http_client.post(url, json=payload)
|
|
65
|
+
if response.status_code == 200:
|
|
66
|
+
return SpanExportResult.SUCCESS
|
|
67
|
+
else:
|
|
68
|
+
logger.warning(
|
|
69
|
+
f"Attempt {attempt + 1} failed with status code {response.status_code}: {response.text}"
|
|
70
|
+
)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error(f"Attempt {attempt + 1} failed with exception: {e}")
|
|
73
|
+
|
|
74
|
+
if attempt < max_retries - 1:
|
|
75
|
+
time.sleep(1.5**attempt) # Exponential backoff
|
|
76
|
+
|
|
77
|
+
return SpanExportResult.FAILURE
|
|
78
|
+
|
|
59
79
|
def _get_base_url(self) -> str:
|
|
60
80
|
uipath_url = (
|
|
61
81
|
os.environ.get("UIPATH_URL")
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from opentelemetry.sdk.trace import ReadableSpan
|
|
6
|
+
from opentelemetry.sdk.trace.export import SpanExportResult
|
|
7
|
+
|
|
8
|
+
from uipath.tracing._otel_exporters import LlmOpsHttpExporter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def mock_env_vars():
|
|
13
|
+
"""Fixture to set and clean up environment variables for testing."""
|
|
14
|
+
original_values = {}
|
|
15
|
+
|
|
16
|
+
# Save original values
|
|
17
|
+
for var in ["UIPATH_URL", "UIPATH_ACCESS_TOKEN"]:
|
|
18
|
+
original_values[var] = os.environ.get(var)
|
|
19
|
+
|
|
20
|
+
# Set test values
|
|
21
|
+
os.environ["UIPATH_URL"] = "https://test.uipath.com/org/tenant/"
|
|
22
|
+
os.environ["UIPATH_ACCESS_TOKEN"] = "test-token"
|
|
23
|
+
|
|
24
|
+
yield
|
|
25
|
+
|
|
26
|
+
# Restore original values
|
|
27
|
+
for var, value in original_values.items():
|
|
28
|
+
if value is None:
|
|
29
|
+
if var in os.environ:
|
|
30
|
+
del os.environ[var]
|
|
31
|
+
else:
|
|
32
|
+
os.environ[var] = value
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.fixture
|
|
36
|
+
def mock_span():
|
|
37
|
+
"""Create a mock ReadableSpan for testing."""
|
|
38
|
+
span = MagicMock(spec=ReadableSpan)
|
|
39
|
+
return span
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture
|
|
43
|
+
def exporter(mock_env_vars):
|
|
44
|
+
"""Create an exporter instance for testing."""
|
|
45
|
+
with patch("uipath.tracing._otel_exporters.Client"):
|
|
46
|
+
exporter = LlmOpsHttpExporter()
|
|
47
|
+
# Mock _build_url to include query parameters as in the actual implementation
|
|
48
|
+
exporter._build_url = MagicMock( # type: ignore
|
|
49
|
+
return_value="https://test.uipath.com/org/tenant/llmopstenant_/api/Traces/spans?traceId=test-trace-id&source=Robots"
|
|
50
|
+
)
|
|
51
|
+
yield exporter
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_init_with_env_vars(mock_env_vars):
|
|
55
|
+
"""Test initialization with environment variables."""
|
|
56
|
+
with patch("uipath.tracing._otel_exporters.Client"):
|
|
57
|
+
exporter = LlmOpsHttpExporter()
|
|
58
|
+
|
|
59
|
+
assert exporter.base_url == "https://test.uipath.com/org/tenant"
|
|
60
|
+
assert exporter.auth_token == "test-token"
|
|
61
|
+
assert exporter.headers == {
|
|
62
|
+
"Content-Type": "application/json",
|
|
63
|
+
"Authorization": "Bearer test-token",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_init_with_default_url():
|
|
68
|
+
"""Test initialization with default URL when environment variable is not set."""
|
|
69
|
+
with (
|
|
70
|
+
patch("uipath.tracing._otel_exporters.Client"),
|
|
71
|
+
patch.dict(os.environ, {"UIPATH_ACCESS_TOKEN": "test-token"}, clear=True),
|
|
72
|
+
):
|
|
73
|
+
exporter = LlmOpsHttpExporter()
|
|
74
|
+
|
|
75
|
+
assert exporter.base_url == "https://cloud.uipath.com/dummyOrg/dummyTennant"
|
|
76
|
+
assert exporter.auth_token == "test-token"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_export_success(exporter, mock_span):
|
|
80
|
+
"""Test successful export of spans."""
|
|
81
|
+
mock_uipath_span = MagicMock()
|
|
82
|
+
mock_uipath_span.to_dict.return_value = {"span": "data", "TraceId": "test-trace-id"}
|
|
83
|
+
|
|
84
|
+
with patch(
|
|
85
|
+
"uipath.tracing._otel_exporters._SpanUtils.otel_span_to_uipath_span",
|
|
86
|
+
return_value=mock_uipath_span,
|
|
87
|
+
):
|
|
88
|
+
mock_response = MagicMock()
|
|
89
|
+
mock_response.status_code = 200
|
|
90
|
+
exporter.http_client.post.return_value = mock_response
|
|
91
|
+
|
|
92
|
+
result = exporter.export([mock_span])
|
|
93
|
+
|
|
94
|
+
assert result == SpanExportResult.SUCCESS
|
|
95
|
+
exporter._build_url.assert_called_once_with(
|
|
96
|
+
[{"span": "data", "TraceId": "test-trace-id"}]
|
|
97
|
+
)
|
|
98
|
+
exporter.http_client.post.assert_called_once_with(
|
|
99
|
+
"https://test.uipath.com/org/tenant/llmopstenant_/api/Traces/spans?traceId=test-trace-id&source=Robots",
|
|
100
|
+
json=[{"span": "data", "TraceId": "test-trace-id"}],
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_export_failure(exporter, mock_span):
|
|
105
|
+
"""Test export failure with multiple retries."""
|
|
106
|
+
mock_uipath_span = MagicMock()
|
|
107
|
+
mock_uipath_span.to_dict.return_value = {"span": "data"}
|
|
108
|
+
|
|
109
|
+
with patch(
|
|
110
|
+
"uipath.tracing._otel_exporters._SpanUtils.otel_span_to_uipath_span",
|
|
111
|
+
return_value=mock_uipath_span,
|
|
112
|
+
):
|
|
113
|
+
mock_response = MagicMock()
|
|
114
|
+
mock_response.status_code = 500
|
|
115
|
+
mock_response.text = "Internal Server Error"
|
|
116
|
+
exporter.http_client.post.return_value = mock_response
|
|
117
|
+
|
|
118
|
+
with patch("uipath.tracing._otel_exporters.time.sleep") as mock_sleep:
|
|
119
|
+
result = exporter.export([mock_span])
|
|
120
|
+
|
|
121
|
+
assert result == SpanExportResult.FAILURE
|
|
122
|
+
assert exporter.http_client.post.call_count == 4 # Default max_retries is 3
|
|
123
|
+
assert (
|
|
124
|
+
mock_sleep.call_count == 3
|
|
125
|
+
) # Should sleep between retries (except after the last one)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_export_exception(exporter, mock_span):
|
|
129
|
+
"""Test export with exceptions during HTTP request."""
|
|
130
|
+
mock_uipath_span = MagicMock()
|
|
131
|
+
mock_uipath_span.to_dict.return_value = {"span": "data"}
|
|
132
|
+
|
|
133
|
+
with patch(
|
|
134
|
+
"uipath.tracing._otel_exporters._SpanUtils.otel_span_to_uipath_span",
|
|
135
|
+
return_value=mock_uipath_span,
|
|
136
|
+
):
|
|
137
|
+
exporter.http_client.post.side_effect = Exception("Connection error")
|
|
138
|
+
|
|
139
|
+
with patch("uipath.tracing._otel_exporters.time.sleep"):
|
|
140
|
+
result = exporter.export([mock_span])
|
|
141
|
+
|
|
142
|
+
assert result == SpanExportResult.FAILURE
|
|
143
|
+
assert exporter.http_client.post.call_count == 4 # Default max_retries is 3
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_force_flush(exporter):
|
|
147
|
+
"""Test force_flush returns True."""
|
|
148
|
+
assert exporter.force_flush() is True
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_get_base_url():
|
|
152
|
+
"""Test _get_base_url method with different environment configurations."""
|
|
153
|
+
# Test with environment variable set
|
|
154
|
+
with patch.dict(
|
|
155
|
+
os.environ, {"UIPATH_URL": "https://custom.uipath.com/org/tenant/"}, clear=True
|
|
156
|
+
):
|
|
157
|
+
with patch("uipath.tracing._otel_exporters.Client"):
|
|
158
|
+
exporter = LlmOpsHttpExporter()
|
|
159
|
+
assert exporter.base_url == "https://custom.uipath.com/org/tenant"
|
|
160
|
+
|
|
161
|
+
# Test with environment variable set but with no trailing slash
|
|
162
|
+
with patch.dict(
|
|
163
|
+
os.environ, {"UIPATH_URL": "https://custom.uipath.com/org/tenant"}, clear=True
|
|
164
|
+
):
|
|
165
|
+
with patch("uipath.tracing._otel_exporters.Client"):
|
|
166
|
+
exporter = LlmOpsHttpExporter()
|
|
167
|
+
assert exporter.base_url == "https://custom.uipath.com/org/tenant"
|
|
168
|
+
|
|
169
|
+
# Test with no environment variable
|
|
170
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
171
|
+
with patch("uipath.tracing._otel_exporters.Client"):
|
|
172
|
+
exporter = LlmOpsHttpExporter()
|
|
173
|
+
assert exporter.base_url == "https://cloud.uipath.com/dummyOrg/dummyTennant"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_send_with_retries_success():
|
|
177
|
+
"""Test _send_with_retries method with successful response."""
|
|
178
|
+
with patch("uipath.tracing._otel_exporters.Client"):
|
|
179
|
+
exporter = LlmOpsHttpExporter()
|
|
180
|
+
|
|
181
|
+
mock_response = MagicMock()
|
|
182
|
+
mock_response.status_code = 200
|
|
183
|
+
exporter.http_client.post.return_value = mock_response # type: ignore
|
|
184
|
+
|
|
185
|
+
result = exporter._send_with_retries("http://example.com", [{"span": "data"}])
|
|
186
|
+
|
|
187
|
+
assert result == SpanExportResult.SUCCESS
|
|
188
|
+
exporter.http_client.post.assert_called_once_with( # type: ignore
|
|
189
|
+
"http://example.com", json=[{"span": "data"}]
|
|
190
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|