uipath 2.1.44__py3-none-any.whl → 2.1.45__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/_evals/_runtime.py +17 -52
- uipath/_cli/_runtime/_contracts.py +89 -36
- uipath/_cli/_runtime/_runtime.py +26 -259
- uipath/_cli/_runtime/_script_executor.py +254 -0
- uipath/_cli/cli_dev.py +4 -2
- uipath/_cli/cli_eval.py +26 -32
- uipath/_cli/cli_run.py +43 -140
- uipath/_services/processes_service.py +2 -2
- {uipath-2.1.44.dist-info → uipath-2.1.45.dist-info}/METADATA +1 -1
- {uipath-2.1.44.dist-info → uipath-2.1.45.dist-info}/RECORD +13 -12
- {uipath-2.1.44.dist-info → uipath-2.1.45.dist-info}/WHEEL +0 -0
- {uipath-2.1.44.dist-info → uipath-2.1.45.dist-info}/entry_points.txt +0 -0
- {uipath-2.1.44.dist-info → uipath-2.1.45.dist-info}/licenses/LICENSE +0 -0
uipath/_cli/_evals/_runtime.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
import copy
|
2
1
|
from collections import defaultdict
|
3
2
|
from time import time
|
4
3
|
from typing import Dict, Generic, List, Optional, Sequence, TypeVar
|
@@ -6,8 +5,6 @@ from typing import Dict, Generic, List, Optional, Sequence, TypeVar
|
|
6
5
|
from opentelemetry.sdk.trace import ReadableSpan
|
7
6
|
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
|
8
7
|
|
9
|
-
from uipath.eval._helpers import auto_discover_entrypoint
|
10
|
-
|
11
8
|
from .._runtime._contracts import (
|
12
9
|
UiPathBaseRuntime,
|
13
10
|
UiPathRuntimeContext,
|
@@ -54,49 +51,21 @@ class ExecutionSpanExporter(SpanExporter):
|
|
54
51
|
self.clear()
|
55
52
|
|
56
53
|
|
57
|
-
class UiPathEvalContext(UiPathRuntimeContext
|
54
|
+
class UiPathEvalContext(UiPathRuntimeContext):
|
58
55
|
"""Context used for evaluation runs."""
|
59
56
|
|
60
|
-
|
61
|
-
|
62
|
-
workers: int
|
57
|
+
no_report: Optional[bool] = False
|
58
|
+
workers: Optional[int] = 1
|
63
59
|
eval_set: Optional[str] = None
|
64
60
|
eval_ids: Optional[List[str]] = None
|
65
61
|
|
66
|
-
def __init__(
|
67
|
-
self,
|
68
|
-
runtime_context: C,
|
69
|
-
no_report: bool,
|
70
|
-
workers: int,
|
71
|
-
eval_set: Optional[str] = None,
|
72
|
-
eval_ids: Optional[List[str]] = None,
|
73
|
-
**kwargs,
|
74
|
-
):
|
75
|
-
super().__init__(
|
76
|
-
runtime_context=runtime_context, # type: ignore
|
77
|
-
no_report=no_report,
|
78
|
-
workers=workers,
|
79
|
-
eval_set=eval_set,
|
80
|
-
eval_ids=eval_ids,
|
81
|
-
**kwargs,
|
82
|
-
)
|
83
|
-
self._auto_discover()
|
84
|
-
|
85
|
-
def _auto_discover(self):
|
86
|
-
self.runtime_context.entrypoint = (
|
87
|
-
self.runtime_context.entrypoint or auto_discover_entrypoint()
|
88
|
-
)
|
89
|
-
self.eval_set = self.eval_set or EvalHelpers.auto_discover_eval_set()
|
90
|
-
|
91
62
|
|
92
63
|
class UiPathEvalRuntime(UiPathBaseRuntime, Generic[T, C]):
|
93
64
|
"""Specialized runtime for evaluation runs, with access to the factory."""
|
94
65
|
|
95
|
-
def __init__(
|
96
|
-
self, context: "UiPathEvalContext[C]", factory: "UiPathRuntimeFactory[T, C]"
|
97
|
-
):
|
66
|
+
def __init__(self, context: UiPathEvalContext, factory: UiPathRuntimeFactory[T, C]):
|
98
67
|
super().__init__(context)
|
99
|
-
self.context:
|
68
|
+
self.context: UiPathEvalContext = context
|
100
69
|
self.factory: UiPathRuntimeFactory[T, C] = factory
|
101
70
|
self.span_exporter: ExecutionSpanExporter = ExecutionSpanExporter()
|
102
71
|
self.factory.add_span_exporter(self.span_exporter)
|
@@ -104,8 +73,8 @@ class UiPathEvalRuntime(UiPathBaseRuntime, Generic[T, C]):
|
|
104
73
|
@classmethod
|
105
74
|
def from_eval_context(
|
106
75
|
cls,
|
107
|
-
context:
|
108
|
-
factory:
|
76
|
+
context: UiPathEvalContext,
|
77
|
+
factory: UiPathRuntimeFactory[T, C],
|
109
78
|
) -> "UiPathEvalRuntime[T, C]":
|
110
79
|
return cls(context, factory)
|
111
80
|
|
@@ -119,30 +88,26 @@ class UiPathEvalRuntime(UiPathBaseRuntime, Generic[T, C]):
|
|
119
88
|
)
|
120
89
|
execution_output_list: list[UiPathEvalRunExecutionOutput] = []
|
121
90
|
for eval_item in evaluation_set.evaluations:
|
122
|
-
|
91
|
+
execution_output = await self.execute_runtime(eval_item)
|
92
|
+
execution_output_list.append(execution_output)
|
123
93
|
|
124
94
|
self.context.result = UiPathRuntimeResult(
|
125
95
|
output={
|
126
96
|
"results": execution_output_list,
|
127
97
|
},
|
128
98
|
status=UiPathRuntimeStatus.SUCCESSFUL,
|
129
|
-
resume=None,
|
130
99
|
)
|
131
100
|
|
132
|
-
return self.context.
|
133
|
-
|
134
|
-
def _prepare_new_runtime_context(self, eval_item: EvaluationItem) -> C:
|
135
|
-
runtime_context = copy.deepcopy(self.context.runtime_context)
|
136
|
-
runtime_context.execution_id = eval_item.id
|
137
|
-
runtime_context.input_json = eval_item.inputs
|
138
|
-
# here we can pass other values from eval_item: expectedAgentBehavior, simulationInstructions etc.
|
139
|
-
return runtime_context
|
101
|
+
return self.context.result
|
140
102
|
|
141
|
-
|
142
|
-
async def execute_agent(
|
103
|
+
async def execute_runtime(
|
143
104
|
self, eval_item: EvaluationItem
|
144
|
-
) ->
|
145
|
-
runtime_context = self.
|
105
|
+
) -> UiPathEvalRunExecutionOutput:
|
106
|
+
runtime_context: C = self.factory.new_context(
|
107
|
+
execution_id=eval_item.id,
|
108
|
+
input_json=eval_item.inputs,
|
109
|
+
is_eval_run=True,
|
110
|
+
)
|
146
111
|
start_time = time()
|
147
112
|
result = await self.factory.execute_in_root_span(
|
148
113
|
runtime_context, root_span=eval_item.name
|
@@ -30,6 +30,9 @@ from ._logging import LogsInterceptor
|
|
30
30
|
|
31
31
|
logger = logging.getLogger(__name__)
|
32
32
|
|
33
|
+
T = TypeVar("T", bound="UiPathBaseRuntime")
|
34
|
+
C = TypeVar("C", bound="UiPathRuntimeContext")
|
35
|
+
|
33
36
|
|
34
37
|
class UiPathResumeTriggerType(str, Enum):
|
35
38
|
"""Constants representing different types of resume job triggers in the system."""
|
@@ -315,21 +318,43 @@ class UiPathRuntimeContext(BaseModel):
|
|
315
318
|
model_config = {"arbitrary_types_allowed": True}
|
316
319
|
|
317
320
|
@classmethod
|
318
|
-
def
|
319
|
-
"""
|
321
|
+
def with_defaults(cls: type[C], config_path: Optional[str] = None, **kwargs) -> C:
|
322
|
+
"""Construct a context with defaults, reading env vars and config file."""
|
323
|
+
resolved_config_path = config_path or os.environ.get(
|
324
|
+
"UIPATH_CONFIG_PATH", "uipath.json"
|
325
|
+
)
|
320
326
|
|
321
|
-
|
322
|
-
|
323
|
-
|
327
|
+
base = cls.from_config(resolved_config_path)
|
328
|
+
|
329
|
+
# Apply defaults from env
|
330
|
+
base.job_id = os.environ.get("UIPATH_JOB_KEY")
|
331
|
+
base.trace_id = os.environ.get("UIPATH_TRACE_ID")
|
332
|
+
base.tracing_enabled = os.environ.get("UIPATH_TRACING_ENABLED", True)
|
333
|
+
base.logs_min_level = os.environ.get("LOG_LEVEL", "INFO")
|
334
|
+
|
335
|
+
base.trace_context = UiPathTraceContext(
|
336
|
+
trace_id=os.environ.get("UIPATH_TRACE_ID"),
|
337
|
+
parent_span_id=os.environ.get("UIPATH_PARENT_SPAN_ID"),
|
338
|
+
root_span_id=os.environ.get("UIPATH_ROOT_SPAN_ID"),
|
339
|
+
enabled=os.environ.get("UIPATH_TRACING_ENABLED", True),
|
340
|
+
job_id=os.environ.get("UIPATH_JOB_KEY"),
|
341
|
+
org_id=os.environ.get("UIPATH_ORGANIZATION_ID"),
|
342
|
+
tenant_id=os.environ.get("UIPATH_TENANT_ID"),
|
343
|
+
process_key=os.environ.get("UIPATH_PROCESS_UUID"),
|
344
|
+
folder_key=os.environ.get("UIPATH_FOLDER_KEY"),
|
345
|
+
reference_id=os.environ.get("UIPATH_JOB_KEY") or str(uuid4()),
|
346
|
+
)
|
324
347
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
import json
|
329
|
-
import os
|
348
|
+
# Override with kwargs
|
349
|
+
for k, v in kwargs.items():
|
350
|
+
setattr(base, k, v)
|
330
351
|
|
331
|
-
|
352
|
+
return base
|
332
353
|
|
354
|
+
@classmethod
|
355
|
+
def from_config(cls: type[C], config_path: Optional[str] = None, **kwargs) -> C:
|
356
|
+
"""Load configuration from uipath.json file."""
|
357
|
+
path = config_path or "uipath.json"
|
333
358
|
config = {}
|
334
359
|
|
335
360
|
if os.path.exists(path):
|
@@ -346,7 +371,6 @@ class UiPathRuntimeContext(BaseModel):
|
|
346
371
|
}
|
347
372
|
|
348
373
|
attributes_set = set()
|
349
|
-
# set values from config file if available
|
350
374
|
if "runtime" in config:
|
351
375
|
runtime_config = config["runtime"]
|
352
376
|
for config_key, attr_name in mapping.items():
|
@@ -354,10 +378,8 @@ class UiPathRuntimeContext(BaseModel):
|
|
354
378
|
attributes_set.add(attr_name)
|
355
379
|
setattr(instance, attr_name, runtime_config[config_key])
|
356
380
|
|
357
|
-
# fallback to kwargs for any values not set from config file
|
358
381
|
for _, attr_name in mapping.items():
|
359
382
|
if attr_name in kwargs and hasattr(instance, attr_name):
|
360
|
-
# Only set from kwargs if not already set from config file
|
361
383
|
if attr_name not in attributes_set:
|
362
384
|
setattr(instance, attr_name, kwargs[attr_name])
|
363
385
|
|
@@ -383,7 +405,7 @@ class UiPathRuntimeError(Exception):
|
|
383
405
|
if (
|
384
406
|
tb and tb.strip() != "NoneType: None"
|
385
407
|
): # Ensure there's an actual traceback
|
386
|
-
detail = f"{detail}\n\
|
408
|
+
detail = f"{detail}\n\n{tb}"
|
387
409
|
|
388
410
|
if status is None:
|
389
411
|
status = self._extract_http_status()
|
@@ -473,6 +495,21 @@ class UiPathBaseRuntime(ABC):
|
|
473
495
|
with open(self.context.input_file) as f:
|
474
496
|
self.context.input = f.read()
|
475
497
|
|
498
|
+
try:
|
499
|
+
if self.context.input:
|
500
|
+
self.context.input_json = json.loads(self.context.input)
|
501
|
+
if self.context.input_json is None:
|
502
|
+
self.context.input_json = {}
|
503
|
+
except json.JSONDecodeError as e:
|
504
|
+
raise UiPathRuntimeError(
|
505
|
+
"INPUT_INVALID_JSON",
|
506
|
+
"Invalid JSON input",
|
507
|
+
f"The input data is not valid JSON: {str(e)}",
|
508
|
+
UiPathErrorCategory.USER,
|
509
|
+
) from e
|
510
|
+
|
511
|
+
await self.validate()
|
512
|
+
|
476
513
|
# Intercept all stdout/stderr/logs and write them to a file (runtime/evals), stdout (debug)
|
477
514
|
self.logs_interceptor = LogsInterceptor(
|
478
515
|
min_level=self.context.logs_min_level,
|
@@ -620,14 +657,16 @@ class UiPathBaseRuntime(ABC):
|
|
620
657
|
return os.path.join("__uipath", "state.db")
|
621
658
|
|
622
659
|
|
623
|
-
T = TypeVar("T", bound=UiPathBaseRuntime)
|
624
|
-
C = TypeVar("C", bound=UiPathRuntimeContext)
|
625
|
-
|
626
|
-
|
627
660
|
class UiPathRuntimeFactory(Generic[T, C]):
|
628
661
|
"""Generic factory for UiPath runtime classes."""
|
629
662
|
|
630
|
-
def __init__(
|
663
|
+
def __init__(
|
664
|
+
self,
|
665
|
+
runtime_class: Type[T],
|
666
|
+
context_class: Type[C],
|
667
|
+
runtime_generator: Optional[Callable[[C], T]] = None,
|
668
|
+
context_generator: Optional[Callable[..., C]] = None,
|
669
|
+
):
|
631
670
|
if not issubclass(runtime_class, UiPathBaseRuntime):
|
632
671
|
raise TypeError(
|
633
672
|
f"runtime_class {runtime_class.__name__} must inherit from UiPathBaseRuntime"
|
@@ -640,10 +679,11 @@ class UiPathRuntimeFactory(Generic[T, C]):
|
|
640
679
|
|
641
680
|
self.runtime_class = runtime_class
|
642
681
|
self.context_class = context_class
|
682
|
+
self.runtime_generator = runtime_generator
|
683
|
+
self.context_generator = context_generator
|
643
684
|
self.tracer_provider: TracerProvider = TracerProvider()
|
644
685
|
self.tracer_span_processors: List[SpanProcessor] = []
|
645
686
|
trace.set_tracer_provider(self.tracer_provider)
|
646
|
-
|
647
687
|
if os.getenv("UIPATH_JOB_KEY"):
|
648
688
|
self.add_span_exporter(LlmOpsHttpExporter())
|
649
689
|
|
@@ -674,36 +714,49 @@ class UiPathRuntimeFactory(Generic[T, C]):
|
|
674
714
|
|
675
715
|
def new_context(self, **kwargs) -> C:
|
676
716
|
"""Create a new context instance."""
|
717
|
+
if self.context_generator:
|
718
|
+
return self.context_generator(**kwargs)
|
677
719
|
return self.context_class(**kwargs)
|
678
720
|
|
721
|
+
def new_runtime(self) -> T:
|
722
|
+
"""Create a new runtime instance."""
|
723
|
+
context = self.new_context()
|
724
|
+
if self.runtime_generator:
|
725
|
+
return self.runtime_generator(context)
|
726
|
+
return self.runtime_class.from_context(context)
|
727
|
+
|
679
728
|
def from_context(self, context: C) -> T:
|
680
729
|
"""Create runtime instance from context."""
|
730
|
+
if self.runtime_generator:
|
731
|
+
return self.runtime_generator(context)
|
681
732
|
return self.runtime_class.from_context(context)
|
682
733
|
|
683
734
|
async def execute(self, context: C) -> Optional[UiPathRuntimeResult]:
|
684
735
|
"""Execute runtime with context."""
|
685
736
|
async with self.from_context(context) as runtime:
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
737
|
+
try:
|
738
|
+
return await runtime.execute()
|
739
|
+
finally:
|
740
|
+
for span_processor in self.tracer_span_processors:
|
741
|
+
span_processor.force_flush()
|
690
742
|
|
691
743
|
async def execute_in_root_span(
|
692
744
|
self, context: C, root_span: str = "root"
|
693
745
|
) -> Optional[UiPathRuntimeResult]:
|
694
746
|
"""Execute runtime with context."""
|
695
747
|
async with self.from_context(context) as runtime:
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
748
|
+
try:
|
749
|
+
tracer: Tracer = trace.get_tracer("uipath-runtime")
|
750
|
+
with tracer.start_as_current_span(
|
751
|
+
root_span,
|
752
|
+
attributes={"execution.id": context.execution_id}
|
753
|
+
if context.execution_id
|
754
|
+
else {},
|
755
|
+
):
|
756
|
+
return await runtime.execute()
|
757
|
+
finally:
|
758
|
+
for span_processor in self.tracer_span_processors:
|
759
|
+
span_processor.force_flush()
|
707
760
|
|
708
761
|
|
709
762
|
class UiPathExecutionTraceProcessorMixin:
|
uipath/_cli/_runtime/_runtime.py
CHANGED
@@ -1,30 +1,37 @@
|
|
1
1
|
"""Python script runtime implementation for executing and managing python scripts."""
|
2
2
|
|
3
|
-
import importlib.util
|
4
|
-
import inspect
|
5
|
-
import json
|
6
3
|
import logging
|
7
|
-
import
|
8
|
-
from dataclasses import asdict, is_dataclass
|
9
|
-
from typing import Any, Dict, Optional, Type, TypeVar, cast, get_type_hints
|
10
|
-
|
11
|
-
from pydantic import BaseModel
|
4
|
+
from typing import Any, Awaitable, Callable, Optional, TypeVar
|
12
5
|
|
13
6
|
from ._contracts import (
|
14
7
|
UiPathBaseRuntime,
|
15
8
|
UiPathErrorCategory,
|
9
|
+
UiPathRuntimeContext,
|
16
10
|
UiPathRuntimeError,
|
17
11
|
UiPathRuntimeResult,
|
18
12
|
UiPathRuntimeStatus,
|
19
13
|
)
|
14
|
+
from ._script_executor import ScriptExecutor
|
20
15
|
|
21
16
|
logger = logging.getLogger(__name__)
|
22
17
|
|
23
18
|
T = TypeVar("T")
|
19
|
+
R = TypeVar("R")
|
20
|
+
AsyncFunc = Callable[[T], Awaitable[R]]
|
24
21
|
|
25
22
|
|
26
23
|
class UiPathRuntime(UiPathBaseRuntime):
|
27
|
-
|
24
|
+
def __init__(self, context: UiPathRuntimeContext, executor: AsyncFunc[Any, Any]):
|
25
|
+
self.context = context
|
26
|
+
self.executor = executor
|
27
|
+
|
28
|
+
async def cleanup(self) -> None:
|
29
|
+
"""Cleanup runtime resources."""
|
30
|
+
pass
|
31
|
+
|
32
|
+
async def validate(self):
|
33
|
+
"""Validate runtime context."""
|
34
|
+
pass
|
28
35
|
|
29
36
|
async def execute(self) -> Optional[UiPathRuntimeResult]:
|
30
37
|
"""Execute the Python script with the provided input and configuration.
|
@@ -35,15 +42,8 @@ class UiPathRuntime(UiPathBaseRuntime):
|
|
35
42
|
Raises:
|
36
43
|
UiPathRuntimeError: If execution fails
|
37
44
|
"""
|
38
|
-
await self.validate()
|
39
|
-
|
40
45
|
try:
|
41
|
-
|
42
|
-
return None
|
43
|
-
|
44
|
-
script_result = await self._execute_python_script(
|
45
|
-
self.context.entrypoint, self.context.input_json
|
46
|
-
)
|
46
|
+
script_result = await self.executor(self.context.input_json)
|
47
47
|
|
48
48
|
if self.context.job_id is None:
|
49
49
|
logger.info(script_result)
|
@@ -65,248 +65,15 @@ class UiPathRuntime(UiPathBaseRuntime):
|
|
65
65
|
UiPathErrorCategory.SYSTEM,
|
66
66
|
) from e
|
67
67
|
|
68
|
-
async def validate(self) -> None:
|
69
|
-
"""Validate runtime inputs."""
|
70
|
-
if not self.context.entrypoint:
|
71
|
-
raise UiPathRuntimeError(
|
72
|
-
"ENTRYPOINT_MISSING",
|
73
|
-
"No entrypoint specified",
|
74
|
-
"Please provide a path to a Python script.",
|
75
|
-
UiPathErrorCategory.USER,
|
76
|
-
)
|
77
|
-
|
78
|
-
if not os.path.exists(self.context.entrypoint):
|
79
|
-
raise UiPathRuntimeError(
|
80
|
-
"ENTRYPOINT_NOT_FOUND",
|
81
|
-
"Script not found",
|
82
|
-
f"Script not found at path {self.context.entrypoint}.",
|
83
|
-
UiPathErrorCategory.USER,
|
84
|
-
)
|
85
|
-
|
86
|
-
try:
|
87
|
-
if self.context.input:
|
88
|
-
self.context.input_json = json.loads(self.context.input)
|
89
|
-
if self.context.input_json is None:
|
90
|
-
self.context.input_json = {}
|
91
|
-
except json.JSONDecodeError as e:
|
92
|
-
raise UiPathRuntimeError(
|
93
|
-
"INPUT_INVALID_JSON",
|
94
|
-
"Invalid JSON input",
|
95
|
-
f"The input data is not valid JSON: {str(e)}",
|
96
|
-
UiPathErrorCategory.USER,
|
97
|
-
) from e
|
98
|
-
|
99
|
-
async def cleanup(self) -> None:
|
100
|
-
"""Cleanup runtime resources."""
|
101
|
-
pass
|
102
|
-
|
103
|
-
async def _execute_python_script(self, script_path: str, input_data: Any) -> Any:
|
104
|
-
"""Execute the Python script with the given input."""
|
105
|
-
spec = importlib.util.spec_from_file_location("dynamic_module", script_path)
|
106
|
-
if not spec or not spec.loader:
|
107
|
-
raise UiPathRuntimeError(
|
108
|
-
"IMPORT_ERROR",
|
109
|
-
"Module import failed",
|
110
|
-
f"Could not load spec for {script_path}",
|
111
|
-
UiPathErrorCategory.USER,
|
112
|
-
)
|
113
|
-
|
114
|
-
module = importlib.util.module_from_spec(spec)
|
115
|
-
try:
|
116
|
-
spec.loader.exec_module(module)
|
117
|
-
except Exception as e:
|
118
|
-
raise UiPathRuntimeError(
|
119
|
-
"MODULE_EXECUTION_ERROR",
|
120
|
-
"Module execution failed",
|
121
|
-
f"Error executing module: {str(e)}",
|
122
|
-
UiPathErrorCategory.USER,
|
123
|
-
) from e
|
124
|
-
|
125
|
-
for func_name in ["main", "run", "execute"]:
|
126
|
-
if hasattr(module, func_name):
|
127
|
-
main_func = getattr(module, func_name)
|
128
|
-
sig = inspect.signature(main_func)
|
129
|
-
params = list(sig.parameters.values())
|
130
|
-
|
131
|
-
# Check if the function is asynchronous
|
132
|
-
is_async = inspect.iscoroutinefunction(main_func)
|
133
|
-
|
134
|
-
# Case 1: No parameters
|
135
|
-
if not params:
|
136
|
-
try:
|
137
|
-
result = await main_func() if is_async else main_func()
|
138
|
-
return (
|
139
|
-
self._convert_from_class(result)
|
140
|
-
if result is not None
|
141
|
-
else {}
|
142
|
-
)
|
143
|
-
except Exception as e:
|
144
|
-
raise UiPathRuntimeError(
|
145
|
-
"FUNCTION_EXECUTION_ERROR",
|
146
|
-
f"Error executing {func_name} function",
|
147
|
-
f"Error: {str(e)}",
|
148
|
-
UiPathErrorCategory.USER,
|
149
|
-
) from e
|
150
|
-
|
151
|
-
input_param = params[0]
|
152
|
-
input_type = input_param.annotation
|
153
|
-
|
154
|
-
# Case 2: Class, dataclass, or Pydantic model parameter
|
155
|
-
if input_type != inspect.Parameter.empty and (
|
156
|
-
is_dataclass(input_type)
|
157
|
-
or self._is_pydantic_model(input_type)
|
158
|
-
or hasattr(input_type, "__annotations__")
|
159
|
-
):
|
160
|
-
try:
|
161
|
-
valid_type = cast(Type[Any], input_type)
|
162
|
-
typed_input = self._convert_to_class(input_data, valid_type)
|
163
|
-
result = (
|
164
|
-
await main_func(typed_input)
|
165
|
-
if is_async
|
166
|
-
else main_func(typed_input)
|
167
|
-
)
|
168
|
-
return (
|
169
|
-
self._convert_from_class(result)
|
170
|
-
if result is not None
|
171
|
-
else {}
|
172
|
-
)
|
173
|
-
except Exception as e:
|
174
|
-
raise UiPathRuntimeError(
|
175
|
-
"FUNCTION_EXECUTION_ERROR",
|
176
|
-
f"Error executing {func_name} function with typed input",
|
177
|
-
f"Error: {str(e)}",
|
178
|
-
UiPathErrorCategory.USER,
|
179
|
-
) from e
|
180
|
-
|
181
|
-
# Case 3: Dict parameter
|
182
|
-
else:
|
183
|
-
try:
|
184
|
-
result = (
|
185
|
-
await main_func(input_data)
|
186
|
-
if is_async
|
187
|
-
else main_func(input_data)
|
188
|
-
)
|
189
|
-
return (
|
190
|
-
self._convert_from_class(result)
|
191
|
-
if result is not None
|
192
|
-
else {}
|
193
|
-
)
|
194
|
-
except Exception as e:
|
195
|
-
raise UiPathRuntimeError(
|
196
|
-
"FUNCTION_EXECUTION_ERROR",
|
197
|
-
f"Error executing {func_name} function with dictionary input",
|
198
|
-
f"Error: {str(e)}",
|
199
|
-
UiPathErrorCategory.USER,
|
200
|
-
) from e
|
201
|
-
|
202
|
-
raise UiPathRuntimeError(
|
203
|
-
"ENTRYPOINT_FUNCTION_MISSING",
|
204
|
-
"No entry function found",
|
205
|
-
f"No main function (main, run, or execute) found in {script_path}",
|
206
|
-
UiPathErrorCategory.USER,
|
207
|
-
)
|
208
|
-
|
209
|
-
def _convert_to_class(self, data: Dict[str, Any], cls: Type[T]) -> T:
|
210
|
-
"""Convert a dictionary to either a dataclass, Pydantic model, or regular class instance."""
|
211
|
-
# Handle Pydantic models
|
212
|
-
try:
|
213
|
-
if inspect.isclass(cls) and issubclass(cls, BaseModel):
|
214
|
-
return cls.model_validate(data)
|
215
|
-
except TypeError:
|
216
|
-
# issubclass can raise TypeError if cls is not a class
|
217
|
-
pass
|
218
|
-
|
219
|
-
# Handle dataclasses
|
220
|
-
if is_dataclass(cls):
|
221
|
-
field_types = get_type_hints(cls)
|
222
|
-
converted_data = {}
|
223
68
|
|
224
|
-
|
225
|
-
|
226
|
-
continue
|
227
|
-
|
228
|
-
value = data[field_name]
|
229
|
-
if (
|
230
|
-
is_dataclass(field_type)
|
231
|
-
or self._is_pydantic_model(field_type)
|
232
|
-
or hasattr(field_type, "__annotations__")
|
233
|
-
) and isinstance(value, dict):
|
234
|
-
typed_field = cast(Type[Any], field_type)
|
235
|
-
value = self._convert_to_class(value, typed_field)
|
236
|
-
converted_data[field_name] = value
|
237
|
-
|
238
|
-
return cls(**converted_data)
|
239
|
-
|
240
|
-
# Handle regular classes
|
241
|
-
else:
|
242
|
-
sig = inspect.signature(cls.__init__)
|
243
|
-
params = sig.parameters
|
244
|
-
|
245
|
-
init_args = {}
|
246
|
-
|
247
|
-
for param_name, param in params.items():
|
248
|
-
if param_name == "self":
|
249
|
-
continue
|
250
|
-
|
251
|
-
if param_name in data:
|
252
|
-
value = data[param_name]
|
253
|
-
param_type = (
|
254
|
-
param.annotation
|
255
|
-
if param.annotation != inspect.Parameter.empty
|
256
|
-
else Any
|
257
|
-
)
|
258
|
-
|
259
|
-
if (
|
260
|
-
is_dataclass(param_type)
|
261
|
-
or self._is_pydantic_model(param_type)
|
262
|
-
or hasattr(param_type, "__annotations__")
|
263
|
-
) and isinstance(value, dict):
|
264
|
-
typed_param = cast(Type[Any], param_type)
|
265
|
-
value = self._convert_to_class(value, typed_param)
|
266
|
-
|
267
|
-
init_args[param_name] = value
|
268
|
-
elif param.default != inspect.Parameter.empty:
|
269
|
-
init_args[param_name] = param.default
|
270
|
-
|
271
|
-
return cls(**init_args)
|
272
|
-
|
273
|
-
def _is_pydantic_model(self, cls: Type[Any]) -> bool:
|
274
|
-
"""Safely check if a class is a Pydantic model."""
|
275
|
-
try:
|
276
|
-
return inspect.isclass(cls) and issubclass(cls, BaseModel)
|
277
|
-
except TypeError:
|
278
|
-
# issubclass can raise TypeError if cls is not a class
|
279
|
-
return False
|
280
|
-
|
281
|
-
def _convert_from_class(self, obj: Any) -> Dict[str, Any]:
|
282
|
-
"""Convert a class instance (dataclass, Pydantic model, or regular) to a dictionary."""
|
283
|
-
if obj is None:
|
284
|
-
return {}
|
285
|
-
|
286
|
-
# Handle Pydantic models
|
287
|
-
if isinstance(obj, BaseModel):
|
288
|
-
return obj.model_dump()
|
69
|
+
class UiPathScriptRuntime(UiPathRuntime):
|
70
|
+
"""Runtime for executing Python scripts."""
|
289
71
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
if isinstance(obj, type):
|
294
|
-
return {}
|
295
|
-
return asdict(obj)
|
72
|
+
def __init__(self, context: UiPathRuntimeContext, entrypoint: str):
|
73
|
+
executor = ScriptExecutor(entrypoint)
|
74
|
+
super().__init__(context, executor)
|
296
75
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
# Skip private attributes
|
302
|
-
if not key.startswith("_"):
|
303
|
-
if (
|
304
|
-
isinstance(value, BaseModel)
|
305
|
-
or hasattr(value, "__dict__")
|
306
|
-
or is_dataclass(value)
|
307
|
-
):
|
308
|
-
result[key] = self._convert_from_class(value)
|
309
|
-
else:
|
310
|
-
result[key] = value
|
311
|
-
return result
|
312
|
-
return {} if obj is None else {str(type(obj).__name__): str(obj)} # Fallback
|
76
|
+
@classmethod
|
77
|
+
def from_context(cls, context: UiPathRuntimeContext):
|
78
|
+
"""Create runtime instance from context."""
|
79
|
+
return UiPathScriptRuntime(context, context.entrypoint or "")
|
@@ -0,0 +1,254 @@
|
|
1
|
+
"""Python script runtime implementation for executing and managing python scripts."""
|
2
|
+
|
3
|
+
import importlib.util
|
4
|
+
import inspect
|
5
|
+
import os
|
6
|
+
from dataclasses import asdict, is_dataclass
|
7
|
+
from typing import Any, Dict, Type, TypeVar, cast, get_type_hints
|
8
|
+
|
9
|
+
from pydantic import BaseModel
|
10
|
+
|
11
|
+
from ._contracts import (
|
12
|
+
UiPathErrorCategory,
|
13
|
+
UiPathRuntimeError,
|
14
|
+
)
|
15
|
+
|
16
|
+
T = TypeVar("T")
|
17
|
+
|
18
|
+
|
19
|
+
class ScriptExecutor:
|
20
|
+
def __init__(self, entrypoint: str):
|
21
|
+
self.entrypoint = entrypoint
|
22
|
+
self.validate_entrypoint()
|
23
|
+
|
24
|
+
async def __call__(self, input: Any) -> Any:
|
25
|
+
return await self._execute_python_script(input)
|
26
|
+
|
27
|
+
def validate_entrypoint(self) -> None:
|
28
|
+
"""Validate runtime inputs."""
|
29
|
+
if not self.entrypoint:
|
30
|
+
raise UiPathRuntimeError(
|
31
|
+
"ENTRYPOINT_MISSING",
|
32
|
+
"No entrypoint specified",
|
33
|
+
"Please provide a path to a Python script.",
|
34
|
+
UiPathErrorCategory.USER,
|
35
|
+
)
|
36
|
+
|
37
|
+
if not os.path.exists(self.entrypoint):
|
38
|
+
raise UiPathRuntimeError(
|
39
|
+
"ENTRYPOINT_NOT_FOUND",
|
40
|
+
"Script not found",
|
41
|
+
f"Script not found at path {self.entrypoint}.",
|
42
|
+
UiPathErrorCategory.USER,
|
43
|
+
)
|
44
|
+
|
45
|
+
async def _execute_python_script(self, input_data: Any) -> Any:
|
46
|
+
"""Execute the Python script with the given input."""
|
47
|
+
spec = importlib.util.spec_from_file_location("dynamic_module", self.entrypoint)
|
48
|
+
if not spec or not spec.loader:
|
49
|
+
raise UiPathRuntimeError(
|
50
|
+
"IMPORT_ERROR",
|
51
|
+
"Module import failed",
|
52
|
+
f"Could not load spec for {self.entrypoint}",
|
53
|
+
UiPathErrorCategory.USER,
|
54
|
+
)
|
55
|
+
|
56
|
+
module = importlib.util.module_from_spec(spec)
|
57
|
+
try:
|
58
|
+
spec.loader.exec_module(module)
|
59
|
+
except Exception as e:
|
60
|
+
raise UiPathRuntimeError(
|
61
|
+
"MODULE_EXECUTION_ERROR",
|
62
|
+
"Module execution failed",
|
63
|
+
f"Error executing module: {str(e)}",
|
64
|
+
UiPathErrorCategory.USER,
|
65
|
+
) from e
|
66
|
+
|
67
|
+
for func_name in ["main", "run", "execute"]:
|
68
|
+
if hasattr(module, func_name):
|
69
|
+
main_func = getattr(module, func_name)
|
70
|
+
sig = inspect.signature(main_func)
|
71
|
+
params = list(sig.parameters.values())
|
72
|
+
|
73
|
+
# Check if the function is asynchronous
|
74
|
+
is_async = inspect.iscoroutinefunction(main_func)
|
75
|
+
|
76
|
+
# Case 1: No parameters
|
77
|
+
if not params:
|
78
|
+
try:
|
79
|
+
result = await main_func() if is_async else main_func()
|
80
|
+
return (
|
81
|
+
self._convert_from_class(result)
|
82
|
+
if result is not None
|
83
|
+
else {}
|
84
|
+
)
|
85
|
+
except Exception as e:
|
86
|
+
raise UiPathRuntimeError(
|
87
|
+
"FUNCTION_EXECUTION_ERROR",
|
88
|
+
f"Error executing {func_name} function",
|
89
|
+
f"Error: {str(e)}",
|
90
|
+
UiPathErrorCategory.USER,
|
91
|
+
) from e
|
92
|
+
|
93
|
+
input_param = params[0]
|
94
|
+
input_type = input_param.annotation
|
95
|
+
|
96
|
+
# Case 2: Class, dataclass, or Pydantic model parameter
|
97
|
+
if input_type != inspect.Parameter.empty and (
|
98
|
+
is_dataclass(input_type)
|
99
|
+
or self._is_pydantic_model(input_type)
|
100
|
+
or hasattr(input_type, "__annotations__")
|
101
|
+
):
|
102
|
+
try:
|
103
|
+
valid_type = cast(Type[Any], input_type)
|
104
|
+
typed_input = self._convert_to_class(input_data, valid_type)
|
105
|
+
result = (
|
106
|
+
await main_func(typed_input)
|
107
|
+
if is_async
|
108
|
+
else main_func(typed_input)
|
109
|
+
)
|
110
|
+
return (
|
111
|
+
self._convert_from_class(result)
|
112
|
+
if result is not None
|
113
|
+
else {}
|
114
|
+
)
|
115
|
+
except Exception as e:
|
116
|
+
raise UiPathRuntimeError(
|
117
|
+
"FUNCTION_EXECUTION_ERROR",
|
118
|
+
f"Error executing {func_name} function with typed input",
|
119
|
+
f"Error: {str(e)}",
|
120
|
+
UiPathErrorCategory.USER,
|
121
|
+
) from e
|
122
|
+
|
123
|
+
# Case 3: Dict parameter
|
124
|
+
else:
|
125
|
+
try:
|
126
|
+
result = (
|
127
|
+
await main_func(input_data)
|
128
|
+
if is_async
|
129
|
+
else main_func(input_data)
|
130
|
+
)
|
131
|
+
return (
|
132
|
+
self._convert_from_class(result)
|
133
|
+
if result is not None
|
134
|
+
else {}
|
135
|
+
)
|
136
|
+
except Exception as e:
|
137
|
+
raise UiPathRuntimeError(
|
138
|
+
"FUNCTION_EXECUTION_ERROR",
|
139
|
+
f"Error executing {func_name} function with dictionary input",
|
140
|
+
f"Error: {str(e)}",
|
141
|
+
UiPathErrorCategory.USER,
|
142
|
+
) from e
|
143
|
+
|
144
|
+
raise UiPathRuntimeError(
|
145
|
+
"ENTRYPOINT_FUNCTION_MISSING",
|
146
|
+
"No entry function found",
|
147
|
+
f"No main function (main, run, or execute) found in {self.entrypoint}",
|
148
|
+
UiPathErrorCategory.USER,
|
149
|
+
)
|
150
|
+
|
151
|
+
def _convert_to_class(self, data: Dict[str, Any], cls: Type[T]) -> T:
|
152
|
+
"""Convert a dictionary to either a dataclass, Pydantic model, or regular class instance."""
|
153
|
+
# Handle Pydantic models
|
154
|
+
try:
|
155
|
+
if inspect.isclass(cls) and issubclass(cls, BaseModel):
|
156
|
+
return cls.model_validate(data)
|
157
|
+
except TypeError:
|
158
|
+
# issubclass can raise TypeError if cls is not a class
|
159
|
+
pass
|
160
|
+
|
161
|
+
# Handle dataclasses
|
162
|
+
if is_dataclass(cls):
|
163
|
+
field_types = get_type_hints(cls)
|
164
|
+
converted_data = {}
|
165
|
+
|
166
|
+
for field_name, field_type in field_types.items():
|
167
|
+
if field_name not in data:
|
168
|
+
continue
|
169
|
+
|
170
|
+
value = data[field_name]
|
171
|
+
if (
|
172
|
+
is_dataclass(field_type)
|
173
|
+
or self._is_pydantic_model(field_type)
|
174
|
+
or hasattr(field_type, "__annotations__")
|
175
|
+
) and isinstance(value, dict):
|
176
|
+
typed_field = cast(Type[Any], field_type)
|
177
|
+
value = self._convert_to_class(value, typed_field)
|
178
|
+
converted_data[field_name] = value
|
179
|
+
|
180
|
+
return cls(**converted_data)
|
181
|
+
|
182
|
+
# Handle regular classes
|
183
|
+
else:
|
184
|
+
sig = inspect.signature(cls.__init__)
|
185
|
+
params = sig.parameters
|
186
|
+
|
187
|
+
init_args = {}
|
188
|
+
|
189
|
+
for param_name, param in params.items():
|
190
|
+
if param_name == "self":
|
191
|
+
continue
|
192
|
+
|
193
|
+
if param_name in data:
|
194
|
+
value = data[param_name]
|
195
|
+
param_type = (
|
196
|
+
param.annotation
|
197
|
+
if param.annotation != inspect.Parameter.empty
|
198
|
+
else Any
|
199
|
+
)
|
200
|
+
|
201
|
+
if (
|
202
|
+
is_dataclass(param_type)
|
203
|
+
or self._is_pydantic_model(param_type)
|
204
|
+
or hasattr(param_type, "__annotations__")
|
205
|
+
) and isinstance(value, dict):
|
206
|
+
typed_param = cast(Type[Any], param_type)
|
207
|
+
value = self._convert_to_class(value, typed_param)
|
208
|
+
|
209
|
+
init_args[param_name] = value
|
210
|
+
elif param.default != inspect.Parameter.empty:
|
211
|
+
init_args[param_name] = param.default
|
212
|
+
|
213
|
+
return cls(**init_args)
|
214
|
+
|
215
|
+
def _is_pydantic_model(self, cls: Type[Any]) -> bool:
|
216
|
+
"""Safely check if a class is a Pydantic model."""
|
217
|
+
try:
|
218
|
+
return inspect.isclass(cls) and issubclass(cls, BaseModel)
|
219
|
+
except TypeError:
|
220
|
+
# issubclass can raise TypeError if cls is not a class
|
221
|
+
return False
|
222
|
+
|
223
|
+
def _convert_from_class(self, obj: Any) -> Dict[str, Any]:
|
224
|
+
"""Convert a class instance (dataclass, Pydantic model, or regular) to a dictionary."""
|
225
|
+
if obj is None:
|
226
|
+
return {}
|
227
|
+
|
228
|
+
# Handle Pydantic models
|
229
|
+
if isinstance(obj, BaseModel):
|
230
|
+
return obj.model_dump()
|
231
|
+
|
232
|
+
# Handle dataclasses
|
233
|
+
elif is_dataclass(obj):
|
234
|
+
# Make sure obj is an instance, not a class
|
235
|
+
if isinstance(obj, type):
|
236
|
+
return {}
|
237
|
+
return asdict(obj)
|
238
|
+
|
239
|
+
# Handle regular classes
|
240
|
+
elif hasattr(obj, "__dict__"):
|
241
|
+
result = {}
|
242
|
+
for key, value in obj.__dict__.items():
|
243
|
+
# Skip private attributes
|
244
|
+
if not key.startswith("_"):
|
245
|
+
if (
|
246
|
+
isinstance(value, BaseModel)
|
247
|
+
or hasattr(value, "__dict__")
|
248
|
+
or is_dataclass(value)
|
249
|
+
):
|
250
|
+
result[key] = self._convert_from_class(value)
|
251
|
+
else:
|
252
|
+
result[key] = value
|
253
|
+
return result
|
254
|
+
return {} if obj is None else {str(type(obj).__name__): str(obj)} # Fallback
|
uipath/_cli/cli_dev.py
CHANGED
@@ -6,7 +6,7 @@ import click
|
|
6
6
|
|
7
7
|
from uipath._cli._dev._terminal import UiPathDevTerminal
|
8
8
|
from uipath._cli._runtime._contracts import UiPathRuntimeContext, UiPathRuntimeFactory
|
9
|
-
from uipath._cli._runtime._runtime import
|
9
|
+
from uipath._cli._runtime._runtime import UiPathScriptRuntime
|
10
10
|
from uipath._cli._utils._console import ConsoleLogger
|
11
11
|
from uipath._cli._utils._debug import setup_debugging
|
12
12
|
from uipath._cli.cli_init import init # type: ignore[attr-defined]
|
@@ -53,7 +53,9 @@ def dev(interface: Optional[str], debug: bool, debug_port: int) -> None:
|
|
53
53
|
|
54
54
|
try:
|
55
55
|
if interface == "terminal":
|
56
|
-
runtime_factory = UiPathRuntimeFactory(
|
56
|
+
runtime_factory = UiPathRuntimeFactory(
|
57
|
+
UiPathScriptRuntime, UiPathRuntimeContext
|
58
|
+
)
|
57
59
|
app = UiPathDevTerminal(runtime_factory)
|
58
60
|
asyncio.run(app.run_async())
|
59
61
|
else:
|
uipath/_cli/cli_eval.py
CHANGED
@@ -10,15 +10,16 @@ import click
|
|
10
10
|
from uipath._cli._evals._runtime import UiPathEvalContext, UiPathEvalRuntime
|
11
11
|
from uipath._cli._runtime._contracts import (
|
12
12
|
UiPathRuntimeContext,
|
13
|
-
UiPathRuntimeContextBuilder,
|
14
13
|
UiPathRuntimeFactory,
|
15
14
|
)
|
16
|
-
from uipath._cli._runtime._runtime import
|
15
|
+
from uipath._cli._runtime._runtime import UiPathScriptRuntime
|
17
16
|
from uipath._cli.middlewares import MiddlewareResult, Middlewares
|
17
|
+
from uipath.eval._helpers import auto_discover_entrypoint
|
18
18
|
|
19
19
|
from .._utils.constants import ENV_JOB_ID
|
20
20
|
from ..telemetry import track
|
21
21
|
from ._utils._console import ConsoleLogger
|
22
|
+
from ._utils._eval_set import EvalHelpers
|
22
23
|
|
23
24
|
console = ConsoleLogger()
|
24
25
|
|
@@ -39,42 +40,35 @@ def eval_agent_middleware(
|
|
39
40
|
no_report: bool = False,
|
40
41
|
**kwargs,
|
41
42
|
) -> MiddlewareResult:
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
**kwargs,
|
61
|
-
**base_context.model_dump(),
|
62
|
-
)
|
43
|
+
"""Middleware to run an evaluation set against the agent."""
|
44
|
+
timestamp = datetime.now(timezone.utc).strftime("%M-%H-%d-%m-%Y")
|
45
|
+
|
46
|
+
eval_context = UiPathEvalContext.with_defaults()
|
47
|
+
eval_context.no_report = no_report
|
48
|
+
eval_context.workers = workers
|
49
|
+
eval_context.eval_set = eval_set or EvalHelpers.auto_discover_eval_set()
|
50
|
+
eval_context.eval_ids = eval_ids
|
51
|
+
eval_context.execution_output_file = (
|
52
|
+
f"evals/results/{timestamp}.json" if not os.getenv("UIPATH_JOB_KEY") else None
|
53
|
+
)
|
54
|
+
|
55
|
+
runtime_entrypoint = entrypoint or auto_discover_entrypoint()
|
56
|
+
|
57
|
+
def generate_runtime_context(**context_kwargs) -> UiPathRuntimeContext:
|
58
|
+
runtime_context = UiPathRuntimeContext.with_defaults(**context_kwargs)
|
59
|
+
runtime_context.entrypoint = runtime_entrypoint
|
60
|
+
return runtime_context
|
63
61
|
|
64
62
|
try:
|
65
|
-
runtime_factory = UiPathRuntimeFactory(
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
.with_entrypoint(entrypoint)
|
70
|
-
.with_entrypoint(entrypoint)
|
71
|
-
.mark_eval_run()
|
72
|
-
.build()
|
63
|
+
runtime_factory = UiPathRuntimeFactory(
|
64
|
+
UiPathScriptRuntime,
|
65
|
+
UiPathRuntimeContext,
|
66
|
+
context_generator=generate_runtime_context,
|
73
67
|
)
|
74
68
|
|
75
69
|
async def execute():
|
76
70
|
async with UiPathEvalRuntime.from_eval_context(
|
77
|
-
factory=runtime_factory, context=
|
71
|
+
factory=runtime_factory, context=eval_context
|
78
72
|
) as eval_runtime:
|
79
73
|
await eval_runtime.execute()
|
80
74
|
|
uipath/_cli/cli_run.py
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
# type: ignore
|
2
2
|
import asyncio
|
3
3
|
import os
|
4
|
-
import traceback
|
5
4
|
from os import environ as env
|
6
|
-
from typing import Optional
|
5
|
+
from typing import Optional
|
7
6
|
|
8
7
|
import click
|
9
8
|
|
@@ -15,138 +14,16 @@ from .._utils.constants import (
|
|
15
14
|
from ..telemetry import track
|
16
15
|
from ._runtime._contracts import (
|
17
16
|
UiPathRuntimeContext,
|
18
|
-
UiPathRuntimeContextBuilder,
|
19
17
|
UiPathRuntimeError,
|
20
18
|
UiPathRuntimeFactory,
|
21
19
|
)
|
22
|
-
from ._runtime._runtime import
|
20
|
+
from ._runtime._runtime import UiPathScriptRuntime
|
23
21
|
from ._utils._console import ConsoleLogger
|
24
|
-
from .middlewares import
|
22
|
+
from .middlewares import Middlewares
|
25
23
|
|
26
24
|
console = ConsoleLogger()
|
27
25
|
|
28
26
|
|
29
|
-
def python_run_middleware(
|
30
|
-
entrypoint: Optional[str],
|
31
|
-
input: Optional[str],
|
32
|
-
resume: bool,
|
33
|
-
**kwargs,
|
34
|
-
) -> MiddlewareResult:
|
35
|
-
"""Middleware to handle Python script execution.
|
36
|
-
|
37
|
-
Args:
|
38
|
-
entrypoint: Path to the Python script to execute
|
39
|
-
input: JSON string with input data
|
40
|
-
resume: Flag indicating if this is a resume execution
|
41
|
-
debug: Enable debugging with debugpy
|
42
|
-
debug_port: Port for debug server (default: 5678)
|
43
|
-
|
44
|
-
Returns:
|
45
|
-
MiddlewareResult with execution status and messages
|
46
|
-
"""
|
47
|
-
if not entrypoint:
|
48
|
-
return MiddlewareResult(
|
49
|
-
should_continue=False,
|
50
|
-
error_message="""No entrypoint specified. Please provide a path to a Python script.
|
51
|
-
Usage: `uipath run <entrypoint_path> <input_arguments> [-f <input_json_file_path>]`""",
|
52
|
-
)
|
53
|
-
|
54
|
-
if not os.path.exists(entrypoint):
|
55
|
-
return MiddlewareResult(
|
56
|
-
should_continue=False,
|
57
|
-
error_message=f"""Script not found at path {entrypoint}.
|
58
|
-
Usage: `uipath run <entrypoint_path> <input_arguments> [-f <input_json_file_path>]`""",
|
59
|
-
)
|
60
|
-
|
61
|
-
try:
|
62
|
-
runtime_factory = UiPathRuntimeFactory(UiPathRuntime, UiPathRuntimeContext)
|
63
|
-
context = (
|
64
|
-
UiPathRuntimeContextBuilder()
|
65
|
-
.with_defaults(**kwargs)
|
66
|
-
.with_entrypoint(entrypoint)
|
67
|
-
.with_input(input, input_file=kwargs.get("input_file"))
|
68
|
-
.with_resume(resume)
|
69
|
-
.build()
|
70
|
-
)
|
71
|
-
|
72
|
-
asyncio.run(runtime_factory.execute(context))
|
73
|
-
|
74
|
-
return MiddlewareResult(should_continue=False)
|
75
|
-
|
76
|
-
except UiPathRuntimeError as e:
|
77
|
-
return MiddlewareResult(
|
78
|
-
should_continue=False,
|
79
|
-
error_message=f"Error: {e.error_info.title} - {e.error_info.detail}",
|
80
|
-
should_include_stacktrace=False,
|
81
|
-
)
|
82
|
-
except Exception as e:
|
83
|
-
# Handle unexpected errors
|
84
|
-
return MiddlewareResult(
|
85
|
-
should_continue=False,
|
86
|
-
error_message=f"Error: Unexpected error occurred - {str(e)}",
|
87
|
-
should_include_stacktrace=True,
|
88
|
-
)
|
89
|
-
|
90
|
-
|
91
|
-
def run_core(
|
92
|
-
entrypoint: Optional[str],
|
93
|
-
resume: bool,
|
94
|
-
input: Optional[str] = None,
|
95
|
-
input_file: Optional[str] = None,
|
96
|
-
execution_output_file: Optional[str] = None,
|
97
|
-
logs_file: Optional[str] = None,
|
98
|
-
**kwargs,
|
99
|
-
) -> Tuple[bool, Optional[str], Optional[str]]:
|
100
|
-
"""Core execution logic that can be called programmatically.
|
101
|
-
|
102
|
-
Args:
|
103
|
-
entrypoint: Path to the Python script to execute
|
104
|
-
input: JSON string with input data
|
105
|
-
resume: Flag indicating if this is a resume execution
|
106
|
-
input_file: Path to input JSON file
|
107
|
-
execution_output_file: Path to execution output file
|
108
|
-
logs_file: Path where execution output will be written
|
109
|
-
**kwargs: Additional arguments to be forwarded to the middleware
|
110
|
-
|
111
|
-
Returns:
|
112
|
-
Tuple containing:
|
113
|
-
- success: True if execution was successful
|
114
|
-
- error_message: Error message if any
|
115
|
-
- info_message: Info message if any
|
116
|
-
"""
|
117
|
-
# Process through middleware chain
|
118
|
-
result = Middlewares.next(
|
119
|
-
"run",
|
120
|
-
entrypoint,
|
121
|
-
input,
|
122
|
-
resume,
|
123
|
-
input_file=input_file,
|
124
|
-
execution_output_file=execution_output_file,
|
125
|
-
logs_file=logs_file,
|
126
|
-
**kwargs,
|
127
|
-
)
|
128
|
-
|
129
|
-
if result.should_continue:
|
130
|
-
result = python_run_middleware(
|
131
|
-
entrypoint=entrypoint,
|
132
|
-
input=input,
|
133
|
-
resume=resume,
|
134
|
-
input_file=input_file,
|
135
|
-
execution_output_file=execution_output_file,
|
136
|
-
logs_file=logs_file,
|
137
|
-
**kwargs,
|
138
|
-
)
|
139
|
-
|
140
|
-
if result.should_continue:
|
141
|
-
return False, "Could not process the request with any available handler.", None
|
142
|
-
|
143
|
-
return (
|
144
|
-
not bool(result.error_message),
|
145
|
-
result.error_message,
|
146
|
-
result.info_message,
|
147
|
-
)
|
148
|
-
|
149
|
-
|
150
27
|
@click.command()
|
151
28
|
@click.argument("entrypoint", required=False)
|
152
29
|
@click.argument("input", required=False, default="{}")
|
@@ -198,27 +75,53 @@ def run(
|
|
198
75
|
if not setup_debugging(debug, debug_port):
|
199
76
|
console.error(f"Failed to start debug server on port {debug_port}")
|
200
77
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
78
|
+
result = Middlewares.next(
|
79
|
+
"run",
|
80
|
+
entrypoint,
|
81
|
+
input,
|
82
|
+
resume,
|
205
83
|
input_file=input_file,
|
206
84
|
execution_output_file=output_file,
|
207
85
|
debug=debug,
|
208
86
|
debug_port=debug_port,
|
209
87
|
)
|
210
88
|
|
211
|
-
if error_message:
|
212
|
-
console.error(error_message
|
213
|
-
if not success: # If there was an error and execution failed
|
214
|
-
console.error(traceback.format_exc())
|
215
|
-
click.get_current_context().exit(1)
|
216
|
-
|
217
|
-
if info_message:
|
218
|
-
console.info(info_message)
|
89
|
+
if result.error_message:
|
90
|
+
console.error(result.error_message)
|
219
91
|
|
220
|
-
if
|
221
|
-
|
92
|
+
if result.should_continue:
|
93
|
+
if not entrypoint:
|
94
|
+
console.error("""No entrypoint specified. Please provide a path to a Python script.
|
95
|
+
Usage: `uipath run <entrypoint_path> <input_arguments> [-f <input_json_file_path>]`""")
|
96
|
+
|
97
|
+
if not os.path.exists(entrypoint):
|
98
|
+
console.error(f"""Script not found at path {entrypoint}.
|
99
|
+
Usage: `uipath run <entrypoint_path> <input_arguments> [-f <input_json_file_path>]`""")
|
100
|
+
|
101
|
+
try:
|
102
|
+
runtime_factory = UiPathRuntimeFactory(
|
103
|
+
UiPathScriptRuntime, UiPathRuntimeContext
|
104
|
+
)
|
105
|
+
context = UiPathRuntimeContext.with_defaults(
|
106
|
+
entrypoint=entrypoint,
|
107
|
+
input=input,
|
108
|
+
input_file=input_file,
|
109
|
+
resume=resume,
|
110
|
+
execution_output_file=output_file,
|
111
|
+
debug=debug,
|
112
|
+
)
|
113
|
+
|
114
|
+
asyncio.run(runtime_factory.execute(context))
|
115
|
+
|
116
|
+
except UiPathRuntimeError as e:
|
117
|
+
console.error(f"{e.error_info.title} - {e.error_info.detail}")
|
118
|
+
except Exception as e:
|
119
|
+
# Handle unexpected errors
|
120
|
+
console.error(
|
121
|
+
f"Error: Unexpected error occurred - {str(e)}", include_traceback=True
|
122
|
+
)
|
123
|
+
|
124
|
+
console.success("Successful execution.")
|
222
125
|
|
223
126
|
|
224
127
|
if __name__ == "__main__":
|
@@ -7,7 +7,7 @@ from .._config import Config
|
|
7
7
|
from .._execution_context import ExecutionContext
|
8
8
|
from .._folder_context import FolderContext
|
9
9
|
from .._utils import Endpoint, RequestSpec, header_folder, infer_bindings
|
10
|
-
from .._utils.constants import
|
10
|
+
from .._utils.constants import ENV_JOB_KEY, HEADER_JOB_KEY
|
11
11
|
from ..models.job import Job
|
12
12
|
from ..tracing._traced import traced
|
13
13
|
from . import AttachmentsService
|
@@ -241,7 +241,7 @@ class ProcessesService(FolderContext, BaseService):
|
|
241
241
|
**header_folder(folder_key, folder_path),
|
242
242
|
},
|
243
243
|
)
|
244
|
-
job_key = os.environ.get(
|
244
|
+
job_key = os.environ.get(ENV_JOB_KEY, None)
|
245
245
|
if job_key:
|
246
246
|
request_scope.headers[HEADER_JOB_KEY] = job_key
|
247
247
|
return request_scope
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: uipath
|
3
|
-
Version: 2.1.
|
3
|
+
Version: 2.1.45
|
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
|
@@ -8,8 +8,8 @@ uipath/_cli/README.md,sha256=GLtCfbeIKZKNnGTCsfSVqRQ27V1btT1i2bSAyW_xZl4,474
|
|
8
8
|
uipath/_cli/__init__.py,sha256=kf4GINkunFGMZkTk2Z4f1Q3-OsxpNnV6u_9BsBt1i0E,2229
|
9
9
|
uipath/_cli/cli_auth.py,sha256=i3ykLlCg68xgPXHHaa0agHwGFIiLiTLzOiF6Su8XaEo,2436
|
10
10
|
uipath/_cli/cli_deploy.py,sha256=KPCmQ0c_NYD5JofSDao5r6QYxHshVCRxlWDVnQvlp5w,645
|
11
|
-
uipath/_cli/cli_dev.py,sha256=
|
12
|
-
uipath/_cli/cli_eval.py,sha256=
|
11
|
+
uipath/_cli/cli_dev.py,sha256=nEfpjw1PZ72O6jmufYWVrueVwihFxDPOeJakdvNHdOA,2146
|
12
|
+
uipath/_cli/cli_eval.py,sha256=fYJWQlyiIc8SpTzY9QPNQWOx40PagMEKdsGZIu9As2A,4402
|
13
13
|
uipath/_cli/cli_init.py,sha256=ls577uNm2zWccknIhtVFS3ah2ds0QSy2_TgMp6z7Wt4,6049
|
14
14
|
uipath/_cli/cli_invoke.py,sha256=4jyhqcy7tPrpxvaUhW-9gut6ddsCGMdJJcpOXXmIe8g,4348
|
15
15
|
uipath/_cli/cli_new.py,sha256=9378NYUBc9j-qKVXV7oja-jahfJhXBg8zKVyaon7ctY,2102
|
@@ -17,7 +17,7 @@ uipath/_cli/cli_pack.py,sha256=NmwZTfwZ2fURiHyiX1BM0juAtBOjPB1Jmcpu-rD7p-4,11025
|
|
17
17
|
uipath/_cli/cli_publish.py,sha256=FmBCdeh4zFaESOLfzTTPxGcOwUtsQ_WkvF_fjHEdU8s,6448
|
18
18
|
uipath/_cli/cli_pull.py,sha256=vwS0KMX6O2L6RaPy8tw_qzXe4dC7kf_G6nbLm0I62eI,6831
|
19
19
|
uipath/_cli/cli_push.py,sha256=-j-gDIbT8GyU2SybLQqFl5L8KI9nu3CDijVtltDgX20,3132
|
20
|
-
uipath/_cli/cli_run.py,sha256=
|
20
|
+
uipath/_cli/cli_run.py,sha256=7CJ3e388KCXzB5qh1eHIOL4fe7lMIxt7_1kC7iAz0A8,3559
|
21
21
|
uipath/_cli/middlewares.py,sha256=GvMhDnx1BmA7rIe12s6Uqv1JdqNZhvraU0a91oqGag4,4976
|
22
22
|
uipath/_cli/spinner.py,sha256=bS-U_HA5yne11ejUERu7CQoXmWdabUD2bm62EfEdV8M,1107
|
23
23
|
uipath/_cli/_auth/_auth_server.py,sha256=22km0F1NFNXgyLbvtAx3ssiQlVGHroLdtDCWEqiCiMg,7106
|
@@ -44,7 +44,7 @@ uipath/_cli/_dev/_terminal/_styles/terminal.tcss,sha256=ktVpKwXIXw2VZp8KIZD6fO9i
|
|
44
44
|
uipath/_cli/_dev/_terminal/_utils/_chat.py,sha256=YUZxYVdmEManwHDuZsczJT1dWIYE1dVBgABlurwMFcE,8493
|
45
45
|
uipath/_cli/_dev/_terminal/_utils/_exporter.py,sha256=oI6D_eMwrh_2aqDYUh4GrJg8VLGrLYhDahR-_o0uJns,4144
|
46
46
|
uipath/_cli/_dev/_terminal/_utils/_logger.py,sha256=jeNShEED27cNIHTe_NNx-2kUiXpSLTmi0onM6tVkqRM,888
|
47
|
-
uipath/_cli/_evals/_runtime.py,sha256=
|
47
|
+
uipath/_cli/_evals/_runtime.py,sha256=q4h3zp_7Ygkhj1zE_YTKKXRp3BhkHaPj8CWqjkzerTk,4748
|
48
48
|
uipath/_cli/_evals/progress_reporter.py,sha256=m1Dio1vG-04nFTFz5ijM_j1dhudlgOzQukmTkkg6wS4,11490
|
49
49
|
uipath/_cli/_evals/_evaluators/__init__.py,sha256=jD7KNLjbsUpsESFXX11eW2MEPXDNuPp2-t-IPB-inlM,734
|
50
50
|
uipath/_cli/_evals/_evaluators/_deterministic_evaluator_base.py,sha256=BTl0puBjp9iCsU3YFfYWqk4TOz4iE19O3q1-dK6qUOI,1723
|
@@ -59,11 +59,12 @@ uipath/_cli/_evals/_models/_agent_execution_output.py,sha256=llvApU4JkTnNgQ5DvHP
|
|
59
59
|
uipath/_cli/_evals/_models/_evaluation_set.py,sha256=tVHykSget-G3sOCs9bSchMYUTpFqzXVlYYbY8L9SI0c,1518
|
60
60
|
uipath/_cli/_evals/_models/_evaluators.py,sha256=l57NEVyYmzSKuoIXuGkE94Br01hAMg35fiS2MlTkaQM,2115
|
61
61
|
uipath/_cli/_push/sw_file_handler.py,sha256=AX4TKM-q6CNGw3JyBW02M8ktPZuFMcAU9LN3Ii0Q2QI,18202
|
62
|
-
uipath/_cli/_runtime/_contracts.py,sha256=
|
62
|
+
uipath/_cli/_runtime/_contracts.py,sha256=Utkyu2540_ytyGN450CRXeK2ilPpfJL_npCGdMs4Jic,28195
|
63
63
|
uipath/_cli/_runtime/_escalation.py,sha256=x3vI98qsfRA-fL_tNkRVTFXioM5Gv2w0GFcXJJ5eQtg,7981
|
64
64
|
uipath/_cli/_runtime/_hitl.py,sha256=VKbM021nVg1HEDnTfucSLJ0LsDn83CKyUtVzofS2qTU,11369
|
65
65
|
uipath/_cli/_runtime/_logging.py,sha256=MGklGKPjYKjs7J5Jy9eplA9zCDsdtEbkZdCbTwgut_4,8311
|
66
|
-
uipath/_cli/_runtime/_runtime.py,sha256=
|
66
|
+
uipath/_cli/_runtime/_runtime.py,sha256=gby9-avNNlEATEfSXtY8FfJ8nREsSCGA4wMgDlSXTDE,2297
|
67
|
+
uipath/_cli/_runtime/_script_executor.py,sha256=PjbmEbyCMofGH2F85b8RFsxdV3Tqw0kVqdWOOk2ZLlI,9687
|
67
68
|
uipath/_cli/_templates/.psmdcp.template,sha256=C7pBJPt98ovEljcBvGtEUGoWjjQhu9jls1bpYjeLOKA,611
|
68
69
|
uipath/_cli/_templates/.rels.template,sha256=-fTcw7OA1AcymHr0LzBqbMAAtzZTRXLTNa_ljq087Jk,406
|
69
70
|
uipath/_cli/_templates/[Content_Types].xml.template,sha256=bYsKDz31PkIF9QksjgAY_bqm57YC8U_owsZeNZAiBxQ,584
|
@@ -94,7 +95,7 @@ uipath/_services/context_grounding_service.py,sha256=EBf7lIIYz_s1ubf_07OAZXQHjS8
|
|
94
95
|
uipath/_services/folder_service.py,sha256=9JqgjKhWD-G_KUnfUTP2BADxL6OK9QNZsBsWZHAULdE,2749
|
95
96
|
uipath/_services/jobs_service.py,sha256=UwsY0Cir7Yd5_mTeH0uHLmcmQZpdbT8KNx3z3F0cHZA,32775
|
96
97
|
uipath/_services/llm_gateway_service.py,sha256=oZR--75V8ULdLjVC7lo-lJ5786J_qfXUDe0R9iWNAKs,24306
|
97
|
-
uipath/_services/processes_service.py,sha256=
|
98
|
+
uipath/_services/processes_service.py,sha256=MxNyNo6EdSUhmp7QX9HYUU2JsZfiOcW7OUl86To2fXs,8507
|
98
99
|
uipath/_services/queues_service.py,sha256=VaG3dWL2QK6AJBOLoW2NQTpkPfZjsqsYPl9-kfXPFzA,13534
|
99
100
|
uipath/_utils/__init__.py,sha256=VdcpnENJIa0R6Y26NoxY64-wUVyvb4pKfTh1wXDQeMk,526
|
100
101
|
uipath/_utils/_endpoint.py,sha256=yYHwqbQuJIevpaTkdfYJS9CrtlFeEyfb5JQK5osTCog,2489
|
@@ -144,8 +145,8 @@ uipath/tracing/_traced.py,sha256=qeVDrds2OUnpdUIA0RhtF0kg2dlAZhyC1RRkI-qivTM,185
|
|
144
145
|
uipath/tracing/_utils.py,sha256=wJRELaPu69iY0AhV432Dk5QYf_N_ViRU4kAUG1BI1ew,10384
|
145
146
|
uipath/utils/__init__.py,sha256=VD-KXFpF_oWexFg6zyiWMkxl2HM4hYJMIUDZ1UEtGx0,105
|
146
147
|
uipath/utils/_endpoints_manager.py,sha256=iRTl5Q0XAm_YgcnMcJOXtj-8052sr6jpWuPNz6CgT0Q,8408
|
147
|
-
uipath-2.1.
|
148
|
-
uipath-2.1.
|
149
|
-
uipath-2.1.
|
150
|
-
uipath-2.1.
|
151
|
-
uipath-2.1.
|
148
|
+
uipath-2.1.45.dist-info/METADATA,sha256=Wl0xmOEDkPCFK1l_eB_Bj2nKUn7y0cusfLzcC5hiVmE,6482
|
149
|
+
uipath-2.1.45.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
150
|
+
uipath-2.1.45.dist-info/entry_points.txt,sha256=9C2_29U6Oq1ExFu7usihR-dnfIVNSKc-0EFbh0rskB4,43
|
151
|
+
uipath-2.1.45.dist-info/licenses/LICENSE,sha256=-KBavWXepyDjimmzH5fVAsi-6jNVpIKFc2kZs0Ri4ng,1058
|
152
|
+
uipath-2.1.45.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|