Skip to content

Commit 3ca8cc1

Browse files
committed
feat: debug and output file support
1 parent 0274ad0 commit 3ca8cc1

6 files changed

Lines changed: 68 additions & 1309 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.82"
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"
@@ -67,9 +67,6 @@ dev = [
6767
"types-toml>=0.10.8",
6868
]
6969

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

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: 29 additions & 6 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(
@@ -118,6 +122,18 @@ async def execute():
118122
type=click.Path(exists=True),
119123
help="File path for the .json input",
120124
)
125+
@click.option(
126+
"--input-file",
127+
required=False,
128+
type=click.Path(exists=True),
129+
help="Alias for '-f/--file' arguments",
130+
)
131+
@click.option(
132+
"--output-file",
133+
required=False,
134+
type=click.Path(exists=False),
135+
help="File path where the output will be written",
136+
)
121137
@click.option(
122138
"--debug",
123139
is_flag=True,
@@ -135,30 +151,37 @@ def run(
135151
input: Optional[str],
136152
resume: bool,
137153
file: Optional[str],
154+
input_file: Optional[str],
155+
output_file: Optional[str],
138156
debug: bool,
139157
debug_port: int,
140158
) -> None:
141159
"""Execute the project."""
142-
if file:
160+
if file:= (file or input_file):
143161
_, file_extension = os.path.splitext(file)
144162
if file_extension != ".json":
145163
console.error("Input file extension must be '.json'.")
146164
with open(file) as f:
147165
input = f.read()
148-
# Setup debugging if requested
149166

167+
# Setup debugging if requested
150168
if not setup_debugging(debug, debug_port):
151169
console.error(f"Failed to start debug server on port {debug_port}")
152170

153171
# Process through middleware chain
154-
result = Middlewares.next("run", entrypoint, input, resume)
172+
result = Middlewares.next(
173+
"run", entrypoint, input, resume, debug=debug, debug_port=debug_port
174+
)
155175

156176
if result.should_continue:
157177
result = python_run_middleware(
158178
entrypoint=entrypoint,
159179
input=input,
160180
resume=resume,
161181
)
182+
if output_file:
183+
with open(output_file, "w") as f:
184+
f.write(str(result.output))
162185

163186
# Handle result from middleware
164187
if result.error_message:

src/uipath/_cli/middlewares.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class MiddlewareResult:
1616
info_message: Optional[str] = None
1717
error_message: Optional[str] = None
1818
should_include_stacktrace: bool = False
19+
output: Optional[str] = None
1920

2021

2122
MiddlewareFunc = Callable[..., MiddlewareResult]
@@ -67,7 +68,7 @@ def next(cls, command: str, *args: Any, **kwargs: Any) -> MiddlewareResult:
6768
name
6869
for name, param in sig.parameters.items()
6970
if param.kind
70-
in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD)
71+
in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD)
7172
]
7273

7374
trimmed_args = args[: len(accepted_args)]

tests/cli/test_run.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,12 @@ def test_run_input_file_success(
100100
assert "Successful execution." in result.output
101101
assert mock_middleware.call_count == 1
102102
assert mock_middleware.call_args == mock.call(
103-
"run", entrypoint, json_content, False
103+
"run",
104+
entrypoint,
105+
json_content,
106+
False,
107+
debug=False,
108+
debug_port=5678,
104109
)
105110

106111
class TestMiddleware:

0 commit comments

Comments
 (0)