Skip to content

Commit 32136c8

Browse files
committed
feat: debug and output file support
1 parent ffd046d commit 32136c8

5 files changed

Lines changed: 98 additions & 1308 deletions

File tree

src/uipath/_cli/_runtime/_contracts.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,16 @@ 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"
156157
state_file: str = "state.db"
157158
result: Optional[UiPathRuntimeResult] = None
159+
execution_output_file: Optional[str] = None
160+
input_file: Optional[str] = None
158161

159162
model_config = {"arbitrary_types_allowed": True}
160163

@@ -295,6 +298,18 @@ async def __aenter__(self):
295298
Returns:
296299
The runtime instance
297300
"""
301+
# Read the input from file if provided
302+
if self.context.input_file:
303+
_, file_extension = os.path.splitext(self.context.input_file)
304+
if file_extension != ".json":
305+
raise UiPathRuntimeError(
306+
code="INVALID_INPUT_FILE_EXTENSION",
307+
title="Invalid Input File Extension",
308+
detail="The provided input file must be in JSON format.",
309+
)
310+
with open(self.context.input_file) as f:
311+
self.context.input = f.read()
312+
298313
# Intercept all stdout/stderr/logs and write them to a file (runtime), stdout (debug)
299314
self.logs_interceptor = LogsInterceptor(
300315
min_level=self.context.logs_min_level,
@@ -370,6 +385,11 @@ async def __aexit__(self, exc_type, exc_val, exc_tb):
370385
with open(self.output_file_path, "w") as f:
371386
json.dump(content, f, indent=2, default=str)
372387

388+
# Write the execution output to file if requested
389+
if self.context.execution_output_file:
390+
with open(self.context.execution_output_file, "w") as f:
391+
json.dump(execution_result.output or {}, f, indent=2, default=str)
392+
373393
# Don't suppress exceptions
374394
return False
375395

src/uipath/_cli/_utils/_debug.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ def setup_debugging(debug: bool, debug_port: int = 5678) -> bool:
4242
console.info(f"🐛 Debug server started on port {debug_port}")
4343
console.info("📌 Waiting for debugger to attach...")
4444
console.info(" - VS Code: Run -> Start Debugging -> Python: Remote Attach")
45+
console.link(
46+
" CLI Documentation reference: ",
47+
"https://uipath.github.io/uipath-python/cli/#run",
48+
)
4549

4650
debugpy.wait_for_client()
4751
console.success("Debugger attached successfully!")

src/uipath/_cli/cli_run.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def python_run_middleware(
3232
entrypoint: Optional[str],
3333
input: Optional[str],
3434
resume: bool,
35+
**kwargs,
3536
) -> MiddlewareResult:
3637
"""Middleware to handle Python script execution.
3738
@@ -70,6 +71,8 @@ async def execute():
7071
context.resume = resume
7172
context.job_id = env.get("UIPATH_JOB_KEY")
7273
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)
7376
context.tracing_enabled = env.get("UIPATH_TRACING_ENABLED", True)
7477
context.trace_context = UiPathTraceContext(
7578
trace_id=env.get("UIPATH_TRACE_ID"),
@@ -118,6 +121,18 @@ async def execute():
118121
type=click.Path(exists=True),
119122
help="File path for the .json input",
120123
)
124+
@click.option(
125+
"--input-file",
126+
required=False,
127+
type=click.Path(exists=True),
128+
help="Alias for '-f/--file' arguments",
129+
)
130+
@click.option(
131+
"--output-file",
132+
required=False,
133+
type=click.Path(exists=False),
134+
help="File path where the output will be written",
135+
)
121136
@click.option(
122137
"--debug",
123138
is_flag=True,
@@ -135,29 +150,36 @@ def run(
135150
input: Optional[str],
136151
resume: bool,
137152
file: Optional[str],
153+
input_file: Optional[str],
154+
output_file: Optional[str],
138155
debug: bool,
139156
debug_port: int,
140157
) -> None:
141158
"""Execute the project."""
142-
if file:
143-
_, file_extension = os.path.splitext(file)
144-
if file_extension != ".json":
145-
console.error("Input file extension must be '.json'.")
146-
with open(file) as f:
147-
input = f.read()
159+
input_file = file or input_file
148160
# Setup debugging if requested
149-
150161
if not setup_debugging(debug, debug_port):
151162
console.error(f"Failed to start debug server on port {debug_port}")
152163

153164
# Process through middleware chain
154-
result = Middlewares.next("run", entrypoint, input, resume)
165+
result = Middlewares.next(
166+
"run",
167+
entrypoint,
168+
input,
169+
resume,
170+
debug=debug,
171+
debug_port=debug_port,
172+
input_file=input_file,
173+
execution_output_file=output_file,
174+
)
155175

156176
if result.should_continue:
157177
result = python_run_middleware(
158178
entrypoint=entrypoint,
159179
input=input,
160180
resume=resume,
181+
input_file=input_file,
182+
execution_output_file=output_file,
161183
)
162184

163185
# Handle result from middleware

tests/cli/test_run.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ def test_run_input_file_not_found(
5353
entrypoint: str,
5454
):
5555
with runner.isolated_filesystem(temp_dir=temp_dir):
56+
file_path = os.path.join(temp_dir, entrypoint)
57+
with open(file_path, "w") as f:
58+
f.write("script content")
5659
result = runner.invoke(run, [entrypoint, "--file", "not-here.json"])
5760
assert result.exit_code != 0
5861
assert "Error: Invalid value for '-f' / '--file'" in result.output
@@ -65,12 +68,15 @@ def test_run_invalid_input_file(
6568
):
6669
file_name = "not-json.txt"
6770
with runner.isolated_filesystem(temp_dir=temp_dir):
71+
script_file_path = os.path.join(temp_dir, entrypoint)
72+
with open(script_file_path, "w") as f:
73+
f.write("script content")
6874
file_path = os.path.join(temp_dir, file_name)
6975
with open(file_path, "w") as f:
7076
f.write("file content")
71-
result = runner.invoke(run, [entrypoint, "--file", file_path])
77+
result = runner.invoke(run, [script_file_path, "--file", file_path])
7278
assert result.exit_code == 1
73-
assert "Input file extension must be '.json'." in result.output
79+
assert "Invalid Input File Extension" in result.output
7480

7581
def test_run_input_file_success(
7682
self,
@@ -100,7 +106,14 @@ def test_run_input_file_success(
100106
assert "Successful execution." in result.output
101107
assert mock_middleware.call_count == 1
102108
assert mock_middleware.call_args == mock.call(
103-
"run", entrypoint, json_content, False
109+
"run",
110+
entrypoint,
111+
"{}",
112+
False,
113+
debug=False,
114+
debug_port=5678,
115+
input_file=file_path,
116+
execution_output_file=None,
104117
)
105118

106119
class TestMiddleware:
@@ -134,6 +147,7 @@ def test_successful_execution(
134147
simple_script: str,
135148
):
136149
input_file_name = "input.json"
150+
output_file_name = "output.json"
137151
input_json_content = """
138152
{
139153
"message": "Hello world",
@@ -142,6 +156,7 @@ def test_successful_execution(
142156
with runner.isolated_filesystem(temp_dir=temp_dir):
143157
# create input file
144158
input_file_path = os.path.join(temp_dir, input_file_name)
159+
output_file_path = os.path.join(temp_dir, output_file_name)
145160
with open(input_file_path, "w") as f:
146161
f.write(input_json_content)
147162
# Create test script
@@ -152,11 +167,22 @@ def test_successful_execution(
152167
with open("uipath.json", "w") as f:
153168
f.write(uipath_json.to_json())
154169
result = runner.invoke(
155-
run, [script_file_path, "--file", input_file_path]
170+
run,
171+
[
172+
script_file_path,
173+
"--input-file",
174+
input_file_path,
175+
"--output-file",
176+
output_file_path,
177+
],
156178
)
157179
assert result.exit_code == 0
158180
assert "Successful execution." in result.output
159181
assert result.output.count("Hello world") == 2
182+
assert os.path.exists(output_file_path)
183+
with open(output_file_path, "r") as f:
184+
output = f.read()
185+
assert output.count("Hello world") == 2
160186

161187
def test_no_main_function_found(
162188
self,

0 commit comments

Comments
 (0)