uipath 2.0.0.dev2__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.

Files changed (76) hide show
  1. uipath/__init__.py +24 -0
  2. uipath/_cli/README.md +11 -0
  3. uipath/_cli/__init__.py +54 -0
  4. uipath/_cli/_auth/_auth_server.py +165 -0
  5. uipath/_cli/_auth/_models.py +51 -0
  6. uipath/_cli/_auth/_oidc_utils.py +69 -0
  7. uipath/_cli/_auth/_portal_service.py +163 -0
  8. uipath/_cli/_auth/_utils.py +51 -0
  9. uipath/_cli/_auth/auth_config.json +6 -0
  10. uipath/_cli/_auth/index.html +167 -0
  11. uipath/_cli/_auth/localhost.crt +25 -0
  12. uipath/_cli/_auth/localhost.key +27 -0
  13. uipath/_cli/_runtime/_contracts.py +429 -0
  14. uipath/_cli/_runtime/_logging.py +193 -0
  15. uipath/_cli/_runtime/_runtime.py +264 -0
  16. uipath/_cli/_templates/.psmdcp.template +9 -0
  17. uipath/_cli/_templates/.rels.template +5 -0
  18. uipath/_cli/_templates/[Content_Types].xml.template +9 -0
  19. uipath/_cli/_templates/main.py.template +25 -0
  20. uipath/_cli/_templates/package.nuspec.template +10 -0
  21. uipath/_cli/_utils/_common.py +24 -0
  22. uipath/_cli/_utils/_input_args.py +126 -0
  23. uipath/_cli/_utils/_parse_ast.py +542 -0
  24. uipath/_cli/cli_auth.py +97 -0
  25. uipath/_cli/cli_deploy.py +13 -0
  26. uipath/_cli/cli_init.py +113 -0
  27. uipath/_cli/cli_new.py +76 -0
  28. uipath/_cli/cli_pack.py +337 -0
  29. uipath/_cli/cli_publish.py +113 -0
  30. uipath/_cli/cli_run.py +133 -0
  31. uipath/_cli/middlewares.py +113 -0
  32. uipath/_config.py +6 -0
  33. uipath/_execution_context.py +83 -0
  34. uipath/_folder_context.py +62 -0
  35. uipath/_models/__init__.py +37 -0
  36. uipath/_models/action_schema.py +26 -0
  37. uipath/_models/actions.py +64 -0
  38. uipath/_models/assets.py +48 -0
  39. uipath/_models/connections.py +51 -0
  40. uipath/_models/context_grounding.py +18 -0
  41. uipath/_models/context_grounding_index.py +60 -0
  42. uipath/_models/exceptions.py +6 -0
  43. uipath/_models/interrupt_models.py +28 -0
  44. uipath/_models/job.py +66 -0
  45. uipath/_models/llm_gateway.py +101 -0
  46. uipath/_models/processes.py +48 -0
  47. uipath/_models/queues.py +167 -0
  48. uipath/_services/__init__.py +26 -0
  49. uipath/_services/_base_service.py +250 -0
  50. uipath/_services/actions_service.py +271 -0
  51. uipath/_services/api_client.py +89 -0
  52. uipath/_services/assets_service.py +257 -0
  53. uipath/_services/buckets_service.py +268 -0
  54. uipath/_services/connections_service.py +185 -0
  55. uipath/_services/connections_service.pyi +50 -0
  56. uipath/_services/context_grounding_service.py +402 -0
  57. uipath/_services/folder_service.py +49 -0
  58. uipath/_services/jobs_service.py +265 -0
  59. uipath/_services/llm_gateway_service.py +311 -0
  60. uipath/_services/processes_service.py +168 -0
  61. uipath/_services/queues_service.py +314 -0
  62. uipath/_uipath.py +98 -0
  63. uipath/_utils/__init__.py +17 -0
  64. uipath/_utils/_endpoint.py +79 -0
  65. uipath/_utils/_infer_bindings.py +30 -0
  66. uipath/_utils/_logs.py +15 -0
  67. uipath/_utils/_request_override.py +18 -0
  68. uipath/_utils/_request_spec.py +23 -0
  69. uipath/_utils/_user_agent.py +16 -0
  70. uipath/_utils/constants.py +25 -0
  71. uipath/py.typed +0 -0
  72. {uipath-2.0.0.dev2.dist-info → uipath-2.0.1.dev1.dist-info}/METADATA +2 -3
  73. uipath-2.0.1.dev1.dist-info/RECORD +75 -0
  74. uipath-2.0.0.dev2.dist-info/RECORD +0 -4
  75. {uipath-2.0.0.dev2.dist-info → uipath-2.0.1.dev1.dist-info}/WHEEL +0 -0
  76. {uipath-2.0.0.dev2.dist-info → uipath-2.0.1.dev1.dist-info}/entry_points.txt +0 -0
uipath/__init__.py ADDED
@@ -0,0 +1,24 @@
1
+ """UiPath SDK for Python.
2
+
3
+ This package provides a Python interface to interact with UiPath's automation platform.
4
+
5
+
6
+ The main entry point is the UiPath class, which provides access to all SDK functionality.
7
+
8
+ Example:
9
+ ```python
10
+ # First set these environment variables:
11
+ # export UIPATH_URL="https://cloud.uipath.com/organization-name/default-tenant"
12
+ # export UIPATH_ACCESS_TOKEN="your_**_token"
13
+ # export UIPATH_FOLDER_PATH="your/folder/path"
14
+
15
+ from uipath import UiPath
16
+ sdk = UiPath()
17
+ # Invoke a process by name
18
+ sdk.processes.invoke("MyProcess")
19
+ ```
20
+ """
21
+
22
+ from ._uipath import UiPath
23
+
24
+ __all__ = ["UiPath"]
uipath/_cli/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # parse
2
+
3
+ `parse-ast.py` will extract names and types of resources to prepare for bindings. currently it's hardcoded to run on `dummy-main.py`
4
+
5
+ # init
6
+
7
+ asks for project name, type (process/agent) description and target directory where to init the project. it inits a script, a requirements.txt for the user to install, and a config.json file that we'll use at packing time.
8
+
9
+ # pack
10
+
11
+ asks for target directory of the package files and the version and will create a nupkg file
@@ -0,0 +1,54 @@
1
+ import importlib.metadata
2
+ import sys
3
+
4
+ import click
5
+
6
+ from .cli_auth import auth as auth # type: ignore
7
+ from .cli_deploy import deploy as deploy # type: ignore
8
+ from .cli_init import init as init # type: ignore
9
+ from .cli_new import new as new # type: ignore
10
+ from .cli_pack import pack as pack # type: ignore
11
+ from .cli_publish import publish as publish # type: ignore
12
+ from .cli_run import run as run # type: ignore
13
+
14
+
15
+ @click.group(invoke_without_command=True)
16
+ @click.version_option(
17
+ importlib.metadata.version("uipath"),
18
+ prog_name="uipath",
19
+ message="%(prog)s version %(version)s",
20
+ )
21
+ @click.option(
22
+ "-lv",
23
+ is_flag=True,
24
+ help="Display the current version of uipath-langchain.",
25
+ )
26
+ @click.option(
27
+ "-v",
28
+ is_flag=True,
29
+ help="Display the current version of uipath.",
30
+ )
31
+ def cli(lv: bool, v: bool) -> None:
32
+ if lv:
33
+ try:
34
+ version = importlib.metadata.version("uipath-langchain")
35
+ click.echo(f"uipath-langchain version {version}")
36
+ except importlib.metadata.PackageNotFoundError:
37
+ click.echo("uipath-langchain is not installed", err=True)
38
+ sys.exit(1)
39
+ if v:
40
+ try:
41
+ version = importlib.metadata.version("uipath")
42
+ click.echo(f"uipath version {version}")
43
+ except importlib.metadata.PackageNotFoundError:
44
+ click.echo("uipath is not installed", err=True)
45
+ sys.exit(1)
46
+
47
+
48
+ cli.add_command(new)
49
+ cli.add_command(init)
50
+ cli.add_command(pack)
51
+ cli.add_command(publish)
52
+ cli.add_command(run)
53
+ cli.add_command(deploy)
54
+ cli.add_command(auth)
@@ -0,0 +1,165 @@
1
+ import http.server
2
+ import json
3
+ import os
4
+ import socketserver
5
+ import ssl
6
+ import time
7
+
8
+ from dotenv import load_dotenv
9
+
10
+ from ._oidc_utils import get_auth_config
11
+
12
+ load_dotenv()
13
+
14
+ # Server port
15
+ PORT = 6234
16
+
17
+
18
+ # Custom exception for token received
19
+ class TokenReceivedSignal(Exception):
20
+ """Exception raised when a token is successfully received."""
21
+
22
+ def __init__(self, token_data):
23
+ self.token_data = token_data
24
+ super().__init__("Token received successfully")
25
+
26
+
27
+ def make_request_handler_class(state, code_verifier, token_callback, domain):
28
+ class SimpleHTTPSRequestHandler(http.server.SimpleHTTPRequestHandler):
29
+ """Simple HTTPS request handler that serves static files."""
30
+
31
+ def log_message(self, format, *args) -> None:
32
+ # do nothing
33
+ pass
34
+
35
+ def do_POST(self):
36
+ """Handle POST requests to /set_token."""
37
+ if self.path == "/set_token":
38
+ content_length = int(self.headers["Content-Length"])
39
+ post_data = self.rfile.read(content_length)
40
+ token_data = json.loads(post_data.decode("utf-8"))
41
+ print("Received authentication information")
42
+
43
+ self.send_response(200)
44
+ self.end_headers()
45
+ self.wfile.write(b"Token received")
46
+
47
+ time.sleep(1)
48
+
49
+ token_callback(token_data)
50
+ elif self.path == "/log":
51
+ content_length = int(self.headers["Content-Length"])
52
+ post_data = self.rfile.read(content_length)
53
+ logs = json.loads(post_data.decode("utf-8"))
54
+ # Write logs to .uipath/.error_log file
55
+ uipath_dir = os.path.join(os.getcwd(), ".uipath")
56
+ os.makedirs(uipath_dir, exist_ok=True)
57
+ error_log_path = os.path.join(uipath_dir, ".error_log")
58
+
59
+ with open(error_log_path, "a", encoding="utf-8") as f:
60
+ f.write(
61
+ f"\n--- Authentication Error Log {time.strftime('%Y-%m-%d %H:%M:%S')} ---\n"
62
+ )
63
+ json.dump(logs, f, indent=2)
64
+ f.write("\n")
65
+ print(logs)
66
+ print("Received log data")
67
+ self.send_response(200)
68
+ self.end_headers()
69
+ self.wfile.write(b"Log received")
70
+ else:
71
+ self.send_error(404, "Path not found")
72
+
73
+ def do_GET(self):
74
+ """Handle GET requests by serving index.html."""
75
+ # Always serve index.html regardless of the path
76
+ try:
77
+ index_path = os.path.join(os.path.dirname(__file__), "index.html")
78
+ with open(index_path, "r") as f:
79
+ content = f.read()
80
+
81
+ # Get the redirect URI from auth config
82
+ auth_config = get_auth_config()
83
+ redirect_uri = auth_config["redirect_uri"]
84
+
85
+ content = content.replace("__PY_REPLACE_EXPECTED_STATE__", state)
86
+ content = content.replace("__PY_REPLACE_CODE_VERIFIER__", code_verifier)
87
+ content = content.replace("__PY_REPLACE_REDIRECT_URI__", redirect_uri)
88
+ content = content.replace(
89
+ "__PY_REPLACE_CLIENT_ID__", auth_config["client_id"]
90
+ )
91
+ content = content.replace("__PY_REPLACE_DOMAIN__", domain)
92
+
93
+ self.send_response(200)
94
+ self.send_header("Content-Type", "text/html")
95
+ self.send_header("Content-Length", str(len(content)))
96
+ self.end_headers()
97
+ self.wfile.write(content.encode("utf-8"))
98
+ except FileNotFoundError:
99
+ self.send_error(404, "File not found")
100
+
101
+ def end_headers(self):
102
+ self.send_header("Access-Control-Allow-Origin", "*")
103
+ self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
104
+ self.send_header("Access-Control-Allow-Headers", "Content-Type")
105
+ super().end_headers()
106
+
107
+ def do_OPTIONS(self):
108
+ self.send_response(200)
109
+ self.end_headers()
110
+
111
+ return SimpleHTTPSRequestHandler
112
+
113
+
114
+ class HTTPSServer:
115
+ def __init__(self, port=6234, cert_file="localhost.crt", key_file="localhost.key"):
116
+ """Initialize HTTPS server with configurable parameters."""
117
+ self.current_path = os.path.dirname(os.path.abspath(__file__))
118
+ self.port = port
119
+ self.cert_file = os.path.join(self.current_path, "localhost.crt")
120
+ self.key_file = os.path.join(self.current_path, "localhost.key")
121
+ self.httpd = None
122
+ self.token_data = None
123
+ self.should_shutdown = False
124
+
125
+ def token_received_callback(self, token_data):
126
+ """Callback for when a token is received."""
127
+ self.token_data = token_data
128
+ self.should_shutdown = True
129
+
130
+ def create_server(self, state, code_verifier, domain):
131
+ """Create and configure the HTTPS server."""
132
+ # Create SSL context
133
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
134
+ context.load_cert_chain(self.cert_file, self.key_file)
135
+
136
+ # Create server
137
+ handler = make_request_handler_class(
138
+ state, code_verifier, self.token_received_callback, domain
139
+ )
140
+ self.httpd = socketserver.TCPServer(("", self.port), handler)
141
+ self.httpd.socket = context.wrap_socket(self.httpd.socket, server_side=True)
142
+
143
+ return self.httpd
144
+
145
+ def start(self, state, code_verifier, domain):
146
+ """Start the server."""
147
+ if not self.httpd:
148
+ self.create_server(state, code_verifier, domain)
149
+
150
+ try:
151
+ if self.httpd:
152
+ while not self.should_shutdown:
153
+ self.httpd.handle_request()
154
+ except KeyboardInterrupt:
155
+ print("Process interrupted by user")
156
+ finally:
157
+ self.stop()
158
+
159
+ return self.token_data if self.token_data else {}
160
+
161
+ def stop(self):
162
+ """Stop the server gracefully."""
163
+ if self.httpd:
164
+ self.httpd.server_close()
165
+ self.httpd = None
@@ -0,0 +1,51 @@
1
+ from typing import TypedDict
2
+
3
+
4
+ class AuthConfig(TypedDict):
5
+ """TypedDict for auth_config.json structure."""
6
+
7
+ client_id: str
8
+ port: int
9
+ redirect_uri: str
10
+ scope: str
11
+
12
+
13
+ class TokenData(TypedDict):
14
+ """TypedDict for token data structure."""
15
+
16
+ access_token: str
17
+ refresh_token: str
18
+ expires_in: int
19
+ token_type: str
20
+ scope: str
21
+ id_token: str
22
+
23
+
24
+ class AccessTokenData(TypedDict):
25
+ """TypedDict for access token data structure."""
26
+
27
+ sub: str
28
+ prt_id: str
29
+ client_id: str
30
+ exp: float
31
+
32
+
33
+ class TenantInfo(TypedDict):
34
+ """TypedDict for tenant info structure."""
35
+
36
+ name: str
37
+ id: str
38
+
39
+
40
+ class OrganizationInfo(TypedDict):
41
+ """TypedDict for organization info structure."""
42
+
43
+ id: str
44
+ name: str
45
+
46
+
47
+ class TenantsAndOrganizationInfoResponse(TypedDict):
48
+ """TypedDict for tenants and organization info response structure."""
49
+
50
+ tenants: list[TenantInfo]
51
+ organization: OrganizationInfo
@@ -0,0 +1,69 @@
1
+ import base64
2
+ import hashlib
3
+ import json
4
+ import os
5
+ from urllib.parse import urlencode
6
+
7
+ from ._models import AuthConfig
8
+
9
+
10
+ def generate_code_verifier_and_challenge():
11
+ """Generate PKCE code verifier and challenge."""
12
+ code_verifier = base64.urlsafe_b64encode(os.urandom(32)).decode("utf-8").rstrip("=")
13
+
14
+ code_challenge_bytes = hashlib.sha256(code_verifier.encode("utf-8")).digest()
15
+ code_challenge = (
16
+ base64.urlsafe_b64encode(code_challenge_bytes).decode("utf-8").rstrip("=")
17
+ )
18
+
19
+ return code_verifier, code_challenge
20
+
21
+
22
+ def get_state_param() -> str:
23
+ return base64.urlsafe_b64encode(os.urandom(32)).decode("utf-8").rstrip("=")
24
+
25
+
26
+ def get_auth_config() -> AuthConfig:
27
+ auth_config = {}
28
+ with open(os.path.join(os.path.dirname(__file__), "auth_config.json"), "r") as f:
29
+ auth_config = json.load(f)
30
+
31
+ port = auth_config.get("port", 8104)
32
+
33
+ redirect_uri = auth_config["redirect_uri"].replace("__PY_REPLACE_PORT__", str(port))
34
+
35
+ return AuthConfig(
36
+ client_id=auth_config["client_id"],
37
+ redirect_uri=redirect_uri,
38
+ scope=auth_config["scope"],
39
+ port=port,
40
+ )
41
+
42
+
43
+ def get_auth_url(domain: str) -> tuple[str, str, str]:
44
+ """Get the authorization URL for OAuth2 PKCE flow.
45
+
46
+ Args:
47
+ domain (str): The UiPath domain to authenticate against (e.g. 'alpha', 'cloud')
48
+
49
+ Returns:
50
+ tuple[str, str]: A tuple containing:
51
+ - The authorization URL with query parameters
52
+ - The code verifier for PKCE flow
53
+ """
54
+ code_verifier, code_challenge = generate_code_verifier_and_challenge()
55
+ auth_config = get_auth_config()
56
+ state = get_state_param()
57
+ query_params = {
58
+ "client_id": auth_config["client_id"],
59
+ "redirect_uri": auth_config["redirect_uri"],
60
+ "response_type": "code",
61
+ "scope": auth_config["scope"],
62
+ "state": state,
63
+ "code_challenge": code_challenge,
64
+ "code_challenge_method": "S256",
65
+ }
66
+
67
+ query_string = urlencode(query_params)
68
+ url = f"https://{domain}.uipath.com/identity_/connect/authorize?{query_string}"
69
+ return url, code_verifier, state
@@ -0,0 +1,163 @@
1
+ import os
2
+ import time
3
+ from typing import Optional
4
+
5
+ import click
6
+ import requests
7
+
8
+ from ._models import TenantsAndOrganizationInfoResponse, TokenData
9
+ from ._oidc_utils import get_auth_config
10
+ from ._utils import (
11
+ get_auth_data,
12
+ get_parsed_token_data,
13
+ update_auth_file,
14
+ update_env_file,
15
+ )
16
+
17
+
18
+ class PortalService:
19
+ """Service for interacting with the UiPath Portal API."""
20
+
21
+ access_token: Optional[str] = None
22
+ prt_id: Optional[str] = None
23
+ domain: Optional[str] = None
24
+ selected_tenant: Optional[str] = None
25
+
26
+ _tenants_and_organizations: Optional[TenantsAndOrganizationInfoResponse] = None
27
+
28
+ def __init__(
29
+ self,
30
+ domain: str,
31
+ access_token: Optional[str] = None,
32
+ prt_id: Optional[str] = None,
33
+ ):
34
+ self.domain = domain
35
+ self.access_token = access_token
36
+ self.prt_id = prt_id
37
+
38
+ def update_token_data(self, token_data: TokenData):
39
+ self.access_token = token_data["access_token"]
40
+ self.prt_id = get_parsed_token_data(token_data).get("prt_id")
41
+
42
+ def get_tenants_and_organizations(self) -> TenantsAndOrganizationInfoResponse:
43
+ url = f"https://{self.domain}.uipath.com/{self.prt_id}/portal_/api/filtering/leftnav/tenantsAndOrganizationInfo"
44
+ response = requests.get(
45
+ url, headers={"Authorization": f"Bearer {self.access_token}"}
46
+ )
47
+ if response.ok:
48
+ result = response.json()
49
+ self._tenants_and_organizations = result
50
+ return result
51
+ elif response.status_code == 401:
52
+ raise Exception("Unauthorized")
53
+ else:
54
+ raise Exception(
55
+ f"Failed to get tenants and organizations: {response.status_code} {response.text}"
56
+ )
57
+
58
+ def get_uipath_orchestrator_url(self) -> str:
59
+ if self._tenants_and_organizations is None:
60
+ self._tenants_and_organizations = self.get_tenants_and_organizations()
61
+ organization = self._tenants_and_organizations.get("organization")
62
+ if organization is None:
63
+ raise Exception("Organization not found")
64
+ account_name = organization.get("name")
65
+ return f"https://{self.domain}.uipath.com/{account_name}/{self.selected_tenant}/orchestrator_"
66
+
67
+ def post_refresh_token_request(self, refresh_token: str) -> TokenData:
68
+ url = f"https://{self.domain}.uipath.com/identity_/connect/token"
69
+ client_id = get_auth_config().get("client_id")
70
+
71
+ data = {
72
+ "grant_type": "refresh_token",
73
+ "refresh_token": refresh_token,
74
+ "client_id": client_id,
75
+ }
76
+
77
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
78
+
79
+ response = requests.post(url, data=data, headers=headers)
80
+ if response.ok:
81
+ return response.json()
82
+ elif response.status_code == 401:
83
+ raise Exception("Unauthorized")
84
+ else:
85
+ raise Exception(f"Failed to refresh token: {response.status_code}")
86
+
87
+ def ensure_valid_token(self):
88
+ """Ensure the access token is valid and refresh it if necessary.
89
+
90
+ This function should be called when running CLI commands to verify authentication.
91
+ It checks if an auth file exists and contains a valid non-expired token.
92
+ If the token is expired, it will attempt to refresh it.
93
+ If no auth file exists, it will raise an exception.
94
+
95
+ Raises:
96
+ Exception: If no auth file exists or token refresh fails
97
+ """
98
+ auth_data = get_auth_data()
99
+ claims = get_parsed_token_data(auth_data)
100
+ exp = claims.get("exp")
101
+
102
+ if exp is not None and float(exp) > time.time():
103
+ if not os.getenv("UIPATH_URL"):
104
+ tenants_and_organizations = self.get_tenants_and_organizations()
105
+ select_tenant(
106
+ self.domain if self.domain else "alpha", tenants_and_organizations
107
+ )
108
+ return auth_data.get("access_token")
109
+
110
+ refresh_token = auth_data.get("refresh_token")
111
+ if refresh_token is None:
112
+ raise Exception("Refresh token not found")
113
+ token_data = self.post_refresh_token_request(refresh_token)
114
+ update_auth_file(token_data)
115
+
116
+ self.access_token = token_data["access_token"]
117
+ self.prt_id = claims.get("prt_id")
118
+
119
+ updated_env_contents = {
120
+ "UIPATH_ACCESS_TOKEN": token_data["access_token"],
121
+ }
122
+ if not os.getenv("UIPATH_URL"):
123
+ tenants_and_organizations = self.get_tenants_and_organizations()
124
+ select_tenant(
125
+ self.domain if self.domain else "alpha", tenants_and_organizations
126
+ )
127
+
128
+ update_env_file(updated_env_contents)
129
+
130
+ def has_initialized_auth(self):
131
+ try:
132
+ auth_data = get_auth_data()
133
+ if not auth_data or "access_token" not in auth_data:
134
+ return False
135
+ if not os.path.exists(".env"):
136
+ return False
137
+ if not os.getenv("UIPATH_ACCESS_TOKEN"):
138
+ return False
139
+
140
+ return True
141
+ except Exception:
142
+ return False
143
+
144
+
145
+ def select_tenant(
146
+ domain: str, tenants_and_organizations: TenantsAndOrganizationInfoResponse
147
+ ):
148
+ 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}")
152
+ tenant_idx = click.prompt("Select tenant", type=int)
153
+ tenant_name = tenant_names[tenant_idx]
154
+ account_name = tenants_and_organizations["organization"]["name"]
155
+ click.echo(f"Selected tenant: {tenant_name}")
156
+
157
+ update_env_file(
158
+ {
159
+ "UIPATH_URL": f"https://{domain if domain else 'alpha'}.uipath.com/{account_name}/{tenant_name}",
160
+ "UIPATH_TENANT_ID": tenants_and_organizations["tenants"][tenant_idx]["id"],
161
+ "UIPATH_ORG_ID": tenants_and_organizations["organization"]["id"],
162
+ }
163
+ )
@@ -0,0 +1,51 @@
1
+ import base64
2
+ import json
3
+ import os
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ from ._models import AccessTokenData, TokenData
8
+
9
+
10
+ def update_auth_file(token_data: TokenData):
11
+ os.makedirs(Path.cwd() / ".uipath", exist_ok=True)
12
+ auth_file = Path.cwd() / ".uipath" / ".auth.json"
13
+ with open(auth_file, "w") as f:
14
+ json.dump(token_data, f)
15
+
16
+
17
+ def get_auth_data() -> TokenData:
18
+ auth_file = Path.cwd() / ".uipath" / ".auth.json"
19
+ if not auth_file.exists():
20
+ raise Exception("No authentication file found")
21
+ return json.load(open(auth_file))
22
+
23
+
24
+ def parse_access_token(access_token: str) -> AccessTokenData:
25
+ token_parts = access_token.split(".")
26
+ if len(token_parts) < 2:
27
+ raise Exception("Invalid access token")
28
+ payload = base64.urlsafe_b64decode(
29
+ token_parts[1] + "=" * (-len(token_parts[1]) % 4)
30
+ )
31
+ return json.loads(payload)
32
+
33
+
34
+ def get_parsed_token_data(token_data: Optional[TokenData] = None) -> AccessTokenData:
35
+ if not token_data:
36
+ token_data = get_auth_data()
37
+ return parse_access_token(token_data["access_token"])
38
+
39
+
40
+ def update_env_file(env_contents):
41
+ env_path = Path.cwd() / ".env"
42
+ if env_path.exists():
43
+ with open(env_path, "r") as f:
44
+ for line in f:
45
+ if "=" in line:
46
+ key, value = line.strip().split("=", 1)
47
+ if key not in env_contents:
48
+ env_contents[key] = value
49
+ lines = [f"{key}={value}\n" for key, value in env_contents.items()]
50
+ with open(env_path, "w") as f:
51
+ f.writelines(lines)
@@ -0,0 +1,6 @@
1
+ {
2
+ "client_id": "36dea5b8-e8bb-423d-8e7b-c808df8f1c00",
3
+ "redirect_uri": "https://localhost:__PY_REPLACE_PORT__/oidc/login",
4
+ "scope": "offline_access OrchestratorApiUserAccess ConnectionService DataService DocumentUnderstanding EnterpriseContextService Directory JamJamApi LLMGateway LLMOps OMS",
5
+ "port": 8104
6
+ }