uipath 2.1.80__py3-none-any.whl → 2.1.81__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of uipath might be problematic. Click here for more details.

@@ -1,22 +1,19 @@
1
1
  import asyncio
2
- import json
3
2
  import os
4
3
  import webbrowser
5
- from socket import AF_INET, SOCK_STREAM, error, socket
6
4
  from typing import Optional
7
- from urllib.parse import urlparse
8
5
 
9
6
  from uipath._cli._auth._auth_server import HTTPServer
10
- from uipath._cli._auth._client_credentials import ClientCredentialsService
11
7
  from uipath._cli._auth._oidc_utils import OidcUtils
12
8
  from uipath._cli._auth._portal_service import (
13
9
  PortalService,
14
- get_tenant_id,
15
- select_tenant,
16
10
  )
17
- from uipath._cli._auth._url_utils import set_force_flag
18
- from uipath._cli._auth._utils import update_auth_file, update_env_file
11
+ from uipath._cli._auth._url_utils import extract_org_tenant, resolve_domain
12
+ from uipath._cli._auth._utils import get_parsed_token_data
19
13
  from uipath._cli._utils._console import ConsoleLogger
14
+ from uipath._services import ExternalApplicationService
15
+ from uipath._utils._auth import update_env_file
16
+ from uipath.models.auth import TokenData
20
17
 
21
18
 
22
19
  class AuthService:
@@ -25,35 +22,20 @@ class AuthService:
25
22
  environment: str,
26
23
  *,
27
24
  force: bool,
28
- client_id: Optional[str],
29
- client_secret: Optional[str],
30
- base_url: Optional[str],
31
- tenant: Optional[str],
32
- scope: Optional[str],
25
+ client_id: Optional[str] = None,
26
+ client_secret: Optional[str] = None,
27
+ base_url: Optional[str] = None,
28
+ tenant: Optional[str] = None,
29
+ scope: Optional[str] = None,
33
30
  ):
34
31
  self._force = force
35
32
  self._console = ConsoleLogger()
36
- self._domain = self._get_domain(environment)
37
33
  self._client_id = client_id
38
34
  self._client_secret = client_secret
39
35
  self._base_url = base_url
40
36
  self._tenant = tenant
37
+ self._domain = resolve_domain(self._base_url, environment, self._force)
41
38
  self._scope = scope
42
- set_force_flag(self._force)
43
-
44
- def _get_domain(self, environment: str) -> str:
45
- # only search env var if not force authentication
46
- if not self._force:
47
- uipath_url = os.getenv("UIPATH_URL")
48
- if uipath_url and environment == "cloud": # "cloud" is the default
49
- parsed_url = urlparse(uipath_url)
50
- if parsed_url.scheme and parsed_url.netloc:
51
- environment = f"{parsed_url.scheme}://{parsed_url.netloc}"
52
- else:
53
- self._console.error(
54
- f"Malformed UIPATH_URL: '{uipath_url}'. Please ensure it includes both scheme and netloc (e.g., 'https://cloud.uipath.com')."
55
- )
56
- return environment
57
39
 
58
40
  def authenticate(self) -> None:
59
41
  if self._client_id and self._client_secret:
@@ -62,103 +44,101 @@ class AuthService:
62
44
 
63
45
  self._authenticate_authorization_code()
64
46
 
65
- def _authenticate_client_credentials(self) -> None:
66
- if not self._base_url:
67
- self._console.error(
68
- "--base-url is required when using client credentials authentication."
69
- )
70
- return
71
- self._console.hint("Using client credentials authentication.")
72
- credentials_service = ClientCredentialsService(self._base_url)
73
- credentials_service.authenticate(
47
+ def _authenticate_client_credentials(self):
48
+ external_app_service = ExternalApplicationService(self._base_url)
49
+ token_data = external_app_service.get_token_data(
74
50
  self._client_id, # type: ignore
75
51
  self._client_secret, # type: ignore
76
52
  self._scope,
77
53
  )
78
54
 
79
- def _authenticate_authorization_code(self) -> None:
80
- with PortalService(self._domain) as portal_service:
81
- if not self._force:
82
- # use existing env vars
83
- if (
84
- os.getenv("UIPATH_URL")
85
- and os.getenv("UIPATH_TENANT_ID")
86
- and os.getenv("UIPATH_ORGANIZATION_ID")
87
- ):
88
- try:
89
- portal_service.ensure_valid_token()
90
- return
91
- except Exception:
92
- self._console.error(
93
- "Authentication token is invalid. Please reauthenticate using the '--force' flag.",
94
- )
95
- auth_url, code_verifier, state = OidcUtils.get_auth_url(self._domain)
96
- webbrowser.open(auth_url, 1)
97
- auth_config = OidcUtils.get_auth_config()
98
-
99
- self._console.link(
100
- "If a browser window did not open, please open the following URL in your browser:",
101
- auth_url,
55
+ organization_name, tenant_name = extract_org_tenant(
56
+ external_app_service._base_url
57
+ )
58
+ if not (organization_name and tenant_name):
59
+ self._console.warning(
60
+ "--base-url should include both organization and tenant, "
61
+ "e.g., 'https://cloud.uipath.com/{organization}/{tenant}'."
102
62
  )
103
- server = HTTPServer(port=auth_config["port"])
104
- token_data = asyncio.run(server.start(state, code_verifier, self._domain))
105
63
 
106
- if not token_data:
107
- self._console.error(
108
- "Authentication failed. Please try again.",
109
- )
64
+ env_vars = {
65
+ "UIPATH_ACCESS_TOKEN": token_data.access_token,
66
+ "UIPATH_URL": external_app_service._base_url,
67
+ "UIPATH_ORGANIZATION_ID": get_parsed_token_data(token_data).get("prt_id"),
68
+ }
69
+
70
+ if tenant_name:
71
+ self._tenant = tenant_name
72
+ with PortalService(
73
+ self._domain, access_token=token_data.access_token
74
+ ) as portal_service:
75
+ tenant_info = portal_service.resolve_tenant_info(self._tenant)
76
+ env_vars["UIPATH_TENANT_ID"] = tenant_info["tenant_id"]
77
+ else:
78
+ self._console.warning("Could not extract tenant from --base-url.")
79
+ update_env_file(env_vars)
80
+
81
+ def _authenticate_authorization_code(self) -> None:
82
+ with PortalService(self._domain) as portal_service:
83
+ if not self._force and self._can_reuse_existing_token(portal_service):
84
+ return
110
85
 
86
+ token_data = self._perform_oauth_flow()
111
87
  portal_service.update_token_data(token_data)
112
- update_auth_file(token_data)
113
- access_token = token_data["access_token"]
114
- update_env_file({"UIPATH_ACCESS_TOKEN": access_token})
115
88
 
116
- tenants_and_organizations = portal_service.get_tenants_and_organizations()
89
+ tenant_info = portal_service.resolve_tenant_info(self._tenant)
90
+ uipath_url = portal_service.build_tenant_url()
117
91
 
118
- if self._tenant:
119
- base_url = get_tenant_id(
120
- self._domain, self._tenant, tenants_and_organizations
92
+ update_env_file(
93
+ {
94
+ "UIPATH_ACCESS_TOKEN": token_data.access_token,
95
+ "UIPATH_URL": uipath_url,
96
+ "UIPATH_TENANT_ID": tenant_info["tenant_id"],
97
+ "UIPATH_ORGANIZATION_ID": tenant_info["organization_id"],
98
+ }
99
+ )
100
+
101
+ try:
102
+ portal_service.enable_studio_web(uipath_url)
103
+ except Exception:
104
+ self._console.error(
105
+ "Could not prepare the environment. Please try again."
121
106
  )
122
- else:
123
- base_url = select_tenant(self._domain, tenants_and_organizations)
124
107
 
108
+ def _can_reuse_existing_token(self, portal_service: PortalService) -> bool:
109
+ if (
110
+ os.getenv("UIPATH_URL")
111
+ and os.getenv("UIPATH_TENANT_ID")
112
+ and os.getenv("UIPATH_ORGANIZATION_ID")
113
+ ):
125
114
  try:
126
- portal_service.post_auth(base_url)
115
+ portal_service.ensure_valid_token()
116
+ return True
127
117
  except Exception:
128
118
  self._console.error(
129
- "Could not prepare the environment. Please try again.",
119
+ "Authentication token is invalid. Please reauthenticate using the '--force' flag."
130
120
  )
121
+ return False
131
122
 
132
- def set_port(self):
133
- def is_port_in_use(target_port: int) -> bool:
134
- with socket(AF_INET, SOCK_STREAM) as s:
135
- try:
136
- s.bind(("localhost", target_port))
137
- s.close()
138
- return False
139
- except error:
140
- return True
123
+ def _perform_oauth_flow(self) -> TokenData:
124
+ auth_url, code_verifier, state = OidcUtils.get_auth_url(self._domain)
125
+ self._open_browser(auth_url)
141
126
 
142
127
  auth_config = OidcUtils.get_auth_config()
143
- port = int(auth_config.get("port", 8104))
144
- port_option_one = int(auth_config.get("portOptionOne", 8104)) # type: ignore
145
- port_option_two = int(auth_config.get("portOptionTwo", 8055)) # type: ignore
146
- port_option_three = int(auth_config.get("portOptionThree", 42042)) # type: ignore
147
- if is_port_in_use(port):
148
- if is_port_in_use(port_option_one):
149
- if is_port_in_use(port_option_two):
150
- if is_port_in_use(port_option_three):
151
- self._console.error(
152
- "All configured ports are in use. Please close applications using ports or configure different ports."
153
- )
154
- else:
155
- port = port_option_three
156
- else:
157
- port = port_option_two
158
- else:
159
- port = port_option_one
160
- auth_config["port"] = port
161
- with open(
162
- os.path.join(os.path.dirname(__file__), "..", "auth_config.json"), "w"
163
- ) as f:
164
- json.dump(auth_config, f)
128
+ server = HTTPServer(port=auth_config["port"])
129
+ token_data = asyncio.run(server.start(state, code_verifier, self._domain))
130
+
131
+ if not token_data:
132
+ self._console.error(
133
+ "Authentication failed. Please try again.",
134
+ )
135
+
136
+ return TokenData.model_validate(token_data)
137
+
138
+ def _open_browser(self, url: str) -> None:
139
+ # Try to open browser. Always print the fallback link.
140
+ webbrowser.open(url, new=1)
141
+ self._console.link(
142
+ "If a browser window did not open, please open the following URL in your browser:",
143
+ url,
144
+ )
@@ -10,17 +10,6 @@ class AuthConfig(TypedDict):
10
10
  scope: str
11
11
 
12
12
 
13
- class TokenData(TypedDict):
14
- """TypedDict for token data structure."""
15
-
16
- access_token: str
17
- refresh_token: str
18
- expires_in: int
19
- token_type: str
20
- scope: str
21
- id_token: str
22
-
23
-
24
13
  class AccessTokenData(TypedDict):
25
14
  """TypedDict for access token data structure."""
26
15
 
@@ -4,6 +4,7 @@ import json
4
4
  import os
5
5
  from urllib.parse import urlencode
6
6
 
7
+ from .._utils._console import ConsoleLogger
7
8
  from ._models import AuthConfig
8
9
  from ._url_utils import build_service_url
9
10
 
@@ -25,6 +26,22 @@ def get_state_param() -> str:
25
26
 
26
27
 
27
28
  class OidcUtils:
29
+ _console = ConsoleLogger()
30
+
31
+ @classmethod
32
+ def _find_free_port(cls, candidates: list[int]):
33
+ from socket import AF_INET, SOCK_STREAM, error, socket
34
+
35
+ def is_free(port: int) -> bool:
36
+ with socket(AF_INET, SOCK_STREAM) as s:
37
+ try:
38
+ s.bind(("localhost", port))
39
+ return True
40
+ except error:
41
+ return False
42
+
43
+ return next((p for p in candidates if is_free(p)), None)
44
+
28
45
  @classmethod
29
46
  def get_auth_config(cls) -> AuthConfig:
30
47
  with open(
@@ -32,7 +49,19 @@ class OidcUtils:
32
49
  ) as f:
33
50
  auth_config = json.load(f)
34
51
 
35
- port = auth_config.get("port", 8104)
52
+ candidates = [
53
+ int(auth_config.get("port", 8104)),
54
+ int(auth_config.get("portOptionOne", 8104)),
55
+ int(auth_config.get("portOptionTwo", 8055)),
56
+ int(auth_config.get("portOptionThree", 42042)),
57
+ ]
58
+
59
+ port = cls._find_free_port(candidates)
60
+ if port is None:
61
+ ports_str = ", ".join(str(p) for p in candidates)
62
+ cls._console.error(
63
+ f"All configured ports ({ports_str}) are in use. Please close applications using these ports or configure different ports."
64
+ )
36
65
 
37
66
  redirect_uri = auth_config["redirect_uri"].replace(
38
67
  "__PY_REPLACE_PORT__", str(port)
@@ -1,24 +1,27 @@
1
- import os
2
1
  import time
3
2
  from typing import Optional
4
3
 
5
4
  import click
6
5
  import httpx
7
6
 
7
+ from ..._utils._auth import update_env_file
8
8
  from ..._utils._ssl_context import get_httpx_client_kwargs
9
+ from ...models.auth import TokenData
10
+ from .._runtime._contracts import UiPathErrorCategory, UiPathRuntimeError
9
11
  from .._utils._console import ConsoleLogger
10
- from ._models import TenantsAndOrganizationInfoResponse, TokenData
12
+ from ._models import (
13
+ OrganizationInfo,
14
+ TenantInfo,
15
+ TenantsAndOrganizationInfoResponse,
16
+ )
11
17
  from ._oidc_utils import OidcUtils
12
- from ._url_utils import build_service_url, get_base_url
18
+ from ._url_utils import build_service_url
13
19
  from ._utils import (
14
20
  get_auth_data,
15
21
  get_parsed_token_data,
16
22
  update_auth_file,
17
- update_env_file,
18
23
  )
19
24
 
20
- console = ConsoleLogger()
21
-
22
25
 
23
26
  class PortalService:
24
27
  """Service for interacting with the UiPath Portal API."""
@@ -29,7 +32,6 @@ class PortalService:
29
32
  selected_tenant: Optional[str] = None
30
33
 
31
34
  _client: Optional[httpx.Client] = None
32
-
33
35
  _tenants_and_organizations: Optional[TenantsAndOrganizationInfoResponse] = None
34
36
 
35
37
  def __init__(
@@ -41,8 +43,15 @@ class PortalService:
41
43
  self.domain = domain
42
44
  self.access_token = access_token
43
45
  self.prt_id = prt_id
46
+ self._console = ConsoleLogger()
47
+ self._tenants_and_organizations = None
48
+ self._client = None
44
49
 
45
- self._client = httpx.Client(**get_httpx_client_kwargs())
50
+ @property
51
+ def client(self) -> httpx.Client:
52
+ if self._client is None:
53
+ self._client = httpx.Client(**get_httpx_client_kwargs())
54
+ return self._client
46
55
 
47
56
  def close(self):
48
57
  """Explicitly close the HTTP client."""
@@ -59,48 +68,35 @@ class PortalService:
59
68
  self.close()
60
69
 
61
70
  def update_token_data(self, token_data: TokenData):
62
- self.access_token = token_data["access_token"]
71
+ self.access_token = token_data.access_token
63
72
  self.prt_id = get_parsed_token_data(token_data).get("prt_id")
73
+ update_auth_file(token_data)
64
74
 
65
- def get_tenants_and_organizations(self) -> TenantsAndOrganizationInfoResponse:
66
- if self._client is None:
67
- raise RuntimeError("HTTP client is not initialized")
75
+ def get_tenants_and_organizations(
76
+ self,
77
+ ):
78
+ if self._tenants_and_organizations is not None:
79
+ return self._tenants_and_organizations
68
80
 
69
81
  url = build_service_url(
70
82
  self.domain,
71
83
  f"/{self.prt_id}/portal_/api/filtering/leftnav/tenantsAndOrganizationInfo",
72
84
  )
73
- response = self._client.get(
85
+ response = self.client.get(
74
86
  url, headers={"Authorization": f"Bearer {self.access_token}"}
75
87
  )
76
88
  if response.status_code < 400:
77
- result = response.json()
78
- self._tenants_and_organizations = result
79
- return result
80
- elif response.status_code == 401:
81
- console.error("Unauthorized")
82
- else:
83
- console.error(
84
- f"Failed to get tenants and organizations: {response.status_code} {response.text}"
85
- )
86
- # Can't reach here, console.error exits, linting
87
- raise Exception("Failed to get tenants")
89
+ self._tenants_and_organizations = response.json()
90
+ return self._tenants_and_organizations
88
91
 
89
- def get_uipath_orchestrator_url(self) -> str:
90
- if self._tenants_and_organizations is None:
91
- self._tenants_and_organizations = self.get_tenants_and_organizations()
92
- organization = self._tenants_and_organizations.get("organization")
93
- if organization is None:
94
- console.error("Organization not found.")
95
- return ""
96
- account_name = organization.get("name")
97
- base_url = get_base_url(self.domain)
98
- return f"{base_url}/{account_name}/{self.selected_tenant}/orchestrator_"
92
+ if response.status_code == 401:
93
+ self._console.error("Unauthorized")
99
94
 
100
- def post_refresh_token_request(self, refresh_token: str) -> TokenData:
101
- if self._client is None:
102
- raise RuntimeError("HTTP client is not initialized")
95
+ self._console.error(
96
+ f"Failed to get tenants and organizations: {response.status_code} {response.text}"
97
+ )
103
98
 
99
+ def refresh_access_token(self, refresh_token: str) -> TokenData: # type: ignore
104
100
  url = build_service_url(self.domain, "/identity_/connect/token")
105
101
  client_id = OidcUtils.get_auth_config().get("client_id")
106
102
 
@@ -111,16 +107,14 @@ class PortalService:
111
107
  }
112
108
 
113
109
  headers = {"Content-Type": "application/x-www-form-urlencoded"}
114
-
115
- response = self._client.post(url, data=data, headers=headers)
110
+ response = self.client.post(url, data=data, headers=headers)
116
111
  if response.status_code < 400:
117
- return response.json()
118
- elif response.status_code == 401:
119
- console.error("Unauthorized")
120
- else:
121
- console.error(f"Failed to refresh token: {response.status_code}")
122
- # Can't reach here, console.error exits, linting
123
- raise Exception("Failed to refresh get token")
112
+ return TokenData.model_validate(response.json())
113
+
114
+ if response.status_code == 401:
115
+ self._console.error("Unauthorized")
116
+
117
+ self._console.error(f"Failed to refresh token: {response.status_code}")
124
118
 
125
119
  def ensure_valid_token(self):
126
120
  """Ensure the access token is valid and refresh it if necessary.
@@ -136,127 +130,101 @@ class PortalService:
136
130
  auth_data = get_auth_data()
137
131
  claims = get_parsed_token_data(auth_data)
138
132
  exp = claims.get("exp")
139
- if exp is not None and float(exp) > time.time():
140
- if not os.getenv("UIPATH_URL"):
141
- tenants_and_organizations = self.get_tenants_and_organizations()
142
- select_tenant(
143
- self.domain if self.domain else "alpha", tenants_and_organizations
144
- )
145
- updated_env_contents = {
146
- "UIPATH_ACCESS_TOKEN": auth_data["access_token"],
147
- }
148
- else:
149
- refresh_token = auth_data.get("refresh_token")
150
- if refresh_token is None:
151
- raise Exception("Refresh token not found")
152
- token_data = self.post_refresh_token_request(refresh_token)
153
- update_auth_file(token_data)
154
-
155
- self.access_token = token_data["access_token"]
156
- self.prt_id = claims.get("prt_id")
157
-
158
- updated_env_contents = {
159
- "UIPATH_ACCESS_TOKEN": token_data["access_token"],
160
- }
161
- if not os.getenv("UIPATH_URL"):
162
- tenants_and_organizations = self.get_tenants_and_organizations()
163
- select_tenant(
164
- self.domain if self.domain else "alpha", tenants_and_organizations
165
- )
166
133
 
167
- update_env_file(updated_env_contents)
134
+ def finalize(token_data: TokenData):
135
+ self.update_token_data(token_data)
136
+ update_env_file({"UIPATH_ACCESS_TOKEN": token_data.access_token})
168
137
 
169
- def post_auth(self, base_url: str) -> None:
170
- if self._client is None:
171
- raise RuntimeError("HTTP client is not initialized")
138
+ if exp is not None and float(exp) > time.time():
139
+ finalize(auth_data)
140
+ return
141
+
142
+ refresh_token = auth_data.refresh_token
143
+ if not refresh_token:
144
+ raise UiPathRuntimeError(
145
+ "REFRESH_TOKEN_MISSING",
146
+ "No refresh token found",
147
+ "The refresh token could not be retrieved. Please retry authenticating.",
148
+ UiPathErrorCategory.SYSTEM,
149
+ )
172
150
 
173
- or_base_url = (
174
- f"{base_url}/orchestrator_"
175
- if base_url
176
- else self.get_uipath_orchestrator_url()
177
- )
151
+ token_data = self.refresh_access_token(refresh_token)
152
+ finalize(token_data)
178
153
 
179
- url_try_enable_first_run = f"{or_base_url}/api/StudioWeb/TryEnableFirstRun"
180
- url_acquire_license = f"{or_base_url}/api/StudioWeb/AcquireLicense"
154
+ def enable_studio_web(self, base_url: str) -> None:
155
+ or_base_url = self.build_orchestrator_url(base_url)
181
156
 
182
- try:
183
- [try_enable_first_run_response, acquire_license_response] = [
184
- self._client.post(
185
- url,
186
- headers={"Authorization": f"Bearer {self.access_token}"},
187
- )
188
- for url in [url_try_enable_first_run, url_acquire_license]
189
- ]
190
- except Exception:
191
- pass
192
-
193
- def has_initialized_auth(self):
194
- try:
195
- auth_data = get_auth_data()
196
- if not auth_data or "access_token" not in auth_data:
197
- return False
198
- if not os.path.exists(".env"):
199
- return False
200
- if not os.getenv("UIPATH_ACCESS_TOKEN"):
201
- return False
202
-
203
- return True
204
- except Exception:
205
- return False
206
-
207
-
208
- def select_tenant(
209
- domain: str, tenants_and_organizations: TenantsAndOrganizationInfoResponse
210
- ):
211
- tenant_names = [tenant["name"] for tenant in tenants_and_organizations["tenants"]]
212
- console.display_options(tenant_names, "Select tenant:")
213
- tenant_idx = (
214
- 0
215
- if len(tenant_names) == 1
216
- else console.prompt("Select tenant number", type=int)
217
- )
218
- tenant_name = tenant_names[tenant_idx]
219
- account_name = tenants_and_organizations["organization"]["name"]
220
- console.info(f"Selected tenant: {click.style(tenant_name, fg='cyan')}")
221
-
222
- base_url = get_base_url(domain)
223
- uipath_url = f"{base_url}/{account_name}/{tenant_name}"
224
-
225
- update_env_file(
226
- {
227
- "UIPATH_URL": uipath_url,
228
- "UIPATH_TENANT_ID": tenants_and_organizations["tenants"][tenant_idx]["id"],
229
- "UIPATH_ORGANIZATION_ID": tenants_and_organizations["organization"]["id"],
230
- }
231
- )
157
+ urls = [
158
+ f"{or_base_url}/api/StudioWeb/TryEnableFirstRun",
159
+ f"{or_base_url}/api/StudioWeb/AcquireLicense",
160
+ ]
232
161
 
233
- return uipath_url
162
+ for url in urls:
163
+ try:
164
+ resp = self.client.post(
165
+ url, headers={"Authorization": f"Bearer {self.access_token}"}
166
+ )
167
+ if resp.status_code >= 400:
168
+ self._console.warning(f"Call to {url} failed: {resp.status_code}")
169
+ except httpx.HTTPError as e:
170
+ self._console.warning(
171
+ f"Exception during enable_studio_web request to {url}: {e}"
172
+ )
234
173
 
174
+ def _set_tenant(self, tenant: TenantInfo, organization: OrganizationInfo):
175
+ self.selected_tenant = tenant["name"]
176
+ return {"tenant_id": tenant["id"], "organization_id": organization["id"]}
235
177
 
236
- def get_tenant_id(
237
- domain: str,
238
- tenant_name: str,
239
- tenants_and_organizations: TenantsAndOrganizationInfoResponse,
240
- ) -> str:
241
- tenants = tenants_and_organizations["tenants"]
178
+ def _select_tenant(self):
179
+ data = self.get_tenants_and_organizations()
180
+ organization = data["organization"]
181
+ tenants = data["tenants"]
242
182
 
243
- matched_tenant = next((t for t in tenants if t["name"] == tenant_name), None)
244
- if not matched_tenant:
245
- console.error(f"Tenant '{tenant_name}' not found.")
246
- raise ValueError(f"Tenant '{tenant_name}' not found.")
183
+ tenant_names = [tenant["name"] for tenant in tenants]
247
184
 
248
- domain = get_base_url(domain)
249
- account_name = tenants_and_organizations["organization"]["name"]
185
+ self._console.display_options(tenant_names, "Select tenant:")
186
+ tenant_idx = (
187
+ 0
188
+ if len(tenant_names) == 1
189
+ else self._console.prompt("Select tenant number", type=int)
190
+ )
250
191
 
251
- base_url = get_base_url(domain)
252
- uipath_url = f"{base_url}/{account_name}/{tenant_name}"
192
+ tenant = data["tenants"][tenant_idx]
253
193
 
254
- update_env_file(
255
- {
256
- "UIPATH_URL": uipath_url,
257
- "UIPATH_TENANT_ID": matched_tenant["id"],
258
- "UIPATH_ORGANIZATION_ID": tenants_and_organizations["organization"]["id"],
259
- }
260
- )
194
+ self._console.info(f"Selected tenant: {click.style(tenant['name'], fg='cyan')}")
195
+ return self._set_tenant(tenant, organization)
261
196
 
262
- return uipath_url
197
+ def _retrieve_tenant(
198
+ self,
199
+ tenant_name: str,
200
+ ):
201
+ data = self.get_tenants_and_organizations()
202
+ organization = data["organization"]
203
+ tenants = data["tenants"]
204
+
205
+ tenant = next((t for t in tenants if t["name"] == tenant_name), None)
206
+ if not tenant:
207
+ self._console.error(f"Tenant '{tenant_name}' not found.")
208
+
209
+ return self._set_tenant(tenant, organization) # type: ignore
210
+
211
+ def resolve_tenant_info(self, tenant: Optional[str] = None):
212
+ if tenant:
213
+ return self._retrieve_tenant(tenant)
214
+ return self._select_tenant()
215
+
216
+ def build_tenant_url(self) -> str:
217
+ data = self.get_tenants_and_organizations()
218
+ organization_name = data["organization"]["name"]
219
+ return f"{self.domain}/{organization_name}/{self.selected_tenant}"
220
+
221
+ def build_orchestrator_url(self, base_url: str) -> str:
222
+ if base_url:
223
+ return f"{base_url}/orchestrator_"
224
+ data = self.get_tenants_and_organizations()
225
+ organization = data.get("organization")
226
+ if organization is None:
227
+ self._console.error("Organization not found.")
228
+ return ""
229
+ organization_name = organization.get("name")
230
+ return f"{self.domain}/{organization_name}/{self.selected_tenant}/orchestrator_"
@@ -1,51 +1,85 @@
1
1
  import os
2
+ from typing import Optional, Tuple
2
3
  from urllib.parse import urlparse
3
4
 
4
- ignore_env_var = False
5
+ from .._utils._console import ConsoleLogger
5
6
 
7
+ console = ConsoleLogger()
6
8
 
7
- def get_base_url(domain: str) -> str:
8
- """Get the base URL for UiPath services.
9
+
10
+ def resolve_domain(
11
+ base_url: Optional[str], environment: Optional[str], force: bool = False
12
+ ) -> str:
13
+ """Resolve the UiPath domain, giving priority to base_url when valid.
9
14
 
10
15
  Args:
11
- domain: Either a domain name (e.g., 'cloud', 'alpha') or a full URL from UIPATH_URL
16
+ base_url: The base URL explicitly provided.
17
+ environment: The environment name (e.g., 'alpha', 'staging', 'cloud').
18
+ force: Whether to ignore UIPATH_URL from environment variables.
12
19
 
13
20
  Returns:
14
- The base URL to use for UiPath services
21
+ A valid base URL for UiPath services.
15
22
  """
16
- global ignore_env_var
17
-
18
- if not ignore_env_var:
19
- # If UIPATH_URL is set and domain is 'cloud' (default), use the base from UIPATH_URL
23
+ if not force:
24
+ # If UIPATH_URL is set, prefer its domain
20
25
  uipath_url = os.getenv("UIPATH_URL")
21
- if uipath_url and domain == "cloud":
22
- parsed_url = urlparse(uipath_url)
23
- return f"{parsed_url.scheme}://{parsed_url.netloc}"
24
-
25
- # If domain is already a full URL, use it directly
26
- if domain.startswith("http"):
27
- return domain
28
-
29
- # Otherwise, construct the URL using the domain
30
- return f"https://{domain if domain else 'cloud'}.uipath.com"
26
+ if uipath_url and environment == "cloud":
27
+ parsed = urlparse(uipath_url)
28
+ if parsed.scheme and parsed.netloc:
29
+ domain = f"{parsed.scheme}://{parsed.netloc}"
30
+ return domain
31
+ else:
32
+ console.error(
33
+ f"Malformed UIPATH_URL: '{uipath_url}'. "
34
+ "Please ensure it includes scheme and netloc (e.g., 'https://cloud.uipath.com')."
35
+ )
31
36
 
37
+ # If base_url is a real URL, prefer it
38
+ if base_url and base_url.startswith("http"):
39
+ parsed = urlparse(base_url)
40
+ domain = f"{parsed.scheme}://{parsed.netloc}"
41
+ if domain:
42
+ return domain
32
43
 
33
- def set_force_flag(force: bool):
34
- global ignore_env_var
35
- ignore_env_var = force
44
+ # Otherwise, fall back to environment
45
+ return f"https://{environment or 'cloud'}.uipath.com"
36
46
 
37
47
 
38
48
  def build_service_url(domain: str, path: str) -> str:
39
49
  """Build a service URL by combining the base URL with a path.
40
50
 
41
51
  Args:
42
- domain: Either a domain name or full URL
52
+ domain: The domain name
43
53
  path: The path to append (should start with /)
44
54
 
45
55
  Returns:
46
56
  The complete service URL
47
57
  """
48
- base_url = get_base_url(domain)
49
- # Remove trailing slash from base_url to avoid double slashes
50
- base_url = base_url.rstrip("/")
51
- return f"{base_url}{path}"
58
+ return f"{domain}{path}"
59
+
60
+
61
+ def extract_org_tenant(uipath_url: str) -> Tuple[Optional[str], Optional[str]]:
62
+ """Extract organization and tenant from a UiPath URL.
63
+
64
+ Accepts values like:
65
+ - https://cloud.uipath.com/myOrg/myTenant
66
+ - https://alpha.uipath.com/myOrg/myTenant/anything_else
67
+ - cloud.uipath.com/myOrg/myTenant (scheme will be assumed https)
68
+
69
+ Args:
70
+ uipath_url: The UiPath URL to parse
71
+
72
+ Returns:
73
+ A tuple of (organization, tenant) where:
74
+ - organization: 'myOrg' or None
75
+ - tenant: 'myTenant' or None
76
+
77
+ Example:
78
+ >>> extract_org_tenant('https://cloud.uipath.com/myOrg/myTenant')
79
+ ('myOrg', 'myTenant')
80
+ """
81
+ parsed = urlparse(uipath_url if "://" in uipath_url else f"https://{uipath_url}")
82
+ parts = [p for p in parsed.path.split("/") if p]
83
+ org = parts[0] if len(parts) >= 1 else None
84
+ tenant = parts[1] if len(parts) >= 2 else None
85
+ return org, tenant
@@ -1,51 +1,28 @@
1
- import base64
2
1
  import json
3
2
  import os
4
3
  from pathlib import Path
5
4
  from typing import Optional
6
5
 
7
- from ._models import AccessTokenData, TokenData
6
+ from ..._utils._auth import parse_access_token
7
+ from ...models.auth import TokenData
8
+ from ._models import AccessTokenData
8
9
 
9
10
 
10
11
  def update_auth_file(token_data: TokenData):
11
12
  os.makedirs(Path.cwd() / ".uipath", exist_ok=True)
12
13
  auth_file = Path.cwd() / ".uipath" / ".auth.json"
13
14
  with open(auth_file, "w") as f:
14
- json.dump(token_data, f)
15
+ json.dump(token_data.model_dump(exclude_none=True), f)
15
16
 
16
17
 
17
18
  def get_auth_data() -> TokenData:
18
19
  auth_file = Path.cwd() / ".uipath" / ".auth.json"
19
20
  if not auth_file.exists():
20
21
  raise Exception("No authentication file found")
21
- return json.load(open(auth_file))
22
-
23
-
24
- def parse_access_token(access_token: str) -> AccessTokenData:
25
- token_parts = access_token.split(".")
26
- if len(token_parts) < 2:
27
- raise Exception("Invalid access token")
28
- payload = base64.urlsafe_b64decode(
29
- token_parts[1] + "=" * (-len(token_parts[1]) % 4)
30
- )
31
- return json.loads(payload)
22
+ return TokenData.model_validate(json.load(open(auth_file)))
32
23
 
33
24
 
34
25
  def get_parsed_token_data(token_data: Optional[TokenData] = None) -> AccessTokenData:
35
26
  if not token_data:
36
27
  token_data = get_auth_data()
37
- return parse_access_token(token_data["access_token"])
38
-
39
-
40
- def update_env_file(env_contents):
41
- env_path = Path.cwd() / ".env"
42
- if env_path.exists():
43
- with open(env_path, "r") as f:
44
- for line in f:
45
- if "=" in line:
46
- key, value = line.strip().split("=", 1)
47
- if key not in env_contents:
48
- env_contents[key] = value
49
- lines = [f"{key}={value}\n" for key, value in env_contents.items()]
50
- with open(env_path, "w") as f:
51
- f.writelines(lines)
28
+ return parse_access_token(token_data.access_token)
@@ -12,19 +12,19 @@ from ..spinner import Spinner
12
12
  def environment_options(function):
13
13
  function = click.option(
14
14
  "--alpha",
15
- "domain",
15
+ "environment",
16
16
  flag_value="alpha",
17
17
  help="Use alpha environment",
18
18
  )(function)
19
19
  function = click.option(
20
20
  "--staging",
21
- "domain",
21
+ "environment",
22
22
  flag_value="staging",
23
23
  help="Use staging environment",
24
24
  )(function)
25
25
  function = click.option(
26
26
  "--cloud",
27
- "domain",
27
+ "environment",
28
28
  flag_value="cloud",
29
29
  default=True,
30
30
  help="Use production environment",
uipath/_cli/cli_auth.py CHANGED
@@ -48,7 +48,7 @@ console = ConsoleLogger()
48
48
  )
49
49
  @track
50
50
  def auth(
51
- domain,
51
+ environment: str,
52
52
  force: bool = False,
53
53
  client_id: Optional[str] = None,
54
54
  client_secret: Optional[str] = None,
@@ -70,7 +70,7 @@ def auth(
70
70
  - Set UIPATH_DISABLE_SSL_VERIFY to disable SSL verification (not recommended)
71
71
  """
72
72
  auth_service = AuthService(
73
- domain,
73
+ environment=environment,
74
74
  force=force,
75
75
  client_id=client_id,
76
76
  client_secret=client_secret,
@@ -7,6 +7,7 @@ from .connections_service import ConnectionsService
7
7
  from .context_grounding_service import ContextGroundingService
8
8
  from .documents_service import DocumentsService
9
9
  from .entities_service import EntitiesService
10
+ from .external_application_service import ExternalApplicationService
10
11
  from .folder_service import FolderService
11
12
  from .jobs_service import JobsService
12
13
  from .llm_gateway_service import UiPathLlmChatService, UiPathOpenAIService
@@ -29,4 +30,5 @@ __all__ = [
29
30
  "UiPathLlmChatService",
30
31
  "FolderService",
31
32
  "EntitiesService",
33
+ "ExternalApplicationService",
32
34
  ]
@@ -1,22 +1,26 @@
1
- from typing import Optional, cast
1
+ from os import environ as env
2
+ from typing import Optional
2
3
  from urllib.parse import urlparse
3
4
 
4
5
  import httpx
6
+ from httpx import HTTPStatusError, Request
5
7
 
6
- from ..._utils._ssl_context import get_httpx_client_kwargs
7
- from .._utils._console import ConsoleLogger
8
- from ._models import TokenData
9
- from ._utils import parse_access_token, update_env_file
8
+ from .._utils._ssl_context import get_httpx_client_kwargs
9
+ from .._utils.constants import ENV_BASE_URL
10
+ from ..models.auth import TokenData
11
+ from ..models.exceptions import EnrichedException
10
12
 
11
- console = ConsoleLogger()
12
13
 
13
-
14
- class ClientCredentialsService:
14
+ class ExternalApplicationService:
15
15
  """Service for client credentials authentication flow."""
16
16
 
17
- def __init__(self, base_url: str):
18
- self._base_url = base_url
19
- self._domain = self._extract_domain_from_base_url(base_url)
17
+ def __init__(self, base_url: Optional[str]):
18
+ if not (resolved_base_url := (base_url or env.get(ENV_BASE_URL))):
19
+ raise ValueError(
20
+ "Base URL must be set either via constructor or the BASE_URL environment variable."
21
+ )
22
+ self._base_url = resolved_base_url
23
+ self._domain = self._extract_environment_from_base_url(self._base_url)
20
24
 
21
25
  def get_token_url(self) -> str:
22
26
  """Get the token URL for the specified domain."""
@@ -40,7 +44,7 @@ class ClientCredentialsService:
40
44
  """
41
45
  return hostname == domain or hostname.endswith(f".{domain}")
42
46
 
43
- def _extract_domain_from_base_url(self, base_url: str) -> str:
47
+ def _extract_environment_from_base_url(self, base_url: str) -> str:
44
48
  """Extract domain from base URL.
45
49
 
46
50
  Args:
@@ -70,9 +74,9 @@ class ClientCredentialsService:
70
74
  # Default to cloud if parsing fails
71
75
  return "cloud"
72
76
 
73
- def authenticate(
77
+ def get_token_data(
74
78
  self, client_id: str, client_secret: str, scope: Optional[str] = "OR.Execution"
75
- ) -> None:
79
+ ) -> TokenData:
76
80
  """Authenticate using client credentials flow.
77
81
 
78
82
  Args:
@@ -81,7 +85,7 @@ class ClientCredentialsService:
81
85
  scope: The scope for the token (default: OR.Execution)
82
86
 
83
87
  Returns:
84
- Token data if successful, None otherwise
88
+ Token data if successful
85
89
  """
86
90
  token_url = self.get_token_url()
87
91
 
@@ -97,49 +101,40 @@ class ClientCredentialsService:
97
101
  response = client.post(token_url, data=data)
98
102
  match response.status_code:
99
103
  case 200:
100
- token_data = response.json()
101
- token_data = cast(
102
- TokenData,
103
- {
104
- "access_token": token_data["access_token"],
105
- "token_type": token_data.get("token_type", "Bearer"),
106
- "expires_in": token_data.get("expires_in", 3600),
107
- "scope": token_data.get("scope", scope),
108
- "refresh_token": "",
109
- "id_token": "",
110
- },
111
- )
112
- self._setup_environment(token_data)
104
+ return TokenData.model_validate(response.json())
113
105
  case 400:
114
- console.error(
115
- "Invalid client credentials or request parameters."
106
+ raise EnrichedException(
107
+ HTTPStatusError(
108
+ message="Invalid client credentials or request parameters.",
109
+ request=Request(
110
+ data=data, url=token_url, method="post"
111
+ ),
112
+ response=response,
113
+ )
116
114
  )
117
115
  case 401:
118
- console.error("Unauthorized: Invalid client credentials.")
116
+ raise EnrichedException(
117
+ HTTPStatusError(
118
+ message="Unauthorized: Invalid client credentials.",
119
+ request=Request(
120
+ data=data, url=token_url, method="post"
121
+ ),
122
+ response=response,
123
+ )
124
+ )
119
125
  case _:
120
- console.error(
121
- f"Authentication failed: {response.status_code} - {response.text}"
126
+ raise EnrichedException(
127
+ HTTPStatusError(
128
+ message=f"Authentication failed with unexpected status: {response.status_code}",
129
+ request=Request(
130
+ data=data, url=token_url, method="post"
131
+ ),
132
+ response=response,
133
+ )
122
134
  )
123
-
135
+ except EnrichedException:
136
+ raise
124
137
  except httpx.RequestError as e:
125
- console.error(f"Network error during authentication: {e}")
138
+ raise Exception(f"Network error during authentication: {e}") from e
126
139
  except Exception as e:
127
- console.error(f"Unexpected error during authentication: {e}")
128
-
129
- def _setup_environment(self, token_data: TokenData):
130
- """Setup environment variables for client credentials authentication.
131
-
132
- Args:
133
- token_data: The token data from authentication
134
- base_url: The base URL for the UiPath instance
135
- """
136
- parsed_access_token = parse_access_token(token_data["access_token"])
137
-
138
- env_vars = {
139
- "UIPATH_ACCESS_TOKEN": token_data["access_token"],
140
- "UIPATH_URL": self._base_url,
141
- "UIPATH_ORGANIZATION_ID": parsed_access_token.get("prt_id", ""),
142
- "UIPATH_TENANT_ID": "",
143
- }
144
-
145
- update_env_file(env_vars)
140
+ raise Exception(f"Unexpected error during authentication: {e}") from e
uipath/_uipath.py CHANGED
@@ -1,4 +1,3 @@
1
- from os import environ as env
2
1
  from typing import Optional
3
2
 
4
3
  from pydantic import ValidationError
@@ -23,11 +22,7 @@ from ._services import (
23
22
  UiPathOpenAIService,
24
23
  )
25
24
  from ._utils import setup_logging
26
- from ._utils.constants import (
27
- ENV_BASE_URL,
28
- ENV_UIPATH_ACCESS_TOKEN,
29
- ENV_UNATTENDED_USER_ACCESS_TOKEN,
30
- )
25
+ from ._utils._auth import resolve_config
31
26
  from .models.errors import BaseUrlMissingError, SecretMissingError
32
27
 
33
28
 
@@ -37,19 +32,18 @@ class UiPath:
37
32
  *,
38
33
  base_url: Optional[str] = None,
39
34
  secret: Optional[str] = None,
35
+ client_id: Optional[str] = None,
36
+ client_secret: Optional[str] = None,
37
+ scope: Optional[str] = None,
40
38
  debug: bool = False,
41
39
  ) -> None:
42
- base_url_value = base_url or env.get(ENV_BASE_URL)
43
- secret_value = (
44
- secret
45
- or env.get(ENV_UNATTENDED_USER_ACCESS_TOKEN)
46
- or env.get(ENV_UIPATH_ACCESS_TOKEN)
47
- )
48
-
49
40
  try:
41
+ base_url, secret = resolve_config(
42
+ base_url, secret, client_id, client_secret, scope
43
+ )
50
44
  self._config = Config(
51
- base_url=base_url_value, # type: ignore
52
- secret=secret_value, # type: ignore
45
+ base_url=base_url,
46
+ secret=secret,
53
47
  )
54
48
  except ValidationError as e:
55
49
  for error in e.errors():
uipath/_utils/_auth.py ADDED
@@ -0,0 +1,82 @@
1
+ import base64
2
+ import json
3
+ from os import environ as env
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ from .._services import ExternalApplicationService
8
+ from .constants import (
9
+ ENV_BASE_URL,
10
+ ENV_UIPATH_ACCESS_TOKEN,
11
+ ENV_UNATTENDED_USER_ACCESS_TOKEN,
12
+ )
13
+
14
+
15
+ def parse_access_token(access_token: str):
16
+ token_parts = access_token.split(".")
17
+ if len(token_parts) < 2:
18
+ raise Exception("Invalid access token")
19
+ payload = base64.urlsafe_b64decode(
20
+ token_parts[1] + "=" * (-len(token_parts[1]) % 4)
21
+ )
22
+ return json.loads(payload)
23
+
24
+
25
+ def update_env_file(env_contents):
26
+ env_path = Path.cwd() / ".env"
27
+ if env_path.exists():
28
+ with open(env_path, "r") as f:
29
+ for line in f:
30
+ if "=" in line:
31
+ key, value = line.strip().split("=", 1)
32
+ if key not in env_contents:
33
+ env_contents[key] = value
34
+ lines = [f"{key}={value}\n" for key, value in env_contents.items()]
35
+ with open(env_path, "w") as f:
36
+ f.writelines(lines)
37
+
38
+
39
+ def _has_valid_client_credentials(
40
+ client_id: Optional[str], client_secret: Optional[str]
41
+ ) -> bool:
42
+ if bool(client_id) != bool(client_secret):
43
+ raise ValueError(
44
+ "Both client_id and client_secret must be provided together for Client Credentials Authentication."
45
+ )
46
+ return bool(client_id and client_secret)
47
+
48
+
49
+ def resolve_config(
50
+ base_url: Optional[str],
51
+ secret: Optional[str],
52
+ client_id: Optional[str],
53
+ client_secret: Optional[str],
54
+ scope: Optional[str],
55
+ ):
56
+ if _has_valid_client_credentials(client_id, client_secret):
57
+ external_app_service = ExternalApplicationService(base_url)
58
+ token_data = external_app_service.get_token_data(
59
+ client_id, # type: ignore
60
+ client_secret, # type: ignore
61
+ scope,
62
+ )
63
+ parsed_access_token = parse_access_token(token_data.access_token)
64
+
65
+ env_vars = {
66
+ "UIPATH_ACCESS_TOKEN": token_data.access_token,
67
+ "UIPATH_URL": external_app_service._base_url,
68
+ "UIPATH_ORGANIZATION_ID": parsed_access_token.get("prt_id", ""),
69
+ }
70
+
71
+ update_env_file(env_vars)
72
+ return external_app_service._base_url, token_data.access_token
73
+
74
+ base_url_value = base_url or env.get(ENV_BASE_URL)
75
+
76
+ secret_value = (
77
+ secret
78
+ or env.get(ENV_UNATTENDED_USER_ACCESS_TOKEN)
79
+ or env.get(ENV_UIPATH_ACCESS_TOKEN)
80
+ )
81
+
82
+ return base_url_value, secret_value
uipath/models/auth.py ADDED
@@ -0,0 +1,14 @@
1
+ from typing import Optional
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class TokenData(BaseModel):
7
+ """Pydantic model for token data structure."""
8
+
9
+ access_token: str
10
+ refresh_token: Optional[str] = None
11
+ expires_in: Optional[int] = None
12
+ token_type: Optional[str] = None
13
+ scope: Optional[str] = None
14
+ id_token: Optional[str] = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: uipath
3
- Version: 2.1.80
3
+ Version: 2.1.81
4
4
  Summary: Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools.
5
5
  Project-URL: Homepage, https://uipath.com
6
6
  Project-URL: Repository, https://github.com/UiPath/uipath-python
@@ -2,11 +2,11 @@ uipath/__init__.py,sha256=IaeKItOOQXMa95avueJ3dAq-XcRHyZVNjcCGwlSB000,634
2
2
  uipath/_config.py,sha256=pi3qxPzDTxMEstj_XkGOgKJqD6RTHHv7vYv8sS_-d5Q,92
3
3
  uipath/_execution_context.py,sha256=Qo8VMUFgtiL-40KsZrvul5bGv1CRERle_fCw1ORCggY,2374
4
4
  uipath/_folder_context.py,sha256=D-bgxdwpwJP4b_QdVKcPODYh15kMDrOar2xNonmMSm4,1861
5
- uipath/_uipath.py,sha256=xvBwczfjrvrB96ZK7wo0IgDcPDQWuEqu4dioH9CKd4k,5021
5
+ uipath/_uipath.py,sha256=mhtdu36-A-2WhPGNFFrTmQWbE07Bn4IV_Vrqgf6kuVM,4900
6
6
  uipath/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  uipath/_cli/README.md,sha256=GLtCfbeIKZKNnGTCsfSVqRQ27V1btT1i2bSAyW_xZl4,474
8
8
  uipath/_cli/__init__.py,sha256=2RUgXYd8uJaYjA67xWb0w4IZuBmZoY8G1ccNmEQk9oM,2343
9
- uipath/_cli/cli_auth.py,sha256=ZEA0Fwoo77Ez9ctpRAIq7sbAwj8F4OouAbMp1g1OvjM,2601
9
+ uipath/_cli/cli_auth.py,sha256=CzetSRqSUvMs02PtI4w5Vi_0fv_ETA307bB2vXalWzY,2628
10
10
  uipath/_cli/cli_deploy.py,sha256=KPCmQ0c_NYD5JofSDao5r6QYxHshVCRxlWDVnQvlp5w,645
11
11
  uipath/_cli/cli_dev.py,sha256=nEfpjw1PZ72O6jmufYWVrueVwihFxDPOeJakdvNHdOA,2146
12
12
  uipath/_cli/cli_eval.py,sha256=oOMywGSUrHDQ1W_54ccbekzCeduPf-KHRyu_r0Dezd0,5444
@@ -21,13 +21,12 @@ uipath/_cli/cli_run.py,sha256=1FKv20EjxrrP1I5rNSnL_HzbWtOAIMjB3M--4RPA_Yo,3709
21
21
  uipath/_cli/middlewares.py,sha256=0D9a-wphyetnH9T97F08o7-1OKWF1lMweFHHAR0xiOw,4979
22
22
  uipath/_cli/spinner.py,sha256=bS-U_HA5yne11ejUERu7CQoXmWdabUD2bm62EfEdV8M,1107
23
23
  uipath/_cli/_auth/_auth_server.py,sha256=22km0F1NFNXgyLbvtAx3ssiQlVGHroLdtDCWEqiCiMg,7106
24
- uipath/_cli/_auth/_auth_service.py,sha256=Thtp2wXZQAHqossPPXuP6sAEe4Px9xThhZutMECRrdU,6386
25
- uipath/_cli/_auth/_client_credentials.py,sha256=VIyzgnGHSZxJXG8kSFIcCH0n2K2zep2SYcb5q1nBFcs,5408
26
- uipath/_cli/_auth/_models.py,sha256=sYMCfvmprIqnZxStlD_Dxx2bcxgn0Ri4D7uwemwkcNg,948
27
- uipath/_cli/_auth/_oidc_utils.py,sha256=j9VCXai_dM9PF1WtoWEsXETvrWUjuTgU-dCJ3oRudyg,2394
28
- uipath/_cli/_auth/_portal_service.py,sha256=vP82BhjA2D6E6y2wDcXqO1cM4thhPl6DeGsnNXCBdhA,9144
29
- uipath/_cli/_auth/_url_utils.py,sha256=Vr-eYbwW_ltmwkULNEAbn6LNsMHnT4uT1n_1zlqD4Ss,1503
30
- uipath/_cli/_auth/_utils.py,sha256=9nb76xe5XmDZ0TAncp-_1SKqL6FdwRi9eS3C2noN1lY,1591
24
+ uipath/_cli/_auth/_auth_service.py,sha256=7UykDSZWhe8B152rnoS1wF0XkAkhAipfDwEtT2cW7rk,5245
25
+ uipath/_cli/_auth/_models.py,sha256=kWhqd5FqBo_oi3DW2x1IaJHsEloXq0I24f-pX5zb_O4,753
26
+ uipath/_cli/_auth/_oidc_utils.py,sha256=SLiOOAEdVwmPKRxIhFkARnNbrf01tvJxBAxa6MF9HHY,3458
27
+ uipath/_cli/_auth/_portal_service.py,sha256=sq3Ls7v8Q1gQWbYlE37_p82gLYPhC_cXJRTuIxMsCPU,7997
28
+ uipath/_cli/_auth/_url_utils.py,sha256=MuMYesMQDgetLBzkwd19dPxw3fctys7EVpByDUQMyLE,2844
29
+ uipath/_cli/_auth/_utils.py,sha256=To4Ara_UF4g7nzUfKqFA11lTjhQWIZWNm4xwa5nNKmU,896
31
30
  uipath/_cli/_auth/auth_config.json,sha256=o8J5BBFwiEtjZLHpJ_64lvnTeYeRIHaJ-Bhg0QvcUX8,521
32
31
  uipath/_cli/_auth/index.html,sha256=uGK0CDTP8Rys_p4O_Pbd2x4tz0frKNVcumjrXnal5Nc,22814
33
32
  uipath/_cli/_auth/localhost.crt,sha256=oGl9oLLOiouHubAt39B4zEfylFvKEtbtr_43SIliXJc,1226
@@ -73,7 +72,7 @@ uipath/_cli/_templates/.rels.template,sha256=-fTcw7OA1AcymHr0LzBqbMAAtzZTRXLTNa_
73
72
  uipath/_cli/_templates/[Content_Types].xml.template,sha256=bYsKDz31PkIF9QksjgAY_bqm57YC8U_owsZeNZAiBxQ,584
74
73
  uipath/_cli/_templates/main.py.template,sha256=QB62qX5HKDbW4lFskxj7h9uuxBITnTWqu_DE6asCwcU,476
75
74
  uipath/_cli/_templates/package.nuspec.template,sha256=YZyLc-u_EsmIoKf42JsLQ55OGeFmb8VkIU2VF7DFbtw,359
76
- uipath/_cli/_utils/_common.py,sha256=CzhhkIRfCuQ1-5HLDtjzOyt8KFs1jm6wzrBeU_v2B7c,3329
75
+ uipath/_cli/_utils/_common.py,sha256=bYgqIdlxxlB_4s8BlZsfGbSdbfm93MFtOj5ydptbkPI,3344
77
76
  uipath/_cli/_utils/_console.py,sha256=scvnrrFoFX6CE451K-PXKV7UN0DUkInbOtDZ5jAdPP0,10070
78
77
  uipath/_cli/_utils/_constants.py,sha256=rS8lQ5Nzull8ytajK6lBsz398qiCp1REoAwlHtyBwF0,1415
79
78
  uipath/_cli/_utils/_debug.py,sha256=zamzIR4VgbdKADAE4gbmjxDsbgF7wvdr7C5Dqp744Oc,1739
@@ -90,7 +89,7 @@ uipath/_events/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
89
  uipath/_events/_event_bus.py,sha256=4-VzstyX69cr7wT1EY7ywp-Ndyz2CyemD3Wk_-QmRpo,5496
91
90
  uipath/_events/_events.py,sha256=EzDfzpVm-EIH27Onad5mo8Go6-WB3S6_-6zZQ7qV58w,1811
92
91
  uipath/_resources/AGENTS.md,sha256=YWhWuX9XIbyVhVT3PnPc4Of3_q6bsNJcuzYu3N8f_Ug,25850
93
- uipath/_services/__init__.py,sha256=eYZElMfYDQTQU6MMjIke5J-GGT9pzLD5QfbwLiTQkEE,1037
92
+ uipath/_services/__init__.py,sha256=_LNy4u--VlhVtTO66bULbCoBjyJBTuyh9jnzjWrv-h4,1140
94
93
  uipath/_services/_base_service.py,sha256=x9-9jhPzn9Z16KRdFHhJNvV-FZHvTniMsDfxlS4Cutk,5782
95
94
  uipath/_services/actions_service.py,sha256=2RPMR-hFMsOlqEyjIf3aF7-lrf57jdrSD0pBjj0Kyko,16040
96
95
  uipath/_services/api_client.py,sha256=kGm04ijk9AOEQd2BMxvQg-2QoB8dmyoDwFFDPyutAGw,1966
@@ -101,12 +100,14 @@ uipath/_services/connections_service.py,sha256=IqhKdRYwNZlRsDL2vY7gyl5nAiYaK1zvj
101
100
  uipath/_services/context_grounding_service.py,sha256=Pjx-QQQEiSKD-hY6ityj3QUSALN3fIcKLLHr_NZ0d_g,37117
102
101
  uipath/_services/documents_service.py,sha256=UnFS8EpOZ_Ng2TZk3OiJJ3iNANvFs7QxuoG_v-lQj6c,24815
103
102
  uipath/_services/entities_service.py,sha256=QKCLE6wRgq3HZraF-M2mljy-8il4vsNHrQhUgkewVVk,14028
103
+ uipath/_services/external_application_service.py,sha256=gZhnGgLn7ZYUZzZF7AumB14QEPanVY-D_02FqEcAFtw,5478
104
104
  uipath/_services/folder_service.py,sha256=9JqgjKhWD-G_KUnfUTP2BADxL6OK9QNZsBsWZHAULdE,2749
105
105
  uipath/_services/jobs_service.py,sha256=tTZNsdZKN3uP7bWPQyBCpJeQxTfuOWbKYOR4L-_yJo4,32736
106
106
  uipath/_services/llm_gateway_service.py,sha256=oFBKSYbZqujGHDuM3A72S3J7mHn9kWjNTgcE3U0c24Y,24411
107
107
  uipath/_services/processes_service.py,sha256=O_uHgQ1rnwiV5quG0OQqabAnE6Rf6cWrMENYY2jKWt8,8585
108
108
  uipath/_services/queues_service.py,sha256=VaG3dWL2QK6AJBOLoW2NQTpkPfZjsqsYPl9-kfXPFzA,13534
109
109
  uipath/_utils/__init__.py,sha256=VdcpnENJIa0R6Y26NoxY64-wUVyvb4pKfTh1wXDQeMk,526
110
+ uipath/_utils/_auth.py,sha256=5R30ALuRrIatf_0Lk-AETJvt6ora7h0R7yeDdliW1Lg,2524
110
111
  uipath/_utils/_endpoint.py,sha256=yYHwqbQuJIevpaTkdfYJS9CrtlFeEyfb5JQK5osTCog,2489
111
112
  uipath/_utils/_infer_bindings.py,sha256=eCxfUjd37fOFZ6vOfKl2BhWVUt7iSSJ-VQ0-nDTBcaA,1836
112
113
  uipath/_utils/_logs.py,sha256=adfX_0UAn3YBeKJ8DQDeZs94rJyHGQO00uDfkaTpNWQ,510
@@ -147,6 +148,7 @@ uipath/models/action_schema.py,sha256=tBn1qQ3NQLU5nwWlBIzIKIx3XK5pO_D1S51IjFlZ1F
147
148
  uipath/models/actions.py,sha256=1vRsJ3JSmMdPkbiYAiHzY8K44vmW3VlMsmQUBAkSgrQ,3141
148
149
  uipath/models/assets.py,sha256=7x3swJRnG_a4VgjdXKKwraJLT5TF0u4wHsl6coOjX0g,2762
149
150
  uipath/models/attachment.py,sha256=lI6BxBY6DY5U6qZbxhkNu-usseA1zovYSTRtLq50ubI,1029
151
+ uipath/models/auth.py,sha256=-CEo5KZVtZZgbAMatN6B1vBmGp8lTTumR8sMthRmL8I,345
150
152
  uipath/models/buckets.py,sha256=N3Lj_dVCv709-ywhOOdyCSvsuLn41eGuAfSiik6Q6F8,1285
151
153
  uipath/models/connections.py,sha256=bTDg8xISSPmKB1GFNEEMD1OEZyBDFHfZVKqw4gab1pE,2524
152
154
  uipath/models/context_grounding.py,sha256=3MaF2Fv2QYle8UUWvKGkCN5XGpx2T4a34fdbBqJ2fCs,1137
@@ -169,8 +171,8 @@ uipath/tracing/_traced.py,sha256=yBIY05PCCrYyx50EIHZnwJaKNdHPNx-YTR1sHQl0a98,199
169
171
  uipath/tracing/_utils.py,sha256=qd7N56tg6VXQ9pREh61esBgUWLNA0ssKsE0QlwrRWFM,11974
170
172
  uipath/utils/__init__.py,sha256=VD-KXFpF_oWexFg6zyiWMkxl2HM4hYJMIUDZ1UEtGx0,105
171
173
  uipath/utils/_endpoints_manager.py,sha256=iRTl5Q0XAm_YgcnMcJOXtj-8052sr6jpWuPNz6CgT0Q,8408
172
- uipath-2.1.80.dist-info/METADATA,sha256=AMUh4rPIDQJzkepk6v3LaIpzwfyFraEhCYdN_0Z96YI,6593
173
- uipath-2.1.80.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
174
- uipath-2.1.80.dist-info/entry_points.txt,sha256=9C2_29U6Oq1ExFu7usihR-dnfIVNSKc-0EFbh0rskB4,43
175
- uipath-2.1.80.dist-info/licenses/LICENSE,sha256=-KBavWXepyDjimmzH5fVAsi-6jNVpIKFc2kZs0Ri4ng,1058
176
- uipath-2.1.80.dist-info/RECORD,,
174
+ uipath-2.1.81.dist-info/METADATA,sha256=8-3eAQ0tiFCXMWOhGm8QTSvM9fATrjo6j-UnceXoMOE,6593
175
+ uipath-2.1.81.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
176
+ uipath-2.1.81.dist-info/entry_points.txt,sha256=9C2_29U6Oq1ExFu7usihR-dnfIVNSKc-0EFbh0rskB4,43
177
+ uipath-2.1.81.dist-info/licenses/LICENSE,sha256=-KBavWXepyDjimmzH5fVAsi-6jNVpIKFc2kZs0Ri4ng,1058
178
+ uipath-2.1.81.dist-info/RECORD,,