Skip to content

Commit ca3cb2a

Browse files
committed
initial implementation of list connections
1 parent 1abc6a1 commit ca3cb2a

File tree

3 files changed

+568
-7
lines changed

3 files changed

+568
-7
lines changed

src/uipath/_services/connections_service.py

Lines changed: 211 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import json
22
import logging
3-
from typing import Any, Dict
3+
from typing import Any, Dict, List, Optional
4+
5+
from httpx import Response
46

57
from .._config import Config
68
from .._execution_context import ExecutionContext
7-
from .._utils import Endpoint, RequestSpec, infer_bindings
9+
from .._utils import Endpoint, RequestSpec, header_folder, infer_bindings
810
from ..models import Connection, ConnectionToken, EventArguments
911
from ..models.connections import ConnectionTokenType
1012
from ..tracing._traced import traced
1113
from ._base_service import BaseService
14+
from .folder_service import FolderService
1215

1316
logger: logging.Logger = logging.getLogger("uipath")
1417

@@ -20,9 +23,16 @@ class ConnectionsService(BaseService):
2023
and secure token management.
2124
"""
2225

23-
def __init__(self, config: Config, execution_context: ExecutionContext) -> None:
26+
def __init__(
27+
self,
28+
config: Config,
29+
execution_context: ExecutionContext,
30+
folders_service: FolderService,
31+
) -> None:
2432
super().__init__(config=config, execution_context=execution_context)
33+
self._folders_service = folders_service
2534

35+
@infer_bindings(resource_type="connection", name="key")
2636
@traced(
2737
name="connections_retrieve",
2838
run_type="uipath",
@@ -45,6 +55,117 @@ def retrieve(self, key: str) -> Connection:
4555
response = self.request(spec.method, url=spec.endpoint)
4656
return Connection.model_validate(response.json())
4757

58+
@traced(name="connections_list", run_type="uipath")
59+
def list(
60+
self,
61+
*,
62+
name: Optional[str] = None,
63+
folder_path: Optional[str] = None,
64+
folder_key: Optional[str] = None,
65+
connector_key: Optional[str] = None,
66+
skip: Optional[int] = None,
67+
top: Optional[int] = None,
68+
) -> List[Connection]:
69+
"""Lists all connections with optional filtering.
70+
71+
Args:
72+
name: Optional connection name to filter (supports partial matching)
73+
folder_path: Optional folder path for filtering connections
74+
folder_key: Optional folder key (mutually exclusive with folder_path)
75+
connector_key: Optional connector key to filter by specific connector type
76+
skip: Number of records to skip (for pagination)
77+
top: Maximum number of records to return
78+
79+
Returns:
80+
List[Connection]: List of connection instances
81+
82+
Raises:
83+
ValueError: If both folder_path and folder_key are provided, or if
84+
folder_path is provided but cannot be resolved to a folder_key
85+
86+
Examples:
87+
>>> # List all connections
88+
>>> connections = sdk.connections.list()
89+
90+
>>> # Find connections by name
91+
>>> salesforce_conns = sdk.connections.list(name="Salesforce")
92+
93+
>>> # List all Slack connections in Finance folder
94+
>>> connections = sdk.connections.list(
95+
... folder_path="Finance",
96+
... connector_key="uipath-slack"
97+
... )
98+
"""
99+
spec = self._list_spec(
100+
name=name,
101+
folder_path=folder_path,
102+
folder_key=folder_key,
103+
connector_key=connector_key,
104+
skip=skip,
105+
top=top,
106+
)
107+
response = self.request(
108+
spec.method, url=spec.endpoint, params=spec.params, headers=spec.headers
109+
)
110+
111+
return self._parse_and_validate_list_response(response)
112+
113+
@traced(name="connections_list", run_type="uipath")
114+
async def list_async(
115+
self,
116+
*,
117+
name: Optional[str] = None,
118+
folder_path: Optional[str] = None,
119+
folder_key: Optional[str] = None,
120+
connector_key: Optional[str] = None,
121+
skip: Optional[int] = None,
122+
top: Optional[int] = None,
123+
) -> List[Connection]:
124+
"""Asynchronously lists all connections with optional filtering.
125+
126+
Args:
127+
name: Optional connection name to filter (supports partial matching)
128+
folder_path: Optional folder path for filtering connections
129+
folder_key: Optional folder key (mutually exclusive with folder_path)
130+
connector_key: Optional connector key to filter by specific connector type
131+
skip: Number of records to skip (for pagination)
132+
top: Maximum number of records to return
133+
134+
Returns:
135+
List[Connection]: List of connection instances
136+
137+
Raises:
138+
ValueError: If both folder_path and folder_key are provided, or if
139+
folder_path is provided but cannot be resolved to a folder_key
140+
141+
Examples:
142+
>>> # List all connections
143+
>>> connections = await sdk.connections.list_async()
144+
145+
>>> # Find connections by name
146+
>>> salesforce_conns = await sdk.connections.list_async(name="Salesforce")
147+
148+
>>> # List all Slack connections in Finance folder
149+
>>> connections = await sdk.connections.list_async(
150+
... folder_path="Finance",
151+
... connector_key="uipath-slack"
152+
... )
153+
"""
154+
spec = self._list_spec(
155+
name=name,
156+
folder_path=folder_path,
157+
folder_key=folder_key,
158+
connector_key=connector_key,
159+
skip=skip,
160+
top=top,
161+
)
162+
response = await self.request_async(
163+
spec.method, url=spec.endpoint, params=spec.params, headers=spec.headers
164+
)
165+
166+
return self._parse_and_validate_list_response(response)
167+
168+
@infer_bindings(resource_type="connection", name="key")
48169
@traced(
49170
name="connections_retrieve",
50171
run_type="uipath",
@@ -213,3 +334,90 @@ def _retrieve_token_spec(
213334
endpoint=Endpoint(f"/connections_/api/v1/Connections/{key}/token"),
214335
params={"tokenType": token_type.value},
215336
)
337+
338+
def _parse_and_validate_list_response(self, response: Response) -> List[Connection]:
339+
"""Parse and validate the list response from the API.
340+
341+
Handles both OData response format (with 'value' field) and raw list responses.
342+
343+
Args:
344+
response: The HTTP response from the API
345+
346+
Returns:
347+
List of validated Connection instances
348+
"""
349+
data = response.json()
350+
351+
# Handle both OData responses (dict with 'value') and raw list responses
352+
if isinstance(data, dict):
353+
connections_data = data.get("value", [])
354+
elif isinstance(data, list):
355+
connections_data = data
356+
else:
357+
connections_data = []
358+
359+
return [Connection.model_validate(conn) for conn in connections_data]
360+
361+
def _list_spec(
362+
self,
363+
name: Optional[str] = None,
364+
folder_path: Optional[str] = None,
365+
folder_key: Optional[str] = None,
366+
connector_key: Optional[str] = None,
367+
skip: Optional[int] = None,
368+
top: Optional[int] = None,
369+
) -> RequestSpec:
370+
"""Build the request specification for listing connections.
371+
372+
Args:
373+
name: Optional connection name to filter (supports partial matching)
374+
folder_path: Optional folder path for filtering connections
375+
folder_key: Optional folder key (mutually exclusive with folder_path)
376+
connector_key: Optional connector key to filter by specific connector type
377+
skip: Number of records to skip (for pagination)
378+
top: Maximum number of records to return
379+
380+
Returns:
381+
RequestSpec with endpoint, params, and headers configured
382+
383+
Raises:
384+
ValueError: If folder_path is provided but cannot be resolved to a folder_key
385+
"""
386+
# Resolve folder_path to folder_key if needed
387+
resolved_folder_key = folder_key
388+
if not resolved_folder_key and folder_path:
389+
resolved_folder_key = self._folders_service.retrieve_key(
390+
folder_path=folder_path
391+
)
392+
if not resolved_folder_key:
393+
raise ValueError(f"Folder with path '{folder_path}' not found")
394+
395+
# Build OData filters
396+
filters = []
397+
if name:
398+
# Escape single quotes in name for OData
399+
escaped_name = name.replace("'", "''")
400+
filters.append(f"contains(Name, '{escaped_name}')")
401+
if connector_key:
402+
filters.append(f"connector/key eq '{connector_key}'")
403+
404+
params = {}
405+
if filters:
406+
params["$filter"] = " and ".join(filters)
407+
if skip is not None:
408+
params["$skip"] = skip
409+
if top is not None:
410+
params["$top"] = top
411+
412+
# Always expand connector and folder for complete information
413+
params["$expand"] = "connector,folder"
414+
415+
# Use header_folder which handles validation
416+
headers = header_folder(resolved_folder_key, None)
417+
418+
return RequestSpec(
419+
method="GET",
420+
endpoint=Endpoint("/connections_/api/v1/Connections"),
421+
params=params,
422+
headers=headers,
423+
)

src/uipath/_uipath.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,11 @@ def buckets(self) -> BucketsService:
9898

9999
@property
100100
def connections(self) -> ConnectionsService:
101-
return ConnectionsService(self._config, self._execution_context)
101+
if not self._folders_service:
102+
self._folders_service = FolderService(self._config, self._execution_context)
103+
return ConnectionsService(
104+
self._config, self._execution_context, self._folders_service
105+
)
102106

103107
@property
104108
def context_grounding(self) -> ContextGroundingService:

0 commit comments

Comments
 (0)