uipath 2.0.26__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.

Files changed (135) hide show
  1. {uipath-2.0.26 → uipath-2.0.27}/PKG-INFO +1 -1
  2. {uipath-2.0.26 → uipath-2.0.27}/pyproject.toml +1 -1
  3. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_auth/_auth_server.py +0 -1
  4. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_auth/_portal_service.py +18 -10
  5. uipath-2.0.27/src/uipath/_cli/_utils/_console.py +215 -0
  6. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_utils/_folders.py +5 -7
  7. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_utils/_processes.py +10 -21
  8. uipath-2.0.27/src/uipath/_cli/cli_auth.py +106 -0
  9. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/cli_deploy.py +1 -0
  10. uipath-2.0.27/src/uipath/_cli/cli_init.py +112 -0
  11. uipath-2.0.27/src/uipath/_cli/cli_invoke.py +97 -0
  12. uipath-2.0.27/src/uipath/_cli/cli_new.py +77 -0
  13. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/cli_pack.py +61 -44
  14. uipath-2.0.27/src/uipath/_cli/cli_publish.py +152 -0
  15. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/cli_run.py +1 -1
  16. {uipath-2.0.26 → uipath-2.0.27}/tests/cli/test_init.py +6 -6
  17. {uipath-2.0.26 → uipath-2.0.27}/uv.lock +1 -1
  18. uipath-2.0.26/src/uipath/_cli/cli_auth.py +0 -107
  19. uipath-2.0.26/src/uipath/_cli/cli_init.py +0 -117
  20. uipath-2.0.26/src/uipath/_cli/cli_invoke.py +0 -104
  21. uipath-2.0.26/src/uipath/_cli/cli_new.py +0 -82
  22. uipath-2.0.26/src/uipath/_cli/cli_publish.py +0 -170
  23. {uipath-2.0.26 → uipath-2.0.27}/.cursorrules +0 -0
  24. {uipath-2.0.26 → uipath-2.0.27}/.editorconfig +0 -0
  25. {uipath-2.0.26 → uipath-2.0.27}/.gitattributes +0 -0
  26. {uipath-2.0.26 → uipath-2.0.27}/.github/workflows/build.yml +0 -0
  27. {uipath-2.0.26 → uipath-2.0.27}/.github/workflows/cd.yml +0 -0
  28. {uipath-2.0.26 → uipath-2.0.27}/.github/workflows/ci.yml +0 -0
  29. {uipath-2.0.26 → uipath-2.0.27}/.github/workflows/commitlint.yml +0 -0
  30. {uipath-2.0.26 → uipath-2.0.27}/.github/workflows/lint.yml +0 -0
  31. {uipath-2.0.26 → uipath-2.0.27}/.github/workflows/publish-dev.yml +0 -0
  32. {uipath-2.0.26 → uipath-2.0.27}/.github/workflows/test.yml +0 -0
  33. {uipath-2.0.26 → uipath-2.0.27}/.gitignore +0 -0
  34. {uipath-2.0.26 → uipath-2.0.27}/.pre-commit-config.yaml +0 -0
  35. {uipath-2.0.26 → uipath-2.0.27}/.python-version +0 -0
  36. {uipath-2.0.26 → uipath-2.0.27}/.vscode/extensions.json +0 -0
  37. {uipath-2.0.26 → uipath-2.0.27}/.vscode/settings.json +0 -0
  38. {uipath-2.0.26 → uipath-2.0.27}/CONTRIBUTING.md +0 -0
  39. {uipath-2.0.26 → uipath-2.0.27}/LICENSE +0 -0
  40. {uipath-2.0.26 → uipath-2.0.27}/README.md +0 -0
  41. {uipath-2.0.26 → uipath-2.0.27}/docs/CONTRIBUTING.md +0 -0
  42. {uipath-2.0.26 → uipath-2.0.27}/docs/actions.md +0 -0
  43. {uipath-2.0.26 → uipath-2.0.27}/docs/assets/uipath-logo.svg +0 -0
  44. {uipath-2.0.26 → uipath-2.0.27}/docs/assets.md +0 -0
  45. {uipath-2.0.26 → uipath-2.0.27}/docs/buckets.md +0 -0
  46. {uipath-2.0.26 → uipath-2.0.27}/docs/connections.md +0 -0
  47. {uipath-2.0.26 → uipath-2.0.27}/docs/context_grounding.md +0 -0
  48. {uipath-2.0.26 → uipath-2.0.27}/docs/getting_started_agent.md +0 -0
  49. {uipath-2.0.26 → uipath-2.0.27}/docs/getting_started_cli.md +0 -0
  50. {uipath-2.0.26 → uipath-2.0.27}/docs/getting_started_sdk.md +0 -0
  51. {uipath-2.0.26 → uipath-2.0.27}/docs/index.md +0 -0
  52. {uipath-2.0.26 → uipath-2.0.27}/docs/jobs.md +0 -0
  53. {uipath-2.0.26 → uipath-2.0.27}/docs/processes.md +0 -0
  54. {uipath-2.0.26 → uipath-2.0.27}/docs/queues.md +0 -0
  55. {uipath-2.0.26 → uipath-2.0.27}/docs/sdk.md +0 -0
  56. {uipath-2.0.26 → uipath-2.0.27}/justfile +0 -0
  57. {uipath-2.0.26 → uipath-2.0.27}/mkdocs.yml +0 -0
  58. {uipath-2.0.26 → uipath-2.0.27}/py.typed +0 -0
  59. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/__init__.py +0 -0
  60. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/README.md +0 -0
  61. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/__init__.py +0 -0
  62. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_auth/_models.py +0 -0
  63. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_auth/_oidc_utils.py +0 -0
  64. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_auth/_utils.py +0 -0
  65. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_auth/auth_config.json +0 -0
  66. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_auth/index.html +0 -0
  67. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_auth/localhost.crt +0 -0
  68. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_auth/localhost.key +0 -0
  69. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_runtime/_contracts.py +0 -0
  70. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_runtime/_logging.py +0 -0
  71. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_runtime/_runtime.py +0 -0
  72. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_templates/.psmdcp.template +0 -0
  73. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_templates/.rels.template +0 -0
  74. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_templates/[Content_Types].xml.template +0 -0
  75. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_templates/main.py.template +0 -0
  76. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_templates/package.nuspec.template +0 -0
  77. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_utils/_common.py +0 -0
  78. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_utils/_input_args.py +0 -0
  79. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/_utils/_parse_ast.py +0 -0
  80. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/middlewares.py +0 -0
  81. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_cli/spinner.py +0 -0
  82. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_config.py +0 -0
  83. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_execution_context.py +0 -0
  84. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_folder_context.py +0 -0
  85. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_services/__init__.py +0 -0
  86. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_services/_base_service.py +0 -0
  87. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_services/actions_service.py +0 -0
  88. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_services/api_client.py +0 -0
  89. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_services/assets_service.py +0 -0
  90. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_services/buckets_service.py +0 -0
  91. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_services/connections_service.py +0 -0
  92. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_services/connections_service.pyi +0 -0
  93. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_services/context_grounding_service.py +0 -0
  94. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_services/folder_service.py +0 -0
  95. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_services/jobs_service.py +0 -0
  96. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_services/llm_gateway_service.py +0 -0
  97. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_services/processes_service.py +0 -0
  98. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_services/queues_service.py +0 -0
  99. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_uipath.py +0 -0
  100. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_utils/__init__.py +0 -0
  101. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_utils/_endpoint.py +0 -0
  102. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_utils/_infer_bindings.py +0 -0
  103. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_utils/_logs.py +0 -0
  104. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_utils/_request_override.py +0 -0
  105. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_utils/_request_spec.py +0 -0
  106. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_utils/_user_agent.py +0 -0
  107. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/_utils/constants.py +0 -0
  108. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/models/__init__.py +0 -0
  109. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/models/action_schema.py +0 -0
  110. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/models/actions.py +0 -0
  111. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/models/assets.py +0 -0
  112. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/models/connections.py +0 -0
  113. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/models/context_grounding.py +0 -0
  114. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/models/context_grounding_index.py +0 -0
  115. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/models/exceptions.py +0 -0
  116. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/models/interrupt_models.py +0 -0
  117. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/models/job.py +0 -0
  118. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/models/llm_gateway.py +0 -0
  119. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/models/processes.py +0 -0
  120. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/models/queues.py +0 -0
  121. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/py.typed +0 -0
  122. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/tracing/__init__.py +0 -0
  123. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/tracing/_otel_exporters.py +0 -0
  124. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/tracing/_traced.py +0 -0
  125. {uipath-2.0.26 → uipath-2.0.27}/src/uipath/tracing/_utils.py +0 -0
  126. {uipath-2.0.26 → uipath-2.0.27}/tests/__init__.py +0 -0
  127. {uipath-2.0.26 → uipath-2.0.27}/tests/conftest.py +0 -0
  128. {uipath-2.0.26 → uipath-2.0.27}/tests/sdk/services/test_llm_integration.py +0 -0
  129. {uipath-2.0.26 → uipath-2.0.27}/tests/sdk/services/test_llm_service.py +0 -0
  130. {uipath-2.0.26 → uipath-2.0.27}/tests/sdk/services/test_uipath_llm_integration.py +0 -0
  131. {uipath-2.0.26 → uipath-2.0.27}/tests/sdk/test_config.py +0 -0
  132. {uipath-2.0.26 → uipath-2.0.27}/tests/tracing/test_otel_exporters.py +0 -0
  133. {uipath-2.0.26 → uipath-2.0.27}/tests/tracing/test_span_utils.py +0 -0
  134. {uipath-2.0.26 → uipath-2.0.27}/tests/tracing/test_traced.py +0 -0
  135. {uipath-2.0.26 → 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.26
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.26"
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
- raise Exception("Unauthorized")
55
+ console.error("Unauthorized")
53
56
  else:
54
- raise Exception(
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
- raise Exception("Organization not found")
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
- raise Exception("Unauthorized")
89
+ console.error("Unauthorized")
84
90
  else:
85
- raise Exception(f"Failed to refresh token: {response.status_code}")
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
- click.echo("Available tenants:")
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 if len(tenant_names) == 1 else click.prompt("Select tenant", type=int)
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
- click.echo(f"Selected tenant: {tenant_name}")
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
@@ -1,22 +1,20 @@
1
1
  from typing import Optional, Tuple
2
2
 
3
- import click
4
3
  import requests
5
4
 
6
- from ..spinner import Spinner
5
+ from ._console import ConsoleLogger
6
+
7
+ console = ConsoleLogger()
7
8
 
8
9
 
9
10
  def get_personal_workspace_info(
10
- base_url: str, token: str, spinner: Optional[Spinner] = None
11
+ base_url: str, token: str
11
12
  ) -> Tuple[Optional[str], Optional[str]]:
12
13
  user_url = f"{base_url}/orchestrator_/odata/Users/UiPath.Server.Configuration.OData.GetCurrentUserExtended?$expand=PersonalWorkspace"
13
14
  user_response = requests.get(user_url, headers={"Authorization": f"Bearer {token}"})
14
15
 
15
16
  if user_response.status_code != 200:
16
- if spinner:
17
- spinner.stop()
18
- click.echo("❌ Failed to get user info. Please try reauthenticating.")
19
- click.get_current_context().exit(1)
17
+ console.error("Error: Failed to fetch user info. Please try reauthenticating.")
20
18
 
21
19
  user_data = user_response.json()
22
20
  feed_id = user_data.get("PersonalWorskpaceFeedId")
@@ -1,19 +1,16 @@
1
1
  import json
2
2
  import urllib.parse
3
- from typing import Any, Optional
3
+ from typing import Any
4
4
 
5
- import click
6
5
  import requests
7
6
 
8
- from ..spinner import Spinner
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
- if spinner:
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
- if spinner:
38
- spinner.stop()
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
- click.get_current_context().exit(1)
43
-
35
+ return None, None
44
36
  else:
45
- if spinner:
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
+ )
@@ -8,6 +8,7 @@ from .cli_publish import publish
8
8
  @click.command()
9
9
  @click.argument("root", type=str, default="./")
10
10
  def deploy(root):
11
+ """Pack and publish the project."""
11
12
  ctx = click.get_current_context()
12
13
  ctx.invoke(pack, root=root)
13
14
  ctx.invoke(publish)
@@ -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)}")