uipath-core 0.1.10__tar.gz → 0.2.1__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.
- {uipath_core-0.1.10 → uipath_core-0.2.1}/PKG-INFO +1 -1
- {uipath_core-0.1.10 → uipath_core-0.2.1}/pyproject.toml +1 -1
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/tracing/__init__.py +2 -0
- uipath_core-0.2.1/src/uipath/core/tracing/processors.py +76 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/tracing/trace_manager.py +13 -3
- uipath_core-0.2.1/src/uipath/core/tracing/types.py +21 -0
- uipath_core-0.2.1/tests/tracing/test_span_filtering.py +245 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/uv.lock +13 -13
- uipath_core-0.1.10/src/uipath/core/tracing/processors.py +0 -46
- {uipath_core-0.1.10 → uipath_core-0.2.1}/.cursorrules +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/.editorconfig +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/.gitattributes +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/.github/workflows/cd.yml +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/.github/workflows/ci.yml +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/.github/workflows/commitlint.yml +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/.github/workflows/lint.yml +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/.github/workflows/publish-dev.yml +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/.github/workflows/test.yml +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/.gitignore +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/.pre-commit-config.yaml +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/.python-version +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/.vscode/extensions.json +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/.vscode/launch.json +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/.vscode/settings.json +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/CONTRIBUTING.md +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/LICENSE +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/README.md +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/justfile +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/__init__.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/chat/__init__.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/chat/async_stream.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/chat/citation.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/chat/content.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/chat/conversation.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/chat/error.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/chat/event.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/chat/exchange.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/chat/interrupt.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/chat/message.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/chat/meta.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/chat/tool.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/errors/__init__.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/errors/errors.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/guardrails/__init__.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/guardrails/_deterministic_guardrails_service.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/guardrails/_evaluators.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/guardrails/guardrails.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/py.typed +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/tracing/_utils.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/tracing/decorators.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/tracing/exporters.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/src/uipath/core/tracing/span_utils.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/tests/__init__.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/tests/conftest.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/tests/guardrails/test_deterministic_guardrails_service.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/tests/tracing/test_external_integration.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/tests/tracing/test_serialization.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/tests/tracing/test_span_nesting.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/tests/tracing/test_span_registry.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/tests/tracing/test_trace_manager.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/tests/tracing/test_traced.py +0 -0
- {uipath_core-0.1.10 → uipath_core-0.2.1}/tests/tracing/test_tracing_utils.py +0 -0
|
@@ -7,9 +7,11 @@ with OpenTelemetry tracing, including custom processors for UiPath execution tra
|
|
|
7
7
|
from uipath.core.tracing.decorators import traced
|
|
8
8
|
from uipath.core.tracing.span_utils import UiPathSpanUtils
|
|
9
9
|
from uipath.core.tracing.trace_manager import UiPathTraceManager
|
|
10
|
+
from uipath.core.tracing.types import UiPathTraceSettings
|
|
10
11
|
|
|
11
12
|
__all__ = [
|
|
12
13
|
"traced",
|
|
13
14
|
"UiPathSpanUtils",
|
|
14
15
|
"UiPathTraceManager",
|
|
16
|
+
"UiPathTraceSettings",
|
|
15
17
|
]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Custom span processors for UiPath execution tracing."""
|
|
2
|
+
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
from opentelemetry import context as context_api
|
|
6
|
+
from opentelemetry import trace
|
|
7
|
+
from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
|
|
8
|
+
from opentelemetry.sdk.trace.export import (
|
|
9
|
+
BatchSpanProcessor,
|
|
10
|
+
SimpleSpanProcessor,
|
|
11
|
+
SpanExporter,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from uipath.core.tracing.types import UiPathTraceSettings
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UiPathExecutionTraceProcessorMixin:
|
|
18
|
+
"""Mixin that propagates execution.id and optionally filters spans."""
|
|
19
|
+
|
|
20
|
+
_settings: UiPathTraceSettings | None = None
|
|
21
|
+
|
|
22
|
+
def on_start(self, span: Span, parent_context: context_api.Context | None = None):
|
|
23
|
+
"""Called when a span is started."""
|
|
24
|
+
parent_span: Span | None
|
|
25
|
+
if parent_context:
|
|
26
|
+
parent_span = cast(Span, trace.get_current_span(parent_context))
|
|
27
|
+
else:
|
|
28
|
+
parent_span = cast(Span, trace.get_current_span())
|
|
29
|
+
|
|
30
|
+
if parent_span and parent_span.is_recording() and parent_span.attributes:
|
|
31
|
+
execution_id = parent_span.attributes.get("execution.id")
|
|
32
|
+
if execution_id:
|
|
33
|
+
span.set_attribute("execution.id", execution_id)
|
|
34
|
+
|
|
35
|
+
def on_end(self, span: ReadableSpan):
|
|
36
|
+
"""Called when a span ends. Filters before delegating to parent."""
|
|
37
|
+
span_filter = self._settings.span_filter if self._settings else None
|
|
38
|
+
if span_filter is None or span_filter(span):
|
|
39
|
+
parent = cast(SpanProcessor, super())
|
|
40
|
+
parent.on_end(span)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class UiPathExecutionBatchTraceProcessor(
|
|
44
|
+
UiPathExecutionTraceProcessorMixin, BatchSpanProcessor
|
|
45
|
+
):
|
|
46
|
+
"""Batch span processor that propagates execution.id and optionally filters."""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
span_exporter: SpanExporter,
|
|
51
|
+
settings: UiPathTraceSettings | None = None,
|
|
52
|
+
):
|
|
53
|
+
"""Initialize the batch trace processor."""
|
|
54
|
+
super().__init__(span_exporter)
|
|
55
|
+
self._settings = settings
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class UiPathExecutionSimpleTraceProcessor(
|
|
59
|
+
UiPathExecutionTraceProcessorMixin, SimpleSpanProcessor
|
|
60
|
+
):
|
|
61
|
+
"""Simple span processor that propagates execution.id and optionally filters."""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
span_exporter: SpanExporter,
|
|
66
|
+
settings: UiPathTraceSettings | None = None,
|
|
67
|
+
):
|
|
68
|
+
"""Initialize the simple trace processor."""
|
|
69
|
+
super().__init__(span_exporter)
|
|
70
|
+
self._settings = settings
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
"UiPathExecutionBatchTraceProcessor",
|
|
75
|
+
"UiPathExecutionSimpleTraceProcessor",
|
|
76
|
+
]
|
|
@@ -15,6 +15,7 @@ from uipath.core.tracing.processors import (
|
|
|
15
15
|
UiPathExecutionBatchTraceProcessor,
|
|
16
16
|
UiPathExecutionSimpleTraceProcessor,
|
|
17
17
|
)
|
|
18
|
+
from uipath.core.tracing.types import UiPathTraceSettings
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class UiPathTraceManager:
|
|
@@ -40,13 +41,22 @@ class UiPathTraceManager:
|
|
|
40
41
|
self,
|
|
41
42
|
span_exporter: SpanExporter,
|
|
42
43
|
batch: bool = True,
|
|
44
|
+
settings: UiPathTraceSettings | None = None,
|
|
43
45
|
) -> UiPathTraceManager:
|
|
44
|
-
"""Add a span
|
|
46
|
+
"""Add a span exporter to the tracer provider.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
span_exporter: The exporter to add.
|
|
50
|
+
batch: Whether to use batch processing (default: True).
|
|
51
|
+
settings: Optional trace settings for filtering, etc.
|
|
52
|
+
"""
|
|
45
53
|
span_processor: SpanProcessor
|
|
46
54
|
if batch:
|
|
47
|
-
span_processor = UiPathExecutionBatchTraceProcessor(span_exporter)
|
|
55
|
+
span_processor = UiPathExecutionBatchTraceProcessor(span_exporter, settings)
|
|
48
56
|
else:
|
|
49
|
-
span_processor = UiPathExecutionSimpleTraceProcessor(
|
|
57
|
+
span_processor = UiPathExecutionSimpleTraceProcessor(
|
|
58
|
+
span_exporter, settings
|
|
59
|
+
)
|
|
50
60
|
self.tracer_span_processors.append(span_processor)
|
|
51
61
|
self.tracer_provider.add_span_processor(span_processor)
|
|
52
62
|
return self
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Tracing types for UiPath SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
from opentelemetry.sdk.trace import ReadableSpan
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class UiPathTraceSettings(BaseModel):
|
|
10
|
+
"""Trace settings for UiPath SDK."""
|
|
11
|
+
|
|
12
|
+
model_config = {"arbitrary_types_allowed": True} # Needed for Callable
|
|
13
|
+
|
|
14
|
+
span_filter: Callable[[ReadableSpan], bool] | None = Field(
|
|
15
|
+
default=None,
|
|
16
|
+
description=(
|
|
17
|
+
"Optional filter to decide whether a span should be exported. "
|
|
18
|
+
"Called when a span ends with a ReadableSpan argument. "
|
|
19
|
+
"Return True to export, False to skip."
|
|
20
|
+
),
|
|
21
|
+
)
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""Tests for span filtering in trace manager and processors."""
|
|
2
|
+
|
|
3
|
+
from opentelemetry import trace
|
|
4
|
+
|
|
5
|
+
from uipath.core.tracing.trace_manager import UiPathTraceManager
|
|
6
|
+
from uipath.core.tracing.types import UiPathTraceSettings
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestSpanFiltering:
|
|
10
|
+
"""Tests for span filtering functionality."""
|
|
11
|
+
|
|
12
|
+
def test_no_filter_exports_all_spans(self):
|
|
13
|
+
"""Test that without a filter, all spans are exported."""
|
|
14
|
+
trace_manager = UiPathTraceManager()
|
|
15
|
+
|
|
16
|
+
tracer = trace.get_tracer("test")
|
|
17
|
+
with trace_manager.start_execution_span("root", "exec-1"):
|
|
18
|
+
with tracer.start_as_current_span("child-1"):
|
|
19
|
+
pass
|
|
20
|
+
with tracer.start_as_current_span("child-2"):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
spans = trace_manager.get_execution_spans("exec-1")
|
|
24
|
+
assert len(spans) == 3
|
|
25
|
+
span_names = {s.name for s in spans}
|
|
26
|
+
assert span_names == {"root", "child-1", "child-2"}
|
|
27
|
+
|
|
28
|
+
def test_filter_drops_non_matching_spans(self):
|
|
29
|
+
"""Test that filter drops spans that don't match the predicate."""
|
|
30
|
+
from unittest.mock import MagicMock
|
|
31
|
+
|
|
32
|
+
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
|
|
33
|
+
|
|
34
|
+
mock_exporter = MagicMock(spec=SpanExporter)
|
|
35
|
+
mock_exporter.export.return_value = SpanExportResult.SUCCESS
|
|
36
|
+
|
|
37
|
+
settings = UiPathTraceSettings(
|
|
38
|
+
span_filter=lambda span: span.attributes is not None
|
|
39
|
+
and span.attributes.get("keep") is True
|
|
40
|
+
)
|
|
41
|
+
trace_manager = UiPathTraceManager()
|
|
42
|
+
trace_manager.add_span_exporter(mock_exporter, batch=False, settings=settings)
|
|
43
|
+
|
|
44
|
+
tracer = trace.get_tracer("test")
|
|
45
|
+
with tracer.start_as_current_span("kept", attributes={"keep": True}):
|
|
46
|
+
pass
|
|
47
|
+
with tracer.start_as_current_span("dropped", attributes={"keep": False}):
|
|
48
|
+
pass
|
|
49
|
+
with tracer.start_as_current_span("also-dropped"):
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
trace_manager.flush_spans()
|
|
53
|
+
|
|
54
|
+
exported_spans = []
|
|
55
|
+
for call in mock_exporter.export.call_args_list:
|
|
56
|
+
exported_spans.extend(call[0][0])
|
|
57
|
+
|
|
58
|
+
exported_names = {s.name for s in exported_spans}
|
|
59
|
+
assert "kept" in exported_names
|
|
60
|
+
assert "dropped" not in exported_names
|
|
61
|
+
assert "also-dropped" not in exported_names
|
|
62
|
+
|
|
63
|
+
def test_filter_by_span_name(self):
|
|
64
|
+
"""Test filtering spans by name pattern."""
|
|
65
|
+
from unittest.mock import MagicMock
|
|
66
|
+
|
|
67
|
+
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
|
|
68
|
+
|
|
69
|
+
mock_exporter = MagicMock(spec=SpanExporter)
|
|
70
|
+
mock_exporter.export.return_value = SpanExportResult.SUCCESS
|
|
71
|
+
|
|
72
|
+
settings = UiPathTraceSettings(
|
|
73
|
+
span_filter=lambda span: span.name.startswith("uipath.")
|
|
74
|
+
)
|
|
75
|
+
trace_manager = UiPathTraceManager()
|
|
76
|
+
trace_manager.add_span_exporter(mock_exporter, batch=False, settings=settings)
|
|
77
|
+
|
|
78
|
+
tracer = trace.get_tracer("test")
|
|
79
|
+
with tracer.start_as_current_span("uipath.action"):
|
|
80
|
+
pass
|
|
81
|
+
with tracer.start_as_current_span("uipath.tool"):
|
|
82
|
+
pass
|
|
83
|
+
with tracer.start_as_current_span("http.request"):
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
trace_manager.flush_spans()
|
|
87
|
+
|
|
88
|
+
exported_spans = []
|
|
89
|
+
for call in mock_exporter.export.call_args_list:
|
|
90
|
+
exported_spans.extend(call[0][0])
|
|
91
|
+
|
|
92
|
+
exported_names = {s.name for s in exported_spans}
|
|
93
|
+
assert "uipath.action" in exported_names
|
|
94
|
+
assert "uipath.tool" in exported_names
|
|
95
|
+
assert "http.request" not in exported_names
|
|
96
|
+
|
|
97
|
+
def test_filter_custom_instrumentation_attribute(self):
|
|
98
|
+
"""Test filtering by custom instrumentation attribute (low-code scenario)."""
|
|
99
|
+
from unittest.mock import MagicMock
|
|
100
|
+
|
|
101
|
+
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
|
|
102
|
+
|
|
103
|
+
mock_exporter = MagicMock(spec=SpanExporter)
|
|
104
|
+
mock_exporter.export.return_value = SpanExportResult.SUCCESS
|
|
105
|
+
|
|
106
|
+
settings = UiPathTraceSettings(
|
|
107
|
+
span_filter=lambda span: bool(
|
|
108
|
+
span.attributes and span.attributes.get("uipath.custom_instrumentation")
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
trace_manager = UiPathTraceManager()
|
|
112
|
+
trace_manager.add_span_exporter(mock_exporter, batch=False, settings=settings)
|
|
113
|
+
|
|
114
|
+
tracer = trace.get_tracer("test")
|
|
115
|
+
with tracer.start_as_current_span(
|
|
116
|
+
"custom-span",
|
|
117
|
+
attributes={"uipath.custom_instrumentation": True},
|
|
118
|
+
):
|
|
119
|
+
pass
|
|
120
|
+
with tracer.start_as_current_span(
|
|
121
|
+
"auto-instrumented",
|
|
122
|
+
attributes={"http.method": "GET"},
|
|
123
|
+
):
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
trace_manager.flush_spans()
|
|
127
|
+
|
|
128
|
+
exported_spans = []
|
|
129
|
+
for call in mock_exporter.export.call_args_list:
|
|
130
|
+
exported_spans.extend(call[0][0])
|
|
131
|
+
|
|
132
|
+
exported_names = {s.name for s in exported_spans}
|
|
133
|
+
assert "custom-span" in exported_names
|
|
134
|
+
assert "auto-instrumented" not in exported_names
|
|
135
|
+
|
|
136
|
+
def test_none_filter_same_as_no_filter(self):
|
|
137
|
+
"""Test that explicit None filter behaves same as no filter."""
|
|
138
|
+
from unittest.mock import MagicMock
|
|
139
|
+
|
|
140
|
+
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
|
|
141
|
+
|
|
142
|
+
mock_exporter = MagicMock(spec=SpanExporter)
|
|
143
|
+
mock_exporter.export.return_value = SpanExportResult.SUCCESS
|
|
144
|
+
|
|
145
|
+
settings = UiPathTraceSettings(span_filter=None)
|
|
146
|
+
trace_manager = UiPathTraceManager()
|
|
147
|
+
trace_manager.add_span_exporter(mock_exporter, batch=False, settings=settings)
|
|
148
|
+
|
|
149
|
+
tracer = trace.get_tracer("test")
|
|
150
|
+
with tracer.start_as_current_span("span-1"):
|
|
151
|
+
pass
|
|
152
|
+
with tracer.start_as_current_span("span-2"):
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
trace_manager.flush_spans()
|
|
156
|
+
|
|
157
|
+
exported_spans = []
|
|
158
|
+
for call in mock_exporter.export.call_args_list:
|
|
159
|
+
exported_spans.extend(call[0][0])
|
|
160
|
+
|
|
161
|
+
assert len(exported_spans) == 2
|
|
162
|
+
|
|
163
|
+
def test_filter_with_empty_attributes(self):
|
|
164
|
+
"""Test that filter handles spans with no attributes gracefully."""
|
|
165
|
+
from unittest.mock import MagicMock
|
|
166
|
+
|
|
167
|
+
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
|
|
168
|
+
|
|
169
|
+
mock_exporter = MagicMock(spec=SpanExporter)
|
|
170
|
+
mock_exporter.export.return_value = SpanExportResult.SUCCESS
|
|
171
|
+
|
|
172
|
+
settings = UiPathTraceSettings(
|
|
173
|
+
span_filter=lambda span: (
|
|
174
|
+
span.attributes is not None and span.attributes.get("required") is True
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
trace_manager = UiPathTraceManager()
|
|
178
|
+
trace_manager.add_span_exporter(mock_exporter, batch=False, settings=settings)
|
|
179
|
+
|
|
180
|
+
tracer = trace.get_tracer("test")
|
|
181
|
+
with tracer.start_as_current_span("no-attrs"):
|
|
182
|
+
pass
|
|
183
|
+
with tracer.start_as_current_span(
|
|
184
|
+
"has-required", attributes={"required": True}
|
|
185
|
+
):
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
trace_manager.flush_spans()
|
|
189
|
+
|
|
190
|
+
exported_spans = []
|
|
191
|
+
for call in mock_exporter.export.call_args_list:
|
|
192
|
+
exported_spans.extend(call[0][0])
|
|
193
|
+
|
|
194
|
+
exported_names = {s.name for s in exported_spans}
|
|
195
|
+
assert "has-required" in exported_names
|
|
196
|
+
assert "no-attrs" not in exported_names
|
|
197
|
+
|
|
198
|
+
def test_different_filters_per_exporter(self):
|
|
199
|
+
"""Test that different exporters can have different filters."""
|
|
200
|
+
from unittest.mock import MagicMock
|
|
201
|
+
|
|
202
|
+
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
|
|
203
|
+
|
|
204
|
+
mock_exporter_a = MagicMock(spec=SpanExporter)
|
|
205
|
+
mock_exporter_a.export.return_value = SpanExportResult.SUCCESS
|
|
206
|
+
|
|
207
|
+
mock_exporter_b = MagicMock(spec=SpanExporter)
|
|
208
|
+
mock_exporter_b.export.return_value = SpanExportResult.SUCCESS
|
|
209
|
+
|
|
210
|
+
settings_a = UiPathTraceSettings(
|
|
211
|
+
span_filter=lambda span: span.attributes is not None
|
|
212
|
+
and span.attributes.get("dest") == "a"
|
|
213
|
+
)
|
|
214
|
+
settings_b = UiPathTraceSettings(
|
|
215
|
+
span_filter=lambda span: span.attributes is not None
|
|
216
|
+
and span.attributes.get("dest") == "b"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
trace_manager = UiPathTraceManager()
|
|
220
|
+
trace_manager.add_span_exporter(
|
|
221
|
+
mock_exporter_a, batch=False, settings=settings_a
|
|
222
|
+
)
|
|
223
|
+
trace_manager.add_span_exporter(
|
|
224
|
+
mock_exporter_b, batch=False, settings=settings_b
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
tracer = trace.get_tracer("test")
|
|
228
|
+
with tracer.start_as_current_span("to-a", attributes={"dest": "a"}):
|
|
229
|
+
pass
|
|
230
|
+
with tracer.start_as_current_span("to-b", attributes={"dest": "b"}):
|
|
231
|
+
pass
|
|
232
|
+
with tracer.start_as_current_span("to-neither", attributes={"dest": "c"}):
|
|
233
|
+
pass
|
|
234
|
+
|
|
235
|
+
trace_manager.flush_spans()
|
|
236
|
+
|
|
237
|
+
exported_a = []
|
|
238
|
+
for call in mock_exporter_a.export.call_args_list:
|
|
239
|
+
exported_a.extend(call[0][0])
|
|
240
|
+
assert {s.name for s in exported_a} == {"to-a"}
|
|
241
|
+
|
|
242
|
+
exported_b = []
|
|
243
|
+
for call in mock_exporter_b.export.call_args_list:
|
|
244
|
+
exported_b.extend(call[0][0])
|
|
245
|
+
assert {s.name for s in exported_b} == {"to-b"}
|
|
@@ -430,20 +430,20 @@ wheels = [
|
|
|
430
430
|
|
|
431
431
|
[[package]]
|
|
432
432
|
name = "opentelemetry-api"
|
|
433
|
-
version = "1.39.
|
|
433
|
+
version = "1.39.1"
|
|
434
434
|
source = { registry = "https://pypi.org/simple" }
|
|
435
435
|
dependencies = [
|
|
436
436
|
{ name = "importlib-metadata" },
|
|
437
437
|
{ name = "typing-extensions" },
|
|
438
438
|
]
|
|
439
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
439
|
+
sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" }
|
|
440
440
|
wheels = [
|
|
441
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
441
|
+
{ url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" },
|
|
442
442
|
]
|
|
443
443
|
|
|
444
444
|
[[package]]
|
|
445
445
|
name = "opentelemetry-instrumentation"
|
|
446
|
-
version = "0.
|
|
446
|
+
version = "0.60b1"
|
|
447
447
|
source = { registry = "https://pypi.org/simple" }
|
|
448
448
|
dependencies = [
|
|
449
449
|
{ name = "opentelemetry-api" },
|
|
@@ -451,36 +451,36 @@ dependencies = [
|
|
|
451
451
|
{ name = "packaging" },
|
|
452
452
|
{ name = "wrapt" },
|
|
453
453
|
]
|
|
454
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
454
|
+
sdist = { url = "https://files.pythonhosted.org/packages/41/0f/7e6b713ac117c1f5e4e3300748af699b9902a2e5e34c9cf443dde25a01fa/opentelemetry_instrumentation-0.60b1.tar.gz", hash = "sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a", size = 31706, upload-time = "2025-12-11T13:36:42.515Z" }
|
|
455
455
|
wheels = [
|
|
456
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
456
|
+
{ url = "https://files.pythonhosted.org/packages/77/d2/6788e83c5c86a2690101681aeef27eeb2a6bf22df52d3f263a22cee20915/opentelemetry_instrumentation-0.60b1-py3-none-any.whl", hash = "sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d", size = 33096, upload-time = "2025-12-11T13:35:33.067Z" },
|
|
457
457
|
]
|
|
458
458
|
|
|
459
459
|
[[package]]
|
|
460
460
|
name = "opentelemetry-sdk"
|
|
461
|
-
version = "1.39.
|
|
461
|
+
version = "1.39.1"
|
|
462
462
|
source = { registry = "https://pypi.org/simple" }
|
|
463
463
|
dependencies = [
|
|
464
464
|
{ name = "opentelemetry-api" },
|
|
465
465
|
{ name = "opentelemetry-semantic-conventions" },
|
|
466
466
|
{ name = "typing-extensions" },
|
|
467
467
|
]
|
|
468
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
468
|
+
sdist = { url = "https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" }
|
|
469
469
|
wheels = [
|
|
470
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
470
|
+
{ url = "https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c", size = 132565, upload-time = "2025-12-11T13:32:35.069Z" },
|
|
471
471
|
]
|
|
472
472
|
|
|
473
473
|
[[package]]
|
|
474
474
|
name = "opentelemetry-semantic-conventions"
|
|
475
|
-
version = "0.
|
|
475
|
+
version = "0.60b1"
|
|
476
476
|
source = { registry = "https://pypi.org/simple" }
|
|
477
477
|
dependencies = [
|
|
478
478
|
{ name = "opentelemetry-api" },
|
|
479
479
|
{ name = "typing-extensions" },
|
|
480
480
|
]
|
|
481
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
481
|
+
sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" }
|
|
482
482
|
wheels = [
|
|
483
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
483
|
+
{ url = "https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb", size = 219982, upload-time = "2025-12-11T13:32:36.955Z" },
|
|
484
484
|
]
|
|
485
485
|
|
|
486
486
|
[[package]]
|
|
@@ -991,7 +991,7 @@ wheels = [
|
|
|
991
991
|
|
|
992
992
|
[[package]]
|
|
993
993
|
name = "uipath-core"
|
|
994
|
-
version = "0.1
|
|
994
|
+
version = "0.2.1"
|
|
995
995
|
source = { editable = "." }
|
|
996
996
|
dependencies = [
|
|
997
997
|
{ name = "opentelemetry-instrumentation" },
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"""Custom span processors for UiPath execution tracing."""
|
|
2
|
-
|
|
3
|
-
from typing import Optional, cast
|
|
4
|
-
|
|
5
|
-
from opentelemetry import context as context_api
|
|
6
|
-
from opentelemetry import trace
|
|
7
|
-
from opentelemetry.sdk.trace import Span
|
|
8
|
-
from opentelemetry.sdk.trace.export import (
|
|
9
|
-
BatchSpanProcessor,
|
|
10
|
-
SimpleSpanProcessor,
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class UiPathExecutionTraceProcessorMixin:
|
|
15
|
-
def on_start(
|
|
16
|
-
self, span: Span, parent_context: Optional[context_api.Context] = None
|
|
17
|
-
):
|
|
18
|
-
"""Called when a span is started."""
|
|
19
|
-
parent_span: Optional[Span]
|
|
20
|
-
if parent_context:
|
|
21
|
-
parent_span = cast(Span, trace.get_current_span(parent_context))
|
|
22
|
-
else:
|
|
23
|
-
parent_span = cast(Span, trace.get_current_span())
|
|
24
|
-
|
|
25
|
-
if parent_span and parent_span.is_recording() and parent_span.attributes:
|
|
26
|
-
execution_id = parent_span.attributes.get("execution.id")
|
|
27
|
-
if execution_id:
|
|
28
|
-
span.set_attribute("execution.id", execution_id)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class UiPathExecutionBatchTraceProcessor(
|
|
32
|
-
UiPathExecutionTraceProcessorMixin, BatchSpanProcessor
|
|
33
|
-
):
|
|
34
|
-
"""Batch span processor that propagates execution.id."""
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class UiPathExecutionSimpleTraceProcessor(
|
|
38
|
-
UiPathExecutionTraceProcessorMixin, SimpleSpanProcessor
|
|
39
|
-
):
|
|
40
|
-
"""Simple span processor that propagates execution.id."""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
__all__ = [
|
|
44
|
-
"UiPathExecutionBatchTraceProcessor",
|
|
45
|
-
"UiPathExecutionSimpleTraceProcessor",
|
|
46
|
-
]
|
|
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
|
{uipath_core-0.1.10 → uipath_core-0.2.1}/tests/guardrails/test_deterministic_guardrails_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|