Skip to content

WIP: add debug functionality #433

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath"
version = "2.0.73"
version = "3.0.0"
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.10"
Expand Down Expand Up @@ -66,8 +66,7 @@ dev = [
"types-toml>=0.10.8",
]

[project.optional-dependencies]
langchain = ["uipath-langchain>=0.0.88,<0.1.0"]
# TODO: add the uipath-langchain and uipath-llamaindex optional dependencies here

[tool.hatch.build.targets.wheel]
packages = ["src/uipath"]
Expand Down
15 changes: 14 additions & 1 deletion src/uipath/_cli/_runtime/_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,13 @@ class UiPathRuntimeContext(BaseModel):
trace_context: Optional[UiPathTraceContext] = None
tracing_enabled: Union[bool, str] = False
resume: bool = False
debug: bool = False
config_path: str = "uipath.json"
runtime_dir: Optional[str] = "__uipath"
logs_file: Optional[str] = "execution.log"
logs_min_level: Optional[str] = "INFO"
output_file: str = "output.json"
execution_output_file: str = "execution_output.json"
state_file: str = "state.db"
result: Optional[UiPathRuntimeResult] = None

Expand Down Expand Up @@ -187,6 +189,7 @@ def from_config(cls, config_path=None):
mapping = {
"dir": "runtime_dir",
"outputFile": "output_file",
"executionOutputFile": "execution_output_file",
"stateFile": "state_file",
"logsFile": "logs_file",
}
Expand Down Expand Up @@ -365,11 +368,14 @@ async def __aexit__(self, exc_type, exc_val, exc_tb):
content = execution_result.to_dict()
logger.debug(content)

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

with open(self.execution_output_file_path, "w") as f:
json.dump(execution_result.output or "", f, indent=2, default=str)

# Don't suppress exceptions
return False

Expand Down Expand Up @@ -424,6 +430,13 @@ def output_file_path(self) -> str:
return os.path.join(self.context.runtime_dir, self.context.output_file)
return os.path.join("__uipath", "output.json")

@cached_property
def execution_output_file_path(self) -> str:
if self.context.runtime_dir and self.context.execution_output_file:
os.makedirs(self.context.runtime_dir, exist_ok=True)
return os.path.join(self.context.runtime_dir, self.context.execution_output_file)
return os.path.join("__uipath", "execution_output.json")

@cached_property
def state_file_path(self) -> str:
if self.context.runtime_dir and self.context.state_file:
Expand Down
25 changes: 19 additions & 6 deletions src/uipath/_cli/cli_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Optional
from uuid import uuid4

from ._utils._common import serialize_object
import click
from dotenv import load_dotenv

Expand All @@ -32,6 +33,7 @@ def python_run_middleware(
entrypoint: Optional[str],
input: Optional[str],
resume: bool,
**kwargs,
) -> MiddlewareResult:
"""Middleware to handle Python script execution.

Expand Down Expand Up @@ -85,12 +87,12 @@ async def execute():
)
context.logs_min_level = env.get("LOG_LEVEL", "INFO")
async with UiPathRuntime.from_context(context) as runtime:
await runtime.execute()
return await runtime.execute()

asyncio.run(execute())
result = asyncio.run(execute())

# Return success
return MiddlewareResult(should_continue=False)
return MiddlewareResult(should_continue=False, output=serialize_object(result.output))

except UiPathRuntimeError as e:
return MiddlewareResult(
Expand All @@ -112,12 +114,19 @@ async def execute():
@click.argument("input", required=False, default="{}")
@click.option("--resume", is_flag=True, help="Resume execution from a previous state")
@click.option(
"-f",
"--file",
"-i",
"--input-file",
required=False,
type=click.Path(exists=True),
help="File path for the .json input",
)
@click.option(
"-o",
"--output-file",
required=False,
type=click.Path(exists=False),
help="File path where the output will be written",
)
@click.option(
"--debug",
is_flag=True,
Expand All @@ -135,6 +144,7 @@ def run(
input: Optional[str],
resume: bool,
file: Optional[str],
output_file: Optional[str],
debug: bool,
debug_port: int,
) -> None:
Expand All @@ -151,14 +161,17 @@ def run(
console.error(f"Failed to start debug server on port {debug_port}")

# Process through middleware chain
result = Middlewares.next("run", entrypoint, input, resume)
result = Middlewares.next("run", entrypoint, input, resume, debug=debug, debug_port=debug_port)

if result.should_continue:
result = python_run_middleware(
entrypoint=entrypoint,
input=input,
resume=resume,
)
if output_file:
with open(output_file, "w") as f:
f.write(str(result.output))

# Handle result from middleware
if result.error_message:
Expand Down
26 changes: 2 additions & 24 deletions src/uipath/_cli/middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class MiddlewareResult:
info_message: Optional[str] = None
error_message: Optional[str] = None
should_include_stacktrace: bool = False

output: Optional[str] = None

MiddlewareFunc = Callable[..., MiddlewareResult]

Expand Down Expand Up @@ -54,30 +54,8 @@ def next(cls, command: str, *args: Any, **kwargs: Any) -> MiddlewareResult:

middlewares = cls.get(command)
for middleware in middlewares:
sig = inspect.signature(middleware)

# handle older versions of plugins that don't support the new signature
try:
bound = sig.bind(*args, **kwargs)
new_args = bound.args
new_kwargs = bound.kwargs
except TypeError:
console.warning("Install the latest version for uipath packages")
accepted_args = [
name
for name, param in sig.parameters.items()
if param.kind
in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD)
]

trimmed_args = args[: len(accepted_args)]
trimmed_kwargs = {k: v for k, v in kwargs.items() if k in accepted_args}

new_args = trimmed_args
new_kwargs = trimmed_kwargs

try:
result = middleware(*new_args, **new_kwargs)
result = middleware(*args, **kwargs)
if not result.should_continue:
logger.debug(
f"Command '{command}' stopped by {middleware.__name__}"
Expand Down
Loading