Skip to content

Commit 43b7443

Browse files
committed
fix: get input from context
1 parent ac4a352 commit 43b7443

File tree

5 files changed

+94
-25
lines changed

5 files changed

+94
-25
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-runtime"
3-
version = "0.0.12"
3+
version = "0.0.13"
44
description = "Runtime abstractions and interfaces for building agents and automation scripts in the UiPath ecosystem"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

src/uipath/runtime/context.py

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import logging
55
import os
66
from functools import cached_property
7+
from pathlib import Path
78
from typing import (
89
Any,
910
TypeVar,
@@ -29,7 +30,7 @@ class UiPathRuntimeContext(BaseModel):
2930
"""Context information passed throughout the runtime execution."""
3031

3132
entrypoint: str | None = None
32-
input: dict[str, Any] | None = None
33+
input: str | None = None
3334
job_id: str | None = None
3435
config_path: str = "uipath.json"
3536
runtime_dir: str | None = "__uipath"
@@ -44,34 +45,99 @@ class UiPathRuntimeContext(BaseModel):
4445

4546
model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
4647

47-
def __enter__(self):
48-
"""Async enter method called when entering the 'async with' block.
48+
def get_input(self) -> dict[str, Any]:
49+
"""Get parsed input data.
4950
50-
Initializes and prepares the runtime contextual environment.
51+
Priority:
52+
1. If input_file is specified, read and parse from file
53+
2. Otherwise, parse the input string
5154
5255
Returns:
53-
The runtime context instance
56+
Parsed input dictionary
57+
58+
Raises:
59+
UiPathRuntimeError: If JSON is invalid or file not found
5460
"""
61+
if self.input_file:
62+
return self._read_input_from_file(self.input_file)
63+
64+
return self._parse_input_string(self.input)
65+
66+
def _read_input_from_file(self, file_path: str) -> dict[str, Any]:
67+
"""Read and parse input from JSON file."""
68+
path = Path(file_path)
69+
70+
# Validate file extension
71+
if path.suffix != ".json":
72+
raise UiPathRuntimeError(
73+
code=UiPathErrorCode.INVALID_INPUT_FILE_EXTENSION,
74+
title="Invalid Input File Extension",
75+
detail=f"The provided input file must be in JSON format. Got: {path.suffix}",
76+
category=UiPathErrorCategory.USER,
77+
)
78+
79+
# Check if file exists
80+
if not path.exists():
81+
raise UiPathRuntimeError(
82+
code=UiPathErrorCode.MISSING_INPUT_FILE,
83+
title="Input File Not Found",
84+
detail=f"The input file does not exist: {file_path}",
85+
category=UiPathErrorCategory.USER,
86+
)
87+
5588
try:
56-
if self.input_file:
57-
# Read the input from file if provided
58-
_, file_extension = os.path.splitext(self.input_file)
59-
if file_extension != ".json":
60-
raise UiPathRuntimeError(
61-
code=UiPathErrorCode.INVALID_INPUT_FILE_EXTENSION,
62-
title="Invalid Input File Extension",
63-
detail="The provided input file must be in JSON format.",
64-
)
65-
with open(self.input_file) as f:
66-
self.input = json.loads(f.read())
89+
with open(path) as f:
90+
return json.load(f)
91+
except json.JSONDecodeError as e:
92+
raise UiPathRuntimeError(
93+
code=UiPathErrorCode.INPUT_INVALID_JSON,
94+
title="Invalid JSON in Input File",
95+
detail=f"The input file contains invalid JSON: {e}",
96+
category=UiPathErrorCategory.USER,
97+
) from e
98+
except Exception as e:
99+
raise UiPathRuntimeError(
100+
code=UiPathErrorCode.INPUT_INVALID_JSON,
101+
title="Failed to Read Input File",
102+
detail=f"Error reading input file: {e}",
103+
category=UiPathErrorCategory.SYSTEM,
104+
) from e
105+
106+
def _parse_input_string(self, input_str: str | None) -> dict[str, Any]:
107+
"""Parse input from JSON string."""
108+
if not input_str or input_str.strip() == "":
109+
return {}
110+
111+
try:
112+
parsed = json.loads(input_str)
113+
114+
# Ensure we return a dict
115+
if not isinstance(parsed, dict):
116+
raise UiPathRuntimeError(
117+
code=UiPathErrorCode.INPUT_INVALID_JSON,
118+
title="Invalid Input Type",
119+
detail=f"Input must be a JSON object, got: {type(parsed).__name__}",
120+
category=UiPathErrorCategory.USER,
121+
)
122+
123+
return parsed
124+
67125
except json.JSONDecodeError as e:
68126
raise UiPathRuntimeError(
69-
UiPathErrorCode.INPUT_INVALID_JSON,
70-
"Invalid JSON input",
71-
f"The input data is not valid JSON: {str(e)}",
72-
UiPathErrorCategory.USER,
127+
code=UiPathErrorCode.INPUT_INVALID_JSON,
128+
title="Invalid JSON Input",
129+
detail=f"The input data is not valid JSON: {e}",
130+
category=UiPathErrorCategory.USER,
73131
) from e
74132

133+
def __enter__(self):
134+
"""Enter method called when entering the 'async with' block.
135+
136+
Initializes and prepares the runtime contextual environment.
137+
138+
Returns:
139+
The runtime context instance
140+
"""
75141
# Intercept all stdout/stderr/logs
76142
# Write to file (runtime), stdout (debug) or log handler (if provided)
77143
self.logs_interceptor = UiPathRuntimeLogsInterceptor(
@@ -194,6 +260,7 @@ def with_defaults(cls: type[C], config_path: str | None = None, **kwargs) -> C:
194260
)
195261

196262
base = cls.from_config(resolved_config_path)
263+
base.config_path = resolved_config_path
197264

198265
bool_map = {"true": True, "false": False}
199266
tracing_enabled = os.environ.get("UIPATH_TRACING_ENABLED", True)

src/uipath/runtime/errors/codes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class UiPathErrorCode(str, Enum):
1919

2020
# Input validation errors
2121
INVALID_INPUT_FILE_EXTENSION = "INVALID_INPUT_FILE_EXTENSION"
22+
MISSING_INPUT_FILE = "MISSING_INPUT_FILE"
23+
2224
INPUT_INVALID_JSON = "INPUT_INVALID_JSON"
2325

2426
# HITL (Human-In-The-Loop) related errors

tests/test_context.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def test_context_loads_json_input_file(tmp_path: Path) -> None:
4444

4545
with ctx:
4646
# input should be loaded from the JSON file
47-
assert ctx.input == input_data
47+
assert ctx.get_input() == input_data
4848
# logs interceptor should have been set up
4949
assert isinstance(ctx.logs_interceptor, DummyLogsInterceptor)
5050
assert ctx.logs_interceptor.setup_called
@@ -61,8 +61,8 @@ def test_context_raises_for_invalid_json(tmp_path: Path) -> None:
6161

6262
with pytest.raises(UiPathRuntimeError) as excinfo:
6363
with ctx:
64-
# __enter__ should fail before body executes
65-
pass
64+
# Explicitly call get_input() which will raise
65+
ctx.get_input()
6666

6767
err = excinfo.value.error_info
6868
assert err.code == f"Python.{UiPathErrorCode.INPUT_INVALID_JSON.value}"

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)