uipath 2.1.27__py3-none-any.whl → 2.1.29__py3-none-any.whl
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/_cli/__init__.py +2 -0
- uipath/_cli/_dev/_terminal/__init__.py +227 -0
- uipath/_cli/_dev/_terminal/_components/_details.py +421 -0
- uipath/_cli/_dev/_terminal/_components/_history.py +57 -0
- uipath/_cli/_dev/_terminal/_components/_new.py +133 -0
- uipath/_cli/_dev/_terminal/_models/_execution.py +43 -0
- uipath/_cli/_dev/_terminal/_models/_messages.py +65 -0
- uipath/_cli/_dev/_terminal/_styles/terminal.tcss +361 -0
- uipath/_cli/_dev/_terminal/_traces/_exporter.py +119 -0
- uipath/_cli/_dev/_terminal/_traces/_logger.py +32 -0
- uipath/_cli/_push/sw_file_handler.py +5 -5
- uipath/_cli/_runtime/_contracts.py +94 -4
- uipath/_cli/_runtime/_logging.py +20 -13
- uipath/_cli/_runtime/_runtime.py +2 -14
- uipath/_cli/cli_dev.py +44 -0
- uipath/_cli/cli_run.py +33 -28
- uipath/_cli/middlewares.py +1 -0
- uipath/telemetry/_track.py +2 -2
- {uipath-2.1.27.dist-info → uipath-2.1.29.dist-info}/METADATA +3 -1
- {uipath-2.1.27.dist-info → uipath-2.1.29.dist-info}/RECORD +23 -13
- {uipath-2.1.27.dist-info → uipath-2.1.29.dist-info}/WHEEL +0 -0
- {uipath-2.1.27.dist-info → uipath-2.1.29.dist-info}/entry_points.txt +0 -0
- {uipath-2.1.27.dist-info → uipath-2.1.29.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
import logging
|
2
|
+
from datetime import datetime
|
3
|
+
from typing import Callable, Sequence
|
4
|
+
|
5
|
+
from opentelemetry import trace
|
6
|
+
from opentelemetry.sdk.trace import Event, ReadableSpan
|
7
|
+
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
|
8
|
+
from opentelemetry.trace import StatusCode
|
9
|
+
|
10
|
+
from .._models._messages import LogMessage, TraceMessage
|
11
|
+
|
12
|
+
|
13
|
+
class RunContextExporter(SpanExporter):
|
14
|
+
"""Custom trace exporter that sends traces and logs to CLI UI."""
|
15
|
+
|
16
|
+
def __init__(
|
17
|
+
self,
|
18
|
+
on_trace: Callable[[TraceMessage], None],
|
19
|
+
on_log: Callable[[LogMessage], None],
|
20
|
+
):
|
21
|
+
self.on_trace = on_trace
|
22
|
+
self.on_log = on_log
|
23
|
+
self.logger = logging.getLogger(__name__)
|
24
|
+
|
25
|
+
def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
|
26
|
+
"""Export spans to CLI UI."""
|
27
|
+
try:
|
28
|
+
for span in spans:
|
29
|
+
self._export_span(span)
|
30
|
+
return SpanExportResult.SUCCESS
|
31
|
+
except Exception as e:
|
32
|
+
self.logger.error(f"Failed to export spans: {e}")
|
33
|
+
return SpanExportResult.FAILURE
|
34
|
+
|
35
|
+
def _export_span(self, span: ReadableSpan):
|
36
|
+
"""Export a single span to CLI UI."""
|
37
|
+
# Calculate duration
|
38
|
+
start_time = (
|
39
|
+
span.start_time / 1_000_000_000 if span.start_time is not None else 0
|
40
|
+
)
|
41
|
+
end_time = span.end_time / 1_000_000_000 if span.end_time is not None else None
|
42
|
+
duration_ms = (end_time - start_time) * 1000 if end_time else None
|
43
|
+
|
44
|
+
# Determine status
|
45
|
+
if span.status.status_code == StatusCode.ERROR:
|
46
|
+
status = "failed"
|
47
|
+
elif end_time:
|
48
|
+
status = "completed"
|
49
|
+
else:
|
50
|
+
status = "running"
|
51
|
+
|
52
|
+
# Extract span context information
|
53
|
+
span_context = span.get_span_context()
|
54
|
+
|
55
|
+
# Convert span IDs to string format (they're usually int64)
|
56
|
+
span_id = f"{span_context.span_id:016x}" # 16-char hex string
|
57
|
+
trace_id = f"{span_context.trace_id:032x}" # 32-char hex string
|
58
|
+
|
59
|
+
run_id = span.attributes.get("execution.id") if span.attributes else None
|
60
|
+
run_id_val = str(run_id) if run_id is not None else None
|
61
|
+
|
62
|
+
if run_id_val is None:
|
63
|
+
return
|
64
|
+
|
65
|
+
# Get parent span ID if available
|
66
|
+
parent_span_id = None
|
67
|
+
if hasattr(span, "parent") and span.parent:
|
68
|
+
parent_span_id = f"{span.parent.span_id:016x}"
|
69
|
+
|
70
|
+
# Create trace message with all required fields
|
71
|
+
trace_msg = TraceMessage(
|
72
|
+
run_id=run_id_val,
|
73
|
+
span_name=span.name,
|
74
|
+
span_id=span_id,
|
75
|
+
parent_span_id=parent_span_id,
|
76
|
+
trace_id=trace_id,
|
77
|
+
status=status,
|
78
|
+
duration_ms=duration_ms,
|
79
|
+
timestamp=datetime.fromtimestamp(start_time),
|
80
|
+
attributes=dict(span.attributes) if span.attributes else {},
|
81
|
+
)
|
82
|
+
|
83
|
+
# Send to UI
|
84
|
+
self.on_trace(trace_msg)
|
85
|
+
|
86
|
+
# Also send logs if there are events
|
87
|
+
if hasattr(span, "events") and span.events:
|
88
|
+
for event in span.events:
|
89
|
+
log_level = self._determine_log_level(event, span.status)
|
90
|
+
log_msg = LogMessage(
|
91
|
+
run_id=run_id_val,
|
92
|
+
level=log_level,
|
93
|
+
message=event.name,
|
94
|
+
timestamp=datetime.fromtimestamp(event.timestamp / 1_000_000_000),
|
95
|
+
)
|
96
|
+
self.on_log(log_msg)
|
97
|
+
|
98
|
+
def _determine_log_level(self, event: Event, span_status: trace.Status) -> str:
|
99
|
+
"""Determine log level from span event."""
|
100
|
+
event_name = event.name.lower()
|
101
|
+
|
102
|
+
if span_status.status_code == StatusCode.ERROR:
|
103
|
+
return "ERROR"
|
104
|
+
elif "error" in event_name or "exception" in event_name:
|
105
|
+
return "ERROR"
|
106
|
+
elif "warn" in event_name:
|
107
|
+
return "WARNING"
|
108
|
+
elif "debug" in event_name:
|
109
|
+
return "DEBUG"
|
110
|
+
else:
|
111
|
+
return "INFO"
|
112
|
+
|
113
|
+
def shutdown(self) -> None:
|
114
|
+
"""Shutdown the exporter."""
|
115
|
+
pass
|
116
|
+
|
117
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
118
|
+
"""Force flush any pending spans."""
|
119
|
+
return True
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import logging
|
2
|
+
from datetime import datetime
|
3
|
+
from typing import Callable
|
4
|
+
|
5
|
+
from .._models._messages import LogMessage
|
6
|
+
|
7
|
+
|
8
|
+
class RunContextLogHandler(logging.Handler):
|
9
|
+
"""Custom log handler that sends logs to CLI UI."""
|
10
|
+
|
11
|
+
def __init__(
|
12
|
+
self,
|
13
|
+
run_id: str,
|
14
|
+
on_log: Callable[[LogMessage], None],
|
15
|
+
):
|
16
|
+
super().__init__()
|
17
|
+
self.run_id = run_id
|
18
|
+
self.on_log = on_log
|
19
|
+
|
20
|
+
def emit(self, record: logging.LogRecord):
|
21
|
+
"""Emit a log record to CLI UI."""
|
22
|
+
try:
|
23
|
+
log_msg = LogMessage(
|
24
|
+
run_id=self.run_id,
|
25
|
+
level=record.levelname,
|
26
|
+
message=self.format(record),
|
27
|
+
timestamp=datetime.fromtimestamp(record.created),
|
28
|
+
)
|
29
|
+
self.on_log(log_msg)
|
30
|
+
except Exception:
|
31
|
+
# Don't let logging errors crash the app
|
32
|
+
pass
|
@@ -395,10 +395,8 @@ class SwFileHandler:
|
|
395
395
|
return toml_data.get("authors", "").strip()
|
396
396
|
|
397
397
|
try:
|
398
|
-
|
399
|
-
|
400
|
-
for entrypoint in uipath_config["entryPoints"]
|
401
|
-
]
|
398
|
+
input_schema = uipath_config["entryPoints"][0]["input"]
|
399
|
+
output_schema = uipath_config["entryPoints"][0]["output"]
|
402
400
|
except (FileNotFoundError, KeyError) as e:
|
403
401
|
self.console.error(
|
404
402
|
f"Unable to extract entrypoints from configuration file. Please run 'uipath init' : {str(e)}",
|
@@ -417,10 +415,12 @@ class SwFileHandler:
|
|
417
415
|
"author": author,
|
418
416
|
"pushDate": datetime.now(timezone.utc).isoformat(),
|
419
417
|
},
|
420
|
-
"
|
418
|
+
"inputSchema": input_schema,
|
419
|
+
"outputSchema": output_schema,
|
421
420
|
"bindings": uipath_config.get(
|
422
421
|
"bindings", {"version": "2.0", "resources": []}
|
423
422
|
),
|
423
|
+
"settings": {},
|
424
424
|
}
|
425
425
|
|
426
426
|
existing = root_files.get("agent.json")
|
@@ -8,10 +8,22 @@ import traceback
|
|
8
8
|
from abc import ABC, abstractmethod
|
9
9
|
from enum import Enum
|
10
10
|
from functools import cached_property
|
11
|
-
from typing import Any, Dict, Generic, Optional, Type, TypeVar, Union
|
12
|
-
|
11
|
+
from typing import Any, Callable, Dict, Generic, List, Optional, Type, TypeVar, Union
|
12
|
+
|
13
|
+
from opentelemetry import context as context_api
|
14
|
+
from opentelemetry import trace
|
15
|
+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor # type: ignore
|
16
|
+
from opentelemetry.sdk.trace import Span, SpanProcessor, TracerProvider
|
17
|
+
from opentelemetry.sdk.trace.export import (
|
18
|
+
BatchSpanProcessor,
|
19
|
+
SimpleSpanProcessor,
|
20
|
+
SpanExporter,
|
21
|
+
)
|
22
|
+
from opentelemetry.trace import Tracer
|
13
23
|
from pydantic import BaseModel, Field
|
14
24
|
|
25
|
+
from uipath.tracing import TracingManager
|
26
|
+
|
15
27
|
from ._logging import LogsInterceptor
|
16
28
|
|
17
29
|
logger = logging.getLogger(__name__)
|
@@ -144,6 +156,7 @@ class UiPathRuntimeContext(BaseModel):
|
|
144
156
|
input: Optional[str] = None
|
145
157
|
input_json: Optional[Any] = None
|
146
158
|
job_id: Optional[str] = None
|
159
|
+
execution_id: Optional[str] = None
|
147
160
|
trace_id: Optional[str] = None
|
148
161
|
trace_context: Optional[UiPathTraceContext] = None
|
149
162
|
tracing_enabled: Union[bool, str] = False
|
@@ -159,7 +172,7 @@ class UiPathRuntimeContext(BaseModel):
|
|
159
172
|
execution_output_file: Optional[str] = None
|
160
173
|
input_file: Optional[str] = None
|
161
174
|
is_eval_run: bool = False
|
162
|
-
|
175
|
+
log_handler: Optional[logging.Handler] = None
|
163
176
|
model_config = {"arbitrary_types_allowed": True}
|
164
177
|
|
165
178
|
@classmethod
|
@@ -328,6 +341,7 @@ class UiPathBaseRuntime(ABC):
|
|
328
341
|
file=self.context.logs_file,
|
329
342
|
job_id=self.context.job_id,
|
330
343
|
is_debug_run=self.is_debug_run(),
|
344
|
+
log_handler=self.context.log_handler,
|
331
345
|
)
|
332
346
|
self.logs_interceptor.setup()
|
333
347
|
|
@@ -487,6 +501,34 @@ class UiPathRuntimeFactory(Generic[T, C]):
|
|
487
501
|
|
488
502
|
self.runtime_class = runtime_class
|
489
503
|
self.context_class = context_class
|
504
|
+
self.tracer_provider: TracerProvider = TracerProvider()
|
505
|
+
self.tracer_span_processors: List[SpanProcessor] = []
|
506
|
+
trace.set_tracer_provider(self.tracer_provider)
|
507
|
+
|
508
|
+
def add_span_exporter(
|
509
|
+
self,
|
510
|
+
span_exporter: SpanExporter,
|
511
|
+
batch: bool = True,
|
512
|
+
) -> "UiPathRuntimeFactory[T, C]":
|
513
|
+
"""Add a span processor to the tracer provider."""
|
514
|
+
span_processor: SpanProcessor
|
515
|
+
if batch:
|
516
|
+
span_processor = UiPathExecutionBatchTraceProcessor(span_exporter)
|
517
|
+
else:
|
518
|
+
span_processor = UiPathExecutionSimpleTraceProcessor(span_exporter)
|
519
|
+
self.tracer_span_processors.append(span_processor)
|
520
|
+
self.tracer_provider.add_span_processor(span_processor)
|
521
|
+
return self
|
522
|
+
|
523
|
+
def add_instrumentor(
|
524
|
+
self,
|
525
|
+
instrumentor_class: Type[BaseInstrumentor],
|
526
|
+
get_current_span_func: Callable[[], Optional[Span]],
|
527
|
+
) -> "UiPathRuntimeFactory[T, C]":
|
528
|
+
"""Add and instrument immediately."""
|
529
|
+
instrumentor_class().instrument(tracer_provider=self.tracer_provider)
|
530
|
+
TracingManager.register_current_span_provider(get_current_span_func)
|
531
|
+
return self
|
490
532
|
|
491
533
|
def new_context(self, **kwargs) -> C:
|
492
534
|
"""Create a new context instance."""
|
@@ -499,4 +541,52 @@ class UiPathRuntimeFactory(Generic[T, C]):
|
|
499
541
|
async def execute(self, context: C) -> Optional[UiPathRuntimeResult]:
|
500
542
|
"""Execute runtime with context."""
|
501
543
|
async with self.from_context(context) as runtime:
|
502
|
-
|
544
|
+
result = await runtime.execute()
|
545
|
+
for span_processor in self.tracer_span_processors:
|
546
|
+
span_processor.force_flush()
|
547
|
+
return result
|
548
|
+
|
549
|
+
async def execute_in_root_span(
|
550
|
+
self, context: C, root_span: str = "root"
|
551
|
+
) -> Optional[UiPathRuntimeResult]:
|
552
|
+
"""Execute runtime with context."""
|
553
|
+
async with self.from_context(context) as runtime:
|
554
|
+
tracer: Tracer = trace.get_tracer("uipath-runtime")
|
555
|
+
with tracer.start_as_current_span(
|
556
|
+
root_span,
|
557
|
+
attributes={"execution.id": context.execution_id}
|
558
|
+
if context.execution_id
|
559
|
+
else {},
|
560
|
+
):
|
561
|
+
result = await runtime.execute()
|
562
|
+
for span_processor in self.tracer_span_processors:
|
563
|
+
span_processor.force_flush()
|
564
|
+
return result
|
565
|
+
|
566
|
+
|
567
|
+
class UiPathExecutionTraceProcessorMixin:
|
568
|
+
def on_start(
|
569
|
+
self, span: Span, parent_context: Optional[context_api.Context] = None
|
570
|
+
):
|
571
|
+
"""Called when a span is started."""
|
572
|
+
if parent_context:
|
573
|
+
parent_span = trace.get_current_span(parent_context)
|
574
|
+
else:
|
575
|
+
parent_span = trace.get_current_span()
|
576
|
+
|
577
|
+
if parent_span and parent_span.is_recording():
|
578
|
+
run_id = parent_span.attributes.get("execution.id") # type: ignore[attr-defined]
|
579
|
+
if run_id:
|
580
|
+
span.set_attribute("execution.id", run_id)
|
581
|
+
|
582
|
+
|
583
|
+
class UiPathExecutionBatchTraceProcessor(
|
584
|
+
UiPathExecutionTraceProcessorMixin, BatchSpanProcessor
|
585
|
+
):
|
586
|
+
"""Batch span processor that propagates execution.id."""
|
587
|
+
|
588
|
+
|
589
|
+
class UiPathExecutionSimpleTraceProcessor(
|
590
|
+
UiPathExecutionTraceProcessorMixin, SimpleSpanProcessor
|
591
|
+
):
|
592
|
+
"""Simple span processor that propagates execution.id."""
|
uipath/_cli/_runtime/_logging.py
CHANGED
@@ -30,6 +30,7 @@ class LogsInterceptor:
|
|
30
30
|
file: Optional[str] = "execution.log",
|
31
31
|
job_id: Optional[str] = None,
|
32
32
|
is_debug_run: bool = False,
|
33
|
+
log_handler: Optional[logging.Handler] = None,
|
33
34
|
):
|
34
35
|
"""Initialize the log interceptor.
|
35
36
|
|
@@ -39,6 +40,7 @@ class LogsInterceptor:
|
|
39
40
|
file (str): The log file name.
|
40
41
|
job_id (str, optional): If provided, logs go to file; otherwise, to stdout.
|
41
42
|
is_debug_run (bool, optional): If True, log the output to stdout/stderr.
|
43
|
+
log_handler (logging.Handler, optional): Custom log handler to use.
|
42
44
|
"""
|
43
45
|
min_level = min_level or "INFO"
|
44
46
|
self.job_id = job_id
|
@@ -57,21 +59,26 @@ class LogsInterceptor:
|
|
57
59
|
self.original_stdout = cast(TextIO, sys.stdout)
|
58
60
|
self.original_stderr = cast(TextIO, sys.stderr)
|
59
61
|
|
60
|
-
self.log_handler: Union[
|
62
|
+
self.log_handler: Union[
|
63
|
+
PersistentLogsHandler, logging.StreamHandler[TextIO], logging.Handler
|
64
|
+
]
|
61
65
|
|
62
|
-
|
63
|
-
|
64
|
-
# Use stdout handler when not running as a job or eval
|
65
|
-
self.log_handler = logging.StreamHandler(sys.stdout)
|
66
|
-
formatter = logging.Formatter("%(message)s")
|
67
|
-
self.log_handler.setFormatter(formatter)
|
66
|
+
if log_handler:
|
67
|
+
self.log_handler = log_handler
|
68
68
|
else:
|
69
|
-
#
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
69
|
+
# Create either file handler (runtime) or stdout handler (debug)
|
70
|
+
if is_debug_run:
|
71
|
+
# Use stdout handler when not running as a job or eval
|
72
|
+
self.log_handler = logging.StreamHandler(sys.stdout)
|
73
|
+
formatter = logging.Formatter("%(message)s")
|
74
|
+
self.log_handler.setFormatter(formatter)
|
75
|
+
else:
|
76
|
+
# Ensure directory exists for file logging
|
77
|
+
dir = dir or "__uipath"
|
78
|
+
file = file or "execution.log"
|
79
|
+
os.makedirs(dir, exist_ok=True)
|
80
|
+
log_file = os.path.join(dir, file)
|
81
|
+
self.log_handler = PersistentLogsHandler(file=log_file)
|
75
82
|
|
76
83
|
self.log_handler.setLevel(self.numeric_min_level)
|
77
84
|
self.logger = logging.getLogger("runtime")
|
uipath/_cli/_runtime/_runtime.py
CHANGED
@@ -8,13 +8,8 @@ import os
|
|
8
8
|
from dataclasses import asdict, is_dataclass
|
9
9
|
from typing import Any, Dict, Optional, Type, TypeVar, cast, get_type_hints
|
10
10
|
|
11
|
-
from opentelemetry import trace
|
12
|
-
from opentelemetry.sdk.trace import TracerProvider
|
13
|
-
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
14
11
|
from pydantic import BaseModel
|
15
12
|
|
16
|
-
from uipath.tracing import LlmOpsHttpExporter
|
17
|
-
|
18
13
|
from ._contracts import (
|
19
14
|
UiPathBaseRuntime,
|
20
15
|
UiPathErrorCategory,
|
@@ -43,11 +38,6 @@ class UiPathRuntime(UiPathBaseRuntime):
|
|
43
38
|
await self.validate()
|
44
39
|
|
45
40
|
try:
|
46
|
-
trace.set_tracer_provider(TracerProvider())
|
47
|
-
trace.get_tracer_provider().add_span_processor( # type: ignore
|
48
|
-
BatchSpanProcessor(LlmOpsHttpExporter())
|
49
|
-
)
|
50
|
-
|
51
41
|
if self.context.entrypoint is None:
|
52
42
|
return None
|
53
43
|
|
@@ -74,8 +64,6 @@ class UiPathRuntime(UiPathBaseRuntime):
|
|
74
64
|
f"Error: {str(e)}",
|
75
65
|
UiPathErrorCategory.SYSTEM,
|
76
66
|
) from e
|
77
|
-
finally:
|
78
|
-
trace.get_tracer_provider().shutdown() # type: ignore
|
79
67
|
|
80
68
|
async def validate(self) -> None:
|
81
69
|
"""Validate runtime inputs."""
|
@@ -223,7 +211,7 @@ class UiPathRuntime(UiPathBaseRuntime):
|
|
223
211
|
# Handle Pydantic models
|
224
212
|
try:
|
225
213
|
if inspect.isclass(cls) and issubclass(cls, BaseModel):
|
226
|
-
return
|
214
|
+
return cls.model_validate(data)
|
227
215
|
except TypeError:
|
228
216
|
# issubclass can raise TypeError if cls is not a class
|
229
217
|
pass
|
@@ -247,7 +235,7 @@ class UiPathRuntime(UiPathBaseRuntime):
|
|
247
235
|
value = self._convert_to_class(value, typed_field)
|
248
236
|
converted_data[field_name] = value
|
249
237
|
|
250
|
-
return
|
238
|
+
return cls(**converted_data)
|
251
239
|
|
252
240
|
# Handle regular classes
|
253
241
|
else:
|
uipath/_cli/cli_dev.py
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
import asyncio
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
import click
|
5
|
+
|
6
|
+
from ..telemetry import track
|
7
|
+
from ._dev._terminal import UiPathDevTerminal
|
8
|
+
from ._runtime._contracts import UiPathRuntimeContext, UiPathRuntimeFactory
|
9
|
+
from ._runtime._runtime import UiPathRuntime
|
10
|
+
from ._utils._console import ConsoleLogger
|
11
|
+
from .middlewares import Middlewares
|
12
|
+
|
13
|
+
console = ConsoleLogger()
|
14
|
+
|
15
|
+
|
16
|
+
@click.command()
|
17
|
+
@click.argument("interface", default="terminal")
|
18
|
+
@track
|
19
|
+
def dev(interface: Optional[str]) -> None:
|
20
|
+
"""Launch interactive debugging interface."""
|
21
|
+
console.info("🚀 Starting UiPath Dev Terminal...")
|
22
|
+
console.info("Use 'q' to quit, 'n' for new run, 'r' to execute")
|
23
|
+
|
24
|
+
result = Middlewares.next(
|
25
|
+
"dev",
|
26
|
+
interface,
|
27
|
+
)
|
28
|
+
|
29
|
+
if result.should_continue is False:
|
30
|
+
return
|
31
|
+
|
32
|
+
try:
|
33
|
+
if interface == "terminal":
|
34
|
+
runtime_factory = UiPathRuntimeFactory(UiPathRuntime, UiPathRuntimeContext)
|
35
|
+
app = UiPathDevTerminal(runtime_factory)
|
36
|
+
asyncio.run(app.run_async())
|
37
|
+
else:
|
38
|
+
console.error(f"Unknown interface: {interface}")
|
39
|
+
except KeyboardInterrupt:
|
40
|
+
console.info("Debug session interrupted by user")
|
41
|
+
except Exception as e:
|
42
|
+
console.error(
|
43
|
+
f"Error running debug interface: {str(e)}", include_traceback=True
|
44
|
+
)
|
uipath/_cli/cli_run.py
CHANGED
@@ -10,6 +10,7 @@ import click
|
|
10
10
|
from dotenv import load_dotenv
|
11
11
|
|
12
12
|
from uipath._cli._utils._debug import setup_debugging
|
13
|
+
from uipath.tracing import LlmOpsHttpExporter
|
13
14
|
|
14
15
|
from .._utils.constants import (
|
15
16
|
ENV_JOB_ID,
|
@@ -18,6 +19,7 @@ from ..telemetry import track
|
|
18
19
|
from ._runtime._contracts import (
|
19
20
|
UiPathRuntimeContext,
|
20
21
|
UiPathRuntimeError,
|
22
|
+
UiPathRuntimeFactory,
|
21
23
|
UiPathTraceContext,
|
22
24
|
)
|
23
25
|
from ._runtime._runtime import UiPathRuntime
|
@@ -61,39 +63,42 @@ Usage: `uipath run <entrypoint_path> <input_arguments> [-f <input_json_file_path
|
|
61
63
|
)
|
62
64
|
|
63
65
|
try:
|
66
|
+
context = UiPathRuntimeContext.from_config(
|
67
|
+
env.get("UIPATH_CONFIG_PATH", "uipath.json"), **kwargs
|
68
|
+
)
|
69
|
+
context.entrypoint = entrypoint
|
70
|
+
context.input = input
|
71
|
+
context.resume = resume
|
72
|
+
context.job_id = env.get("UIPATH_JOB_KEY")
|
73
|
+
context.trace_id = env.get("UIPATH_TRACE_ID")
|
74
|
+
context.input_file = kwargs.get("input_file", None)
|
75
|
+
context.execution_output_file = kwargs.get("execution_output_file", None)
|
76
|
+
context.is_eval_run = kwargs.get("is_eval_run", False)
|
77
|
+
context.logs_min_level = env.get("LOG_LEVEL", "INFO")
|
78
|
+
context.tracing_enabled = env.get("UIPATH_TRACING_ENABLED", True)
|
79
|
+
context.trace_context = UiPathTraceContext(
|
80
|
+
trace_id=env.get("UIPATH_TRACE_ID"),
|
81
|
+
parent_span_id=env.get("UIPATH_PARENT_SPAN_ID"),
|
82
|
+
root_span_id=env.get("UIPATH_ROOT_SPAN_ID"),
|
83
|
+
enabled=env.get("UIPATH_TRACING_ENABLED", True),
|
84
|
+
job_id=env.get("UIPATH_JOB_KEY"),
|
85
|
+
org_id=env.get("UIPATH_ORGANIZATION_ID"),
|
86
|
+
tenant_id=env.get("UIPATH_TENANT_ID"),
|
87
|
+
process_key=env.get("UIPATH_PROCESS_UUID"),
|
88
|
+
folder_key=env.get("UIPATH_FOLDER_KEY"),
|
89
|
+
reference_id=env.get("UIPATH_JOB_KEY") or str(uuid4()),
|
90
|
+
)
|
91
|
+
|
92
|
+
runtime_factory = UiPathRuntimeFactory(UiPathRuntime, UiPathRuntimeContext)
|
93
|
+
|
94
|
+
if context.job_id:
|
95
|
+
runtime_factory.add_span_exporter(LlmOpsHttpExporter())
|
64
96
|
|
65
97
|
async def execute():
|
66
|
-
|
67
|
-
env.get("UIPATH_CONFIG_PATH", "uipath.json"), **kwargs
|
68
|
-
)
|
69
|
-
context.entrypoint = entrypoint
|
70
|
-
context.input = input
|
71
|
-
context.resume = resume
|
72
|
-
context.job_id = env.get("UIPATH_JOB_KEY")
|
73
|
-
context.trace_id = env.get("UIPATH_TRACE_ID")
|
74
|
-
context.input_file = kwargs.get("input_file", None)
|
75
|
-
context.execution_output_file = kwargs.get("execution_output_file", None)
|
76
|
-
context.is_eval_run = kwargs.get("is_eval_run", False)
|
77
|
-
context.tracing_enabled = env.get("UIPATH_TRACING_ENABLED", True)
|
78
|
-
context.trace_context = UiPathTraceContext(
|
79
|
-
trace_id=env.get("UIPATH_TRACE_ID"),
|
80
|
-
parent_span_id=env.get("UIPATH_PARENT_SPAN_ID"),
|
81
|
-
root_span_id=env.get("UIPATH_ROOT_SPAN_ID"),
|
82
|
-
enabled=env.get("UIPATH_TRACING_ENABLED", True),
|
83
|
-
job_id=env.get("UIPATH_JOB_KEY"),
|
84
|
-
org_id=env.get("UIPATH_ORGANIZATION_ID"),
|
85
|
-
tenant_id=env.get("UIPATH_TENANT_ID"),
|
86
|
-
process_key=env.get("UIPATH_PROCESS_UUID"),
|
87
|
-
folder_key=env.get("UIPATH_FOLDER_KEY"),
|
88
|
-
reference_id=env.get("UIPATH_JOB_KEY") or str(uuid4()),
|
89
|
-
)
|
90
|
-
context.logs_min_level = env.get("LOG_LEVEL", "INFO")
|
91
|
-
async with UiPathRuntime.from_context(context) as runtime:
|
92
|
-
await runtime.execute()
|
98
|
+
await runtime_factory.execute(context)
|
93
99
|
|
94
100
|
asyncio.run(execute())
|
95
101
|
|
96
|
-
# Return success
|
97
102
|
return MiddlewareResult(should_continue=False)
|
98
103
|
|
99
104
|
except UiPathRuntimeError as e:
|
uipath/_cli/middlewares.py
CHANGED
uipath/telemetry/_track.py
CHANGED
@@ -58,7 +58,7 @@ def _get_project_key() -> str:
|
|
58
58
|
|
59
59
|
class _AzureMonitorOpenTelemetryEventHandler(LoggingHandler):
|
60
60
|
@staticmethod
|
61
|
-
def _get_attributes(record: LogRecord) -> Attributes:
|
61
|
+
def _get_attributes(record: LogRecord) -> Attributes: # type: ignore[override]
|
62
62
|
attributes = dict(LoggingHandler._get_attributes(record) or {})
|
63
63
|
attributes[_APP_INSIGHTS_EVENT_MARKER_ATTRIBUTE] = True
|
64
64
|
attributes[_CLOUD_TENANT_ID] = os.getenv(ENV_TENANT_ID, _UNKNOWN)
|
@@ -75,7 +75,7 @@ class _AzureMonitorOpenTelemetryEventHandler(LoggingHandler):
|
|
75
75
|
if _CODE_LINENO in attributes:
|
76
76
|
del attributes[_CODE_LINENO]
|
77
77
|
|
78
|
-
return attributes
|
78
|
+
return attributes # type: ignore[return-value]
|
79
79
|
|
80
80
|
|
81
81
|
class _TelemetryClient:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: uipath
|
3
|
-
Version: 2.1.
|
3
|
+
Version: 2.1.29
|
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
|
@@ -17,12 +17,14 @@ Requires-Python: >=3.10
|
|
17
17
|
Requires-Dist: azure-monitor-opentelemetry>=1.6.8
|
18
18
|
Requires-Dist: click>=8.1.8
|
19
19
|
Requires-Dist: httpx>=0.28.1
|
20
|
+
Requires-Dist: opentelemetry-instrumentation>=0.52b1
|
20
21
|
Requires-Dist: opentelemetry-sdk>=1.31.1
|
21
22
|
Requires-Dist: pathlib>=1.0.1
|
22
23
|
Requires-Dist: pydantic>=2.11.1
|
23
24
|
Requires-Dist: python-dotenv>=1.0.1
|
24
25
|
Requires-Dist: rich>=13.0.0
|
25
26
|
Requires-Dist: tenacity>=9.0.0
|
27
|
+
Requires-Dist: textual>=5.3.0
|
26
28
|
Requires-Dist: tomli>=2.2.1
|
27
29
|
Requires-Dist: truststore>=0.10.1
|
28
30
|
Description-Content-Type: text/markdown
|