uipath 2.1.4__py3-none-any.whl → 2.1.5__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/__init__.py +2 -0
- uipath/_cli/_auth/auth_config.json +2 -2
- uipath/_cli/_utils/_constants.py +6 -0
- uipath/_cli/_utils/_project_files.py +361 -0
- uipath/_cli/_utils/_studio_project.py +138 -0
- uipath/_cli/_utils/_uv_helpers.py +114 -0
- uipath/_cli/cli_pack.py +42 -341
- uipath/_cli/cli_push.py +546 -0
- {uipath-2.1.4.dist-info → uipath-2.1.5.dist-info}/METADATA +1 -1
- {uipath-2.1.4.dist-info → uipath-2.1.5.dist-info}/RECORD +13 -9
- {uipath-2.1.4.dist-info → uipath-2.1.5.dist-info}/WHEEL +0 -0
- {uipath-2.1.4.dist-info → uipath-2.1.5.dist-info}/entry_points.txt +0 -0
- {uipath-2.1.4.dist-info → uipath-2.1.5.dist-info}/licenses/LICENSE +0 -0
uipath/_cli/cli_pack.py
CHANGED
@@ -1,29 +1,37 @@
|
|
1
1
|
# type: ignore
|
2
2
|
import json
|
3
3
|
import os
|
4
|
-
import re
|
5
|
-
import subprocess
|
6
4
|
import uuid
|
7
5
|
import zipfile
|
8
6
|
from string import Template
|
9
|
-
from typing import Dict, Tuple
|
10
7
|
|
11
8
|
import click
|
12
9
|
|
13
|
-
try:
|
14
|
-
import tomllib
|
15
|
-
except ImportError:
|
16
|
-
import tomli as tomllib
|
17
|
-
|
18
10
|
from ..telemetry import track
|
19
11
|
from ._utils._console import ConsoleLogger
|
20
|
-
from ._utils.
|
12
|
+
from ._utils._project_files import (
|
13
|
+
ensure_config_file,
|
14
|
+
files_to_include,
|
15
|
+
get_project_config,
|
16
|
+
read_toml_project,
|
17
|
+
validate_config,
|
18
|
+
)
|
19
|
+
from ._utils._uv_helpers import handle_uv_operations
|
21
20
|
|
22
21
|
console = ConsoleLogger()
|
23
22
|
|
24
23
|
schema = "https://cloud.uipath.com/draft/2024-12/entry-point"
|
25
24
|
|
26
25
|
|
26
|
+
def get_project_version(directory):
|
27
|
+
toml_path = os.path.join(directory, "pyproject.toml")
|
28
|
+
if not os.path.exists(toml_path):
|
29
|
+
console.warning("pyproject.toml not found. Using default version 0.0.1")
|
30
|
+
return "0.0.1"
|
31
|
+
toml_data = read_toml_project(toml_path)
|
32
|
+
return toml_data["version"]
|
33
|
+
|
34
|
+
|
27
35
|
def validate_config_structure(config_data):
|
28
36
|
required_fields = ["entryPoints"]
|
29
37
|
for field in required_fields:
|
@@ -31,92 +39,6 @@ def validate_config_structure(config_data):
|
|
31
39
|
console.error(f"uipath.json is missing the required field: {field}.")
|
32
40
|
|
33
41
|
|
34
|
-
def check_config(directory):
|
35
|
-
config_path = os.path.join(directory, "uipath.json")
|
36
|
-
toml_path = os.path.join(directory, "pyproject.toml")
|
37
|
-
|
38
|
-
if not os.path.isfile(config_path):
|
39
|
-
console.error("uipath.json not found, please run `uipath init`.")
|
40
|
-
if not os.path.isfile(toml_path):
|
41
|
-
console.error("pyproject.toml not found.")
|
42
|
-
|
43
|
-
with open(config_path, "r") as config_file:
|
44
|
-
config_data = json.load(config_file)
|
45
|
-
|
46
|
-
validate_config_structure(config_data)
|
47
|
-
|
48
|
-
toml_data = read_toml_project(toml_path)
|
49
|
-
|
50
|
-
return {
|
51
|
-
"project_name": toml_data["name"],
|
52
|
-
"description": toml_data["description"],
|
53
|
-
"entryPoints": config_data["entryPoints"],
|
54
|
-
"version": toml_data["version"],
|
55
|
-
"authors": toml_data["authors"],
|
56
|
-
"dependencies": toml_data.get("dependencies", {}),
|
57
|
-
}
|
58
|
-
|
59
|
-
|
60
|
-
def is_uv_available():
|
61
|
-
"""Check if uv command is available in the system."""
|
62
|
-
try:
|
63
|
-
subprocess.run(["uv", "--version"], capture_output=True, check=True, timeout=20)
|
64
|
-
return True
|
65
|
-
except (subprocess.CalledProcessError, FileNotFoundError):
|
66
|
-
return False
|
67
|
-
except Exception as e:
|
68
|
-
console.warning(
|
69
|
-
f"An unexpected error occurred while checking uv availability: {str(e)}"
|
70
|
-
)
|
71
|
-
return False
|
72
|
-
|
73
|
-
|
74
|
-
def is_uv_project(directory):
|
75
|
-
"""Check if this is a uv project by looking for the uv.lock file."""
|
76
|
-
uv_lock_path = os.path.join(directory, "uv.lock")
|
77
|
-
|
78
|
-
# If uv.lock exists, it's definitely a uv project
|
79
|
-
if os.path.exists(uv_lock_path):
|
80
|
-
return True
|
81
|
-
|
82
|
-
return False
|
83
|
-
|
84
|
-
|
85
|
-
def run_uv_lock(directory):
|
86
|
-
"""Run uv lock to update the lock file."""
|
87
|
-
try:
|
88
|
-
subprocess.run(
|
89
|
-
["uv", "lock"],
|
90
|
-
cwd=directory,
|
91
|
-
capture_output=True,
|
92
|
-
text=True,
|
93
|
-
check=True,
|
94
|
-
timeout=60,
|
95
|
-
)
|
96
|
-
return True
|
97
|
-
except subprocess.CalledProcessError as e:
|
98
|
-
console.warning(f"uv lock failed: {e.stderr}")
|
99
|
-
return False
|
100
|
-
except FileNotFoundError:
|
101
|
-
console.warning("uv command not found. Skipping lock file update.")
|
102
|
-
return False
|
103
|
-
except Exception as e:
|
104
|
-
console.warning(f"An unexpected error occurred while running uv lock: {str(e)}")
|
105
|
-
return False
|
106
|
-
|
107
|
-
|
108
|
-
def handle_uv_operations(directory):
|
109
|
-
"""Handle uv operations if uv is detected and available."""
|
110
|
-
if not is_uv_available():
|
111
|
-
return
|
112
|
-
|
113
|
-
if not is_uv_project(directory):
|
114
|
-
return
|
115
|
-
|
116
|
-
# Always run uv lock to ensure lock file is up to date
|
117
|
-
run_uv_lock(directory)
|
118
|
-
|
119
|
-
|
120
42
|
def generate_operate_file(entryPoints, dependencies=None):
|
121
43
|
project_id = str(uuid.uuid4())
|
122
44
|
|
@@ -263,22 +185,12 @@ def pack_fn(
|
|
263
185
|
if not os.path.exists(config_path):
|
264
186
|
console.error("uipath.json not found, please run `uipath init`.")
|
265
187
|
|
266
|
-
# Define the allowlist of file extensions to include
|
267
|
-
file_extensions_included = [".py", ".mermaid", ".json", ".yaml", ".yml", ".md"]
|
268
|
-
files_included = []
|
269
|
-
|
270
188
|
with open(config_path, "r") as f:
|
271
189
|
config_data = json.load(f)
|
272
190
|
if "bindings" in config_data:
|
273
191
|
bindings_content = config_data["bindings"]
|
274
192
|
else:
|
275
193
|
bindings_content = generate_bindings_content()
|
276
|
-
if "settings" in config_data:
|
277
|
-
settings = config_data["settings"]
|
278
|
-
if "fileExtensionsIncluded" in settings:
|
279
|
-
file_extensions_included.extend(settings["fileExtensionsIncluded"])
|
280
|
-
if "filesIncluded" in settings:
|
281
|
-
files_included = settings["filesIncluded"]
|
282
194
|
|
283
195
|
content_types_content = generate_content_types_content()
|
284
196
|
[psmdcp_file_name, psmdcp_content] = generate_psmdcp_content(
|
@@ -313,46 +225,31 @@ def pack_fn(
|
|
313
225
|
z.writestr(f"{projectName}.nuspec", nuspec_content)
|
314
226
|
z.writestr("_rels/.rels", rels_content)
|
315
227
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
338
|
-
z.writestr(f"content/{rel_path}", f.read())
|
339
|
-
except UnicodeDecodeError:
|
340
|
-
# If UTF-8 fails, try with utf-8-sig (for files with BOM)
|
341
|
-
try:
|
342
|
-
with open(file_path, "r", encoding="utf-8-sig") as f:
|
343
|
-
z.writestr(f"content/{rel_path}", f.read())
|
344
|
-
except UnicodeDecodeError:
|
345
|
-
# If that also fails, try with latin-1 as a fallback
|
346
|
-
with open(file_path, "r", encoding="latin-1") as f:
|
347
|
-
z.writestr(f"content/{rel_path}", f.read())
|
228
|
+
files = files_to_include(config_data, directory)
|
229
|
+
|
230
|
+
for file in files:
|
231
|
+
if file.is_binary:
|
232
|
+
# Read binary files in binary mode
|
233
|
+
with open(file.file_path, "rb") as f:
|
234
|
+
z.writestr(f"content/{file.relative_path}", f.read())
|
235
|
+
else:
|
236
|
+
try:
|
237
|
+
# Try UTF-8 first
|
238
|
+
with open(file.file_path, "r", encoding="utf-8") as f:
|
239
|
+
z.writestr(f"content/{file.relative_path}", f.read())
|
240
|
+
except UnicodeDecodeError:
|
241
|
+
# If UTF-8 fails, try with utf-8-sig (for files with BOM)
|
242
|
+
try:
|
243
|
+
with open(file.file_path, "r", encoding="utf-8-sig") as f:
|
244
|
+
z.writestr(f"content/{file.relative_path}", f.read())
|
245
|
+
except UnicodeDecodeError:
|
246
|
+
# If that also fails, try with latin-1 as a fallback
|
247
|
+
with open(file.file_path, "r", encoding="latin-1") as f:
|
248
|
+
z.writestr(f"content/{file.relative_path}", f.read())
|
348
249
|
|
349
250
|
# Handle optional files, conditionally including uv.lock
|
350
|
-
optional_files = ["pyproject.toml"]
|
351
251
|
if include_uv_lock:
|
352
|
-
|
353
|
-
|
354
|
-
for file in optional_files:
|
355
|
-
file_path = os.path.join(directory, file)
|
252
|
+
file_path = os.path.join(directory, "uv.lock")
|
356
253
|
if os.path.exists(file_path):
|
357
254
|
try:
|
358
255
|
with open(file_path, "r", encoding="utf-8") as f:
|
@@ -362,177 +259,6 @@ def pack_fn(
|
|
362
259
|
z.writestr(f"content/{file}", f.read())
|
363
260
|
|
364
261
|
|
365
|
-
def parse_dependency_string(dependency: str) -> Tuple[str, str]:
|
366
|
-
"""Parse a dependency string into package name and version specifier.
|
367
|
-
|
368
|
-
Handles PEP 508 dependency specifications including:
|
369
|
-
- Simple names: "requests"
|
370
|
-
- Version specifiers: "requests>=2.28.0"
|
371
|
-
- Complex specifiers: "requests>=2.28.0,<3.0.0"
|
372
|
-
- Extras: "requests[security]>=2.28.0"
|
373
|
-
- Environment markers: "requests>=2.28.0; python_version>='3.8'"
|
374
|
-
|
375
|
-
Args:
|
376
|
-
dependency: Raw dependency string from pyproject.toml
|
377
|
-
|
378
|
-
Returns:
|
379
|
-
Tuple of (package_name, version_specifier)
|
380
|
-
|
381
|
-
Examples:
|
382
|
-
"requests" -> ("requests", "*")
|
383
|
-
"requests>=2.28.0" -> ("requests", ">=2.28.0")
|
384
|
-
"requests>=2.28.0,<3.0.0" -> ("requests", ">=2.28.0,<3.0.0")
|
385
|
-
"requests[security]>=2.28.0" -> ("requests", ">=2.28.0")
|
386
|
-
"""
|
387
|
-
# Remove whitespace
|
388
|
-
dependency = dependency.strip()
|
389
|
-
|
390
|
-
# Handle environment markers (everything after semicolon)
|
391
|
-
if ";" in dependency:
|
392
|
-
dependency = dependency.split(";")[0].strip()
|
393
|
-
|
394
|
-
# Pattern to match package name with optional extras and version specifiers
|
395
|
-
# Matches: package_name[extras] version_specs
|
396
|
-
pattern = r"^([a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?)(\[[^\]]+\])?(.*)"
|
397
|
-
match = re.match(pattern, dependency)
|
398
|
-
|
399
|
-
if not match:
|
400
|
-
# Fallback for edge cases
|
401
|
-
return dependency, "*"
|
402
|
-
|
403
|
-
package_name = match.group(1)
|
404
|
-
version_part = match.group(4).strip() if match.group(4) else ""
|
405
|
-
|
406
|
-
# If no version specifier, return wildcard
|
407
|
-
if not version_part:
|
408
|
-
return package_name, "*"
|
409
|
-
|
410
|
-
# Clean up version specifier
|
411
|
-
version_spec = version_part.strip()
|
412
|
-
|
413
|
-
# Validate that version specifier starts with a valid operator
|
414
|
-
valid_operators = [">=", "<=", "==", "!=", "~=", ">", "<"]
|
415
|
-
if not any(version_spec.startswith(op) for op in valid_operators):
|
416
|
-
# If it doesn't start with an operator, treat as exact version
|
417
|
-
if version_spec:
|
418
|
-
version_spec = f"=={version_spec}"
|
419
|
-
else:
|
420
|
-
version_spec = "*"
|
421
|
-
|
422
|
-
return package_name, version_spec
|
423
|
-
|
424
|
-
|
425
|
-
def extract_dependencies_from_toml(project_data: Dict) -> Dict[str, str]:
|
426
|
-
"""Extract and parse dependencies from pyproject.toml project data.
|
427
|
-
|
428
|
-
Args:
|
429
|
-
project_data: The "project" section from pyproject.toml
|
430
|
-
|
431
|
-
Returns:
|
432
|
-
Dictionary mapping package names to version specifiers
|
433
|
-
"""
|
434
|
-
dependencies = {}
|
435
|
-
|
436
|
-
if "dependencies" not in project_data:
|
437
|
-
return dependencies
|
438
|
-
|
439
|
-
deps_list = project_data["dependencies"]
|
440
|
-
if not isinstance(deps_list, list):
|
441
|
-
console.warning("dependencies should be a list in pyproject.toml")
|
442
|
-
return dependencies
|
443
|
-
|
444
|
-
for dep in deps_list:
|
445
|
-
if not isinstance(dep, str):
|
446
|
-
console.warning(f"Skipping non-string dependency: {dep}")
|
447
|
-
continue
|
448
|
-
|
449
|
-
try:
|
450
|
-
name, version_spec = parse_dependency_string(dep)
|
451
|
-
if name: # Only add if we got a valid name
|
452
|
-
dependencies[name] = version_spec
|
453
|
-
except Exception as e:
|
454
|
-
console.warning(f"Failed to parse dependency '{dep}': {e}")
|
455
|
-
continue
|
456
|
-
|
457
|
-
return dependencies
|
458
|
-
|
459
|
-
|
460
|
-
def read_toml_project(file_path: str) -> dict:
|
461
|
-
"""Read and parse pyproject.toml file with improved error handling and validation.
|
462
|
-
|
463
|
-
Args:
|
464
|
-
file_path: Path to pyproject.toml file
|
465
|
-
|
466
|
-
Returns:
|
467
|
-
Dictionary containing project metadata and dependencies
|
468
|
-
"""
|
469
|
-
try:
|
470
|
-
with open(file_path, "rb") as f:
|
471
|
-
content = tomllib.load(f)
|
472
|
-
except Exception as e:
|
473
|
-
console.error(f"Failed to read or parse pyproject.toml: {e}")
|
474
|
-
|
475
|
-
# Validate required sections
|
476
|
-
if "project" not in content:
|
477
|
-
console.error("pyproject.toml is missing the required field: project.")
|
478
|
-
|
479
|
-
project = content["project"]
|
480
|
-
|
481
|
-
# Validate required fields with better error messages
|
482
|
-
required_fields = {
|
483
|
-
"name": "Project name is required in pyproject.toml",
|
484
|
-
"description": "Project description is required in pyproject.toml",
|
485
|
-
"version": "Project version is required in pyproject.toml",
|
486
|
-
}
|
487
|
-
|
488
|
-
for field, error_msg in required_fields.items():
|
489
|
-
if field not in project:
|
490
|
-
console.error(
|
491
|
-
f"pyproject.toml is missing the required field: project.{field}. {error_msg}"
|
492
|
-
)
|
493
|
-
|
494
|
-
# Check for empty values only if field exists
|
495
|
-
if field in project and (
|
496
|
-
not project[field]
|
497
|
-
or (isinstance(project[field], str) and not project[field].strip())
|
498
|
-
):
|
499
|
-
console.error(
|
500
|
-
f"Project {field} cannot be empty. Please specify a {field} in pyproject.toml."
|
501
|
-
)
|
502
|
-
|
503
|
-
# Extract author information safely
|
504
|
-
authors = project.get("authors", [])
|
505
|
-
author_name = ""
|
506
|
-
|
507
|
-
if authors and isinstance(authors, list) and len(authors) > 0:
|
508
|
-
first_author = authors[0]
|
509
|
-
if isinstance(first_author, dict):
|
510
|
-
author_name = first_author.get("name", "")
|
511
|
-
elif isinstance(first_author, str):
|
512
|
-
# Handle case where authors is a list of strings
|
513
|
-
author_name = first_author
|
514
|
-
|
515
|
-
# Extract dependencies with improved parsing
|
516
|
-
dependencies = extract_dependencies_from_toml(project)
|
517
|
-
|
518
|
-
return {
|
519
|
-
"name": project["name"].strip(),
|
520
|
-
"description": project["description"].strip(),
|
521
|
-
"version": project["version"].strip(),
|
522
|
-
"authors": author_name.strip(),
|
523
|
-
"dependencies": dependencies,
|
524
|
-
}
|
525
|
-
|
526
|
-
|
527
|
-
def get_project_version(directory):
|
528
|
-
toml_path = os.path.join(directory, "pyproject.toml")
|
529
|
-
if not os.path.exists(toml_path):
|
530
|
-
console.warning("pyproject.toml not found. Using default version 0.0.1")
|
531
|
-
return "0.0.1"
|
532
|
-
toml_data = read_toml_project(toml_path)
|
533
|
-
return toml_data["version"]
|
534
|
-
|
535
|
-
|
536
262
|
def display_project_info(config):
|
537
263
|
max_label_length = max(
|
538
264
|
len(label) for label in ["Name", "Version", "Description", "Authors"]
|
@@ -561,34 +287,9 @@ def pack(root, nolock):
|
|
561
287
|
"""Pack the project."""
|
562
288
|
version = get_project_version(root)
|
563
289
|
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
)
|
568
|
-
config = check_config(root)
|
569
|
-
if not config["project_name"] or config["project_name"].strip() == "":
|
570
|
-
console.error(
|
571
|
-
"Project name cannot be empty. Please specify a name in pyproject.toml."
|
572
|
-
)
|
573
|
-
|
574
|
-
if not config["description"] or config["description"].strip() == "":
|
575
|
-
console.error(
|
576
|
-
"Project description cannot be empty. Please specify a description in pyproject.toml."
|
577
|
-
)
|
578
|
-
|
579
|
-
if not config["authors"] or config["authors"].strip() == "":
|
580
|
-
console.error(
|
581
|
-
'Project authors cannot be empty. Please specify authors in pyproject.toml:\n authors = [{ name = "John Doe" }]'
|
582
|
-
)
|
583
|
-
|
584
|
-
invalid_chars = ["&", "<", ">", '"', "'", ";"]
|
585
|
-
for char in invalid_chars:
|
586
|
-
if char in config["project_name"]:
|
587
|
-
console.error(f"Project name contains invalid character: '{char}'")
|
588
|
-
|
589
|
-
for char in invalid_chars:
|
590
|
-
if char in config["description"]:
|
591
|
-
console.error(f"Project description contains invalid character: '{char}'")
|
290
|
+
ensure_config_file(root)
|
291
|
+
config = get_project_config(root)
|
292
|
+
validate_config(config)
|
592
293
|
|
593
294
|
with console.spinner("Packaging project ..."):
|
594
295
|
try:
|