uipath 2.1.14__py3-none-any.whl → 2.1.15__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.
- uipath/_cli/_push/sw_file_handler.py +464 -0
- uipath/_cli/_utils/_constants.py +2 -0
- uipath/_cli/_utils/_project_files.py +15 -3
- uipath/_cli/_utils/_studio_project.py +261 -40
- uipath/_cli/cli_init.py +3 -1
- uipath/_cli/cli_pack.py +7 -12
- uipath/_cli/cli_pull.py +28 -42
- uipath/_cli/cli_push.py +21 -459
- uipath/_services/_base_service.py +8 -0
- uipath/_services/api_client.py +8 -2
- uipath/_utils/constants.py +1 -0
- uipath/models/exceptions.py +6 -6
- {uipath-2.1.14.dist-info → uipath-2.1.15.dist-info}/METADATA +1 -1
- {uipath-2.1.14.dist-info → uipath-2.1.15.dist-info}/RECORD +17 -16
- {uipath-2.1.14.dist-info → uipath-2.1.15.dist-info}/WHEEL +0 -0
- {uipath-2.1.14.dist-info → uipath-2.1.15.dist-info}/entry_points.txt +0 -0
- {uipath-2.1.14.dist-info → uipath-2.1.15.dist-info}/licenses/LICENSE +0 -0
uipath/_cli/cli_push.py
CHANGED
@@ -1,360 +1,41 @@
|
|
1
1
|
# type: ignore
|
2
|
-
|
3
|
-
|
4
|
-
This module provides functionality to push local project files to a UiPath StudioWeb solution.
|
5
|
-
It handles:
|
6
|
-
- File uploads and updates
|
7
|
-
- File deletions for removed local files
|
8
|
-
- Optional UV lock file management
|
9
|
-
- Project structure pushing
|
10
|
-
|
11
|
-
The push process ensures that the remote project structure matches the local files,
|
12
|
-
taking into account:
|
13
|
-
- Entry point files from uipath.json
|
14
|
-
- Project configuration from pyproject.toml
|
15
|
-
- Optional UV lock file for dependency management
|
16
|
-
"""
|
17
|
-
|
18
|
-
import json
|
2
|
+
import asyncio
|
19
3
|
import os
|
20
|
-
from
|
21
|
-
from typing import Any, Dict, Optional, Set, Tuple
|
4
|
+
from typing import Any
|
22
5
|
from urllib.parse import urlparse
|
23
6
|
|
24
7
|
import click
|
25
|
-
import httpx
|
26
|
-
import jwt
|
27
8
|
from dotenv import load_dotenv
|
28
9
|
|
29
|
-
from .._utils._ssl_context import get_httpx_client_kwargs
|
30
10
|
from ..telemetry import track
|
31
|
-
from .
|
11
|
+
from ._push.sw_file_handler import SwFileHandler
|
32
12
|
from ._utils._console import ConsoleLogger
|
33
13
|
from ._utils._constants import (
|
34
|
-
|
35
|
-
AGENT_STORAGE_VERSION,
|
36
|
-
AGENT_TARGET_RUNTIME,
|
37
|
-
AGENT_VERSION,
|
14
|
+
UIPATH_PROJECT_ID,
|
38
15
|
)
|
39
16
|
from ._utils._project_files import (
|
40
17
|
ensure_config_file,
|
41
|
-
files_to_include,
|
42
18
|
get_project_config,
|
43
|
-
read_toml_project,
|
44
19
|
validate_config,
|
45
20
|
)
|
46
|
-
from ._utils._studio_project import ProjectFile, ProjectFolder, ProjectStructure
|
47
21
|
from ._utils._uv_helpers import handle_uv_operations
|
48
22
|
|
49
23
|
console = ConsoleLogger()
|
50
24
|
load_dotenv(override=True)
|
51
25
|
|
52
26
|
|
53
|
-
def get_author_from_token_or_toml(directory: str) -> str:
|
54
|
-
"""Extract preferred_username from JWT token or fall back to pyproject.toml author.
|
55
|
-
|
56
|
-
Args:
|
57
|
-
directory: Project directory containing pyproject.toml
|
58
|
-
|
59
|
-
Returns:
|
60
|
-
str: Author name from JWT preferred_username or pyproject.toml authors field
|
61
|
-
"""
|
62
|
-
# Try to get author from JWT token first
|
63
|
-
token = os.getenv("UIPATH_ACCESS_TOKEN")
|
64
|
-
if token:
|
65
|
-
try:
|
66
|
-
decoded_token = jwt.decode(token, options={"verify_signature": False})
|
67
|
-
preferred_username = decoded_token.get("preferred_username")
|
68
|
-
if preferred_username:
|
69
|
-
return preferred_username
|
70
|
-
except Exception:
|
71
|
-
# If JWT decoding fails, fall back to toml
|
72
|
-
pass
|
73
|
-
|
74
|
-
toml_data = read_toml_project(os.path.join(directory, "pyproject.toml"))
|
75
|
-
return toml_data.get("authors", "").strip()
|
76
|
-
|
77
|
-
|
78
27
|
def get_org_scoped_url(base_url: str) -> str:
|
79
28
|
parsed = urlparse(base_url)
|
80
29
|
org_name, *_ = parsed.path.strip("/").split("/")
|
81
30
|
|
82
|
-
# Construct the new scoped URL (scheme + domain + org_name)
|
83
31
|
org_scoped_url = f"{parsed.scheme}://{parsed.netloc}/{org_name}"
|
84
32
|
return org_scoped_url
|
85
33
|
|
86
34
|
|
87
|
-
def
|
88
|
-
project_id: str,
|
89
|
-
base_url: str,
|
90
|
-
token: str,
|
91
|
-
client: httpx.Client,
|
92
|
-
) -> ProjectStructure:
|
93
|
-
"""Retrieve the project's file structure from UiPath Cloud.
|
94
|
-
|
95
|
-
Makes an API call to fetch the complete file structure of a project,
|
96
|
-
including all files and folders. The response is validated against
|
97
|
-
the ProjectStructure model.
|
98
|
-
|
99
|
-
Args:
|
100
|
-
project_id: The ID of the project
|
101
|
-
base_url: The base URL for the API
|
102
|
-
token: Authentication token
|
103
|
-
client: HTTP client to use for requests
|
104
|
-
|
105
|
-
Returns:
|
106
|
-
ProjectStructure: The complete project structure
|
107
|
-
|
108
|
-
Raises:
|
109
|
-
httpx.HTTPError: If the API request fails
|
110
|
-
"""
|
111
|
-
url = get_org_scoped_url(base_url)
|
112
|
-
headers = {"Authorization": f"Bearer {token}"}
|
113
|
-
url = f"{url}/studio_/backend/api/Project/{project_id}/FileOperations/Structure"
|
114
|
-
|
115
|
-
response = client.get(url, headers=headers)
|
116
|
-
response.raise_for_status()
|
117
|
-
return ProjectStructure.model_validate(response.json())
|
118
|
-
|
119
|
-
|
120
|
-
def collect_all_files(
|
121
|
-
folder: ProjectFolder, files_dict: Dict[str, ProjectFile]
|
122
|
-
) -> None:
|
123
|
-
"""Recursively collect all files from a folder and its subfolders.
|
124
|
-
|
125
|
-
Traverses the folder structure recursively and adds all files to the
|
126
|
-
provided dictionary, using the file name as the key.
|
127
|
-
"""
|
128
|
-
# Add files from current folder
|
129
|
-
for file in folder.files:
|
130
|
-
files_dict[file.name] = file
|
131
|
-
|
132
|
-
# Recursively process subfolders
|
133
|
-
for subfolder in folder.folders:
|
134
|
-
collect_all_files(subfolder, files_dict)
|
135
|
-
|
136
|
-
|
137
|
-
def get_folder_by_name(
|
138
|
-
structure: ProjectStructure, folder_name: str
|
139
|
-
) -> Optional[ProjectFolder]:
|
140
|
-
"""Get a folder from the project structure by name.
|
141
|
-
|
142
|
-
Args:
|
143
|
-
structure: The project structure
|
144
|
-
folder_name: Name of the folder to find
|
145
|
-
|
146
|
-
Returns:
|
147
|
-
Optional[ProjectFolder]: The found folder or None
|
148
|
-
"""
|
149
|
-
for folder in structure.folders:
|
150
|
-
if folder.name == folder_name:
|
151
|
-
return folder
|
152
|
-
return None
|
153
|
-
|
154
|
-
|
155
|
-
def get_all_remote_files(
|
156
|
-
structure: ProjectStructure, source_code_folder: Optional[ProjectFolder] = None
|
157
|
-
) -> Tuple[Dict[str, ProjectFile], Dict[str, ProjectFile]]:
|
158
|
-
"""Get all files from the project structure indexed by name.
|
159
|
-
|
160
|
-
Creates two flat dictionaries of files in the project:
|
161
|
-
1. Root level files
|
162
|
-
2. Files in the source_code folder (if exists)
|
163
|
-
|
164
|
-
Args:
|
165
|
-
structure: The project structure
|
166
|
-
source_code_folder: Optional source_code folder to collect files from
|
167
|
-
|
168
|
-
Returns:
|
169
|
-
Tuple[Dict[str, ProjectFile], Dict[str, ProjectFile]]:
|
170
|
-
(root_files, source_code_files)
|
171
|
-
"""
|
172
|
-
root_files: Dict[str, ProjectFile] = {}
|
173
|
-
source_code_files: Dict[str, ProjectFile] = {}
|
174
|
-
|
175
|
-
# Add files from root level
|
176
|
-
for file in structure.files:
|
177
|
-
root_files[file.name] = file
|
178
|
-
|
179
|
-
# Add files from source_code folder if it exists
|
180
|
-
if source_code_folder:
|
181
|
-
collect_all_files(source_code_folder, source_code_files)
|
182
|
-
|
183
|
-
return root_files, source_code_files
|
184
|
-
|
185
|
-
|
186
|
-
def delete_remote_file(
|
187
|
-
project_id: str, file_id: str, base_url: str, token: str, client: httpx.Client
|
188
|
-
) -> None:
|
189
|
-
"""Delete a file from the remote project.
|
190
|
-
|
191
|
-
Makes an API call to delete a specific file from the UiPath Cloud project.
|
192
|
-
|
193
|
-
Args:
|
194
|
-
project_id: The ID of the project
|
195
|
-
file_id: The ID of the file to delete
|
196
|
-
base_url: The base URL for the API
|
197
|
-
token: Authentication token
|
198
|
-
client: HTTP client to use for the request
|
199
|
-
|
200
|
-
Raises:
|
201
|
-
httpx.HTTPError: If the API request fails
|
202
|
-
"""
|
203
|
-
url = get_org_scoped_url(base_url)
|
204
|
-
headers = {"Authorization": f"Bearer {token}"}
|
205
|
-
url = f"{url}/studio_/backend/api/Project/{project_id}/FileOperations/Delete/{file_id}"
|
206
|
-
|
207
|
-
response = client.delete(url, headers=headers)
|
208
|
-
response.raise_for_status()
|
209
|
-
|
210
|
-
|
211
|
-
def update_agent_json(
|
212
|
-
project_id: str,
|
213
|
-
base_url: str,
|
214
|
-
token: str,
|
215
|
-
directory: str,
|
216
|
-
client: httpx.Client,
|
217
|
-
processed_files: Optional[Set[str]] = None,
|
218
|
-
agent_json_file: Optional[ProjectFile] = None,
|
219
|
-
) -> None:
|
220
|
-
"""Update agent.json file with metadata from uipath.json.
|
221
|
-
|
222
|
-
This function:
|
223
|
-
1. Downloads existing agent.json if it exists
|
224
|
-
2. Updates metadata based on uipath.json content
|
225
|
-
3. Increments code version
|
226
|
-
4. Updates author from JWT or pyproject.toml
|
227
|
-
5. Uploads updated agent.json
|
228
|
-
|
229
|
-
Args:
|
230
|
-
project_id: The ID of the project
|
231
|
-
base_url: The base URL for the API
|
232
|
-
token: Authentication token
|
233
|
-
directory: Project root directory
|
234
|
-
client: HTTP client to use for requests
|
235
|
-
processed_files: Optional set to track processed files
|
236
|
-
agent_json_file: Optional existing agent.json file
|
237
|
-
|
238
|
-
Raises:
|
239
|
-
httpx.HTTPError: If API requests fail
|
240
|
-
FileNotFoundError: If required files are missing
|
241
|
-
json.JSONDecodeError: If JSON parsing fails
|
242
|
-
"""
|
243
|
-
url = get_org_scoped_url(base_url)
|
244
|
-
headers = {"Authorization": f"Bearer {token}"}
|
245
|
-
|
246
|
-
# Read uipath.json
|
247
|
-
with open(os.path.join(directory, "uipath.json"), "r") as f:
|
248
|
-
uipath_config = json.load(f)
|
249
|
-
|
250
|
-
try:
|
251
|
-
entrypoints = [
|
252
|
-
{"input": entry_point["input"], "output": entry_point["output"]}
|
253
|
-
for entry_point in uipath_config["entryPoints"]
|
254
|
-
]
|
255
|
-
except (FileNotFoundError, KeyError) as e:
|
256
|
-
console.error(
|
257
|
-
f"Unable to extract entrypoints from configuration file. Please run 'uipath init' : {str(e)}",
|
258
|
-
)
|
259
|
-
|
260
|
-
author = get_author_from_token_or_toml(directory)
|
261
|
-
|
262
|
-
# Initialize agent.json structure
|
263
|
-
agent_json = {
|
264
|
-
"version": AGENT_VERSION,
|
265
|
-
"metadata": {
|
266
|
-
"storageVersion": AGENT_STORAGE_VERSION,
|
267
|
-
"targetRuntime": AGENT_TARGET_RUNTIME,
|
268
|
-
"isConversational": False,
|
269
|
-
"codeVersion": AGENT_INITIAL_CODE_VERSION,
|
270
|
-
"author": author,
|
271
|
-
"pushDate": datetime.now(timezone.utc).isoformat(),
|
272
|
-
},
|
273
|
-
"entryPoints": entrypoints,
|
274
|
-
"bindings": uipath_config.get("bindings", {"version": "2.0", "resources": []}),
|
275
|
-
}
|
276
|
-
|
277
|
-
base_api_url = f"{url}/studio_/backend/api/Project/{project_id}/FileOperations"
|
278
|
-
if agent_json_file:
|
279
|
-
# Download existing agent.json
|
280
|
-
file_url = f"{base_api_url}/File/{agent_json_file.id}"
|
281
|
-
response = client.get(file_url, headers=headers)
|
282
|
-
response.raise_for_status()
|
283
|
-
|
284
|
-
try:
|
285
|
-
existing_agent = response.json()
|
286
|
-
# Get current version and increment patch version
|
287
|
-
version_parts = existing_agent["metadata"]["codeVersion"].split(".")
|
288
|
-
if len(version_parts) >= 3:
|
289
|
-
version_parts[-1] = str(int(version_parts[-1]) + 1)
|
290
|
-
agent_json["metadata"]["codeVersion"] = ".".join(version_parts)
|
291
|
-
else:
|
292
|
-
# If version format is invalid, start from initial version + 1
|
293
|
-
agent_json["metadata"]["codeVersion"] = (
|
294
|
-
AGENT_INITIAL_CODE_VERSION[:-1] + "1"
|
295
|
-
)
|
296
|
-
except (json.JSONDecodeError, KeyError, ValueError):
|
297
|
-
console.warning(
|
298
|
-
"Could not parse existing agent.json, using default version"
|
299
|
-
)
|
300
|
-
|
301
|
-
# Upload updated agent.json
|
302
|
-
files_data = {"file": ("agent.json", json.dumps(agent_json), "application/json")}
|
303
|
-
|
304
|
-
# if agent.json already exists update it, otherwise upload it
|
305
|
-
if agent_json_file:
|
306
|
-
url = f"{base_api_url}/File/{agent_json_file.id}"
|
307
|
-
response = client.put(url, files=files_data, headers=headers)
|
308
|
-
else:
|
309
|
-
url = f"{base_api_url}/File"
|
310
|
-
response = client.post(url, files=files_data, headers=headers)
|
311
|
-
|
312
|
-
response.raise_for_status()
|
313
|
-
console.success(f"Updated {click.style('agent.json', fg='cyan')}")
|
314
|
-
|
315
|
-
# Mark agent.json as processed to prevent deletion
|
316
|
-
if processed_files is not None:
|
317
|
-
processed_files.add("agent.json")
|
318
|
-
|
319
|
-
|
320
|
-
def create_project_folder(
|
321
|
-
project_id: str,
|
322
|
-
folder_name: str,
|
323
|
-
base_url: str,
|
324
|
-
token: str,
|
325
|
-
client: httpx.Client,
|
326
|
-
) -> ProjectFolder:
|
327
|
-
"""Create a new folder in the project.
|
328
|
-
|
329
|
-
Args:
|
330
|
-
project_id: The ID of the project
|
331
|
-
folder_name: Name of the folder to create
|
332
|
-
base_url: The base URL for the API
|
333
|
-
token: Authentication token
|
334
|
-
client: HTTP client to use for requests
|
335
|
-
|
336
|
-
Returns:
|
337
|
-
ProjectFolder: The created folder object
|
338
|
-
|
339
|
-
Raises:
|
340
|
-
httpx.HTTPError: If the API request fails
|
341
|
-
"""
|
342
|
-
url = get_org_scoped_url(base_url)
|
343
|
-
headers = {"Authorization": f"Bearer {token}"}
|
344
|
-
url = f"{url}/studio_/backend/api/Project/{project_id}/FileOperations/Folder"
|
345
|
-
|
346
|
-
data = {"name": folder_name}
|
347
|
-
response = client.post(url, json=data, headers=headers)
|
348
|
-
response.raise_for_status()
|
349
|
-
return ProjectFolder(name="source_code", id=response.content.decode("utf-8"))
|
350
|
-
|
351
|
-
|
352
|
-
def upload_source_files_to_project(
|
35
|
+
async def upload_source_files_to_project(
|
353
36
|
project_id: str,
|
354
37
|
config_data: dict[Any, str],
|
355
38
|
directory: str,
|
356
|
-
base_url: str,
|
357
|
-
token: str,
|
358
39
|
include_uv_lock: bool = True,
|
359
40
|
) -> None:
|
360
41
|
"""Upload source files to UiPath project.
|
@@ -365,130 +46,13 @@ def upload_source_files_to_project(
|
|
365
46
|
- Deletes remote files that no longer exist locally
|
366
47
|
- Optionally includes the UV lock file
|
367
48
|
"""
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
files.append("uv.lock")
|
374
|
-
|
375
|
-
url = get_org_scoped_url(base_url)
|
376
|
-
headers = {"Authorization": f"Bearer {token}"}
|
377
|
-
base_api_url = f"{url}/studio_/backend/api/Project/{project_id}/FileOperations"
|
378
|
-
|
379
|
-
with httpx.Client(**get_httpx_client_kwargs()) as client:
|
380
|
-
# get existing project structure
|
381
|
-
try:
|
382
|
-
structure = get_project_structure(project_id, base_url, token, client)
|
383
|
-
source_code_folder = get_folder_by_name(structure, "source_code")
|
384
|
-
root_files, source_code_files = get_all_remote_files(
|
385
|
-
structure, source_code_folder
|
386
|
-
)
|
387
|
-
except Exception as e:
|
388
|
-
console.error(f"Failed to get project structure: {str(e)}")
|
389
|
-
raise
|
49
|
+
sw_file_handler = SwFileHandler(
|
50
|
+
project_id=project_id,
|
51
|
+
directory=directory,
|
52
|
+
include_uv_lock=include_uv_lock,
|
53
|
+
)
|
390
54
|
|
391
|
-
|
392
|
-
processed_root_files: Set[str] = set()
|
393
|
-
processed_source_files: Set[str] = set()
|
394
|
-
|
395
|
-
# Create source_code folder if it doesn't exist
|
396
|
-
if not source_code_folder:
|
397
|
-
try:
|
398
|
-
source_code_folder = create_project_folder(
|
399
|
-
project_id, "source_code", base_url, token, client
|
400
|
-
)
|
401
|
-
console.success(
|
402
|
-
f"Created {click.style('source_code', fg='cyan')} folder"
|
403
|
-
)
|
404
|
-
source_code_files = {} # Initialize empty dict for new folder
|
405
|
-
except httpx.HTTPStatusError as http_err:
|
406
|
-
if http_err.response.status_code == 423:
|
407
|
-
console.error(
|
408
|
-
"Resource is locked. Unable to create 'source_code' folder."
|
409
|
-
)
|
410
|
-
raise
|
411
|
-
|
412
|
-
except Exception as e:
|
413
|
-
console.error(f"Failed to create 'source_code' folder: {str(e)}")
|
414
|
-
raise
|
415
|
-
|
416
|
-
# Update agent.json first at root level
|
417
|
-
try:
|
418
|
-
update_agent_json(
|
419
|
-
project_id,
|
420
|
-
base_url,
|
421
|
-
token,
|
422
|
-
directory,
|
423
|
-
client,
|
424
|
-
processed_root_files,
|
425
|
-
root_files.get("agent.json", None),
|
426
|
-
)
|
427
|
-
except Exception as e:
|
428
|
-
console.error(f"Failed to update agent.json: {str(e)}")
|
429
|
-
raise
|
430
|
-
|
431
|
-
# Continue with rest of files in source_code folder
|
432
|
-
for file_path in files:
|
433
|
-
try:
|
434
|
-
abs_path = os.path.abspath(os.path.join(directory, file_path))
|
435
|
-
if not os.path.exists(abs_path):
|
436
|
-
console.warning(
|
437
|
-
f"File not found: {click.style(abs_path, fg='cyan')}"
|
438
|
-
)
|
439
|
-
continue
|
440
|
-
|
441
|
-
file_name = os.path.basename(file_path)
|
442
|
-
|
443
|
-
# Skip agent.json as it's already handled
|
444
|
-
if file_name == "agent.json":
|
445
|
-
continue
|
446
|
-
|
447
|
-
remote_file = source_code_files.get(file_name)
|
448
|
-
processed_source_files.add(file_name)
|
449
|
-
|
450
|
-
with open(abs_path, "rb") as f:
|
451
|
-
files_data = {"file": (file_name, f, "application/octet-stream")}
|
452
|
-
form_data = {"parentId": source_code_folder.id}
|
453
|
-
|
454
|
-
if remote_file:
|
455
|
-
# File exists in source_code folder, use PUT to update
|
456
|
-
url = f"{base_api_url}/File/{remote_file.id}"
|
457
|
-
response = client.put(url, files=files_data, headers=headers)
|
458
|
-
action = "Updated"
|
459
|
-
else:
|
460
|
-
# File doesn't exist, use POST to create in source_code folder
|
461
|
-
url = f"{base_api_url}/File"
|
462
|
-
response = client.post(
|
463
|
-
url, files=files_data, data=form_data, headers=headers
|
464
|
-
)
|
465
|
-
action = "Uploaded"
|
466
|
-
|
467
|
-
response.raise_for_status()
|
468
|
-
console.success(f"{action} {click.style(file_path, fg='cyan')}")
|
469
|
-
|
470
|
-
except Exception as e:
|
471
|
-
console.error(
|
472
|
-
f"Failed to upload {click.style(file_path, fg='cyan')}: {str(e)}"
|
473
|
-
)
|
474
|
-
raise
|
475
|
-
|
476
|
-
# Delete files that no longer exist locally
|
477
|
-
if source_code_files:
|
478
|
-
for file_name, remote_file in source_code_files.items():
|
479
|
-
if file_name not in processed_source_files:
|
480
|
-
try:
|
481
|
-
delete_remote_file(
|
482
|
-
project_id, remote_file.id, base_url, token, client
|
483
|
-
)
|
484
|
-
console.success(
|
485
|
-
f"Deleted remote file {click.style(file_name, fg='cyan')}"
|
486
|
-
)
|
487
|
-
except Exception as e:
|
488
|
-
console.error(
|
489
|
-
f"Failed to delete remote file {click.style(file_name, fg='cyan')}: {str(e)}"
|
490
|
-
)
|
491
|
-
raise
|
55
|
+
await sw_file_handler.upload_source_files(config_data)
|
492
56
|
|
493
57
|
|
494
58
|
@click.command()
|
@@ -526,23 +90,21 @@ def push(root: str, nolock: bool) -> None:
|
|
526
90
|
config = get_project_config(root)
|
527
91
|
validate_config(config)
|
528
92
|
|
529
|
-
if not os.getenv(
|
93
|
+
if not os.getenv(UIPATH_PROJECT_ID, False):
|
530
94
|
console.error("UIPATH_PROJECT_ID environment variable not found.")
|
531
|
-
[base_url, token] = get_env_vars()
|
532
95
|
|
533
96
|
with console.spinner("Pushing coded UiPath project to Studio Web..."):
|
534
97
|
try:
|
535
|
-
# Handle uv operations before packaging, unless nolock is specified
|
536
98
|
if not nolock:
|
537
99
|
handle_uv_operations(root)
|
538
100
|
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
101
|
+
asyncio.run(
|
102
|
+
upload_source_files_to_project(
|
103
|
+
os.getenv(UIPATH_PROJECT_ID),
|
104
|
+
config,
|
105
|
+
root,
|
106
|
+
include_uv_lock=not nolock,
|
107
|
+
)
|
546
108
|
)
|
547
|
-
except Exception:
|
548
|
-
console.error("Failed to push UiPath project")
|
109
|
+
except Exception as e:
|
110
|
+
console.error(f"Failed to push UiPath project: {e}")
|
@@ -74,6 +74,7 @@ class BaseService:
|
|
74
74
|
url: Union[URL, str],
|
75
75
|
*,
|
76
76
|
scoped: Literal["org", "tenant"] = "tenant",
|
77
|
+
infer_content_type=False,
|
77
78
|
**kwargs: Any,
|
78
79
|
) -> Response:
|
79
80
|
self._logger.debug(f"Request: {method} {url}")
|
@@ -105,6 +106,9 @@ class BaseService:
|
|
105
106
|
|
106
107
|
scoped_url = self._url.scope_url(str(url), scoped)
|
107
108
|
|
109
|
+
if infer_content_type:
|
110
|
+
self._client_async.headers.pop("Content-Type", None)
|
111
|
+
|
108
112
|
response = self._client.request(method, scoped_url, **kwargs)
|
109
113
|
|
110
114
|
try:
|
@@ -128,6 +132,7 @@ class BaseService:
|
|
128
132
|
url: Union[URL, str],
|
129
133
|
*,
|
130
134
|
scoped: Literal["org", "tenant"] = "tenant",
|
135
|
+
infer_content_type=False,
|
131
136
|
**kwargs: Any,
|
132
137
|
) -> Response:
|
133
138
|
self._logger.debug(f"Request: {method} {url}")
|
@@ -142,6 +147,9 @@ class BaseService:
|
|
142
147
|
|
143
148
|
scoped_url = self._url.scope_url(str(url), scoped)
|
144
149
|
|
150
|
+
if infer_content_type:
|
151
|
+
self._client_async.headers.pop("Content-Type", None)
|
152
|
+
|
145
153
|
response = await self._client_async.request(method, scoped_url, **kwargs)
|
146
154
|
|
147
155
|
try:
|
uipath/_services/api_client.py
CHANGED
@@ -25,6 +25,7 @@ class ApiClient(FolderContext, BaseService):
|
|
25
25
|
method: str,
|
26
26
|
url: Union[URL, str],
|
27
27
|
scoped: Literal["org", "tenant"] = "tenant",
|
28
|
+
infer_content_type=False,
|
28
29
|
**kwargs: Any,
|
29
30
|
) -> Response:
|
30
31
|
if kwargs.get("include_folder_headers", False):
|
@@ -36,13 +37,16 @@ class ApiClient(FolderContext, BaseService):
|
|
36
37
|
if "include_folder_headers" in kwargs:
|
37
38
|
del kwargs["include_folder_headers"]
|
38
39
|
|
39
|
-
return super().request(
|
40
|
+
return super().request(
|
41
|
+
method, url, scoped=scoped, infer_content_type=infer_content_type, **kwargs
|
42
|
+
)
|
40
43
|
|
41
44
|
async def request_async(
|
42
45
|
self,
|
43
46
|
method: str,
|
44
47
|
url: Union[URL, str],
|
45
48
|
scoped: Literal["org", "tenant"] = "tenant",
|
49
|
+
infer_content_type=False,
|
46
50
|
**kwargs: Any,
|
47
51
|
) -> Response:
|
48
52
|
if kwargs.get("include_folder_headers", False):
|
@@ -54,4 +58,6 @@ class ApiClient(FolderContext, BaseService):
|
|
54
58
|
if "include_folder_headers" in kwargs:
|
55
59
|
del kwargs["include_folder_headers"]
|
56
60
|
|
57
|
-
return await super().request_async(
|
61
|
+
return await super().request_async(
|
62
|
+
method, url, scoped=scoped, infer_content_type=infer_content_type, **kwargs
|
63
|
+
)
|
uipath/_utils/constants.py
CHANGED
@@ -18,6 +18,7 @@ HEADER_USER_AGENT = "x-uipath-user-agent"
|
|
18
18
|
HEADER_TENANT_ID = "x-uipath-tenantid"
|
19
19
|
HEADER_INTERNAL_TENANT_ID = "x-uipath-internal-tenantid"
|
20
20
|
HEADER_JOB_KEY = "x-uipath-jobkey"
|
21
|
+
HEADER_SW_LOCK_KEY = "x-uipath-sw-lockkey"
|
21
22
|
|
22
23
|
# Data sources
|
23
24
|
ORCHESTRATOR_STORAGE_BUCKET_DATA_SOURCE = (
|
uipath/models/exceptions.py
CHANGED
@@ -18,18 +18,18 @@ class IngestionInProgressException(Exception):
|
|
18
18
|
class EnrichedException(Exception):
|
19
19
|
def __init__(self, error: HTTPStatusError) -> None:
|
20
20
|
# Extract the relevant details from the HTTPStatusError
|
21
|
-
status_code = error.response.status_code if error.response else "Unknown"
|
22
|
-
url = str(error.request.url) if error.request else "Unknown"
|
23
|
-
response_content = (
|
21
|
+
self.status_code = error.response.status_code if error.response else "Unknown"
|
22
|
+
self.url = str(error.request.url) if error.request else "Unknown"
|
23
|
+
self.response_content = (
|
24
24
|
error.response.content.decode("utf-8")
|
25
25
|
if error.response and error.response.content
|
26
26
|
else "No content"
|
27
27
|
)
|
28
28
|
|
29
29
|
enriched_message = (
|
30
|
-
f"\nRequest URL: {url}"
|
31
|
-
f"\nStatus Code: {status_code}"
|
32
|
-
f"\nResponse Content: {response_content}"
|
30
|
+
f"\nRequest URL: {self.url}"
|
31
|
+
f"\nStatus Code: {self.status_code}"
|
32
|
+
f"\nResponse Content: {self.response_content}"
|
33
33
|
)
|
34
34
|
|
35
35
|
# Initialize the parent Exception class with the formatted message
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: uipath
|
3
|
-
Version: 2.1.
|
3
|
+
Version: 2.1.15
|
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
|