Skip to content

Commit 0e9bf11

Browse files
committed
feat: debug and output file support
1 parent 55de34c commit 0e9bf11

5 files changed

Lines changed: 932 additions & 2169 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath"
3-
version = "2.0.73"
3+
version = "2.1.0"
44
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.10"
@@ -66,9 +66,6 @@ dev = [
6666
"types-toml>=0.10.8",
6767
]
6868

69-
[project.optional-dependencies]
70-
langchain = ["uipath-langchain>=0.0.88,<0.1.0"]
71-
7269
[tool.hatch.build.targets.wheel]
7370
packages = ["src/uipath"]
7471

src/uipath/_cli/_runtime/_contracts.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,13 @@ class UiPathRuntimeContext(BaseModel):
148148
trace_context: Optional[UiPathTraceContext] = None
149149
tracing_enabled: Union[bool, str] = False
150150
resume: bool = False
151+
debug: bool = False
151152
config_path: str = "uipath.json"
152153
runtime_dir: Optional[str] = "__uipath"
153154
logs_file: Optional[str] = "execution.log"
154155
logs_min_level: Optional[str] = "INFO"
155156
output_file: str = "output.json"
157+
execution_output_file: str = "execution_output.json"
156158
state_file: str = "state.db"
157159
result: Optional[UiPathRuntimeResult] = None
158160

@@ -187,6 +189,7 @@ def from_config(cls, config_path=None):
187189
mapping = {
188190
"dir": "runtime_dir",
189191
"outputFile": "output_file",
192+
"executionOutputFile": "execution_output_file",
190193
"stateFile": "state_file",
191194
"logsFile": "logs_file",
192195
}
@@ -365,11 +368,14 @@ async def __aexit__(self, exc_type, exc_val, exc_tb):
365368
content = execution_result.to_dict()
366369
logger.debug(content)
367370

368-
# Always write output file at runtime
371+
# Always write output and execution_output files at runtime
369372
if self.context.job_id:
370373
with open(self.output_file_path, "w") as f:
371374
json.dump(content, f, indent=2, default=str)
372375

376+
with open(self.execution_output_file_path, "w") as f:
377+
json.dump(execution_result.output or "", f, indent=2, default=str)
378+
373379
# Don't suppress exceptions
374380
return False
375381

@@ -424,6 +430,15 @@ def output_file_path(self) -> str:
424430
return os.path.join(self.context.runtime_dir, self.context.output_file)
425431
return os.path.join("__uipath", "output.json")
426432

433+
@cached_property
434+
def execution_output_file_path(self) -> str:
435+
if self.context.runtime_dir and self.context.execution_output_file:
436+
os.makedirs(self.context.runtime_dir, exist_ok=True)
437+
return os.path.join(
438+
self.context.runtime_dir, self.context.execution_output_file
439+
)
440+
return os.path.join("__uipath", "execution_output.json")
441+
427442
@cached_property
428443
def state_file_path(self) -> str:
429444
if self.context.runtime_dir and self.context.state_file:

src/uipath/_cli/cli_run.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
UiPathTraceContext,
2222
)
2323
from ._runtime._runtime import UiPathRuntime
24+
from ._utils._common import serialize_object
2425
from ._utils._console import ConsoleLogger
2526
from .middlewares import MiddlewareResult, Middlewares
2627

@@ -32,6 +33,7 @@ def python_run_middleware(
3233
entrypoint: Optional[str],
3334
input: Optional[str],
3435
resume: bool,
36+
**kwargs,
3537
) -> MiddlewareResult:
3638
"""Middleware to handle Python script execution.
3739
@@ -85,12 +87,14 @@ async def execute():
8587
)
8688
context.logs_min_level = env.get("LOG_LEVEL", "INFO")
8789
async with UiPathRuntime.from_context(context) as runtime:
88-
await runtime.execute()
90+
return await runtime.execute()
8991

90-
asyncio.run(execute())
92+
result = asyncio.run(execute())
9193

9294
# Return success
93-
return MiddlewareResult(should_continue=False)
95+
return MiddlewareResult(
96+
should_continue=False, output=serialize_object(result.output)
97+
)
9498

9599
except UiPathRuntimeError as e:
96100
return MiddlewareResult(
@@ -114,10 +118,17 @@ async def execute():
114118
@click.option(
115119
"-f",
116120
"--file",
121+
"--input-file",
117122
required=False,
118123
type=click.Path(exists=True),
119124
help="File path for the .json input",
120125
)
126+
@click.option(
127+
"--output-file",
128+
required=False,
129+
type=click.Path(exists=False),
130+
help="File path where the output will be written",
131+
)
121132
@click.option(
122133
"--debug",
123134
is_flag=True,
@@ -135,6 +146,7 @@ def run(
135146
input: Optional[str],
136147
resume: bool,
137148
file: Optional[str],
149+
output_file: Optional[str],
138150
debug: bool,
139151
debug_port: int,
140152
) -> None:
@@ -145,20 +157,25 @@ def run(
145157
console.error("Input file extension must be '.json'.")
146158
with open(file) as f:
147159
input = f.read()
148-
# Setup debugging if requested
149160

161+
# Setup debugging if requested
150162
if not setup_debugging(debug, debug_port):
151163
console.error(f"Failed to start debug server on port {debug_port}")
152164

153165
# Process through middleware chain
154-
result = Middlewares.next("run", entrypoint, input, resume)
166+
result = Middlewares.next(
167+
"run", entrypoint, input, resume, debug=debug, debug_port=debug_port
168+
)
155169

156170
if result.should_continue:
157171
result = python_run_middleware(
158172
entrypoint=entrypoint,
159173
input=input,
160174
resume=resume,
161175
)
176+
if output_file:
177+
with open(output_file, "w") as f:
178+
f.write(str(result.output))
162179

163180
# Handle result from middleware
164181
if result.error_message:

src/uipath/_cli/middlewares.py

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import importlib.metadata
2-
import inspect
32
import logging
43
from dataclasses import dataclass
54
from typing import Any, Callable, Dict, List, Optional
@@ -16,6 +15,7 @@ class MiddlewareResult:
1615
info_message: Optional[str] = None
1716
error_message: Optional[str] = None
1817
should_include_stacktrace: bool = False
18+
output: Optional[str] = None
1919

2020

2121
MiddlewareFunc = Callable[..., MiddlewareResult]
@@ -54,30 +54,8 @@ def next(cls, command: str, *args: Any, **kwargs: Any) -> MiddlewareResult:
5454

5555
middlewares = cls.get(command)
5656
for middleware in middlewares:
57-
sig = inspect.signature(middleware)
58-
59-
# handle older versions of plugins that don't support the new signature
60-
try:
61-
bound = sig.bind(*args, **kwargs)
62-
new_args = bound.args
63-
new_kwargs = bound.kwargs
64-
except TypeError:
65-
console.warning("Install the latest version for uipath packages")
66-
accepted_args = [
67-
name
68-
for name, param in sig.parameters.items()
69-
if param.kind
70-
in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD)
71-
]
72-
73-
trimmed_args = args[: len(accepted_args)]
74-
trimmed_kwargs = {k: v for k, v in kwargs.items() if k in accepted_args}
75-
76-
new_args = trimmed_args
77-
new_kwargs = trimmed_kwargs
78-
7957
try:
80-
result = middleware(*new_args, **new_kwargs)
58+
result = middleware(*args, **kwargs)
8159
if not result.should_continue:
8260
logger.debug(
8361
f"Command '{command}' stopped by {middleware.__name__}"

0 commit comments

Comments
 (0)