uipath 2.1.43__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.
@@ -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,58 +51,30 @@ class ExecutionSpanExporter(SpanExporter):
54
51
  self.clear()
55
52
 
56
53
 
57
- class UiPathEvalContext(UiPathRuntimeContext, Generic[C]):
54
+ class UiPathEvalContext(UiPathRuntimeContext):
58
55
  """Context used for evaluation runs."""
59
56
 
60
- runtime_context: C
61
- no_report: bool
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: "UiPathEvalContext[C]" = 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)
103
72
 
104
73
  @classmethod
105
- def from__eval_context(
74
+ def from_eval_context(
106
75
  cls,
107
- context: "UiPathEvalContext[C]",
108
- factory: "UiPathRuntimeFactory[T, C]",
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
- execution_output_list.append(await self.execute_agent(eval_item))
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.runtime_context.result
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
- # TODO: this would most likely need to be ported to a public AgentEvaluator class
142
- async def execute_agent(
103
+ async def execute_runtime(
143
104
  self, eval_item: EvaluationItem
144
- ) -> "UiPathEvalRunExecutionOutput":
145
- runtime_context = self._prepare_new_runtime_context(eval_item)
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 from_config(cls, config_path=None, **kwargs):
319
- """Load configuration from uipath.json file.
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
- Args:
322
- config_path: Path to the configuration file. If None, uses the default "uipath.json"
323
- **kwargs: Additional keyword arguments to use as fallback for configuration values
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
- Returns:
326
- An instance of the class with fields populated from the config file
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
- path = config_path or "uipath.json"
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\nTraceback:\n{tb}"
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__(self, runtime_class: Type[T], context_class: Type[C]):
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
- result = await runtime.execute()
687
- for span_processor in self.tracer_span_processors:
688
- span_processor.force_flush()
689
- return result
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
- tracer: Tracer = trace.get_tracer("uipath-runtime")
697
- with tracer.start_as_current_span(
698
- root_span,
699
- attributes={"execution.id": context.execution_id}
700
- if context.execution_id
701
- else {},
702
- ):
703
- result = await runtime.execute()
704
- for span_processor in self.tracer_span_processors:
705
- span_processor.force_flush()
706
- return result
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:
@@ -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 os
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
- """Runtime for executing Python scripts."""
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
- if self.context.entrypoint is None:
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
- for field_name, field_type in field_types.items():
225
- if field_name not in data:
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
- # Handle dataclasses
291
- elif is_dataclass(obj):
292
- # Make sure obj is an instance, not a class
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
- # Handle regular classes
298
- elif hasattr(obj, "__dict__"):
299
- result = {}
300
- for key, value in obj.__dict__.items():
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 "")