uipath 2.0.0.dev3__py3-none-any.whl → 2.0.1.dev1__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/__init__.py +24 -0
- uipath/_cli/README.md +11 -0
- uipath/_cli/__init__.py +54 -0
- uipath/_cli/_auth/_auth_server.py +165 -0
- uipath/_cli/_auth/_models.py +51 -0
- uipath/_cli/_auth/_oidc_utils.py +69 -0
- uipath/_cli/_auth/_portal_service.py +163 -0
- uipath/_cli/_auth/_utils.py +51 -0
- uipath/_cli/_auth/auth_config.json +6 -0
- uipath/_cli/_auth/index.html +167 -0
- uipath/_cli/_auth/localhost.crt +25 -0
- uipath/_cli/_auth/localhost.key +27 -0
- uipath/_cli/_runtime/_contracts.py +429 -0
- uipath/_cli/_runtime/_logging.py +193 -0
- uipath/_cli/_runtime/_runtime.py +264 -0
- uipath/_cli/_templates/.psmdcp.template +9 -0
- uipath/_cli/_templates/.rels.template +5 -0
- uipath/_cli/_templates/[Content_Types].xml.template +9 -0
- uipath/_cli/_templates/main.py.template +25 -0
- uipath/_cli/_templates/package.nuspec.template +10 -0
- uipath/_cli/_utils/_common.py +24 -0
- uipath/_cli/_utils/_input_args.py +126 -0
- uipath/_cli/_utils/_parse_ast.py +542 -0
- uipath/_cli/cli_auth.py +97 -0
- uipath/_cli/cli_deploy.py +13 -0
- uipath/_cli/cli_init.py +113 -0
- uipath/_cli/cli_new.py +76 -0
- uipath/_cli/cli_pack.py +337 -0
- uipath/_cli/cli_publish.py +113 -0
- uipath/_cli/cli_run.py +133 -0
- uipath/_cli/middlewares.py +113 -0
- uipath/_config.py +6 -0
- uipath/_execution_context.py +83 -0
- uipath/_folder_context.py +62 -0
- uipath/_models/__init__.py +37 -0
- uipath/_models/action_schema.py +26 -0
- uipath/_models/actions.py +64 -0
- uipath/_models/assets.py +48 -0
- uipath/_models/connections.py +51 -0
- uipath/_models/context_grounding.py +18 -0
- uipath/_models/context_grounding_index.py +60 -0
- uipath/_models/exceptions.py +6 -0
- uipath/_models/interrupt_models.py +28 -0
- uipath/_models/job.py +66 -0
- uipath/_models/llm_gateway.py +101 -0
- uipath/_models/processes.py +48 -0
- uipath/_models/queues.py +167 -0
- uipath/_services/__init__.py +26 -0
- uipath/_services/_base_service.py +250 -0
- uipath/_services/actions_service.py +271 -0
- uipath/_services/api_client.py +89 -0
- uipath/_services/assets_service.py +257 -0
- uipath/_services/buckets_service.py +268 -0
- uipath/_services/connections_service.py +185 -0
- uipath/_services/connections_service.pyi +50 -0
- uipath/_services/context_grounding_service.py +402 -0
- uipath/_services/folder_service.py +49 -0
- uipath/_services/jobs_service.py +265 -0
- uipath/_services/llm_gateway_service.py +311 -0
- uipath/_services/processes_service.py +168 -0
- uipath/_services/queues_service.py +314 -0
- uipath/_uipath.py +98 -0
- uipath/_utils/__init__.py +17 -0
- uipath/_utils/_endpoint.py +79 -0
- uipath/_utils/_infer_bindings.py +30 -0
- uipath/_utils/_logs.py +15 -0
- uipath/_utils/_request_override.py +18 -0
- uipath/_utils/_request_spec.py +23 -0
- uipath/_utils/_user_agent.py +16 -0
- uipath/_utils/constants.py +25 -0
- uipath/py.typed +0 -0
- {uipath-2.0.0.dev3.dist-info → uipath-2.0.1.dev1.dist-info}/METADATA +2 -3
- uipath-2.0.1.dev1.dist-info/RECORD +75 -0
- uipath-2.0.0.dev3.dist-info/RECORD +0 -4
- {uipath-2.0.0.dev3.dist-info → uipath-2.0.1.dev1.dist-info}/WHEEL +0 -0
- {uipath-2.0.0.dev3.dist-info → uipath-2.0.1.dev1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
# type: ignore
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import os
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from ..._services import AssetsService, BucketsService, ProcessesService
|
|
9
|
+
from ..._utils import get_inferred_bindings_names
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ServiceMethodCall:
|
|
14
|
+
"""Represents a call to a service method with its parameters."""
|
|
15
|
+
|
|
16
|
+
method_name: str
|
|
17
|
+
args: List[Any] = field(default_factory=list)
|
|
18
|
+
kwargs: Dict[str, Any] = field(default_factory=dict)
|
|
19
|
+
line_number: int = 0
|
|
20
|
+
|
|
21
|
+
def extract_string_arg(self, index: int = 0) -> Optional[str]:
|
|
22
|
+
"""Extract a string argument at the given position if it exists."""
|
|
23
|
+
if index < len(self.args):
|
|
24
|
+
if isinstance(self.args[index], str):
|
|
25
|
+
return self.args[index]
|
|
26
|
+
elif isinstance(self.args[index], (int, float, bool)):
|
|
27
|
+
return str(self.args[index])
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
service_name_resource_mapping = {
|
|
32
|
+
"assets": "asset",
|
|
33
|
+
"processes": "process",
|
|
34
|
+
"buckets": "bucket",
|
|
35
|
+
"connections": "connection",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def transform_connector_name(connector_name: str) -> str:
|
|
40
|
+
"""Transform connector name from underscore format to hyphenated format.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
connector_name: Connector name in format "one_two"
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
str: Connector name in format "uipath-one-two"
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
>>> transform_connector_name("google_gmail")
|
|
50
|
+
'uipath-google-gmail'
|
|
51
|
+
>>> transform_connector_name("salesforce_sfdc")
|
|
52
|
+
'uipath-salesforce-sfdc'
|
|
53
|
+
"""
|
|
54
|
+
if not connector_name:
|
|
55
|
+
return ""
|
|
56
|
+
parts = connector_name.split("_")
|
|
57
|
+
return f"uipath-{'-'.join(parts)}"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class ServiceUsage:
|
|
62
|
+
"""Collects all method calls for a specific service type."""
|
|
63
|
+
|
|
64
|
+
service_name: str
|
|
65
|
+
method_calls: List[ServiceMethodCall] = field(default_factory=list)
|
|
66
|
+
|
|
67
|
+
def get_component_info(self) -> List[Dict[str, str]]:
|
|
68
|
+
"""Extract component names and folders based on the service type."""
|
|
69
|
+
result = []
|
|
70
|
+
|
|
71
|
+
if self.service_name == "assets":
|
|
72
|
+
for call in self.method_calls:
|
|
73
|
+
inferred_bindings = get_inferred_bindings_names(AssetsService)
|
|
74
|
+
if call.method_name in inferred_bindings:
|
|
75
|
+
name = extract_parameter(
|
|
76
|
+
call, inferred_bindings[call.method_name]["name"], 0
|
|
77
|
+
)
|
|
78
|
+
folder_path = extract_parameter(
|
|
79
|
+
call, inferred_bindings[call.method_name]["folder_path"]
|
|
80
|
+
)
|
|
81
|
+
if name:
|
|
82
|
+
result.append(
|
|
83
|
+
{
|
|
84
|
+
"name": name,
|
|
85
|
+
"folder": folder_path or "",
|
|
86
|
+
"method": call.method_name,
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
elif self.service_name == "processes":
|
|
91
|
+
for call in self.method_calls:
|
|
92
|
+
inferred_bindings = get_inferred_bindings_names(ProcessesService)
|
|
93
|
+
if call.method_name in inferred_bindings:
|
|
94
|
+
name = extract_parameter(
|
|
95
|
+
call, inferred_bindings[call.method_name]["name"], 0
|
|
96
|
+
)
|
|
97
|
+
folder_path = extract_parameter(
|
|
98
|
+
call, inferred_bindings[call.method_name]["folder_path"]
|
|
99
|
+
)
|
|
100
|
+
if name:
|
|
101
|
+
result.append(
|
|
102
|
+
{
|
|
103
|
+
"name": name,
|
|
104
|
+
"folder": folder_path or "",
|
|
105
|
+
"method": call.method_name,
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
elif self.service_name == "buckets":
|
|
110
|
+
for call in self.method_calls:
|
|
111
|
+
inferred_bindings = get_inferred_bindings_names(BucketsService)
|
|
112
|
+
if call.method_name in inferred_bindings:
|
|
113
|
+
name = extract_parameter(
|
|
114
|
+
call, inferred_bindings[call.method_name]["name"], 0
|
|
115
|
+
)
|
|
116
|
+
folder_path = extract_parameter(
|
|
117
|
+
call, inferred_bindings[call.method_name]["folder_path"]
|
|
118
|
+
)
|
|
119
|
+
if name:
|
|
120
|
+
result.append(
|
|
121
|
+
{
|
|
122
|
+
"name": name,
|
|
123
|
+
"folder": folder_path or "",
|
|
124
|
+
"method": call.method_name,
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
elif self.service_name == "connections":
|
|
129
|
+
for call in self.method_calls:
|
|
130
|
+
connection_id = None
|
|
131
|
+
if len(call.args) > 0:
|
|
132
|
+
connection_id = call.args[0]
|
|
133
|
+
if connection_id:
|
|
134
|
+
result.append(
|
|
135
|
+
{
|
|
136
|
+
"name": str(connection_id),
|
|
137
|
+
"connector": call.method_name,
|
|
138
|
+
"method": "connector",
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return result
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def extract_parameter(
|
|
146
|
+
method_call: ServiceMethodCall,
|
|
147
|
+
param_name: str,
|
|
148
|
+
position_index: Optional[int] = None,
|
|
149
|
+
) -> Optional[Any]:
|
|
150
|
+
"""Extract a parameter from a method call, checking both keyword and positional arguments.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
method_call: The ServiceMethodCall object
|
|
154
|
+
param_name: The name of the parameter to extract
|
|
155
|
+
position_index: Optional position of the parameter if passed as a positional argument
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
The parameter value if found, None otherwise
|
|
159
|
+
"""
|
|
160
|
+
if param_name in method_call.kwargs:
|
|
161
|
+
return method_call.kwargs[param_name]
|
|
162
|
+
|
|
163
|
+
if position_index is not None and position_index < len(method_call.args):
|
|
164
|
+
return method_call.args[position_index]
|
|
165
|
+
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def parse_local_module(
|
|
170
|
+
module_path: str, base_dir: str
|
|
171
|
+
) -> Dict[str, List[Dict[str, str]]]:
|
|
172
|
+
"""Parse a local module and extract SDK usage.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
module_path: Import path of the module (e.g., 'myapp.utils')
|
|
176
|
+
base_dir: Base directory to resolve relative imports
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Dictionary of SDK usage from the module
|
|
180
|
+
"""
|
|
181
|
+
# Convert module path to file path
|
|
182
|
+
file_path = os.path.join(base_dir, *module_path.split(".")) + ".py"
|
|
183
|
+
|
|
184
|
+
# Check if the file exists
|
|
185
|
+
if not os.path.exists(file_path):
|
|
186
|
+
# Try as a package with __init__.py
|
|
187
|
+
file_path = os.path.join(base_dir, *module_path.split("."), "__init__.py")
|
|
188
|
+
if not os.path.exists(file_path):
|
|
189
|
+
return {}
|
|
190
|
+
|
|
191
|
+
# Parse the module
|
|
192
|
+
try:
|
|
193
|
+
with open(file_path, "r") as f:
|
|
194
|
+
source_code = f.read()
|
|
195
|
+
return parse_sdk_usage(source_code, base_dir)
|
|
196
|
+
except Exception:
|
|
197
|
+
return {}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class UiPathTracker:
|
|
201
|
+
"""Tracks UiPath usage throughout the code."""
|
|
202
|
+
|
|
203
|
+
def __init__(self, source_code: str, base_dir: str = ""):
|
|
204
|
+
self.source_code = source_code
|
|
205
|
+
self.base_dir = base_dir
|
|
206
|
+
self.tree = ast.parse(source_code)
|
|
207
|
+
self.sdk_imports: Dict[str, str] = {} # Import alias -> original module
|
|
208
|
+
self.sdk_instances: Dict[str, str] = {} # Instance name -> class
|
|
209
|
+
self.local_imports: List[str] = [] # List of local module imports
|
|
210
|
+
self.service_usage: Dict[str, ServiceUsage] = {
|
|
211
|
+
"assets": ServiceUsage("assets"),
|
|
212
|
+
"processes": ServiceUsage("processes"),
|
|
213
|
+
"buckets": ServiceUsage("buckets"),
|
|
214
|
+
"actions": ServiceUsage("actions"),
|
|
215
|
+
"context_grounding": ServiceUsage("context_grounding"),
|
|
216
|
+
"api_client": ServiceUsage("api_client"),
|
|
217
|
+
"connections": ServiceUsage("connections"), # Add connections service
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
def analyze(self) -> None:
|
|
221
|
+
"""Run all analysis steps."""
|
|
222
|
+
self._find_imports()
|
|
223
|
+
self._find_instances()
|
|
224
|
+
self._find_method_calls()
|
|
225
|
+
|
|
226
|
+
def _find_imports(self) -> None:
|
|
227
|
+
"""Find all imports of UiPath and local modules."""
|
|
228
|
+
|
|
229
|
+
class ImportVisitor(ast.NodeVisitor):
|
|
230
|
+
def __init__(self):
|
|
231
|
+
self.imports = {}
|
|
232
|
+
self.local_imports = []
|
|
233
|
+
|
|
234
|
+
def visit_Import(self, node):
|
|
235
|
+
for alias in node.names:
|
|
236
|
+
if alias.name == "uipath":
|
|
237
|
+
self.imports[alias.asname or alias.name] = alias.name
|
|
238
|
+
elif (
|
|
239
|
+
not alias.name.startswith(("__", "builtins", "typing"))
|
|
240
|
+
and "." not in alias.name
|
|
241
|
+
):
|
|
242
|
+
# Potential local import
|
|
243
|
+
self.local_imports.append(alias.name)
|
|
244
|
+
self.generic_visit(node)
|
|
245
|
+
|
|
246
|
+
def visit_ImportFrom(self, node):
|
|
247
|
+
if node.module == "uipath":
|
|
248
|
+
for alias in node.names:
|
|
249
|
+
if alias.name == "UiPath":
|
|
250
|
+
self.imports[alias.asname or alias.name] = "uipath.UiPath"
|
|
251
|
+
elif node.module and not node.module.startswith(
|
|
252
|
+
("__", "builtins", "typing")
|
|
253
|
+
):
|
|
254
|
+
# Potential local import
|
|
255
|
+
self.local_imports.append(node.module)
|
|
256
|
+
self.generic_visit(node)
|
|
257
|
+
|
|
258
|
+
visitor = ImportVisitor()
|
|
259
|
+
visitor.visit(self.tree)
|
|
260
|
+
self.sdk_imports = visitor.imports
|
|
261
|
+
self.local_imports = visitor.local_imports
|
|
262
|
+
|
|
263
|
+
def _find_instances(self) -> None:
|
|
264
|
+
"""Find all instances created from UiPath."""
|
|
265
|
+
|
|
266
|
+
class InstanceVisitor(ast.NodeVisitor):
|
|
267
|
+
def __init__(self, sdk_imports):
|
|
268
|
+
self.sdk_imports = sdk_imports
|
|
269
|
+
self.instances = {}
|
|
270
|
+
|
|
271
|
+
def visit_Assign(self, node):
|
|
272
|
+
if isinstance(node.value, ast.Call):
|
|
273
|
+
call = node.value
|
|
274
|
+
if (
|
|
275
|
+
isinstance(call.func, ast.Name)
|
|
276
|
+
and call.func.id in self.sdk_imports
|
|
277
|
+
):
|
|
278
|
+
for target in node.targets:
|
|
279
|
+
if isinstance(target, ast.Name):
|
|
280
|
+
self.instances[target.id] = "UiPath"
|
|
281
|
+
self.generic_visit(node)
|
|
282
|
+
|
|
283
|
+
visitor = InstanceVisitor(self.sdk_imports)
|
|
284
|
+
visitor.visit(self.tree)
|
|
285
|
+
self.sdk_instances = visitor.instances
|
|
286
|
+
|
|
287
|
+
def _find_method_calls(self) -> None:
|
|
288
|
+
"""Find all method calls on UiPath instances."""
|
|
289
|
+
|
|
290
|
+
class MethodCallVisitor(ast.NodeVisitor):
|
|
291
|
+
def __init__(self, source_code, sdk_instances, service_usage):
|
|
292
|
+
self.source_code = source_code
|
|
293
|
+
self.sdk_instances = sdk_instances
|
|
294
|
+
self.service_usage = service_usage
|
|
295
|
+
|
|
296
|
+
def visit_Call(self, node):
|
|
297
|
+
if isinstance(node.func, ast.Attribute) and isinstance(
|
|
298
|
+
node.func.value, ast.Attribute
|
|
299
|
+
):
|
|
300
|
+
if (
|
|
301
|
+
isinstance(node.func.value.value, ast.Name)
|
|
302
|
+
and node.func.value.value.id in self.sdk_instances
|
|
303
|
+
):
|
|
304
|
+
service_name = node.func.value.attr
|
|
305
|
+
method_name = node.func.attr
|
|
306
|
+
|
|
307
|
+
if service_name in self.service_usage:
|
|
308
|
+
# Extract arguments
|
|
309
|
+
args = []
|
|
310
|
+
for arg in node.args:
|
|
311
|
+
if isinstance(arg, ast.Constant) and isinstance(
|
|
312
|
+
arg.value, str
|
|
313
|
+
):
|
|
314
|
+
args.append(arg.value)
|
|
315
|
+
elif isinstance(arg, ast.Constant):
|
|
316
|
+
# Handle non-string constants normally
|
|
317
|
+
args.append(arg.value)
|
|
318
|
+
else:
|
|
319
|
+
# For expressions and variables, prefix with EXPR$
|
|
320
|
+
source_segment = ast.get_source_segment(
|
|
321
|
+
self.source_code, arg
|
|
322
|
+
)
|
|
323
|
+
args.append(f"EXPR${source_segment}")
|
|
324
|
+
|
|
325
|
+
kwargs = {}
|
|
326
|
+
for keyword in node.keywords:
|
|
327
|
+
if isinstance(keyword.value, ast.Constant):
|
|
328
|
+
kwargs[keyword.arg] = keyword.value.value
|
|
329
|
+
else:
|
|
330
|
+
source_segment = ast.get_source_segment(
|
|
331
|
+
self.source_code, keyword.value
|
|
332
|
+
)
|
|
333
|
+
kwargs[keyword.arg] = f"EXPR${source_segment}"
|
|
334
|
+
|
|
335
|
+
method_call = ServiceMethodCall(
|
|
336
|
+
method_name=method_name,
|
|
337
|
+
args=args,
|
|
338
|
+
kwargs=kwargs,
|
|
339
|
+
line_number=node.lineno,
|
|
340
|
+
)
|
|
341
|
+
self.service_usage[service_name].method_calls.append(
|
|
342
|
+
method_call
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
elif isinstance(node.func, ast.Attribute) and isinstance(
|
|
346
|
+
node.func.value, ast.Attribute
|
|
347
|
+
):
|
|
348
|
+
if (
|
|
349
|
+
isinstance(node.func.value.value, ast.Name)
|
|
350
|
+
and node.func.value.value.id in self.sdk_instances
|
|
351
|
+
and node.func.value.attr == "connections"
|
|
352
|
+
):
|
|
353
|
+
connector_name = node.func.attr
|
|
354
|
+
|
|
355
|
+
args = []
|
|
356
|
+
for arg in node.args:
|
|
357
|
+
if isinstance(arg, ast.Constant) and isinstance(
|
|
358
|
+
arg.value, (str, int)
|
|
359
|
+
):
|
|
360
|
+
args.append(arg.value)
|
|
361
|
+
elif isinstance(arg, ast.Constant):
|
|
362
|
+
args.append(arg.value)
|
|
363
|
+
else:
|
|
364
|
+
source_segment = ast.get_source_segment(
|
|
365
|
+
self.source_code, arg
|
|
366
|
+
)
|
|
367
|
+
args.append(f"EXPR${source_segment}")
|
|
368
|
+
|
|
369
|
+
kwargs = {}
|
|
370
|
+
for keyword in node.keywords:
|
|
371
|
+
if isinstance(keyword.value, ast.Constant):
|
|
372
|
+
kwargs[keyword.arg] = keyword.value.value
|
|
373
|
+
else:
|
|
374
|
+
source_segment = ast.get_source_segment(
|
|
375
|
+
self.source_code, keyword.value
|
|
376
|
+
)
|
|
377
|
+
kwargs[keyword.arg] = f"EXPR${source_segment}"
|
|
378
|
+
|
|
379
|
+
method_call = ServiceMethodCall(
|
|
380
|
+
method_name=connector_name,
|
|
381
|
+
args=args,
|
|
382
|
+
kwargs=kwargs,
|
|
383
|
+
line_number=node.lineno,
|
|
384
|
+
)
|
|
385
|
+
self.service_usage["connections"].method_calls.append(
|
|
386
|
+
method_call
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
self.generic_visit(node)
|
|
390
|
+
|
|
391
|
+
visitor = MethodCallVisitor(
|
|
392
|
+
self.source_code, self.sdk_instances, self.service_usage
|
|
393
|
+
)
|
|
394
|
+
visitor.visit(self.tree)
|
|
395
|
+
|
|
396
|
+
def get_results(self) -> Dict[str, List[Dict[str, str]]]:
|
|
397
|
+
"""Get the analysis results organized by service type."""
|
|
398
|
+
results = {}
|
|
399
|
+
for service_name, usage in self.service_usage.items():
|
|
400
|
+
components = usage.get_component_info()
|
|
401
|
+
if components:
|
|
402
|
+
results[service_name] = components
|
|
403
|
+
return results
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def parse_sdk_usage(
|
|
407
|
+
source_code: str, base_dir: str = ""
|
|
408
|
+
) -> Dict[str, List[Dict[str, str]]]:
|
|
409
|
+
"""Parse the source code and return UiPath usage information.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
source_code: The Python source code to analyze
|
|
413
|
+
base_dir: Base directory to resolve relative imports
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
Dictionary of SDK usage information
|
|
417
|
+
"""
|
|
418
|
+
tracker = UiPathTracker(source_code, base_dir)
|
|
419
|
+
tracker.analyze()
|
|
420
|
+
results = tracker.get_results()
|
|
421
|
+
|
|
422
|
+
# Parse local imports recursively
|
|
423
|
+
if base_dir:
|
|
424
|
+
for module_path in tracker.local_imports:
|
|
425
|
+
module_results = parse_local_module(module_path, base_dir)
|
|
426
|
+
|
|
427
|
+
# Merge results
|
|
428
|
+
for service_name, components in module_results.items():
|
|
429
|
+
if service_name in results:
|
|
430
|
+
results[service_name].extend(components)
|
|
431
|
+
else:
|
|
432
|
+
results[service_name] = components
|
|
433
|
+
|
|
434
|
+
return results
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def convert_to_bindings_format(sdk_usage_data):
|
|
438
|
+
"""Convert the output of parse_sdk_usage to a structure similar to bindings_v2.json.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
sdk_usage_data: Dictionary output from parse_sdk_usage
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
Dictionary in bindings_v2.json format
|
|
445
|
+
"""
|
|
446
|
+
bindings = {"version": "2.0", "resources": []}
|
|
447
|
+
|
|
448
|
+
for resource_type, components in sdk_usage_data.items():
|
|
449
|
+
for component in components:
|
|
450
|
+
if resource_type == "connections":
|
|
451
|
+
connection_id = component.get("name", "")
|
|
452
|
+
connector_name = transform_connector_name(
|
|
453
|
+
component.get("connector", "")
|
|
454
|
+
)
|
|
455
|
+
is_connection_id_expression = connection_id.startswith("EXPR$")
|
|
456
|
+
connection_id = connection_id.replace("EXPR$", "")
|
|
457
|
+
resource_entry = {
|
|
458
|
+
"resource": "connection",
|
|
459
|
+
"key": connection_id,
|
|
460
|
+
"value": {
|
|
461
|
+
"ConnectionId": {
|
|
462
|
+
"defaultValue": connection_id,
|
|
463
|
+
"isExpression": is_connection_id_expression,
|
|
464
|
+
"displayName": "Connection",
|
|
465
|
+
"description": "The connection to be used",
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
"metadata": {
|
|
469
|
+
"BindingsVersion": "2.1",
|
|
470
|
+
"Connector": connector_name,
|
|
471
|
+
"UseConnectionService": "True",
|
|
472
|
+
},
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
bindings["resources"].append(resource_entry)
|
|
476
|
+
continue
|
|
477
|
+
|
|
478
|
+
resource_name = component.get("name", "")
|
|
479
|
+
folder_path = component.get("folder", None)
|
|
480
|
+
method_name = component.get("method", "Unknown")
|
|
481
|
+
|
|
482
|
+
name = resource_name
|
|
483
|
+
|
|
484
|
+
is_expression = name.startswith("EXPR$")
|
|
485
|
+
is_folder_path_expression = folder_path and folder_path.startswith("EXPR$")
|
|
486
|
+
name = name.replace("EXPR$", "")
|
|
487
|
+
folder_path = folder_path.replace("EXPR$", "") if folder_path else None
|
|
488
|
+
key = name
|
|
489
|
+
if folder_path:
|
|
490
|
+
key = f"{folder_path}.{name}"
|
|
491
|
+
resource_entry = {
|
|
492
|
+
"resource": service_name_resource_mapping[resource_type],
|
|
493
|
+
"key": key,
|
|
494
|
+
"value": {
|
|
495
|
+
"name": {
|
|
496
|
+
"defaultValue": name,
|
|
497
|
+
"isExpression": is_expression,
|
|
498
|
+
"displayName": "Name",
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
"metadata": {
|
|
502
|
+
"ActivityName": method_name,
|
|
503
|
+
"BindingsVersion": "2.1",
|
|
504
|
+
"DisplayLabel": "FullName",
|
|
505
|
+
},
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if folder_path:
|
|
509
|
+
resource_entry["value"]["folderPath"] = {
|
|
510
|
+
"defaultValue": folder_path,
|
|
511
|
+
"isExpression": is_folder_path_expression,
|
|
512
|
+
"displayName": "Folder Path",
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
bindings["resources"].append(resource_entry)
|
|
516
|
+
|
|
517
|
+
return bindings
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def generate_bindings_json(file_path: str) -> str:
|
|
521
|
+
"""Generate bindings JSON from a Python file.
|
|
522
|
+
|
|
523
|
+
Args:
|
|
524
|
+
file_path: Path to the Python file to analyze
|
|
525
|
+
|
|
526
|
+
Returns:
|
|
527
|
+
JSON string representation of the bindings
|
|
528
|
+
"""
|
|
529
|
+
try:
|
|
530
|
+
with open(file_path, "r") as f:
|
|
531
|
+
source_code = f.read()
|
|
532
|
+
|
|
533
|
+
# Get the base directory for resolving imports
|
|
534
|
+
base_dir = os.path.dirname(os.path.abspath(file_path))
|
|
535
|
+
|
|
536
|
+
sdk_usage = parse_sdk_usage(source_code, base_dir)
|
|
537
|
+
bindings = convert_to_bindings_format(sdk_usage)
|
|
538
|
+
|
|
539
|
+
return bindings
|
|
540
|
+
|
|
541
|
+
except Exception as e:
|
|
542
|
+
raise Exception(f"Error generating bindings JSON: {e}") from e
|
uipath/_cli/cli_auth.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# type: ignore
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import socket
|
|
5
|
+
import webbrowser
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from dotenv import load_dotenv
|
|
9
|
+
|
|
10
|
+
from ._auth._auth_server import HTTPSServer
|
|
11
|
+
from ._auth._oidc_utils import get_auth_config, get_auth_url
|
|
12
|
+
from ._auth._portal_service import PortalService, select_tenant
|
|
13
|
+
from ._auth._utils import update_auth_file, update_env_file
|
|
14
|
+
from ._utils._common import environment_options
|
|
15
|
+
|
|
16
|
+
load_dotenv()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def is_port_in_use(port: int) -> bool:
|
|
20
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
21
|
+
try:
|
|
22
|
+
s.bind(("localhost", port))
|
|
23
|
+
s.close()
|
|
24
|
+
return False
|
|
25
|
+
except socket.error:
|
|
26
|
+
return True
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def set_port():
|
|
30
|
+
auth_config = get_auth_config()
|
|
31
|
+
port = auth_config.get("port", 8104)
|
|
32
|
+
port_option_one = auth_config.get("portOptionOne", 8104)
|
|
33
|
+
port_option_two = auth_config.get("portOptionTwo", 8055)
|
|
34
|
+
port_option_three = auth_config.get("portOptionThree", 42042)
|
|
35
|
+
if is_port_in_use(port):
|
|
36
|
+
if is_port_in_use(port_option_one):
|
|
37
|
+
if is_port_in_use(port_option_two):
|
|
38
|
+
if is_port_in_use(port_option_three):
|
|
39
|
+
raise RuntimeError(
|
|
40
|
+
"All configured ports are in use. Please close applications using ports or configure different ports."
|
|
41
|
+
)
|
|
42
|
+
else:
|
|
43
|
+
port = port_option_three
|
|
44
|
+
else:
|
|
45
|
+
port = port_option_two
|
|
46
|
+
else:
|
|
47
|
+
port = port_option_one
|
|
48
|
+
auth_config["port"] = port
|
|
49
|
+
with open(
|
|
50
|
+
os.path.join(os.path.dirname(__file__), "..", "auth_config.json"), "w"
|
|
51
|
+
) as f:
|
|
52
|
+
json.dump(auth_config, f)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@click.command()
|
|
56
|
+
@environment_options
|
|
57
|
+
def auth(domain="alpha"):
|
|
58
|
+
"""Authenticate with UiPath Cloud Platform."""
|
|
59
|
+
portal_service = PortalService(domain)
|
|
60
|
+
if (
|
|
61
|
+
os.getenv("UIPATH_URL")
|
|
62
|
+
and os.getenv("UIPATH_TENANT_ID")
|
|
63
|
+
and os.getenv("UIPATH_ORGANIZATION_ID")
|
|
64
|
+
):
|
|
65
|
+
try:
|
|
66
|
+
portal_service.ensure_valid_token()
|
|
67
|
+
click.echo("Authentication successful")
|
|
68
|
+
return
|
|
69
|
+
except Exception:
|
|
70
|
+
click.echo(
|
|
71
|
+
"Authentication not found or expired. Please authenticate again."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
auth_url, code_verifier, state = get_auth_url(domain)
|
|
75
|
+
|
|
76
|
+
webbrowser.open(auth_url, 1)
|
|
77
|
+
auth_config = get_auth_config()
|
|
78
|
+
|
|
79
|
+
print(
|
|
80
|
+
"If a browser window did not open, please open the following URL in your browser:"
|
|
81
|
+
)
|
|
82
|
+
print(auth_url)
|
|
83
|
+
server = HTTPSServer(port=auth_config["port"])
|
|
84
|
+
token_data = server.start(state, code_verifier, domain)
|
|
85
|
+
try:
|
|
86
|
+
if token_data:
|
|
87
|
+
portal_service.update_token_data(token_data)
|
|
88
|
+
update_auth_file(token_data)
|
|
89
|
+
access_token = token_data["access_token"]
|
|
90
|
+
update_env_file({"UIPATH_ACCESS_TOKEN": access_token})
|
|
91
|
+
|
|
92
|
+
tenants_and_organizations = portal_service.get_tenants_and_organizations()
|
|
93
|
+
select_tenant(domain, tenants_and_organizations)
|
|
94
|
+
else:
|
|
95
|
+
click.echo("Authentication failed")
|
|
96
|
+
except Exception as e:
|
|
97
|
+
click.echo(f"Authentication failed: {e}")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# type: ignore
|
|
2
|
+
import click
|
|
3
|
+
|
|
4
|
+
from .cli_pack import pack
|
|
5
|
+
from .cli_publish import publish
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.command()
|
|
9
|
+
@click.argument("root", type=str, default="./")
|
|
10
|
+
def deploy(root):
|
|
11
|
+
ctx = click.get_current_context()
|
|
12
|
+
ctx.invoke(pack, root=root)
|
|
13
|
+
ctx.invoke(publish)
|