Skip to content

Commit ad798c7

Browse files
committed
feat(api): add jobs and process search
1 parent f3b6e20 commit ad798c7

File tree

5 files changed

+934
-13
lines changed

5 files changed

+934
-13
lines changed

src/uipath/_services/jobs_service.py

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,230 @@ def _link_job_attachment_spec(
641641
},
642642
)
643643

644+
@traced(name="jobs_list", run_type="uipath")
645+
def list(
646+
self,
647+
*,
648+
folder_key: Optional[str] = None,
649+
folder_path: Optional[str] = None,
650+
release_id: Optional[int] = None,
651+
creation_time_start: Optional[str] = None,
652+
creation_time_end: Optional[str] = None,
653+
top: int = 25,
654+
skip: int = 0,
655+
expand: Optional[List[str]] = None,
656+
include_test_automation: bool = False,
657+
) -> List[Job]:
658+
"""List jobs from a folder with optional filters and pagination.
659+
660+
Args:
661+
folder_key (Optional[str]): The key of the folder to query jobs from. Override the default one set in the SDK config.
662+
folder_path (Optional[str]): The path of the folder to query jobs from. Override the default one set in the SDK config.
663+
release_id (Optional[int]): Filter by release ID. If not provided, all releases are included. To filter by release name, use processes.get_by_name() first to get the ID.
664+
creation_time_start (Optional[str]): Filter jobs created on or after this time (ISO 8601 format, e.g., "2025-11-12T23:30:00.000Z").
665+
creation_time_end (Optional[str]): Filter jobs created on or before this time (ISO 8601 format, e.g., "2025-11-14T00:00:00.000Z").
666+
top (int): Maximum number of jobs to return (OData $top). Defaults to 25.
667+
skip (int): Number of jobs to skip (OData $skip). Defaults to 0.
668+
expand (Optional[List[str]]): List of entities to expand (e.g., ["Robot", "Machine", "Release"]). Defaults to None (no expansion).
669+
include_test_automation (bool): Whether to include test automation processes. Defaults to False.
670+
671+
Returns:
672+
List[Job]: List of jobs matching the filters.
673+
674+
Examples:
675+
```python
676+
from uipath import UiPath
677+
678+
sdk = UiPath()
679+
680+
# List first 25 jobs from a folder
681+
jobs = sdk.jobs.list(folder_path="Shared")
682+
683+
# List jobs filtered by release ID with pagination
684+
jobs = sdk.jobs.list(folder_path="Shared", release_id=619188, top=10, skip=0)
685+
686+
# List jobs filtered by release name (get the release first)
687+
release = sdk.processes.get_by_name("llamaindex-agent-no-llm")
688+
jobs = sdk.jobs.list(folder_path="Shared", release_id=release.id)
689+
690+
# List jobs with custom expand options
691+
jobs = sdk.jobs.list(folder_path="Shared", expand=["Release"], top=50)
692+
693+
# List jobs within a time range
694+
jobs = sdk.jobs.list(
695+
folder_path="Shared",
696+
creation_time_start="2025-11-12T23:30:00.000Z",
697+
creation_time_end="2025-11-14T00:00:00.000Z"
698+
)
699+
```
700+
"""
701+
spec = self._list_spec(
702+
folder_key=folder_key,
703+
folder_path=folder_path,
704+
release_id=release_id,
705+
creation_time_start=creation_time_start,
706+
creation_time_end=creation_time_end,
707+
top=top,
708+
skip=skip,
709+
expand=expand,
710+
include_test_automation=include_test_automation,
711+
)
712+
response = self.request(
713+
spec.method,
714+
url=spec.endpoint,
715+
params=spec.params,
716+
headers=spec.headers,
717+
)
718+
719+
data = response.json()
720+
jobs = [Job.model_validate(item) for item in data.get("value", [])]
721+
return jobs
722+
723+
@traced(name="jobs_list", run_type="uipath")
724+
async def list_async(
725+
self,
726+
*,
727+
folder_key: Optional[str] = None,
728+
folder_path: Optional[str] = None,
729+
release_id: Optional[int] = None,
730+
creation_time_start: Optional[str] = None,
731+
creation_time_end: Optional[str] = None,
732+
top: int = 25,
733+
skip: int = 0,
734+
expand: Optional[List[str]] = None,
735+
include_test_automation: bool = False,
736+
) -> List[Job]:
737+
"""Asynchronously list jobs from a folder with optional filters and pagination.
738+
739+
Args:
740+
folder_key (Optional[str]): The key of the folder to query jobs from. Override the default one set in the SDK config.
741+
folder_path (Optional[str]): The path of the folder to query jobs from. Override the default one set in the SDK config.
742+
release_id (Optional[int]): Filter by release ID. If not provided, all releases are included. To filter by release name, use processes.get_by_name_async() first to get the ID.
743+
creation_time_start (Optional[str]): Filter jobs created on or after this time (ISO 8601 format, e.g., "2025-11-12T23:30:00.000Z").
744+
creation_time_end (Optional[str]): Filter jobs created on or before this time (ISO 8601 format, e.g., "2025-11-14T00:00:00.000Z").
745+
top (int): Maximum number of jobs to return (OData $top). Defaults to 25.
746+
skip (int): Number of jobs to skip (OData $skip). Defaults to 0.
747+
expand (Optional[List[str]]): List of entities to expand (e.g., ["Robot", "Machine", "Release"]). Defaults to None (no expansion).
748+
include_test_automation (bool): Whether to include test automation processes. Defaults to False.
749+
750+
Returns:
751+
List[Job]: List of jobs matching the filters.
752+
753+
Examples:
754+
```python
755+
import asyncio
756+
from uipath import UiPath
757+
758+
sdk = UiPath()
759+
760+
async def main():
761+
# List first 25 jobs from a folder
762+
jobs = await sdk.jobs.list_async(folder_path="Shared")
763+
764+
# List jobs filtered by release ID with pagination
765+
jobs = await sdk.jobs.list_async(folder_path="Shared", release_id=619188, top=10, skip=0)
766+
767+
# List jobs filtered by release name (get the release first)
768+
release = await sdk.processes.get_by_name_async("llamaindex-agent-no-llm")
769+
jobs = await sdk.jobs.list_async(folder_path="Shared", release_id=release.id)
770+
771+
# List jobs within a time range
772+
jobs = await sdk.jobs.list_async(
773+
folder_path="Shared",
774+
creation_time_start="2025-11-12T23:30:00.000Z",
775+
creation_time_end="2025-11-14T00:00:00.000Z"
776+
)
777+
778+
asyncio.run(main())
779+
```
780+
"""
781+
spec = self._list_spec(
782+
folder_key=folder_key,
783+
folder_path=folder_path,
784+
release_id=release_id,
785+
creation_time_start=creation_time_start,
786+
creation_time_end=creation_time_end,
787+
top=top,
788+
skip=skip,
789+
expand=expand,
790+
include_test_automation=include_test_automation,
791+
)
792+
response = await self.request_async(
793+
spec.method,
794+
url=spec.endpoint,
795+
params=spec.params,
796+
headers=spec.headers,
797+
)
798+
799+
data = response.json()
800+
jobs = [Job.model_validate(item) for item in data.get("value", [])]
801+
return jobs
802+
803+
def _list_spec(
804+
self,
805+
*,
806+
folder_key: Optional[str] = None,
807+
folder_path: Optional[str] = None,
808+
release_id: Optional[int] = None,
809+
creation_time_start: Optional[str] = None,
810+
creation_time_end: Optional[str] = None,
811+
top: int = 25,
812+
skip: int = 0,
813+
expand: Optional[List[str]] = None,
814+
include_test_automation: bool = False,
815+
) -> RequestSpec:
816+
# Build the filter expression
817+
filter_parts = []
818+
819+
# Add time range filters if provided
820+
if creation_time_start is not None:
821+
filter_parts.append(f"CreationTime ge {creation_time_start}")
822+
823+
if creation_time_end is not None:
824+
filter_parts.append(f"CreationTime le {creation_time_end}")
825+
826+
# Add release filter if provided
827+
if release_id is not None:
828+
filter_parts.append(f"Release/Id eq {release_id}")
829+
830+
831+
# Exclude test automation processes by default
832+
if not include_test_automation:
833+
filter_parts.append("ProcessType ne 'TestAutomationProcess'")
834+
835+
# Build the full filter string with proper OData syntax
836+
if filter_parts:
837+
filter_expr = "(" + " and ".join(f"({part})" for part in filter_parts) + ")"
838+
else:
839+
filter_expr = None
840+
841+
# Build expand parameter
842+
expand_expr = ",".join(expand) if expand else None
843+
844+
# Build params dictionary
845+
params = {
846+
"$top": top,
847+
"$orderby": "CreationTime desc",
848+
}
849+
850+
if skip > 0:
851+
params["$skip"] = skip
852+
853+
if filter_expr:
854+
params["$filter"] = filter_expr
855+
856+
if expand_expr:
857+
params["$expand"] = expand_expr
858+
859+
return RequestSpec(
860+
method="GET",
861+
endpoint=Endpoint("/orchestrator_/odata/Jobs"),
862+
params=params,
863+
headers={
864+
**header_folder(folder_key, folder_path),
865+
},
866+
)
867+
644868
@traced(name="jobs_create_attachment", run_type="uipath")
645869
def create_attachment(
646870
self,

src/uipath/_services/processes_service.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .._utils import Endpoint, RequestSpec, header_folder, resource_override
1010
from .._utils.constants import ENV_JOB_KEY, HEADER_JOB_KEY
1111
from ..models.job import Job
12+
from ..models.processes import Process
1213
from ..tracing import traced
1314
from . import AttachmentsService
1415
from ._base_service import BaseService
@@ -160,6 +161,131 @@ async def main():
160161
def custom_headers(self) -> Dict[str, str]:
161162
return self.folder_headers
162163

164+
@traced(name="processes_get_by_name", run_type="uipath")
165+
def get_by_name(
166+
self,
167+
name: str,
168+
*,
169+
folder_key: Optional[str] = None,
170+
folder_path: Optional[str] = None,
171+
) -> Process:
172+
"""Get a release (process) by its exact name.
173+
174+
Args:
175+
name (str): The exact name of the release to retrieve.
176+
folder_key (Optional[str]): The key of the folder to search in. Override the default one set in the SDK config.
177+
folder_path (Optional[str]): The path of the folder to search in. Override the default one set in the SDK config.
178+
179+
Returns:
180+
Process: The process (release) matching the name.
181+
182+
Raises:
183+
ValueError: If the release is not found or multiple releases match.
184+
185+
Examples:
186+
```python
187+
from uipath import UiPath
188+
189+
client = UiPath()
190+
191+
# Get a release by exact name
192+
release = client.processes.get_by_name("llamaindex-agent-no-llm")
193+
print(release.id)
194+
```
195+
"""
196+
spec = RequestSpec(
197+
method="GET",
198+
endpoint=Endpoint("/orchestrator_/odata/Releases"),
199+
params={
200+
"$filter": f"Name eq '{name}'",
201+
"$top": 2, # Get 2 to detect if there are multiple matches
202+
},
203+
headers={
204+
**header_folder(folder_key, folder_path),
205+
},
206+
)
207+
208+
response = self.request(
209+
spec.method,
210+
url=spec.endpoint,
211+
params=spec.params,
212+
headers=spec.headers,
213+
)
214+
215+
data = response.json()
216+
releases = data.get("value", [])
217+
218+
if len(releases) == 0:
219+
raise ValueError(f"Release '{name}' not found")
220+
elif len(releases) > 1:
221+
raise ValueError(f"Multiple releases found with name '{name}'")
222+
223+
return Process.model_validate(releases[0])
224+
225+
@traced(name="processes_get_by_name", run_type="uipath")
226+
async def get_by_name_async(
227+
self,
228+
name: str,
229+
*,
230+
folder_key: Optional[str] = None,
231+
folder_path: Optional[str] = None,
232+
) -> Process:
233+
"""Asynchronously get a release (process) by its exact name.
234+
235+
Args:
236+
name (str): The exact name of the release to retrieve.
237+
folder_key (Optional[str]): The key of the folder to search in. Override the default one set in the SDK config.
238+
folder_path (Optional[str]): The path of the folder to search in. Override the default one set in the SDK config.
239+
240+
Returns:
241+
Process: The process (release) matching the name.
242+
243+
Raises:
244+
ValueError: If the release is not found or multiple releases match.
245+
246+
Examples:
247+
```python
248+
import asyncio
249+
from uipath import UiPath
250+
251+
sdk = UiPath()
252+
253+
async def main():
254+
release = await sdk.processes.get_by_name_async("llamaindex-agent-no-llm")
255+
print(release.id)
256+
257+
asyncio.run(main())
258+
```
259+
"""
260+
spec = RequestSpec(
261+
method="GET",
262+
endpoint=Endpoint("/orchestrator_/odata/Releases"),
263+
params={
264+
"$filter": f"Name eq '{name}'",
265+
"$top": 2, # Get 2 to detect if there are multiple matches
266+
},
267+
headers={
268+
**header_folder(folder_key, folder_path),
269+
},
270+
)
271+
272+
response = await self.request_async(
273+
spec.method,
274+
url=spec.endpoint,
275+
params=spec.params,
276+
headers=spec.headers,
277+
)
278+
279+
data = response.json()
280+
releases = data.get("value", [])
281+
282+
if len(releases) == 0:
283+
raise ValueError(f"Release '{name}' not found")
284+
elif len(releases) > 1:
285+
raise ValueError(f"Multiple releases found with name '{name}'")
286+
287+
return Process.model_validate(releases[0])
288+
163289
def _handle_input_arguments(
164290
self,
165291
input_arguments: Optional[Dict[str, Any]] = None,

0 commit comments

Comments
 (0)