uipath 2.0.25__tar.gz → 2.0.27__tar.gz
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-2.0.25 → uipath-2.0.27}/PKG-INFO +1 -1
- {uipath-2.0.25 → uipath-2.0.27}/pyproject.toml +1 -1
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_auth/_auth_server.py +0 -1
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_auth/_portal_service.py +18 -10
- uipath-2.0.27/src/uipath/_cli/_utils/_console.py +215 -0
- uipath-2.0.27/src/uipath/_cli/_utils/_folders.py +27 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_utils/_processes.py +10 -21
- uipath-2.0.27/src/uipath/_cli/cli_auth.py +106 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/cli_deploy.py +1 -0
- uipath-2.0.27/src/uipath/_cli/cli_init.py +112 -0
- uipath-2.0.27/src/uipath/_cli/cli_invoke.py +97 -0
- uipath-2.0.27/src/uipath/_cli/cli_new.py +77 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/cli_pack.py +61 -44
- uipath-2.0.27/src/uipath/_cli/cli_publish.py +152 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/cli_run.py +1 -1
- {uipath-2.0.25 → uipath-2.0.27}/tests/cli/test_init.py +6 -6
- {uipath-2.0.25 → uipath-2.0.27}/uv.lock +1 -1
- uipath-2.0.25/src/uipath/_cli/_utils/_folders.py +0 -29
- uipath-2.0.25/src/uipath/_cli/cli_auth.py +0 -107
- uipath-2.0.25/src/uipath/_cli/cli_init.py +0 -117
- uipath-2.0.25/src/uipath/_cli/cli_invoke.py +0 -104
- uipath-2.0.25/src/uipath/_cli/cli_new.py +0 -82
- uipath-2.0.25/src/uipath/_cli/cli_publish.py +0 -163
- {uipath-2.0.25 → uipath-2.0.27}/.cursorrules +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/.editorconfig +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/.gitattributes +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/.github/workflows/build.yml +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/.github/workflows/cd.yml +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/.github/workflows/ci.yml +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/.github/workflows/commitlint.yml +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/.github/workflows/lint.yml +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/.github/workflows/publish-dev.yml +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/.github/workflows/test.yml +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/.gitignore +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/.pre-commit-config.yaml +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/.python-version +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/.vscode/extensions.json +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/.vscode/settings.json +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/CONTRIBUTING.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/LICENSE +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/README.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/docs/CONTRIBUTING.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/docs/actions.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/docs/assets/uipath-logo.svg +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/docs/assets.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/docs/buckets.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/docs/connections.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/docs/context_grounding.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/docs/getting_started_agent.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/docs/getting_started_cli.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/docs/getting_started_sdk.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/docs/index.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/docs/jobs.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/docs/processes.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/docs/queues.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/docs/sdk.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/justfile +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/mkdocs.yml +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/py.typed +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/__init__.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/README.md +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/__init__.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_auth/_models.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_auth/_oidc_utils.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_auth/_utils.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_auth/auth_config.json +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_auth/index.html +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_auth/localhost.crt +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_auth/localhost.key +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_runtime/_contracts.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_runtime/_logging.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_runtime/_runtime.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_templates/.psmdcp.template +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_templates/.rels.template +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_templates/[Content_Types].xml.template +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_templates/main.py.template +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_templates/package.nuspec.template +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_utils/_common.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_utils/_input_args.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/_utils/_parse_ast.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/middlewares.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_cli/spinner.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_config.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_execution_context.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_folder_context.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_services/__init__.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_services/_base_service.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_services/actions_service.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_services/api_client.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_services/assets_service.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_services/buckets_service.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_services/connections_service.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_services/connections_service.pyi +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_services/context_grounding_service.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_services/folder_service.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_services/jobs_service.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_services/llm_gateway_service.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_services/processes_service.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_services/queues_service.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_uipath.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_utils/__init__.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_utils/_endpoint.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_utils/_infer_bindings.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_utils/_logs.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_utils/_request_override.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_utils/_request_spec.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_utils/_user_agent.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/_utils/constants.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/models/__init__.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/models/action_schema.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/models/actions.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/models/assets.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/models/connections.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/models/context_grounding.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/models/context_grounding_index.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/models/exceptions.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/models/interrupt_models.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/models/job.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/models/llm_gateway.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/models/processes.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/models/queues.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/py.typed +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/tracing/__init__.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/tracing/_otel_exporters.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/tracing/_traced.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/src/uipath/tracing/_utils.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/tests/__init__.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/tests/conftest.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/tests/sdk/services/test_llm_integration.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/tests/sdk/services/test_llm_service.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/tests/sdk/services/test_uipath_llm_integration.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/tests/sdk/test_config.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/tests/tracing/test_otel_exporters.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/tests/tracing/test_span_utils.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/tests/tracing/test_traced.py +0 -0
- {uipath-2.0.25 → uipath-2.0.27}/tests/tracing/test_tracing_manager.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: uipath
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.27
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "uipath"
|
|
3
|
-
version = "2.0.
|
|
3
|
+
version = "2.0.27"
|
|
4
4
|
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
|
|
5
5
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -39,7 +39,6 @@ def make_request_handler_class(state, code_verifier, token_callback, domain):
|
|
|
39
39
|
content_length = int(self.headers["Content-Length"])
|
|
40
40
|
post_data = self.rfile.read(content_length)
|
|
41
41
|
token_data = json.loads(post_data.decode("utf-8"))
|
|
42
|
-
click.echo("Received authentication information")
|
|
43
42
|
|
|
44
43
|
self.send_response(200)
|
|
45
44
|
self.end_headers()
|
|
@@ -5,6 +5,7 @@ from typing import Optional
|
|
|
5
5
|
import click
|
|
6
6
|
import requests
|
|
7
7
|
|
|
8
|
+
from .._utils._console import ConsoleLogger
|
|
8
9
|
from ._models import TenantsAndOrganizationInfoResponse, TokenData
|
|
9
10
|
from ._oidc_utils import get_auth_config
|
|
10
11
|
from ._utils import (
|
|
@@ -14,6 +15,8 @@ from ._utils import (
|
|
|
14
15
|
update_env_file,
|
|
15
16
|
)
|
|
16
17
|
|
|
18
|
+
console = ConsoleLogger()
|
|
19
|
+
|
|
17
20
|
|
|
18
21
|
class PortalService:
|
|
19
22
|
"""Service for interacting with the UiPath Portal API."""
|
|
@@ -49,18 +52,21 @@ class PortalService:
|
|
|
49
52
|
self._tenants_and_organizations = result
|
|
50
53
|
return result
|
|
51
54
|
elif response.status_code == 401:
|
|
52
|
-
|
|
55
|
+
console.error("Unauthorized")
|
|
53
56
|
else:
|
|
54
|
-
|
|
57
|
+
console.error(
|
|
55
58
|
f"Failed to get tenants and organizations: {response.status_code} {response.text}"
|
|
56
59
|
)
|
|
60
|
+
# Can't reach here, console.error exits, linting
|
|
61
|
+
raise Exception("Failed to get tenants")
|
|
57
62
|
|
|
58
63
|
def get_uipath_orchestrator_url(self) -> str:
|
|
59
64
|
if self._tenants_and_organizations is None:
|
|
60
65
|
self._tenants_and_organizations = self.get_tenants_and_organizations()
|
|
61
66
|
organization = self._tenants_and_organizations.get("organization")
|
|
62
67
|
if organization is None:
|
|
63
|
-
|
|
68
|
+
console.error("Organization not found.")
|
|
69
|
+
return ""
|
|
64
70
|
account_name = organization.get("name")
|
|
65
71
|
return f"https://{self.domain}.uipath.com/{account_name}/{self.selected_tenant}/orchestrator_"
|
|
66
72
|
|
|
@@ -80,9 +86,11 @@ class PortalService:
|
|
|
80
86
|
if response.ok:
|
|
81
87
|
return response.json()
|
|
82
88
|
elif response.status_code == 401:
|
|
83
|
-
|
|
89
|
+
console.error("Unauthorized")
|
|
84
90
|
else:
|
|
85
|
-
|
|
91
|
+
console.error(f"Failed to refresh token: {response.status_code}")
|
|
92
|
+
# Can't reach here, console.error exits, linting
|
|
93
|
+
raise Exception("Failed to refresh get token")
|
|
86
94
|
|
|
87
95
|
def ensure_valid_token(self):
|
|
88
96
|
"""Ensure the access token is valid and refresh it if necessary.
|
|
@@ -146,15 +154,15 @@ def select_tenant(
|
|
|
146
154
|
domain: str, tenants_and_organizations: TenantsAndOrganizationInfoResponse
|
|
147
155
|
):
|
|
148
156
|
tenant_names = [tenant["name"] for tenant in tenants_and_organizations["tenants"]]
|
|
149
|
-
|
|
150
|
-
for idx, name in enumerate(tenant_names):
|
|
151
|
-
click.echo(f" {idx}: {name}")
|
|
157
|
+
console.display_options(tenant_names, "Select tenant:")
|
|
152
158
|
tenant_idx = (
|
|
153
|
-
0
|
|
159
|
+
0
|
|
160
|
+
if len(tenant_names) == 1
|
|
161
|
+
else console.prompt("Select tenant number", type=int)
|
|
154
162
|
)
|
|
155
163
|
tenant_name = tenant_names[tenant_idx]
|
|
156
164
|
account_name = tenants_and_organizations["organization"]["name"]
|
|
157
|
-
|
|
165
|
+
console.info(f"Selected tenant: {click.style(tenant_name, fg='cyan')}")
|
|
158
166
|
|
|
159
167
|
update_env_file(
|
|
160
168
|
{
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Any, Iterator, List, Optional, Type, TypeVar
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.live import Live
|
|
8
|
+
from rich.spinner import Spinner as RichSpinner
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LogLevel(Enum):
|
|
13
|
+
"""Enum for log levels with corresponding emojis."""
|
|
14
|
+
|
|
15
|
+
INFO = ""
|
|
16
|
+
SUCCESS = click.style("✓ ", fg="green", bold=True)
|
|
17
|
+
WARNING = "⚠️"
|
|
18
|
+
ERROR = "❌"
|
|
19
|
+
HINT = "💡"
|
|
20
|
+
CONFIG = "🔧"
|
|
21
|
+
SELECT = "👇"
|
|
22
|
+
LINK = "🔗"
|
|
23
|
+
MAGIC = "✨"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
T = TypeVar("T", bound="ConsoleLogger")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ConsoleLogger:
|
|
30
|
+
"""A singleton wrapper class for terminal output with emoji support and spinners."""
|
|
31
|
+
|
|
32
|
+
# Class variable to hold the singleton instance
|
|
33
|
+
_instance: Optional["ConsoleLogger"] = None
|
|
34
|
+
|
|
35
|
+
def __new__(cls: Type[T]) -> T:
|
|
36
|
+
"""Ensure only one instance of ConsoleLogger is created.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
The singleton instance of ConsoleLogger
|
|
40
|
+
"""
|
|
41
|
+
if cls._instance is None:
|
|
42
|
+
cls._instance = super(ConsoleLogger, cls).__new__(cls)
|
|
43
|
+
cls._instance._initialized = False
|
|
44
|
+
return cls._instance # type: ignore
|
|
45
|
+
|
|
46
|
+
def __init__(self):
|
|
47
|
+
"""Initialize the ConsoleLogger (only once)."""
|
|
48
|
+
# Only initialize once
|
|
49
|
+
if not getattr(self, "_initialized", False):
|
|
50
|
+
self._console = Console()
|
|
51
|
+
self._spinner_live: Optional[Live] = None
|
|
52
|
+
self._spinner = RichSpinner("dots")
|
|
53
|
+
self._initialized = True
|
|
54
|
+
|
|
55
|
+
def _stop_spinner_if_active(self) -> None:
|
|
56
|
+
"""Internal method to stop the spinner if it's active."""
|
|
57
|
+
if self._spinner_live and self._spinner_live.is_started:
|
|
58
|
+
self._spinner_live.stop()
|
|
59
|
+
self._spinner_live = None
|
|
60
|
+
|
|
61
|
+
def log(
|
|
62
|
+
self, message: str, level: LogLevel = LogLevel.INFO, fg: Optional[str] = None
|
|
63
|
+
) -> None:
|
|
64
|
+
"""Log a message with the specified level and optional color.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
message: The message to log
|
|
68
|
+
level: The log level (determines the emoji)
|
|
69
|
+
fg: Optional foreground color for the message
|
|
70
|
+
"""
|
|
71
|
+
# Stop any active spinner before logging
|
|
72
|
+
self._stop_spinner_if_active()
|
|
73
|
+
|
|
74
|
+
if not level == LogLevel.INFO:
|
|
75
|
+
emoji = level.value
|
|
76
|
+
if fg:
|
|
77
|
+
formatted_message = f"{emoji} {click.style(message, fg=fg)}"
|
|
78
|
+
else:
|
|
79
|
+
formatted_message = f"{emoji} {message}"
|
|
80
|
+
else:
|
|
81
|
+
formatted_message = message
|
|
82
|
+
|
|
83
|
+
click.echo(formatted_message, err=LogLevel.ERROR in (level,))
|
|
84
|
+
|
|
85
|
+
def success(self, message: str) -> None:
|
|
86
|
+
"""Log a success message."""
|
|
87
|
+
self.log(message, LogLevel.SUCCESS)
|
|
88
|
+
|
|
89
|
+
def error(self, message: str, include_traceback: bool = False) -> None:
|
|
90
|
+
"""Log an error message with optional traceback.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
message: The error message to display
|
|
94
|
+
include_traceback: Whether to include the current exception traceback
|
|
95
|
+
"""
|
|
96
|
+
self.log(message, LogLevel.ERROR, "red")
|
|
97
|
+
|
|
98
|
+
if include_traceback:
|
|
99
|
+
import traceback
|
|
100
|
+
|
|
101
|
+
click.echo(traceback.format_exc(), err=True)
|
|
102
|
+
|
|
103
|
+
click.get_current_context().exit(1)
|
|
104
|
+
|
|
105
|
+
def warning(self, message: str) -> None:
|
|
106
|
+
"""Log a warning message."""
|
|
107
|
+
self.log(message, LogLevel.WARNING, "yellow")
|
|
108
|
+
|
|
109
|
+
def info(self, message: str) -> None:
|
|
110
|
+
"""Log an informational message."""
|
|
111
|
+
self.log(message, LogLevel.INFO)
|
|
112
|
+
|
|
113
|
+
def hint(self, message: str) -> None:
|
|
114
|
+
"""Log a hint message."""
|
|
115
|
+
self.log(message, LogLevel.HINT)
|
|
116
|
+
|
|
117
|
+
def magic(self, message: str) -> None:
|
|
118
|
+
"""Log a magic message."""
|
|
119
|
+
self.log(message, LogLevel.MAGIC, "green")
|
|
120
|
+
|
|
121
|
+
def config(self, message: str) -> None:
|
|
122
|
+
"""Log a configuration message."""
|
|
123
|
+
self.log(message, LogLevel.CONFIG)
|
|
124
|
+
|
|
125
|
+
def select(self, message: str) -> None:
|
|
126
|
+
"""Log a selection message."""
|
|
127
|
+
self.log(message, LogLevel.SELECT)
|
|
128
|
+
|
|
129
|
+
def link(self, message: str, url: str) -> None:
|
|
130
|
+
"""Log a clickable link.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
message: The message to display
|
|
134
|
+
url: The URL to link to
|
|
135
|
+
"""
|
|
136
|
+
formatted_url = f"\u001b]8;;{url}\u001b\\{url}\u001b]8;;\u001b\\"
|
|
137
|
+
self.log(
|
|
138
|
+
f"{message} {click.style(formatted_url, fg='bright_blue', bold=True)}",
|
|
139
|
+
LogLevel.LINK,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def prompt(self, message: str, **kwargs: Any) -> Any:
|
|
143
|
+
"""Wrapper for click.prompt with emoji.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
message: The prompt message
|
|
147
|
+
**kwargs: Additional arguments to pass to click.prompt
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
The user's input
|
|
151
|
+
"""
|
|
152
|
+
# Stop any active spinner before prompting
|
|
153
|
+
self._stop_spinner_if_active()
|
|
154
|
+
|
|
155
|
+
return click.prompt(click.style(f"{message}", fg="yellow", bold=True), **kwargs)
|
|
156
|
+
|
|
157
|
+
def display_options(
|
|
158
|
+
self, options: List[Any], message: str = "Select an option:"
|
|
159
|
+
) -> None:
|
|
160
|
+
"""Display a list of options with indices.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
options: List of options to display
|
|
164
|
+
message: Optional message to display before the options
|
|
165
|
+
"""
|
|
166
|
+
self.select(message)
|
|
167
|
+
for idx, option in enumerate(options, start=0):
|
|
168
|
+
click.echo(f" {idx}: {option}")
|
|
169
|
+
|
|
170
|
+
@contextmanager
|
|
171
|
+
def spinner(self, message: str = "") -> Iterator[None]:
|
|
172
|
+
"""Context manager for spinner operations.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
message: The message to display alongside the spinner
|
|
176
|
+
|
|
177
|
+
Yields:
|
|
178
|
+
None
|
|
179
|
+
"""
|
|
180
|
+
try:
|
|
181
|
+
# Stop any existing spinner before starting a new one
|
|
182
|
+
self._stop_spinner_if_active()
|
|
183
|
+
|
|
184
|
+
self._spinner.text = Text(message)
|
|
185
|
+
self._spinner_live = Live(
|
|
186
|
+
self._spinner,
|
|
187
|
+
console=self._console,
|
|
188
|
+
refresh_per_second=10,
|
|
189
|
+
transient=False,
|
|
190
|
+
auto_refresh=True,
|
|
191
|
+
)
|
|
192
|
+
self._spinner_live.start()
|
|
193
|
+
yield
|
|
194
|
+
finally:
|
|
195
|
+
self._stop_spinner_if_active()
|
|
196
|
+
|
|
197
|
+
def update_spinner(self, message: str) -> None:
|
|
198
|
+
"""Update the message of an active spinner.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
message: The new message to display
|
|
202
|
+
"""
|
|
203
|
+
if self._spinner_live and self._spinner_live.is_started:
|
|
204
|
+
self._spinner.text = Text(message)
|
|
205
|
+
|
|
206
|
+
@classmethod
|
|
207
|
+
def get_instance(cls) -> "ConsoleLogger":
|
|
208
|
+
"""Get the singleton instance of ConsoleLogger.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
The singleton instance
|
|
212
|
+
"""
|
|
213
|
+
if cls._instance is None:
|
|
214
|
+
return cls()
|
|
215
|
+
return cls._instance
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from typing import Optional, Tuple
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from ._console import ConsoleLogger
|
|
6
|
+
|
|
7
|
+
console = ConsoleLogger()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_personal_workspace_info(
|
|
11
|
+
base_url: str, token: str
|
|
12
|
+
) -> Tuple[Optional[str], Optional[str]]:
|
|
13
|
+
user_url = f"{base_url}/orchestrator_/odata/Users/UiPath.Server.Configuration.OData.GetCurrentUserExtended?$expand=PersonalWorkspace"
|
|
14
|
+
user_response = requests.get(user_url, headers={"Authorization": f"Bearer {token}"})
|
|
15
|
+
|
|
16
|
+
if user_response.status_code != 200:
|
|
17
|
+
console.error("Error: Failed to fetch user info. Please try reauthenticating.")
|
|
18
|
+
|
|
19
|
+
user_data = user_response.json()
|
|
20
|
+
feed_id = user_data.get("PersonalWorskpaceFeedId")
|
|
21
|
+
personal_workspace = user_data.get("PersonalWorkspace")
|
|
22
|
+
|
|
23
|
+
if not personal_workspace or not feed_id or "Id" not in personal_workspace:
|
|
24
|
+
return None, None
|
|
25
|
+
|
|
26
|
+
folder_id = personal_workspace.get("Id")
|
|
27
|
+
return feed_id, folder_id
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import urllib.parse
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
|
-
import click
|
|
6
5
|
import requests
|
|
7
6
|
|
|
8
|
-
from
|
|
7
|
+
from ._console import ConsoleLogger
|
|
8
|
+
|
|
9
|
+
console = ConsoleLogger()
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def get_release_info(
|
|
12
|
-
base_url: str,
|
|
13
|
-
token: str,
|
|
14
|
-
package_name: str,
|
|
15
|
-
folder_id: str,
|
|
16
|
-
spinner: Optional[Spinner] = None,
|
|
13
|
+
base_url: str, token: str, package_name: str, folder_id: str
|
|
17
14
|
) -> None | tuple[Any, Any] | tuple[None, None]:
|
|
18
15
|
headers = {
|
|
19
16
|
"Authorization": f"Bearer {token}",
|
|
@@ -29,21 +26,13 @@ def get_release_info(
|
|
|
29
26
|
release_key = data["value"][0]["Key"]
|
|
30
27
|
return release_id, release_key
|
|
31
28
|
except KeyError:
|
|
32
|
-
|
|
33
|
-
spinner.stop()
|
|
34
|
-
click.echo("\n⚠️ Warning: Failed to deserialize release data")
|
|
29
|
+
console.warning("Warning: Failed to deserialize release data")
|
|
35
30
|
return None, None
|
|
36
31
|
except IndexError:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
click.echo(
|
|
40
|
-
"\n❌ Process not found in your workspace. Try publishing it first."
|
|
32
|
+
console.error(
|
|
33
|
+
"Error: No process found in your workspace. Please publish the process first."
|
|
41
34
|
)
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
return None, None
|
|
44
36
|
else:
|
|
45
|
-
|
|
46
|
-
spinner.stop()
|
|
47
|
-
click.echo("\n⚠️ Warning: Failed to fetch release info")
|
|
48
|
-
click.echo(f"Status code: {response.status_code}")
|
|
37
|
+
console.warning(f"Warning: Failed to fetch release info {response.status_code}")
|
|
49
38
|
return None, None
|
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
from ._utils._console import ConsoleLogger
|
|
16
|
+
|
|
17
|
+
load_dotenv()
|
|
18
|
+
console = ConsoleLogger()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def is_port_in_use(port: int) -> bool:
|
|
22
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
23
|
+
try:
|
|
24
|
+
s.bind(("localhost", port))
|
|
25
|
+
s.close()
|
|
26
|
+
return False
|
|
27
|
+
except socket.error:
|
|
28
|
+
return True
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def set_port():
|
|
32
|
+
auth_config = get_auth_config()
|
|
33
|
+
port = auth_config.get("port", 8104)
|
|
34
|
+
port_option_one = auth_config.get("portOptionOne", 8104)
|
|
35
|
+
port_option_two = auth_config.get("portOptionTwo", 8055)
|
|
36
|
+
port_option_three = auth_config.get("portOptionThree", 42042)
|
|
37
|
+
if is_port_in_use(port):
|
|
38
|
+
if is_port_in_use(port_option_one):
|
|
39
|
+
if is_port_in_use(port_option_two):
|
|
40
|
+
if is_port_in_use(port_option_three):
|
|
41
|
+
console.error(
|
|
42
|
+
"All configured ports are in use. Please close applications using ports or configure different ports."
|
|
43
|
+
)
|
|
44
|
+
else:
|
|
45
|
+
port = port_option_three
|
|
46
|
+
else:
|
|
47
|
+
port = port_option_two
|
|
48
|
+
else:
|
|
49
|
+
port = port_option_one
|
|
50
|
+
auth_config["port"] = port
|
|
51
|
+
with open(
|
|
52
|
+
os.path.join(os.path.dirname(__file__), "..", "auth_config.json"), "w"
|
|
53
|
+
) as f:
|
|
54
|
+
json.dump(auth_config, f)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@click.command()
|
|
58
|
+
@environment_options
|
|
59
|
+
def auth(domain="alpha"):
|
|
60
|
+
"""Authenticate with UiPath Cloud Platform."""
|
|
61
|
+
with console.spinner("Authenticating with UiPath ..."):
|
|
62
|
+
portal_service = PortalService(domain)
|
|
63
|
+
if (
|
|
64
|
+
os.getenv("UIPATH_URL")
|
|
65
|
+
and os.getenv("UIPATH_TENANT_ID")
|
|
66
|
+
and os.getenv("UIPATH_ORGANIZATION_ID")
|
|
67
|
+
):
|
|
68
|
+
try:
|
|
69
|
+
portal_service.ensure_valid_token()
|
|
70
|
+
console.success(
|
|
71
|
+
"Authentication successful.",
|
|
72
|
+
)
|
|
73
|
+
return
|
|
74
|
+
except Exception:
|
|
75
|
+
console.info(
|
|
76
|
+
"Authentication token is invalid. Please reauthenticate.",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
auth_url, code_verifier, state = get_auth_url(domain)
|
|
80
|
+
|
|
81
|
+
webbrowser.open(auth_url, 1)
|
|
82
|
+
auth_config = get_auth_config()
|
|
83
|
+
|
|
84
|
+
console.link(
|
|
85
|
+
"If a browser window did not open, please open the following URL in your browser:",
|
|
86
|
+
auth_url,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
server = HTTPSServer(port=auth_config["port"])
|
|
90
|
+
token_data = server.start(state, code_verifier, domain)
|
|
91
|
+
|
|
92
|
+
if token_data:
|
|
93
|
+
portal_service.update_token_data(token_data)
|
|
94
|
+
update_auth_file(token_data)
|
|
95
|
+
access_token = token_data["access_token"]
|
|
96
|
+
update_env_file({"UIPATH_ACCESS_TOKEN": access_token})
|
|
97
|
+
|
|
98
|
+
tenants_and_organizations = portal_service.get_tenants_and_organizations()
|
|
99
|
+
select_tenant(domain, tenants_and_organizations)
|
|
100
|
+
console.success(
|
|
101
|
+
"Authentication successful.",
|
|
102
|
+
)
|
|
103
|
+
else:
|
|
104
|
+
console.error(
|
|
105
|
+
"Authentication failed. Please try again.",
|
|
106
|
+
)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# type: ignore
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import uuid
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from ._utils._console import ConsoleLogger
|
|
11
|
+
from ._utils._input_args import generate_args
|
|
12
|
+
from ._utils._parse_ast import generate_bindings_json
|
|
13
|
+
from .middlewares import Middlewares
|
|
14
|
+
|
|
15
|
+
console = ConsoleLogger()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def generate_env_file(target_directory):
|
|
19
|
+
env_path = os.path.join(target_directory, ".env")
|
|
20
|
+
|
|
21
|
+
if not os.path.exists(env_path):
|
|
22
|
+
relative_path = os.path.relpath(env_path, target_directory)
|
|
23
|
+
console.info(f"Created {relative_path} file.")
|
|
24
|
+
with open(env_path, "w") as f:
|
|
25
|
+
f.write("UIPATH_ACCESS_TOKEN=YOUR_TOKEN_HERE\n")
|
|
26
|
+
f.write("UIPATH_URL=https://cloud.uipath.com/ACCOUNT_NAME/TENANT_NAME\n")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_user_script(directory: str, entrypoint: Optional[str] = None) -> Optional[str]:
|
|
30
|
+
"""Find the Python script to process."""
|
|
31
|
+
if entrypoint:
|
|
32
|
+
script_path = os.path.join(directory, entrypoint)
|
|
33
|
+
if not os.path.isfile(script_path):
|
|
34
|
+
console.error(
|
|
35
|
+
f"The {entrypoint} file does not exist in the current directory."
|
|
36
|
+
)
|
|
37
|
+
return None
|
|
38
|
+
return script_path
|
|
39
|
+
|
|
40
|
+
python_files = [f for f in os.listdir(directory) if f.endswith(".py")]
|
|
41
|
+
|
|
42
|
+
if not python_files:
|
|
43
|
+
console.error("No python files found in the current directory.")
|
|
44
|
+
return None
|
|
45
|
+
elif len(python_files) == 1:
|
|
46
|
+
return os.path.join(directory, python_files[0])
|
|
47
|
+
else:
|
|
48
|
+
console.error(
|
|
49
|
+
"Multiple python files found in the current directory.\nPlease specify the entrypoint: `uipath init <entrypoint_path>`"
|
|
50
|
+
)
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@click.command()
|
|
55
|
+
@click.argument("entrypoint", required=False, default=None)
|
|
56
|
+
def init(entrypoint: str) -> None:
|
|
57
|
+
"""Create uipath.json with input/output schemas and bindings."""
|
|
58
|
+
with console.spinner("Initializing UiPath project ..."):
|
|
59
|
+
current_directory = os.getcwd()
|
|
60
|
+
generate_env_file(current_directory)
|
|
61
|
+
|
|
62
|
+
result = Middlewares.next("init", entrypoint)
|
|
63
|
+
|
|
64
|
+
if result.error_message:
|
|
65
|
+
console.error(
|
|
66
|
+
result.error_message, include_traceback=result.should_include_stacktrace
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
if result.info_message:
|
|
70
|
+
console.info(result.info_message)
|
|
71
|
+
|
|
72
|
+
if not result.should_continue:
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
script_path = get_user_script(current_directory, entrypoint=entrypoint)
|
|
76
|
+
|
|
77
|
+
if not script_path:
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
args = generate_args(script_path)
|
|
82
|
+
|
|
83
|
+
relative_path = Path(script_path).relative_to(current_directory).as_posix()
|
|
84
|
+
|
|
85
|
+
config_data = {
|
|
86
|
+
"entryPoints": [
|
|
87
|
+
{
|
|
88
|
+
"filePath": relative_path,
|
|
89
|
+
"uniqueId": str(uuid.uuid4()),
|
|
90
|
+
# "type": "process", OR BE doesn't offer json schema support for type: Process
|
|
91
|
+
"type": "agent",
|
|
92
|
+
"input": args["input"],
|
|
93
|
+
"output": args["output"],
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Generate bindings JSON based on the script path
|
|
99
|
+
try:
|
|
100
|
+
bindings_data = generate_bindings_json(script_path)
|
|
101
|
+
# Add bindings to the config data
|
|
102
|
+
config_data["bindings"] = bindings_data
|
|
103
|
+
except Exception as e:
|
|
104
|
+
console.warning(f"Warning: Could not generate bindings: {str(e)}")
|
|
105
|
+
|
|
106
|
+
config_path = "uipath.json"
|
|
107
|
+
with open(config_path, "w") as config_file:
|
|
108
|
+
json.dump(config_data, config_file, indent=4)
|
|
109
|
+
|
|
110
|
+
console.success(f"Created '{config_path}' file.")
|
|
111
|
+
except Exception as e:
|
|
112
|
+
console.error(f"Error creating configuration file: {str(e)}")
|