uipath 2.1.85__py3-none-any.whl → 2.1.87__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.

uipath/_cli/__init__.py CHANGED
@@ -8,7 +8,6 @@ from .cli_auth import auth as auth
8
8
  from .cli_deploy import deploy as deploy # type: ignore
9
9
  from .cli_dev import dev as dev
10
10
  from .cli_eval import eval as eval # type: ignore
11
- from .cli_init import generate_agents_md as generate_agents_md # type: ignore
12
11
  from .cli_init import init as init # type: ignore
13
12
  from .cli_invoke import invoke as invoke # type: ignore
14
13
  from .cli_new import new as new # type: ignore
@@ -2,6 +2,7 @@ import importlib.util
2
2
  import inspect
3
3
  import sys
4
4
  from dataclasses import fields, is_dataclass
5
+ from enum import Enum
5
6
  from types import ModuleType
6
7
  from typing import (
7
8
  Any,
@@ -16,11 +17,11 @@ from typing import (
16
17
 
17
18
  from pydantic import BaseModel
18
19
 
19
- SchemaType = Literal["object", "integer", "double", "string", "boolean", "array"]
20
+ SchemaType = Literal["object", "integer", "number", "string", "boolean", "array"]
20
21
 
21
22
  TYPE_MAP: Dict[str, SchemaType] = {
22
23
  "int": "integer",
23
- "float": "double",
24
+ "float": "number",
24
25
  "str": "string",
25
26
  "bool": "boolean",
26
27
  "list": "array",
@@ -52,7 +53,25 @@ def get_type_schema(type_hint: Any) -> Dict[str, Any]:
52
53
  return {"type": "object"}
53
54
 
54
55
  if inspect.isclass(type_hint):
55
- # Handle Pydantic models
56
+ if issubclass(type_hint, Enum):
57
+ enum_values = [member.value for member in type_hint]
58
+ if not enum_values:
59
+ return {"type": "string", "enum": []}
60
+
61
+ first_value = enum_values[0]
62
+ if isinstance(first_value, str):
63
+ enum_type = "string"
64
+ elif isinstance(first_value, int):
65
+ enum_type = "integer"
66
+ elif isinstance(first_value, float):
67
+ enum_type = "number"
68
+ elif isinstance(first_value, bool):
69
+ enum_type = "boolean"
70
+ else:
71
+ enum_type = "string"
72
+
73
+ return {"type": enum_type, "enum": enum_values}
74
+
56
75
  if issubclass(type_hint, BaseModel):
57
76
  properties = {}
58
77
  required = []
@@ -1,13 +1,22 @@
1
1
  # type: ignore
2
+ import hashlib
2
3
  import json
3
4
  import os
4
5
  import re
6
+ from pathlib import Path
5
7
  from typing import Any, Dict, Optional, Tuple
6
8
 
9
+ import click
7
10
  from pydantic import BaseModel
8
11
 
9
12
  from .._utils._console import ConsoleLogger
10
13
  from ._constants import is_binary_file
14
+ from ._studio_project import (
15
+ ProjectFile,
16
+ ProjectFolder,
17
+ StudioClient,
18
+ get_folder_by_name,
19
+ )
11
20
 
12
21
  try:
13
22
  import tomllib
@@ -431,3 +440,116 @@ def files_to_include(
431
440
  )
432
441
  )
433
442
  return extra_files
443
+
444
+
445
+ def compute_normalized_hash(content: str) -> str:
446
+ """Compute hash of normalized content.
447
+
448
+ Args:
449
+ content: Content to hash
450
+
451
+ Returns:
452
+ str: SHA256 hash of the normalized content
453
+ """
454
+ try:
455
+ # Try to parse as JSON to handle formatting
456
+ json_content = json.loads(content)
457
+ normalized = json.dumps(json_content, indent=2)
458
+ except json.JSONDecodeError:
459
+ # Not JSON, normalize line endings
460
+ normalized = content.replace("\r\n", "\n").replace("\r", "\n")
461
+
462
+ return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
463
+
464
+
465
+ def collect_files_from_folder(
466
+ folder: ProjectFolder, base_path: str, files_dict: Dict[str, ProjectFile]
467
+ ) -> None:
468
+ """Recursively collect all files from a folder and its subfolders.
469
+
470
+ Args:
471
+ folder: The folder to collect files from
472
+ base_path: Base path for file paths
473
+ files_dict: Dictionary to store collected files
474
+ """
475
+ # Add files from current folder
476
+ for file in folder.files:
477
+ file_path = os.path.join(base_path, file.name)
478
+ files_dict[file_path] = file
479
+
480
+ # Recursively process subfolders
481
+ for subfolder in folder.folders:
482
+ subfolder_path = os.path.join(base_path, subfolder.name)
483
+ collect_files_from_folder(subfolder, subfolder_path, files_dict)
484
+
485
+
486
+ async def download_folder_files(
487
+ studio_client: StudioClient,
488
+ folder: ProjectFolder,
489
+ base_path: Path,
490
+ ) -> None:
491
+ """Download files from a folder recursively.
492
+
493
+ Args:
494
+ studio_client: Studio client
495
+ folder: The folder to download files from
496
+ base_path: Base path for local file storage
497
+ """
498
+ files_dict: Dict[str, ProjectFile] = {}
499
+ collect_files_from_folder(folder, "", files_dict)
500
+ for file_path, remote_file in files_dict.items():
501
+ local_path = base_path / file_path
502
+ local_path.parent.mkdir(parents=True, exist_ok=True)
503
+
504
+ # Download remote file
505
+ response = await studio_client.download_file_async(remote_file.id)
506
+ remote_content = response.read().decode("utf-8")
507
+ remote_hash = compute_normalized_hash(remote_content)
508
+
509
+ if os.path.exists(local_path):
510
+ # Read and hash local file
511
+ with open(local_path, "r", encoding="utf-8") as f:
512
+ local_content = f.read()
513
+ local_hash = compute_normalized_hash(local_content)
514
+
515
+ # Compare hashes
516
+ if local_hash != remote_hash:
517
+ styled_path = click.style(str(file_path), fg="cyan")
518
+ console.warning(f"File {styled_path}" + " differs from remote version.")
519
+ response = click.prompt("Do you want to overwrite it? (y/n)", type=str)
520
+ if response.lower() == "y":
521
+ with open(local_path, "w", encoding="utf-8", newline="\n") as f:
522
+ f.write(remote_content)
523
+ console.success(f"Updated {click.style(str(file_path), fg='cyan')}")
524
+ else:
525
+ console.info(f"Skipped {click.style(str(file_path), fg='cyan')}")
526
+ else:
527
+ console.info(
528
+ f"File {click.style(str(file_path), fg='cyan')} is up to date"
529
+ )
530
+ else:
531
+ # File doesn't exist locally, create it
532
+ with open(local_path, "w", encoding="utf-8", newline="\n") as f:
533
+ f.write(remote_content)
534
+ console.success(f"Downloaded {click.style(str(file_path), fg='cyan')}")
535
+
536
+
537
+ async def pull_project(project_id: str, download_configuration: dict[str, Path]):
538
+ studio_client = StudioClient(project_id)
539
+
540
+ with console.spinner("Pulling UiPath project files..."):
541
+ try:
542
+ structure = await studio_client.get_project_structure_async()
543
+ for source_key, destination in download_configuration.items():
544
+ source_folder = get_folder_by_name(structure, source_key)
545
+ if source_folder:
546
+ await download_folder_files(
547
+ studio_client,
548
+ source_folder,
549
+ destination,
550
+ )
551
+ else:
552
+ console.warning(f"No {source_key} folder found in remote project")
553
+
554
+ except Exception as e:
555
+ console.error(f"Failed to pull UiPath project: {str(e)}")
uipath/_cli/cli_init.py CHANGED
@@ -58,32 +58,28 @@ def generate_env_file(target_directory):
58
58
  console.success(f" Created '{relative_path}' file.")
59
59
 
60
60
 
61
- def generate_agents_md(target_directory: str) -> None:
62
- """Generate AGENTS.md file from the packaged resource.
61
+ def generate_agent_specific_file_md(target_directory: str, file_name: str) -> None:
62
+ """Generate an agent-specific file from the packaged resource.
63
63
 
64
64
  Args:
65
- target_directory: The directory where AGENTS.md should be created.
65
+ target_directory: The directory where the file should be created.
66
+ file_name: The name of the file should be created.
66
67
  """
67
- target_path = os.path.join(target_directory, "AGENTS.md")
68
+ target_path = os.path.join(target_directory, file_name)
68
69
 
69
- # Skip if file already exists
70
70
  if os.path.exists(target_path):
71
- console.info("Skipping 'AGENTS.md' creation as it already exists.")
71
+ console.info(f"Skipping '{file_name}' creation as it already exists.")
72
72
  return
73
73
 
74
74
  try:
75
- # Get the resource path using importlib.resources
76
- source_path = importlib.resources.files("uipath._resources").joinpath(
77
- "AGENTS.md"
78
- )
75
+ source_path = importlib.resources.files("uipath._resources").joinpath(file_name)
79
76
 
80
- # Copy the file to the target directory
81
77
  with importlib.resources.as_file(source_path) as s_path:
82
78
  shutil.copy(s_path, target_path)
83
79
 
84
- console.success(" Created 'AGENTS.md' file.")
80
+ console.success(f" Created '{file_name}' file.")
85
81
  except Exception as e:
86
- console.warning(f"Could not create AGENTS.md: {e}")
82
+ console.warning(f"Could not create {file_name}: {e}")
87
83
 
88
84
 
89
85
  def get_existing_settings(config_path: str) -> Optional[Dict[str, Any]]:
@@ -160,7 +156,17 @@ def init(entrypoint: str, infer_bindings: bool) -> None:
160
156
  current_directory = os.getcwd()
161
157
  generate_env_file(current_directory)
162
158
  create_telemetry_config_file(current_directory)
163
- generate_agents_md(current_directory)
159
+ generate_agent_specific_file_md(current_directory, "AGENTS.md")
160
+ generate_agent_specific_file_md(
161
+ os.path.join(current_directory, ".agent"), "CLI_REFERENCE.md"
162
+ )
163
+ generate_agent_specific_file_md(
164
+ os.path.join(current_directory, ".agent"), "REQUIRED_STRUCTURE.md"
165
+ )
166
+ generate_agent_specific_file_md(
167
+ os.path.join(current_directory, ".agent"), "SDK_REFERENCE.md"
168
+ )
169
+ generate_agent_specific_file_md(current_directory, "CLAUDE.md")
164
170
 
165
171
  result = Middlewares.next(
166
172
  "init",
uipath/_cli/cli_pull.py CHANGED
@@ -11,133 +11,27 @@ It handles:
11
11
 
12
12
  # type: ignore
13
13
  import asyncio
14
- import hashlib
15
- import json
16
14
  import os
17
- from typing import Dict, Set
15
+ from pathlib import Path
18
16
 
19
17
  import click
20
18
 
21
19
  from ..telemetry import track
22
20
  from ._utils._console import ConsoleLogger
23
21
  from ._utils._constants import UIPATH_PROJECT_ID
24
- from ._utils._studio_project import (
25
- ProjectFile,
26
- ProjectFolder,
27
- StudioClient,
28
- get_folder_by_name,
29
- )
22
+ from ._utils._project_files import pull_project
30
23
 
31
24
  console = ConsoleLogger()
32
25
 
33
26
 
34
- def compute_normalized_hash(content: str) -> str:
35
- """Compute hash of normalized content.
36
-
37
- Args:
38
- content: Content to hash
39
-
40
- Returns:
41
- str: SHA256 hash of the normalized content
42
- """
43
- try:
44
- # Try to parse as JSON to handle formatting
45
- json_content = json.loads(content)
46
- normalized = json.dumps(json_content, indent=2)
47
- except json.JSONDecodeError:
48
- # Not JSON, normalize line endings
49
- normalized = content.replace("\r\n", "\n").replace("\r", "\n")
50
-
51
- return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
52
-
53
-
54
- def collect_files_from_folder(
55
- folder: ProjectFolder, base_path: str, files_dict: Dict[str, ProjectFile]
56
- ) -> None:
57
- """Recursively collect all files from a folder and its subfolders.
58
-
59
- Args:
60
- folder: The folder to collect files from
61
- base_path: Base path for file paths
62
- files_dict: Dictionary to store collected files
63
- """
64
- # Add files from current folder
65
- for file in folder.files:
66
- file_path = os.path.join(base_path, file.name)
67
- files_dict[file_path] = file
68
-
69
- # Recursively process subfolders
70
- for subfolder in folder.folders:
71
- subfolder_path = os.path.join(base_path, subfolder.name)
72
- collect_files_from_folder(subfolder, subfolder_path, files_dict)
73
-
74
-
75
- async def download_folder_files(
76
- studio_client: StudioClient,
77
- folder: ProjectFolder,
78
- base_path: str,
79
- processed_files: Set[str],
80
- ) -> None:
81
- """Download files from a folder recursively.
82
-
83
- Args:
84
- studio_client: Studio client
85
- folder: The folder to download files from
86
- base_path: Base path for local file storage
87
- processed_files: Set to track processed files
88
- """
89
- files_dict: Dict[str, ProjectFile] = {}
90
- collect_files_from_folder(folder, "", files_dict)
91
-
92
- for file_path, remote_file in files_dict.items():
93
- local_path = os.path.join(base_path, file_path)
94
- local_dir = os.path.dirname(local_path)
95
-
96
- # Create directory if it doesn't exist
97
- if not os.path.exists(local_dir):
98
- os.makedirs(local_dir)
99
-
100
- # Download remote file
101
- response = await studio_client.download_file_async(remote_file.id)
102
- remote_content = response.read().decode("utf-8")
103
- remote_hash = compute_normalized_hash(remote_content)
104
-
105
- if os.path.exists(local_path):
106
- # Read and hash local file
107
- with open(local_path, "r", encoding="utf-8") as f:
108
- local_content = f.read()
109
- local_hash = compute_normalized_hash(local_content)
110
-
111
- # Compare hashes
112
- if local_hash != remote_hash:
113
- styled_path = click.style(str(file_path), fg="cyan")
114
- console.warning(f"File {styled_path}" + " differs from remote version.")
115
- response = click.prompt("Do you want to overwrite it? (y/n)", type=str)
116
- if response.lower() == "y":
117
- with open(local_path, "w", encoding="utf-8", newline="\n") as f:
118
- f.write(remote_content)
119
- console.success(f"Updated {click.style(str(file_path), fg='cyan')}")
120
- else:
121
- console.info(f"Skipped {click.style(str(file_path), fg='cyan')}")
122
- else:
123
- console.info(
124
- f"File {click.style(str(file_path), fg='cyan')} is up to date"
125
- )
126
- else:
127
- # File doesn't exist locally, create it
128
- with open(local_path, "w", encoding="utf-8", newline="\n") as f:
129
- f.write(remote_content)
130
- console.success(f"Downloaded {click.style(str(file_path), fg='cyan')}")
131
-
132
- processed_files.add(file_path)
133
-
134
-
135
27
  @click.command()
136
28
  @click.argument(
137
- "root", type=click.Path(exists=True, file_okay=False, dir_okay=True), default="."
29
+ "root",
30
+ type=click.Path(exists=False, file_okay=False, dir_okay=True, path_type=Path),
31
+ default=Path("."),
138
32
  )
139
33
  @track
140
- def pull(root: str) -> None:
34
+ def pull(root: Path) -> None:
141
35
  """Pull remote project files from Studio Web Project.
142
36
 
143
37
  This command pulls the remote project files from a UiPath Studio Web project.
@@ -158,42 +52,8 @@ def pull(root: str) -> None:
158
52
  if not (project_id := os.getenv(UIPATH_PROJECT_ID, False)):
159
53
  console.error("UIPATH_PROJECT_ID environment variable not found.")
160
54
 
161
- studio_client = StudioClient(project_id)
162
-
163
- with console.spinner("Pulling UiPath project files..."):
164
- try:
165
- structure = asyncio.run(studio_client.get_project_structure_async())
166
-
167
- processed_files: Set[str] = set()
168
-
169
- # Process source_code folder
170
- source_code_folder = get_folder_by_name(structure, "source_code")
171
- if source_code_folder:
172
- asyncio.run(
173
- download_folder_files(
174
- studio_client,
175
- source_code_folder,
176
- root,
177
- processed_files,
178
- )
179
- )
180
- else:
181
- console.warning("No source_code folder found in remote project")
182
-
183
- # Process evals folder
184
- evals_folder = get_folder_by_name(structure, "evals")
185
- if evals_folder:
186
- evals_path = os.path.join(root, "evals")
187
- asyncio.run(
188
- download_folder_files(
189
- studio_client,
190
- evals_folder,
191
- evals_path,
192
- processed_files,
193
- )
194
- )
195
- else:
196
- console.warning("No evals folder found in remote project")
197
-
198
- except Exception as e:
199
- console.error(f"Failed to pull UiPath project: {str(e)}")
55
+ default_download_configuration = {
56
+ "source_code": root,
57
+ "evals": root / "evals",
58
+ }
59
+ asyncio.run(pull_project(project_id, default_download_configuration))