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

@@ -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
uipath/_cli/cli_auth.py CHANGED
@@ -12,9 +12,10 @@ from ._auth._oidc_utils import get_auth_config, get_auth_url
12
12
  from ._auth._portal_service import PortalService, select_tenant
13
13
  from ._auth._utils import update_auth_file, update_env_file
14
14
  from ._utils._common import environment_options
15
- from .spinner import Spinner
15
+ from ._utils._console import ConsoleLogger
16
16
 
17
17
  load_dotenv()
18
+ console = ConsoleLogger()
18
19
 
19
20
 
20
21
  def is_port_in_use(port: int) -> bool:
@@ -37,8 +38,8 @@ def set_port():
37
38
  if is_port_in_use(port_option_one):
38
39
  if is_port_in_use(port_option_two):
39
40
  if is_port_in_use(port_option_three):
40
- raise RuntimeError(
41
- "All configured ports are in use. Please close applications using ports or configure different ports."
41
+ console.error(
42
+ "All configured ports are in use. Please close applications using ports or configure different ports."
42
43
  )
43
44
  else:
44
45
  port = port_option_three
@@ -57,51 +58,49 @@ def set_port():
57
58
  @environment_options
58
59
  def auth(domain="alpha"):
59
60
  """Authenticate with UiPath Cloud Platform."""
60
- spinner = Spinner("Authenticating with UiPath...")
61
- spinner.start()
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
- spinner.stop()
71
- click.echo(
72
- click.style("✓ ", fg="green", bold=True) + "Authentication successful"
73
- )
74
- return
75
- except Exception:
76
- spinner.stop()
77
- click.echo(
78
- "Authentication not found or expired. Please authenticate again."
79
- )
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
+ )
80
78
 
81
- auth_url, code_verifier, state = get_auth_url(domain)
79
+ auth_url, code_verifier, state = get_auth_url(domain)
82
80
 
83
- webbrowser.open(auth_url, 1)
84
- auth_config = get_auth_config()
85
- spinner.stop()
86
- print(
87
- "If a browser window did not open, please open the following URL in your browser:"
88
- )
89
- print(auth_url)
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
+ )
90
88
 
91
- server = HTTPSServer(port=auth_config["port"])
92
- token_data = server.start(state, code_verifier, domain)
89
+ server = HTTPSServer(port=auth_config["port"])
90
+ token_data = server.start(state, code_verifier, domain)
93
91
 
94
- if token_data:
95
- portal_service.update_token_data(token_data)
96
- update_auth_file(token_data)
97
- access_token = token_data["access_token"]
98
- update_env_file({"UIPATH_ACCESS_TOKEN": access_token})
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})
99
97
 
100
- tenants_and_organizations = portal_service.get_tenants_and_organizations()
101
- select_tenant(domain, tenants_and_organizations)
102
- click.echo(
103
- click.style(" ", fg="green", bold=True)
104
- + "Authentication completed successfully!"
105
- )
106
- else:
107
- click.echo("Authentication failed")
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
+ )
uipath/_cli/cli_deploy.py CHANGED
@@ -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)