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

@@ -0,0 +1,218 @@
1
+ """Service metadata models using Pydantic.
2
+
3
+ This module defines the metadata structure for CLI service command generation.
4
+ All models are immutable (frozen) to prevent accidental modification after creation.
5
+
6
+ The metadata describes:
7
+ - Service identification (name, resource type)
8
+ - CRUD operation parameters
9
+ - Command configuration (confirmation, dry-run support)
10
+
11
+ Example:
12
+ >>> from ._service_metadata import ServiceMetadata, CreateParameter
13
+ >>>
14
+ >>> BUCKETS_METADATA = ServiceMetadata(
15
+ ... service_name="buckets",
16
+ ... service_attr="buckets",
17
+ ... resource_type="Bucket",
18
+ ... create_params={
19
+ ... "description": CreateParameter(
20
+ ... type="str",
21
+ ... required=False,
22
+ ... help="Bucket description",
23
+ ... )
24
+ ... },
25
+ ... )
26
+ """
27
+
28
+ from typing import Any
29
+
30
+ from pydantic import BaseModel, Field, model_validator
31
+
32
+
33
+ class CreateParameter(BaseModel):
34
+ """Metadata for a single create command parameter.
35
+
36
+ This defines how a parameter should appear in the CLI command.
37
+
38
+ Attributes:
39
+ type: Type name from TYPE_REGISTRY (e.g., "str", "int", "bool", "float")
40
+ required: Whether the parameter is required (default: False)
41
+ help: Help text shown in --help output
42
+ default: Default value if not provided by user
43
+ option_name: CLI option name (default: derived from field name)
44
+
45
+ Example:
46
+ >>> description_param = CreateParameter(
47
+ ... type="str",
48
+ ... required=False,
49
+ ... help="Resource description",
50
+ ... default=None,
51
+ ... )
52
+ """
53
+
54
+ type: str
55
+ required: bool = False
56
+ help: str = ""
57
+ default: Any = None
58
+ option_name: str | None = None
59
+
60
+ class Config:
61
+ """Pydantic configuration."""
62
+
63
+ frozen = True
64
+
65
+
66
+ class DeleteCommandConfig(BaseModel):
67
+ """Configuration for delete command behavior.
68
+
69
+ Attributes:
70
+ confirmation_required: Whether to require --confirm flag (default: True)
71
+ dry_run_supported: Whether to support --dry-run flag (default: True)
72
+ confirmation_prompt: Custom confirmation prompt template
73
+
74
+ Example:
75
+ >>> delete_config = DeleteCommandConfig(
76
+ ... confirmation_required=True,
77
+ ... dry_run_supported=True,
78
+ ... confirmation_prompt="Delete {resource} '{identifier}'?",
79
+ ... )
80
+ """
81
+
82
+ confirmation_required: bool = True
83
+ dry_run_supported: bool = True
84
+ confirmation_prompt: str | None = None
85
+
86
+ class Config:
87
+ """Pydantic configuration."""
88
+
89
+ frozen = True
90
+
91
+
92
+ class ExistsCommandConfig(BaseModel):
93
+ """Configuration for exists command behavior.
94
+
95
+ Attributes:
96
+ identifier_arg_name: Name of the identifier argument (default: "name")
97
+ return_format: Format of the return value ("bool", "dict", "text")
98
+
99
+ Example:
100
+ >>> exists_config = ExistsCommandConfig(
101
+ ... identifier_arg_name="key",
102
+ ... return_format="dict",
103
+ ... )
104
+ """
105
+
106
+ identifier_arg_name: str = "name"
107
+ return_format: str = "dict" # "bool", "dict", or "text"
108
+
109
+ class Config:
110
+ """Pydantic configuration."""
111
+
112
+ frozen = True
113
+
114
+
115
+ class ServiceMetadata(BaseModel):
116
+ """Complete metadata for a service's CLI commands.
117
+
118
+ This metadata is used by ServiceCLIGenerator to auto-generate standard
119
+ CRUD commands (list, retrieve, create, delete, exists).
120
+
121
+ Attributes:
122
+ service_name: CLI command group name (e.g., "buckets", "assets")
123
+ service_attr: Attribute name on UiPath client (e.g., client.buckets)
124
+ resource_type: Human-readable resource name (e.g., "Bucket", "Asset")
125
+ resource_plural: Plural form of resource type (auto-generated if not provided)
126
+ create_params: Dictionary of parameters for create command
127
+ delete_cmd: Configuration for delete command behavior
128
+ exists_cmd: Configuration for exists command behavior
129
+ list_supports_filters: Whether list command supports additional filters
130
+ retrieve_identifier: Name of the identifier argument for retrieve
131
+
132
+ Example:
133
+ >>> metadata = ServiceMetadata(
134
+ ... service_name="buckets",
135
+ ... service_attr="buckets",
136
+ ... resource_type="Bucket",
137
+ ... create_params={
138
+ ... "description": CreateParameter(
139
+ ... type="str",
140
+ ... required=False,
141
+ ... help="Bucket description",
142
+ ... )
143
+ ... },
144
+ ... )
145
+ >>> metadata.resource_plural
146
+ 'Buckets'
147
+ """
148
+
149
+ service_name: str
150
+ service_attr: str
151
+ resource_type: str
152
+ resource_plural: str | None = None
153
+ create_params: dict[str, CreateParameter] = Field(default_factory=dict)
154
+ delete_cmd: DeleteCommandConfig = Field(default_factory=DeleteCommandConfig)
155
+ exists_cmd: ExistsCommandConfig = Field(default_factory=ExistsCommandConfig)
156
+ list_supports_filters: bool = False
157
+ retrieve_identifier: str = "name"
158
+
159
+ class Config:
160
+ """Pydantic configuration."""
161
+
162
+ frozen = True
163
+
164
+ @model_validator(mode="after")
165
+ def set_defaults(self) -> "ServiceMetadata":
166
+ """Set default values that depend on other fields.
167
+
168
+ This validator runs after model creation to set derived defaults:
169
+ - resource_plural from resource_type
170
+ - delete_cmd defaults if not explicitly set
171
+
172
+ Note:
173
+ Since the model is frozen, we use object.__setattr__ to modify fields.
174
+ """
175
+ if self.resource_plural is None:
176
+ object.__setattr__(self, "resource_plural", f"{self.resource_type}s")
177
+
178
+ return self
179
+
180
+ def validate_types(self) -> None:
181
+ """Validate that all parameter types are in TYPE_REGISTRY.
182
+
183
+ Raises:
184
+ ValueError: If any parameter type is not registered
185
+
186
+ Example:
187
+ >>> metadata = ServiceMetadata(
188
+ ... service_name="test",
189
+ ... service_attr="test",
190
+ ... resource_type="Test",
191
+ ... create_params={
192
+ ... "invalid": CreateParameter(type="InvalidType", required=False)
193
+ ... },
194
+ ... )
195
+ >>> metadata.validate_types()
196
+ Traceback (most recent call last):
197
+ ...
198
+ ValueError: Invalid type 'InvalidType' for parameter 'invalid' in service 'test'...
199
+ """
200
+ from ._type_registry import is_valid_type
201
+
202
+ for param_name, param in self.create_params.items():
203
+ if not is_valid_type(param.type):
204
+ from ._type_registry import TYPE_REGISTRY
205
+
206
+ valid_types = ", ".join(sorted(TYPE_REGISTRY.keys()))
207
+ raise ValueError(
208
+ f"Invalid type '{param.type}' for parameter '{param_name}' "
209
+ f"in service '{self.service_name}'. Valid types: {valid_types}"
210
+ )
211
+
212
+
213
+ __all__ = [
214
+ "CreateParameter",
215
+ "DeleteCommandConfig",
216
+ "ExistsCommandConfig",
217
+ "ServiceMetadata",
218
+ ]
@@ -0,0 +1,223 @@
1
+ """Protocol definition for CRUD services.
2
+
3
+ This module defines the CRUDServiceProtocol that services must implement
4
+ to be compatible with the CLI command generator.
5
+
6
+ The protocol uses Python's Protocol (PEP 544) for structural subtyping,
7
+ allowing duck-typing without requiring explicit inheritance.
8
+
9
+ Example:
10
+ >>> from typing import Protocol, runtime_checkable
11
+ >>> from ._service_protocol import CRUDServiceProtocol
12
+ >>>
13
+ >>> # A service that implements the protocol
14
+ >>> class BucketsService:
15
+ ... def list(self, folder_path=None, folder_key=None):
16
+ ... return iter([])
17
+ ...
18
+ ... def retrieve(self, name, folder_path=None, folder_key=None):
19
+ ... return {"name": name}
20
+ ...
21
+ ... # ... other CRUD methods
22
+ >>>
23
+ >>> # No explicit inheritance needed - structural typing
24
+ >>> def use_service(service: CRUDServiceProtocol):
25
+ ... return list(service.list())
26
+ """
27
+
28
+ from typing import Any, Iterator, Protocol, runtime_checkable
29
+
30
+
31
+ @runtime_checkable
32
+ class CRUDServiceProtocol(Protocol):
33
+ """Protocol for services that support standard CRUD operations.
34
+
35
+ Services implementing this protocol can use the ServiceCLIGenerator
36
+ to automatically generate CLI commands.
37
+
38
+ All methods support folder-scoped operations via folder_path or folder_key
39
+ parameters. This matches the UiPath platform's hierarchical folder structure.
40
+
41
+ Required Methods:
42
+ list: List all resources (returns iterator)
43
+ retrieve: Get a single resource by identifier
44
+ create: Create a new resource
45
+ delete: Delete a resource by identifier
46
+
47
+ Optional Methods (NOT in protocol, checked separately):
48
+ exists: Check if a resource exists (will be used if present on service)
49
+
50
+ Note:
51
+ This is a Protocol, not a base class. Services don't need to inherit
52
+ from it; they just need to implement the required methods with
53
+ compatible signatures.
54
+
55
+ The exists() method is NOT part of the protocol to allow optional
56
+ implementation. Use hasattr(service, 'exists') to check for it.
57
+ """
58
+
59
+ def list(
60
+ self,
61
+ *,
62
+ folder_path: str | None = None,
63
+ folder_key: str | None = None,
64
+ **kwargs: Any,
65
+ ) -> Iterator[Any]:
66
+ """List all resources in the specified folder.
67
+
68
+ Args:
69
+ folder_path: Folder path (e.g., "Shared")
70
+ folder_key: Folder UUID key
71
+ **kwargs: Additional service-specific parameters
72
+
73
+ Returns:
74
+ Iterator of resource objects
75
+
76
+ Example:
77
+ >>> buckets_service.list(folder_path="Shared")
78
+ <iterator of Bucket objects>
79
+ """
80
+ ...
81
+
82
+ def retrieve(
83
+ self,
84
+ name: str,
85
+ *,
86
+ folder_path: str | None = None,
87
+ folder_key: str | None = None,
88
+ **kwargs: Any,
89
+ ) -> Any:
90
+ """Retrieve a single resource by identifier.
91
+
92
+ Args:
93
+ name: Resource identifier (usually name, but could be key)
94
+ folder_path: Folder path (e.g., "Shared")
95
+ folder_key: Folder UUID key
96
+ **kwargs: Additional service-specific parameters
97
+
98
+ Returns:
99
+ Resource object
100
+
101
+ Raises:
102
+ LookupError: If resource not found
103
+
104
+ Example:
105
+ >>> buckets_service.retrieve("my-bucket", folder_path="Shared")
106
+ Bucket(name="my-bucket", ...)
107
+ """
108
+ ...
109
+
110
+ def create(
111
+ self,
112
+ name: str,
113
+ *,
114
+ folder_path: str | None = None,
115
+ folder_key: str | None = None,
116
+ **kwargs: Any,
117
+ ) -> Any:
118
+ """Create a new resource.
119
+
120
+ Args:
121
+ name: Resource name
122
+ folder_path: Folder path (e.g., "Shared")
123
+ folder_key: Folder UUID key
124
+ **kwargs: Additional service-specific parameters (from metadata)
125
+
126
+ Returns:
127
+ Created resource object
128
+
129
+ Example:
130
+ >>> buckets_service.create(
131
+ ... "my-bucket",
132
+ ... folder_path="Shared",
133
+ ... description="My bucket"
134
+ ... )
135
+ Bucket(name="my-bucket", description="My bucket", ...)
136
+ """
137
+ ...
138
+
139
+ def delete(
140
+ self,
141
+ name: str,
142
+ *,
143
+ folder_path: str | None = None,
144
+ folder_key: str | None = None,
145
+ **kwargs: Any,
146
+ ) -> None:
147
+ """Delete a resource by identifier.
148
+
149
+ Args:
150
+ name: Resource identifier to delete
151
+ folder_path: Folder path (e.g., "Shared")
152
+ folder_key: Folder UUID key
153
+ **kwargs: Additional service-specific parameters
154
+
155
+ Raises:
156
+ LookupError: If resource not found
157
+
158
+ Example:
159
+ >>> buckets_service.delete("my-bucket", folder_path="Shared")
160
+ """
161
+ ...
162
+
163
+
164
+ def has_exists_method(service: Any) -> bool:
165
+ """Check if a service has the optional exists() method.
166
+
167
+ This is separate from the Protocol because exists() is optional.
168
+ Not all services need to implement it.
169
+
170
+ Args:
171
+ service: Service instance to check
172
+
173
+ Returns:
174
+ True if service has a callable exists() method
175
+
176
+ Example:
177
+ >>> class ServiceWithExists:
178
+ ... def exists(self, name, **kwargs):
179
+ ... return True
180
+ >>>
181
+ >>> service = ServiceWithExists()
182
+ >>> has_exists_method(service)
183
+ True
184
+ """
185
+ return hasattr(service, "exists") and callable(service.exists)
186
+
187
+
188
+ def validate_service_protocol(service: Any, service_name: str) -> None:
189
+ """Validate that a service implements the CRUDServiceProtocol.
190
+
191
+ Args:
192
+ service: Service instance to validate
193
+ service_name: Name of the service for error messages
194
+
195
+ Raises:
196
+ TypeError: If service doesn't implement required methods
197
+
198
+ Example:
199
+ >>> class IncompleteService:
200
+ ... def list(self):
201
+ ... pass
202
+ ... # Missing other methods
203
+ >>>
204
+ >>> validate_service_protocol(IncompleteService(), "incomplete")
205
+ Traceback (most recent call last):
206
+ ...
207
+ TypeError: Service 'incomplete' must implement: retrieve, create, delete
208
+ """
209
+ required_methods = ["list", "retrieve", "create", "delete"]
210
+ missing_methods = []
211
+ for method_name in required_methods:
212
+ if not hasattr(service, method_name) or not callable(
213
+ getattr(service, method_name)
214
+ ):
215
+ missing_methods.append(method_name)
216
+
217
+ if missing_methods:
218
+ raise TypeError(
219
+ f"Service '{service_name}' must implement: {', '.join(missing_methods)}"
220
+ )
221
+
222
+
223
+ __all__ = ["CRUDServiceProtocol", "validate_service_protocol", "has_exists_method"]
@@ -0,0 +1,106 @@
1
+ """Type registry for safe parameter type resolution.
2
+
3
+ This module provides a safe way to resolve type strings to Python types,
4
+ replacing the use of eval() which is a security vulnerability.
5
+
6
+ The TYPE_REGISTRY is used by the ServiceMetadata system to convert type
7
+ strings (e.g., "str", "int") to actual Python types for Click parameter
8
+ validation.
9
+
10
+ Security Note:
11
+ This registry approach prevents arbitrary code execution that would be
12
+ possible with eval(). Only explicitly registered types can be used.
13
+
14
+ Example:
15
+ >>> from ._type_registry import get_type
16
+ >>> str_type = get_type("str")
17
+ >>> str_type is str
18
+ True
19
+ >>> get_type("InvalidType") # Raises ValueError
20
+ ValueError: Unknown type: 'InvalidType'. Valid types: bool, float, int, str
21
+ """
22
+
23
+ from typing import Any, Type
24
+
25
+ TYPE_REGISTRY: dict[str, Type[Any]] = {
26
+ "str": str,
27
+ "int": int,
28
+ "bool": bool,
29
+ "float": float,
30
+ }
31
+
32
+
33
+ def get_type(type_name: str) -> Type[Any]:
34
+ """Get Python type from string name safely.
35
+
36
+ Args:
37
+ type_name: String name of the type (e.g., "str", "int", "bool", "float")
38
+
39
+ Returns:
40
+ The Python type corresponding to the type name
41
+
42
+ Raises:
43
+ ValueError: If type_name is not in the registry
44
+
45
+ Example:
46
+ >>> get_type("str")
47
+ <class 'str'>
48
+ >>> get_type("int")
49
+ <class 'int'>
50
+ >>> get_type("InvalidType")
51
+ Traceback (most recent call last):
52
+ ...
53
+ ValueError: Unknown type: 'InvalidType'. Valid types: bool, float, int, str
54
+ """
55
+ param_type = TYPE_REGISTRY.get(type_name)
56
+ if param_type is None:
57
+ valid_types = ", ".join(sorted(TYPE_REGISTRY.keys()))
58
+ raise ValueError(f"Unknown type: '{type_name}'. Valid types: {valid_types}")
59
+ return param_type
60
+
61
+
62
+ def register_type(type_name: str, type_class: Type[Any]) -> None:
63
+ """Register a new type in the registry.
64
+
65
+ This function allows extending the registry with custom types if needed.
66
+ Use with caution and only register types that are safe for CLI parameters.
67
+
68
+ Args:
69
+ type_name: String name for the type
70
+ type_class: The Python type class
71
+
72
+ Raises:
73
+ ValueError: If type_name already exists in registry
74
+
75
+ Example:
76
+ >>> from pathlib import Path
77
+ >>> register_type("path", Path)
78
+ >>> get_type("path")
79
+ <class 'pathlib.Path'>
80
+ """
81
+ if type_name in TYPE_REGISTRY:
82
+ raise ValueError(
83
+ f"Type '{type_name}' is already registered as {TYPE_REGISTRY[type_name]}"
84
+ )
85
+ TYPE_REGISTRY[type_name] = type_class
86
+
87
+
88
+ def is_valid_type(type_name: str) -> bool:
89
+ """Check if a type name is registered.
90
+
91
+ Args:
92
+ type_name: String name to check
93
+
94
+ Returns:
95
+ True if type is registered, False otherwise
96
+
97
+ Example:
98
+ >>> is_valid_type("str")
99
+ True
100
+ >>> is_valid_type("InvalidType")
101
+ False
102
+ """
103
+ return type_name in TYPE_REGISTRY
104
+
105
+
106
+ __all__ = ["TYPE_REGISTRY", "get_type", "register_type", "is_valid_type"]
@@ -0,0 +1,127 @@
1
+ """Common validators for CLI commands.
2
+
3
+ This module provides reusable validation functions for common CLI inputs
4
+ like folder paths, UUIDs, and resource names.
5
+ """
6
+
7
+ import re
8
+ from typing import Optional
9
+
10
+ import click
11
+
12
+
13
+ def validate_folder_path(ctx, param, value: Optional[str]) -> Optional[str]:
14
+ """Validate folder path format.
15
+
16
+ Folder paths should be in the format: "Folder1/Subfolder2"
17
+
18
+ Args:
19
+ ctx: Click context
20
+ param: Click parameter
21
+ value: Folder path to validate
22
+
23
+ Returns:
24
+ Validated folder path
25
+
26
+ Raises:
27
+ click.BadParameter: If folder path is invalid
28
+ """
29
+ if value is None:
30
+ return None
31
+
32
+ if value == "":
33
+ return value
34
+
35
+ if value.startswith("/") or value.endswith("/"):
36
+ raise click.BadParameter("Folder path should not start or end with '/'")
37
+
38
+ return value
39
+
40
+
41
+ def validate_uuid(ctx, param, value: Optional[str]) -> Optional[str]:
42
+ """Validate UUID format.
43
+
44
+ Args:
45
+ ctx: Click context
46
+ param: Click parameter
47
+ value: UUID to validate
48
+
49
+ Returns:
50
+ Validated UUID
51
+
52
+ Raises:
53
+ click.BadParameter: If UUID is invalid
54
+ """
55
+ if value is None:
56
+ return None
57
+
58
+ # UUID regex pattern
59
+ uuid_pattern = re.compile(
60
+ r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.IGNORECASE
61
+ )
62
+
63
+ if not uuid_pattern.match(value):
64
+ raise click.BadParameter(
65
+ f"'{value}' is not a valid UUID. "
66
+ "Expected format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
67
+ )
68
+
69
+ return value
70
+
71
+
72
+ def validate_resource_name(ctx, param, value: Optional[str]) -> Optional[str]:
73
+ """Validate resource name.
74
+
75
+ Resource names should:
76
+ - Not be empty
77
+ - Not contain special characters that might cause issues
78
+
79
+ Args:
80
+ ctx: Click context
81
+ param: Click parameter
82
+ value: Resource name to validate
83
+
84
+ Returns:
85
+ Validated resource name
86
+
87
+ Raises:
88
+ click.BadParameter: If resource name is invalid
89
+ """
90
+ if value is None:
91
+ return None
92
+
93
+ if not value.strip():
94
+ raise click.BadParameter("Resource name cannot be empty")
95
+
96
+ invalid_chars = ["<", ">", ":", '"', "|", "?", "*"]
97
+ for char in invalid_chars:
98
+ if char in value:
99
+ raise click.BadParameter(
100
+ f"Resource name contains invalid character: '{char}'"
101
+ )
102
+
103
+ return value
104
+
105
+
106
+ def validate_mutually_exclusive(ctx, param, value, exclusive_with: str):
107
+ """Validate that two options are mutually exclusive.
108
+
109
+ Args:
110
+ ctx: Click context
111
+ param: Click parameter
112
+ value: Current parameter value (can be any type)
113
+ exclusive_with: Name of the mutually exclusive parameter
114
+
115
+ Returns:
116
+ Validated value
117
+
118
+ Raises:
119
+ click.UsageError: If both parameters are provided
120
+ """
121
+ if value is not None and ctx.params.get(exclusive_with) is not None:
122
+ raise click.UsageError(
123
+ f"--{param.name} and --{exclusive_with} are mutually exclusive. "
124
+ "Provide only one."
125
+ )
126
+
127
+ return value