intuned-runtime 1.2.1__py3-none-any.whl → 1.2.2__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.
- intuned_cli/__init__.py +12 -2
- intuned_cli/commands/__init__.py +1 -0
- intuned_cli/commands/attempt_api_command.py +1 -1
- intuned_cli/commands/attempt_authsession_check_command.py +1 -1
- intuned_cli/commands/attempt_authsession_create_command.py +1 -1
- intuned_cli/commands/deploy_command.py +4 -4
- intuned_cli/commands/run_api_command.py +1 -1
- intuned_cli/commands/run_authsession_create_command.py +1 -1
- intuned_cli/commands/run_authsession_update_command.py +1 -1
- intuned_cli/commands/run_authsession_validate_command.py +1 -1
- intuned_cli/commands/save_command.py +47 -0
- intuned_cli/controller/__test__/test_api.py +0 -1
- intuned_cli/controller/api.py +0 -1
- intuned_cli/controller/authsession.py +0 -1
- intuned_cli/controller/deploy.py +8 -210
- intuned_cli/controller/save.py +260 -0
- intuned_cli/utils/backend.py +26 -0
- intuned_cli/utils/error.py +7 -3
- intuned_internal_cli/commands/project/auth_session/check.py +0 -1
- intuned_internal_cli/commands/project/run.py +0 -2
- intuned_runtime/__init__.py +2 -1
- {intuned_runtime-1.2.1.dist-info → intuned_runtime-1.2.2.dist-info}/METADATA +4 -2
- {intuned_runtime-1.2.1.dist-info → intuned_runtime-1.2.2.dist-info}/RECORD +43 -36
- {intuned_runtime-1.2.1.dist-info → intuned_runtime-1.2.2.dist-info}/WHEEL +1 -1
- runtime/backend_functions/_call_backend_function.py +18 -2
- runtime/browser/helpers.py +22 -0
- runtime/browser/launch_browser.py +43 -15
- runtime/browser/launch_chromium.py +26 -22
- runtime/constants.py +1 -0
- runtime/context/context.py +1 -0
- runtime/env.py +15 -1
- runtime/errors/run_api_errors.py +8 -0
- runtime/helpers/__init__.py +2 -1
- runtime/helpers/attempt_store.py +14 -0
- runtime/run/playwright_context.py +147 -0
- runtime/run/playwright_tracing.py +27 -0
- runtime/run/run_api.py +71 -91
- runtime/run/setup_context_hook.py +40 -0
- runtime/run/types.py +14 -0
- runtime/types/run_types.py +0 -1
- runtime_helpers/__init__.py +2 -1
- runtime/run/playwright_constructs.py +0 -20
- {intuned_runtime-1.2.1.dist-info → intuned_runtime-1.2.2.dist-info}/entry_points.txt +0 -0
- {intuned_runtime-1.2.1.dist-info → intuned_runtime-1.2.2.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,260 @@
|
|
1
|
+
import io
|
2
|
+
import json
|
3
|
+
import re
|
4
|
+
import uuid
|
5
|
+
from typing import Any
|
6
|
+
|
7
|
+
import httpx
|
8
|
+
import pathspec
|
9
|
+
import toml
|
10
|
+
from anyio import Path
|
11
|
+
from dotenv.main import DotEnv
|
12
|
+
from pydantic import BaseModel
|
13
|
+
from pydantic import ValidationError
|
14
|
+
|
15
|
+
from intuned_cli.types import DirectoryNode
|
16
|
+
from intuned_cli.types import FileNode
|
17
|
+
from intuned_cli.types import FileNodeContent
|
18
|
+
from intuned_cli.types import FileSystemTree
|
19
|
+
from intuned_cli.utils.api_helpers import load_intuned_json
|
20
|
+
from intuned_cli.utils.backend import get_base_url
|
21
|
+
from intuned_cli.utils.console import console
|
22
|
+
from intuned_cli.utils.error import CLIError
|
23
|
+
from intuned_cli.utils.exclusions import exclusions
|
24
|
+
from runtime.constants import api_key_header_name
|
25
|
+
from runtime.env import api_key_env_var_key
|
26
|
+
from runtime.env import project_env_var_key
|
27
|
+
from runtime.env import workspace_env_var_key
|
28
|
+
|
29
|
+
supported_playwright_versions = ["1.46.0", "1.52.0"]
|
30
|
+
|
31
|
+
|
32
|
+
class IntunedPyprojectToml(BaseModel):
|
33
|
+
class _Tool(BaseModel):
|
34
|
+
class _Poetry(BaseModel):
|
35
|
+
dependencies: dict[str, Any]
|
36
|
+
|
37
|
+
poetry: _Poetry
|
38
|
+
|
39
|
+
tool: _Tool
|
40
|
+
|
41
|
+
|
42
|
+
async def validate_intuned_project():
|
43
|
+
cwd = await Path().resolve()
|
44
|
+
|
45
|
+
pyproject_toml_path = cwd / "pyproject.toml"
|
46
|
+
|
47
|
+
if not await pyproject_toml_path.exists():
|
48
|
+
raise CLIError("pyproject.toml file is missing in the current directory.")
|
49
|
+
|
50
|
+
content = await pyproject_toml_path.read_text()
|
51
|
+
json_content = toml.loads(content)
|
52
|
+
try:
|
53
|
+
pyproject_toml = IntunedPyprojectToml.model_validate(json_content)
|
54
|
+
except Exception as e:
|
55
|
+
raise CLIError(f"Failed to parse pyproject.toml: {e}") from e
|
56
|
+
|
57
|
+
playwright_version = pyproject_toml.tool.poetry.dependencies.get("playwright")
|
58
|
+
|
59
|
+
if playwright_version not in supported_playwright_versions:
|
60
|
+
raise CLIError(
|
61
|
+
f"Unsupported Playwright version '{playwright_version}'. "
|
62
|
+
f"Supported versions are: {', '.join(supported_playwright_versions)}."
|
63
|
+
)
|
64
|
+
|
65
|
+
intuned_json = await load_intuned_json()
|
66
|
+
|
67
|
+
api_folder = cwd / "api"
|
68
|
+
if not await api_folder.exists() or not await api_folder.is_dir():
|
69
|
+
raise CLIError("api directory does not exist in the current directory.")
|
70
|
+
|
71
|
+
if intuned_json.auth_sessions.enabled:
|
72
|
+
auth_sessions_folder = cwd / "auth-sessions"
|
73
|
+
if not await auth_sessions_folder.exists() or not await auth_sessions_folder.is_dir():
|
74
|
+
raise CLIError("auth-sessions directory does not exist in the api directory.")
|
75
|
+
|
76
|
+
return intuned_json
|
77
|
+
|
78
|
+
|
79
|
+
def validate_project_name(project_name: str):
|
80
|
+
if len(project_name) > 50:
|
81
|
+
raise CLIError("Project name must be 50 characters or less.")
|
82
|
+
|
83
|
+
project_name_regex = r"^[a-z0-9]+(?:[-_][a-z0-9]+)*$"
|
84
|
+
if not re.match(project_name_regex, project_name):
|
85
|
+
raise CLIError("Project name can only contain lowercase letters, numbers, hyphens, and underscores in between.")
|
86
|
+
|
87
|
+
try:
|
88
|
+
import uuid
|
89
|
+
|
90
|
+
uuid.UUID(project_name)
|
91
|
+
raise CLIError("Project name cannot be a UUID.")
|
92
|
+
except ValueError:
|
93
|
+
# Not a valid UUID, continue
|
94
|
+
pass
|
95
|
+
|
96
|
+
|
97
|
+
async def get_file_tree_from_project(path: Path, *, exclude: list[str] | None = None):
|
98
|
+
# Create pathspec object for gitignore-style pattern matching
|
99
|
+
spec = None
|
100
|
+
if exclude:
|
101
|
+
spec = pathspec.PathSpec.from_lines("gitwildmatch", exclude)
|
102
|
+
|
103
|
+
async def traverse(current_path: Path, tree: FileSystemTree):
|
104
|
+
async for entry in current_path.iterdir():
|
105
|
+
relative_path_name = entry.relative_to(path).as_posix()
|
106
|
+
basename = entry.name
|
107
|
+
|
108
|
+
# Check if this path should be excluded
|
109
|
+
if spec and spec.match_file(relative_path_name):
|
110
|
+
continue
|
111
|
+
|
112
|
+
if await entry.is_dir():
|
113
|
+
subtree = FileSystemTree(root={})
|
114
|
+
tree.root[basename] = DirectoryNode(directory=subtree)
|
115
|
+
# For directories, check if the directory itself is excluded
|
116
|
+
# If not excluded, traverse into it
|
117
|
+
await traverse(entry, subtree)
|
118
|
+
elif await entry.is_file():
|
119
|
+
tree.root[basename] = FileNode(file=FileNodeContent(contents=await entry.read_text()))
|
120
|
+
|
121
|
+
results = FileSystemTree(root={})
|
122
|
+
await traverse(path, results)
|
123
|
+
return results
|
124
|
+
|
125
|
+
|
126
|
+
def mapFileTreeToIdeFileTree(file_tree: FileSystemTree):
|
127
|
+
"""
|
128
|
+
Maps the file tree to IDE parameters format by processing parameters directory
|
129
|
+
and converting it to ____testParameters structure.
|
130
|
+
"""
|
131
|
+
|
132
|
+
if not file_tree:
|
133
|
+
return
|
134
|
+
|
135
|
+
parameters_node = file_tree.root.get("parameters")
|
136
|
+
if parameters_node is None:
|
137
|
+
return
|
138
|
+
|
139
|
+
if not isinstance(parameters_node, DirectoryNode):
|
140
|
+
return
|
141
|
+
|
142
|
+
api_parameters_map: dict[str, list[dict[str, Any]]] = {}
|
143
|
+
cli_parameters = list(parameters_node.directory.root.keys())
|
144
|
+
test_parameters = DirectoryNode(directory=FileSystemTree(root={}))
|
145
|
+
|
146
|
+
for parameter_key in cli_parameters:
|
147
|
+
# If parameter of type directory, discard it and continue
|
148
|
+
parameter = parameters_node.directory.root[parameter_key]
|
149
|
+
|
150
|
+
if isinstance(parameter, DirectoryNode):
|
151
|
+
continue
|
152
|
+
|
153
|
+
if not parameter.file.contents.strip():
|
154
|
+
continue
|
155
|
+
|
156
|
+
try:
|
157
|
+
parameter_payload = json.loads(parameter.file.contents)
|
158
|
+
except json.JSONDecodeError:
|
159
|
+
continue
|
160
|
+
|
161
|
+
if "__api-name" not in parameter_payload:
|
162
|
+
continue
|
163
|
+
|
164
|
+
api = parameter_payload["__api-name"]
|
165
|
+
# Create parameter value by excluding __api-name
|
166
|
+
parameter_value = {k: v for k, v in parameter_payload.items() if k != "__api-name"}
|
167
|
+
|
168
|
+
test_parameter: dict[str, Any] = {
|
169
|
+
"name": parameter_key.replace(".json", ""),
|
170
|
+
"lastUsed": False,
|
171
|
+
"id": str(uuid.uuid4()),
|
172
|
+
"value": json.dumps(parameter_value),
|
173
|
+
}
|
174
|
+
|
175
|
+
if api not in api_parameters_map:
|
176
|
+
api_parameters_map[api] = []
|
177
|
+
api_parameters_map[api].append(test_parameter)
|
178
|
+
|
179
|
+
for api, parameters in api_parameters_map.items():
|
180
|
+
# By default, last one used is the last one in the map
|
181
|
+
if len(parameters) > 0:
|
182
|
+
parameters[-1]["lastUsed"] = True
|
183
|
+
|
184
|
+
test_parameters.directory.root[f"{api}.json"] = FileNode(
|
185
|
+
file=FileNodeContent(contents=json.dumps(parameters, indent=2))
|
186
|
+
)
|
187
|
+
|
188
|
+
del file_tree.root["parameters"]
|
189
|
+
file_tree.root["____testParameters"] = test_parameters
|
190
|
+
|
191
|
+
|
192
|
+
class SaveProjectResponse(BaseModel):
|
193
|
+
id: str
|
194
|
+
|
195
|
+
|
196
|
+
async def save_project(
|
197
|
+
*,
|
198
|
+
project_name: str,
|
199
|
+
workspace_id: str,
|
200
|
+
api_key: str,
|
201
|
+
):
|
202
|
+
base_url = get_base_url()
|
203
|
+
url = f"{base_url}/api/v1/workspace/{workspace_id}/projects/{project_name}"
|
204
|
+
print(f"calling {url}")
|
205
|
+
headers = {
|
206
|
+
api_key_header_name: api_key,
|
207
|
+
"Content-Type": "application/json",
|
208
|
+
}
|
209
|
+
cwd = await Path().resolve()
|
210
|
+
file_tree = await get_file_tree_from_project(cwd, exclude=exclusions)
|
211
|
+
mapFileTreeToIdeFileTree(file_tree)
|
212
|
+
|
213
|
+
payload: dict[str, Any] = {
|
214
|
+
"codeTree": file_tree.model_dump(mode="json"),
|
215
|
+
"platformType": "CLI",
|
216
|
+
"language": "python",
|
217
|
+
}
|
218
|
+
|
219
|
+
async with httpx.AsyncClient() as client:
|
220
|
+
response = await client.put(url, headers=headers, json=payload)
|
221
|
+
if response.status_code < 200 or response.status_code >= 300:
|
222
|
+
if response.status_code == 401:
|
223
|
+
raise CLIError("Invalid API key. Please check your API key and try again.")
|
224
|
+
|
225
|
+
raise CLIError(
|
226
|
+
f"[red bold]Invalid response from server:[/red bold]\n [bright_red]{response.status_code} {response.text}[/bright_red][red bold]\nProject save failed.[/red bold]"
|
227
|
+
)
|
228
|
+
|
229
|
+
console.print("[green]Project saved successfully.[/green]")
|
230
|
+
try:
|
231
|
+
response = SaveProjectResponse.model_validate(response.json())
|
232
|
+
except ValidationError:
|
233
|
+
console.print(f"[yellow]Could not parse response:[/yellow]\n {response.text}")
|
234
|
+
return
|
235
|
+
|
236
|
+
dotenv_path = cwd / ".env"
|
237
|
+
if not await dotenv_path.exists():
|
238
|
+
content_to_write = f"""{workspace_env_var_key}={workspace_id}
|
239
|
+
{project_env_var_key}={response.id}
|
240
|
+
{api_key_env_var_key}={api_key}
|
241
|
+
"""
|
242
|
+
await dotenv_path.write_text(content_to_write)
|
243
|
+
console.print("[green]Created .env with project credentials.[/green]")
|
244
|
+
return
|
245
|
+
|
246
|
+
dotenv_content = await dotenv_path.read_text()
|
247
|
+
dotenv = DotEnv(
|
248
|
+
dotenv_path=None,
|
249
|
+
stream=io.StringIO(dotenv_content),
|
250
|
+
).dict()
|
251
|
+
content_to_append = ""
|
252
|
+
if dotenv.get(project_env_var_key) is None or dotenv.get(project_env_var_key) != response.id:
|
253
|
+
content_to_append += f"{project_env_var_key}={response.id}"
|
254
|
+
if dotenv.get(workspace_env_var_key) is None:
|
255
|
+
content_to_append += f"\n{workspace_env_var_key}={workspace_id}"
|
256
|
+
if dotenv.get(api_key_env_var_key) is None:
|
257
|
+
content_to_append += f"\n{api_key_env_var_key}={api_key}"
|
258
|
+
|
259
|
+
await dotenv_path.write_text(f"{dotenv_content}\n{content_to_append}\n")
|
260
|
+
console.print("[green]Updated .env with project credentials.[/green]")
|
intuned_cli/utils/backend.py
CHANGED
@@ -1,5 +1,31 @@
|
|
1
1
|
import os
|
2
2
|
|
3
|
+
from intuned_cli.types import IntunedJson
|
4
|
+
from intuned_cli.utils.error import CLIError
|
5
|
+
|
3
6
|
|
4
7
|
def get_base_url():
|
5
8
|
return os.environ.get("INTUNED_API_BASE_URL") or os.environ.get("INTUNED_API_DOMAIN") or "https://app.intuned.io"
|
9
|
+
|
10
|
+
|
11
|
+
async def get_intuned_api_auth_credentials(
|
12
|
+
*, intuned_json: IntunedJson, workspace_id: str | None, api_key: str | None
|
13
|
+
) -> tuple[str, str]:
|
14
|
+
"""
|
15
|
+
Retrieves the Intuned API authentication credentials from environment variables.
|
16
|
+
|
17
|
+
Returns:
|
18
|
+
tuple: A tuple containing the workspace ID and API key.
|
19
|
+
"""
|
20
|
+
workspace_id = workspace_id or intuned_json.workspace_id
|
21
|
+
api_key = api_key or os.environ.get("INTUNED_API_KEY")
|
22
|
+
|
23
|
+
if not workspace_id:
|
24
|
+
raise CLIError("Workspace ID is required. Please provide it via command line options or Intuned.json")
|
25
|
+
|
26
|
+
if not api_key:
|
27
|
+
raise CLIError(
|
28
|
+
"API key is required. Please provide it via command line options or INTUNED_API_KEY environment variable."
|
29
|
+
)
|
30
|
+
|
31
|
+
return workspace_id, api_key
|
intuned_cli/utils/error.py
CHANGED
@@ -2,6 +2,7 @@ import traceback
|
|
2
2
|
|
3
3
|
from intuned_cli.utils.console import console
|
4
4
|
from runtime.errors.run_api_errors import AutomationError
|
5
|
+
from runtime.errors.run_api_errors import RunApiError
|
5
6
|
|
6
7
|
|
7
8
|
class CLIError(Exception):
|
@@ -20,8 +21,11 @@ class CLIError(Exception):
|
|
20
21
|
self.auto_color = auto_color
|
21
22
|
|
22
23
|
|
23
|
-
def log_automation_error(e:
|
24
|
+
def log_automation_error(e: RunApiError):
|
24
25
|
console.print("[bold red]An error occurred while running the API:[/bold red]")
|
25
26
|
|
26
|
-
|
27
|
-
|
27
|
+
if isinstance(e, AutomationError):
|
28
|
+
stack_trace = traceback.format_exception(type(e.error), value=e.error, tb=e.error.__traceback__)
|
29
|
+
console.print(f"[red]{''.join(stack_trace)}[/red]")
|
30
|
+
else:
|
31
|
+
console.print(f"[red]{e}[/red]")
|
@@ -258,7 +258,6 @@ async def _run_payloads_and_extend(
|
|
258
258
|
headless=headless,
|
259
259
|
cdp_address=cdp_address,
|
260
260
|
auth=Auth(
|
261
|
-
run_check=True,
|
262
261
|
session=StateSession(state=session),
|
263
262
|
)
|
264
263
|
if session is not None
|
@@ -292,7 +291,6 @@ async def run_api_for_ide_mode(
|
|
292
291
|
session=StateSession(
|
293
292
|
state=session,
|
294
293
|
),
|
295
|
-
run_check=False,
|
296
294
|
)
|
297
295
|
if session is not None
|
298
296
|
else None,
|
intuned_runtime/__init__.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from runtime.helpers import extend_payload
|
2
2
|
from runtime.helpers import extend_timeout
|
3
|
+
from runtime.helpers.attempt_store import attempt_store
|
3
4
|
from runtime.helpers.get_auth_session_parameters import get_auth_session_parameters
|
4
5
|
|
5
|
-
__all__ = ["extend_payload", "extend_timeout", "get_auth_session_parameters"]
|
6
|
+
__all__ = ["extend_payload", "extend_timeout", "get_auth_session_parameters", "attempt_store"]
|
@@ -1,8 +1,9 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: intuned-runtime
|
3
|
-
Version: 1.2.
|
3
|
+
Version: 1.2.2
|
4
4
|
Summary: Runtime commands for Intuned platform Python scrapers
|
5
5
|
License: Elastic-2.0
|
6
|
+
License-File: LICENSE
|
6
7
|
Keywords: runtime,intuned
|
7
8
|
Author: Intuned Developers
|
8
9
|
Author-email: engineering@intunedhq.com
|
@@ -15,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
15
16
|
Classifier: Programming Language :: Python :: 3.11
|
16
17
|
Classifier: Programming Language :: Python :: 3.12
|
17
18
|
Classifier: Programming Language :: Python :: 3.13
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
18
20
|
Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
|
19
21
|
Requires-Dist: arguably (>=1.3.0,<2.0.0)
|
20
22
|
Requires-Dist: browserforge[all]
|
@@ -1,32 +1,34 @@
|
|
1
|
-
intuned_cli/__init__.py,sha256=
|
2
|
-
intuned_cli/commands/__init__.py,sha256=
|
3
|
-
intuned_cli/commands/attempt_api_command.py,sha256=
|
4
|
-
intuned_cli/commands/attempt_authsession_check_command.py,sha256=
|
1
|
+
intuned_cli/__init__.py,sha256=COYx7VKi1Qdsusc58Tt3P1DpGUd6LFomjbf53GTCVKI,1466
|
2
|
+
intuned_cli/commands/__init__.py,sha256=pN4XuY6EAAaiJRoRcpcyKw4Lei9ZfS7zmoaoNuwbz9I,1291
|
3
|
+
intuned_cli/commands/attempt_api_command.py,sha256=3iN3L_vd--wMb96zgW-qNfyaqrGTUdcxKhYgifQu9XU,1949
|
4
|
+
intuned_cli/commands/attempt_authsession_check_command.py,sha256=y7ODZLLSF5Pzm3LHOdRr7frGeuFn18NKHNtkg6_NH9Q,1329
|
5
5
|
intuned_cli/commands/attempt_authsession_command.py,sha256=hir9y1XyW9VYioOWT6C1-dH43f3JcHhIlfEEijMg2Lc,277
|
6
|
-
intuned_cli/commands/attempt_authsession_create_command.py,sha256=
|
6
|
+
intuned_cli/commands/attempt_authsession_create_command.py,sha256=tgnBqtboV2PRfGGr0kxTVo6yAOf2M2kD1DBlwNPW7pA,1703
|
7
7
|
intuned_cli/commands/attempt_command.py,sha256=7NZC2dXA8GViCH3ZS2PMziR43hv2RmS6-jgW7l9bu40,253
|
8
8
|
intuned_cli/commands/command.py,sha256=b0OlQIFhoCjCo5QIerfysccBKcU9iIsvqiU7oxshA2M,727
|
9
|
-
intuned_cli/commands/deploy_command.py,sha256=
|
9
|
+
intuned_cli/commands/deploy_command.py,sha256=vZQy6AJUuyfdsM_7DIvMIBhmk7RUxBsUDLOB8uPNzVw,1549
|
10
10
|
intuned_cli/commands/init_command.py,sha256=JIDr9vY_SRKCvVbXE_lCEeHQlmC48LMZz0cghMPAs90,492
|
11
|
-
intuned_cli/commands/run_api_command.py,sha256=
|
11
|
+
intuned_cli/commands/run_api_command.py,sha256=Hwltx9eMEzksUoZUcpzTe2teGSD7tyu8f08uXSGBJOs,3008
|
12
12
|
intuned_cli/commands/run_authsession_command.py,sha256=kM_TANy8M3yx8iBUsgSDO42byzccikLOd9KJfytfLmQ,269
|
13
|
-
intuned_cli/commands/run_authsession_create_command.py,sha256=
|
14
|
-
intuned_cli/commands/run_authsession_update_command.py,sha256=
|
15
|
-
intuned_cli/commands/run_authsession_validate_command.py,sha256=
|
13
|
+
intuned_cli/commands/run_authsession_create_command.py,sha256=kVxQR8j8yARGRKY2FyUhW_E3hRA3KmGzP2zG3gVLRBc,2084
|
14
|
+
intuned_cli/commands/run_authsession_update_command.py,sha256=vK3rJcHkqL-mVfL5IGrLb_4tWRQ6xpufwXymBr0kTCw,2140
|
15
|
+
intuned_cli/commands/run_authsession_validate_command.py,sha256=lri5CyV-1cHOgKa83BTHHLeMT6_xlpbkS5vH1pUjHNA,2003
|
16
16
|
intuned_cli/commands/run_command.py,sha256=JN1yCewcyb4zFquMcv0wEZ_aRmhJZIBMheY8L9yBeDE,245
|
17
|
+
intuned_cli/commands/save_command.py,sha256=iW8i42B_NBrhmuGveV0XXkhaKW0Xx63ofOgMfH1FxTM,1543
|
17
18
|
intuned_cli/controller/__test__/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
|
-
intuned_cli/controller/__test__/test_api.py,sha256=
|
19
|
+
intuned_cli/controller/__test__/test_api.py,sha256=yuKxW6yWlAs1Pj6QBJ4DyryR8A8suG-A7xtBiUcMJ-E,19242
|
19
20
|
intuned_cli/controller/__test__/test_authsession.py,sha256=y7O3dkDPHZTf2W7WRLEhysYYRati0hg93-J8BvGjRUM,35110
|
20
|
-
intuned_cli/controller/api.py,sha256=
|
21
|
-
intuned_cli/controller/authsession.py,sha256=
|
22
|
-
intuned_cli/controller/deploy.py,sha256=
|
21
|
+
intuned_cli/controller/api.py,sha256=cIxfuKsMibn7XRbTQCELy1QVtWH-UMWO8KGyOCvKx2A,7135
|
22
|
+
intuned_cli/controller/authsession.py,sha256=KGpRYXO9W6DFbuM1ofdH5aba0Zs4A_A3nhbNj-KaCE8,14272
|
23
|
+
intuned_cli/controller/deploy.py,sha256=krbVm0-c1XDIsiOPIf0lesFmFuVF_VfGYNWCOQ70Mmo,5452
|
24
|
+
intuned_cli/controller/save.py,sha256=_ujsh4C9cgFGW4GTkj43JDSpIq0TuBLhZRJsH_1wgX8,9025
|
23
25
|
intuned_cli/types.py,sha256=Lsykui4mbq5T1_1L7Gg5OZpJvCOmaz9EiQcZVfUEoLc,766
|
24
26
|
intuned_cli/utils/api_helpers.py,sha256=57gvXVYM_9hMGIkMqSEN_jzs3mYWlXzinPjYXzzqgg8,1122
|
25
27
|
intuned_cli/utils/auth_session_helpers.py,sha256=acKAPUjOfdybstLur4Lk3huaLFP2Ipl4AjPSqQPQLzY,1899
|
26
|
-
intuned_cli/utils/backend.py,sha256=
|
28
|
+
intuned_cli/utils/backend.py,sha256=CtGoX6eWO6dUnND_FUurIgBXgp9Z4y89k8j3tYK9G-k,1030
|
27
29
|
intuned_cli/utils/confirmation.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
28
30
|
intuned_cli/utils/console.py,sha256=jgin2xB-0kpANPMoiDBOnWhrc8Ey5zT4IHRxQa8f5CQ,93
|
29
|
-
intuned_cli/utils/error.py,sha256=
|
31
|
+
intuned_cli/utils/error.py,sha256=KzqWG0e8PLerCY-AL534zygCLbIBD6uReo2WEuej-EA,990
|
30
32
|
intuned_cli/utils/exclusions.py,sha256=Qe7NkWA3lsX73dOC9WprdD0KyYc_vuiwkrUCwD105gk,796
|
31
33
|
intuned_cli/utils/get_auth_parameters.py,sha256=HmMSjBE8bPulkUdX319Ipr383Ko2Gtz3y8_WT9CK3Kw,798
|
32
34
|
intuned_cli/utils/import_function.py,sha256=UmE2yw5std1ENMFdBU-TUBuQ01qsv7Qr5ElnAhqE6Yc,453
|
@@ -41,11 +43,11 @@ intuned_internal_cli/commands/browser/save_state.py,sha256=_gfspwLkk6j1zBzqY1Qr7
|
|
41
43
|
intuned_internal_cli/commands/init.py,sha256=8rWBenWZfwNtLxOBqhEMbOATyQNEnmDUmrFJ1xBGyxI,4384
|
42
44
|
intuned_internal_cli/commands/project/__init__.py,sha256=t97wvhSenerYRdbSeCKXqHASA6EWA3lc1hnRhF9baOE,734
|
43
45
|
intuned_internal_cli/commands/project/auth_session/__init__.py,sha256=gt7mlaW6xmqAc_4-pfF_FiecsR51C6fqCaq_NFbcbwA,300
|
44
|
-
intuned_internal_cli/commands/project/auth_session/check.py,sha256=
|
46
|
+
intuned_internal_cli/commands/project/auth_session/check.py,sha256=UDG2iF6ULmYH6_WdUTVRTAZq3fBHuVSzhWB1tGkkGb0,4524
|
45
47
|
intuned_internal_cli/commands/project/auth_session/create.py,sha256=r-eYu3uLUo2mzF836CbVgu4oBzcIIDekzzFwwekxmg0,3374
|
46
48
|
intuned_internal_cli/commands/project/auth_session/load.py,sha256=Y0V6bFZQeHq_lTtR-rQvM9SSbExWhVVMFu-Uykl-9k4,1199
|
47
49
|
intuned_internal_cli/commands/project/project.py,sha256=_MSh6Xor2Cbh-ItifwgOPq_BP8UDuKB7S6w796FULKQ,137
|
48
|
-
intuned_internal_cli/commands/project/run.py,sha256=
|
50
|
+
intuned_internal_cli/commands/project/run.py,sha256=7lqrFrTOaW0W5UF6WTVwwDauAoGbABNv6RVQ6_DEIfQ,12209
|
49
51
|
intuned_internal_cli/commands/project/run_interface.py,sha256=4RyR8WZriIF7Va4z1wt-q6zZDQOI31n62Ho2dyimzUY,8717
|
50
52
|
intuned_internal_cli/commands/project/type_check.py,sha256=QFnXL93Y-z250EQRln5sRqwkVqjGJ-eP_p1jGrXB7NA,3522
|
51
53
|
intuned_internal_cli/commands/project/upgrade.py,sha256=XmkZLflM4O-mwvhwcswlZpazRotwi3xesLgE0Zz8fTI,3061
|
@@ -57,42 +59,47 @@ intuned_internal_cli/utils/code_tree.py,sha256=1wfxZoQ5kRCfqs2SEPAicbAIPTiD6P1Lx
|
|
57
59
|
intuned_internal_cli/utils/run_apis.py,sha256=Zee4zkgt9R8XY1XCGzj2Nc4zJ3jlRz1xnO493wotuWw,4690
|
58
60
|
intuned_internal_cli/utils/setup_ide_functions_token.py,sha256=72-hf5HOPO9hj_eo3MTSVEPIwtkaIma_NRepsw_FHQM,304
|
59
61
|
intuned_internal_cli/utils/unix_socket.py,sha256=UISmkJMHrir5iBLUm6vxC3uzTGIFyOk_wa0C9LUw4Cc,1889
|
60
|
-
intuned_runtime/__init__.py,sha256=
|
62
|
+
intuned_runtime/__init__.py,sha256=1BPzEc-qC2WAYiCWDOJChpgnFyO3ZNYRKHEZqdHUGwM,322
|
61
63
|
runtime/__init__.py,sha256=87gDXuxUv_kGzQfuB1mh6DF-dDysJN8r684c7jGnHxc,144
|
62
64
|
runtime/backend_functions/__init__.py,sha256=j2EaK4FK8bmdFtqc5FxtFwx1KhIn_7qKPChrrAhJI3s,119
|
63
|
-
runtime/backend_functions/_call_backend_function.py,sha256=
|
65
|
+
runtime/backend_functions/_call_backend_function.py,sha256=NpbQmEieuRan0fgbJQ0L_skU6MgjMmR99bWsQQYuFtI,3402
|
64
66
|
runtime/backend_functions/get_auth_session_parameters.py,sha256=pOvB7XiWpphEuBpazdKALw9EWgBU1PeY3gkzBfVLpkc,869
|
65
67
|
runtime/browser/__init__.py,sha256=EPWfa4ZmdR8GJqh2qcsx1ZvHmCYiUYrQ-zeHYVapH9s,285
|
66
|
-
runtime/browser/helpers.py,sha256=
|
67
|
-
runtime/browser/launch_browser.py,sha256=
|
68
|
+
runtime/browser/helpers.py,sha256=CwgiBToawPgwAY9nIGkGHW544N7Db_OgKmS-SHkN2pU,1255
|
69
|
+
runtime/browser/launch_browser.py,sha256=ym97J4RffOGUwhi9iNjAR5Ek2f8pKiAtAcDQFSqMJw0,1899
|
68
70
|
runtime/browser/launch_camoufox.py,sha256=TBOAwwipNGlbtMdFYnGkVM0ppLU44vWNkMGZA5uPZCE,1787
|
69
|
-
runtime/browser/launch_chromium.py,sha256=
|
71
|
+
runtime/browser/launch_chromium.py,sha256=YvZig28k1e_txdNyheOAnh1P8r2o5sVQVcKIEXkjQRA,8038
|
70
72
|
runtime/browser/storage_state.py,sha256=fwLg8sP-H-vgt_6AJKNl03CpgyMVCQWWcN2cqswTQMs,3603
|
73
|
+
runtime/constants.py,sha256=YMYQgCWZdJXUpxz_IN2TvZO5rFye9k_Lk9CS8m-shLg,34
|
71
74
|
runtime/context/__init__.py,sha256=hg8ejm4bJy4tNkwmZ9lKgYJx6bU7OgOdBS684Uv5XGg,73
|
72
|
-
runtime/context/context.py,sha256=
|
73
|
-
runtime/env.py,sha256=
|
75
|
+
runtime/context/context.py,sha256=uZxt5cF7Q66Ng6F5J83LMCn9UG7swWOM3f-Wz4MYq7w,1805
|
76
|
+
runtime/env.py,sha256=DYEU5a-XtPbImdmLEDqdWn40JGSOvG-V7mIXrt0sM9A,660
|
74
77
|
runtime/errors/__init__.py,sha256=oqiBSvT_yFLQ3hG0AbCUA3WYFaxkTDVkDMSy59xvBCo,688
|
75
78
|
runtime/errors/auth_session_errors.py,sha256=6b4XTI8UCDHDPX4jEA8_HyrNUp4VZ1TrEA8DRh6Z3rM,228
|
76
|
-
runtime/errors/run_api_errors.py,sha256=
|
79
|
+
runtime/errors/run_api_errors.py,sha256=D3zHsJnsjSOy0JUac_CDgHWOYEsOG2CUo7boVcteuCI,4070
|
77
80
|
runtime/errors/trace_errors.py,sha256=Lzfo0sH3zGaWz1kn5DHcAXQMn3aR2y2bnauj6xP1LYE,110
|
78
|
-
runtime/helpers/__init__.py,sha256=
|
81
|
+
runtime/helpers/__init__.py,sha256=rkywrr_76AMaIsAAjsVmmfLh8BjB5_E6AeJY5sWFm6U,292
|
82
|
+
runtime/helpers/attempt_store.py,sha256=YPwGHkdJNbC8rSiQ1vOReKNOQrjPT1wpO9ZDcQf530I,339
|
79
83
|
runtime/helpers/extend_payload.py,sha256=towZF08WTpTTDBL4AV1bUU3XpKAQHEB66kGUfTICDe0,246
|
80
84
|
runtime/helpers/extend_timeout.py,sha256=KjfSLEUrqoz7v00rhnPAKq2OmUzEzcv-eQ3M8c2U46s,348
|
81
85
|
runtime/helpers/get_auth_session_parameters.py,sha256=7bopGhJ7vjKAn_UxnHSAah-k2rVOPbq0zi9FQOOCFds,472
|
82
86
|
runtime/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
83
87
|
runtime/run/__init__.py,sha256=zxMYVb7hn147YTrhMLsrcX6-KTd71HLrYHstJOWeWXQ,52
|
84
88
|
runtime/run/intuned_settings.py,sha256=vy2-ktEzUfUp5Z90dp3l7jPKHNjgB-8GSMDgAY-rYaU,1074
|
85
|
-
runtime/run/
|
89
|
+
runtime/run/playwright_context.py,sha256=pSTWIg_XpnuHtV5noApabAlZGZJYqtX82YW77n1FGFU,4675
|
90
|
+
runtime/run/playwright_tracing.py,sha256=oqr9b6xjqdYPCEOUvIVE1M7ord7LAvXAUQAz38Jwjw8,708
|
86
91
|
runtime/run/pydantic_encoder.py,sha256=wJCljwwINSICvCJ0i2izp2RLkQ15nYglUQCyyjM40Jk,332
|
87
|
-
runtime/run/run_api.py,sha256=
|
92
|
+
runtime/run/run_api.py,sha256=AFhZerLTyHGznGCJl0fLbiTqhZ_WRmphaJK-RqrupTQ,8981
|
93
|
+
runtime/run/setup_context_hook.py,sha256=KTX4mRmeUEmYS9zrmobR1V09GakOk6uz81Uo_xXTJZk,1156
|
88
94
|
runtime/run/traces.py,sha256=fKzh11LqV47ujgq_9I2tdp-dgld566wffWaHwU_4gis,1123
|
95
|
+
runtime/run/types.py,sha256=RR4RGiYVBIK6f2qXvzfLhQMZ8kmrziu265k5eqoIh98,346
|
89
96
|
runtime/types/__init__.py,sha256=IJkDfqsau8F8R_j8TO6j-JwW4ElQr6aU6LNaWRehg5U,401
|
90
97
|
runtime/types/payload.py,sha256=sty8HgDEn3nJbZrwEOMCXyuG7_ICGDwlBIIWSON5ABY,124
|
91
|
-
runtime/types/run_types.py,sha256
|
92
|
-
runtime_helpers/__init__.py,sha256=
|
98
|
+
runtime/types/run_types.py,sha256=GcYLkL2BHxOjT4O3KvBP6xjBKsmJbjltMt_5bCVnfCI,4554
|
99
|
+
runtime_helpers/__init__.py,sha256=1BPzEc-qC2WAYiCWDOJChpgnFyO3ZNYRKHEZqdHUGwM,322
|
93
100
|
runtime_helpers/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
94
|
-
intuned_runtime-1.2.
|
95
|
-
intuned_runtime-1.2.
|
96
|
-
intuned_runtime-1.2.
|
97
|
-
intuned_runtime-1.2.
|
98
|
-
intuned_runtime-1.2.
|
101
|
+
intuned_runtime-1.2.2.dist-info/METADATA,sha256=IyR56IaaeamglTzQ0OOENJP78Kb9GNzSegBhcactZM0,5372
|
102
|
+
intuned_runtime-1.2.2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
103
|
+
intuned_runtime-1.2.2.dist-info/entry_points.txt,sha256=ToMS2cqDeRmF1FGkflwoeD-Xz6jJV5p1zIbw9G7IxMg,85
|
104
|
+
intuned_runtime-1.2.2.dist-info/licenses/LICENSE,sha256=9LIjQdgyU_ptzNIfItNCR7VmEHqYnrY1f1XwOreKFI0,3714
|
105
|
+
intuned_runtime-1.2.2.dist-info/RECORD,,
|
@@ -5,8 +5,11 @@ from typing import Literal
|
|
5
5
|
from httpx import AsyncClient
|
6
6
|
from pydantic import BaseModel
|
7
7
|
|
8
|
+
from runtime.constants import api_key_header_name
|
8
9
|
from runtime.context.context import IntunedContext
|
10
|
+
from runtime.env import get_api_key
|
9
11
|
from runtime.env import get_functions_domain
|
12
|
+
from runtime.env import get_is_running_in_cli
|
10
13
|
from runtime.env import get_project_id
|
11
14
|
from runtime.env import get_workspace_id
|
12
15
|
|
@@ -24,19 +27,27 @@ async def call_backend_function[T: BaseModel](
|
|
24
27
|
functions_domain, workspace_id, project_id = get_functions_domain(), get_workspace_id(), get_project_id()
|
25
28
|
|
26
29
|
if functions_domain is None or workspace_id is None or project_id is None:
|
27
|
-
|
30
|
+
if get_is_running_in_cli():
|
31
|
+
raise Exception(
|
32
|
+
"API credentials not set - make sure to save your project to Intuned to set up the correct API credentials."
|
33
|
+
)
|
34
|
+
|
28
35
|
raise Exception("No workspace ID or project ID found.")
|
29
36
|
|
30
37
|
context = IntunedContext.current()
|
31
38
|
|
32
39
|
async with AsyncClient() as client:
|
40
|
+
api_key = get_api_key()
|
41
|
+
if api_key is not None:
|
42
|
+
client.headers[api_key_header_name] = api_key
|
43
|
+
|
33
44
|
if context.functions_token:
|
34
45
|
client.headers["Authorization"] = f"Bearer {context.functions_token}"
|
35
46
|
if params:
|
36
47
|
client.headers["Content-Type"] = "application/json"
|
37
48
|
path = f"{functions_domain}/api/{workspace_id}/functions/{project_id}/{name}"
|
38
49
|
body = params.model_dump() if params else None
|
39
|
-
|
50
|
+
|
40
51
|
response = await client.request(
|
41
52
|
method,
|
42
53
|
path,
|
@@ -56,6 +67,11 @@ async def call_backend_function[T: BaseModel](
|
|
56
67
|
)
|
57
68
|
if 200 <= response.status_code < 300:
|
58
69
|
return validation_model.model_validate(response_json)
|
70
|
+
if response.status_code == 401 and get_is_running_in_cli():
|
71
|
+
raise CallBackendException(
|
72
|
+
response.status_code,
|
73
|
+
"Unauthorized backend function call - make sure to save your project to Intuned to set up the correct API credentials",
|
74
|
+
)
|
59
75
|
raise CallBackendException(
|
60
76
|
response.status_code,
|
61
77
|
f"Calling backend function errored with status {response.status_code}: {response_json}",
|
runtime/browser/helpers.py
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
+
import asyncio
|
1
2
|
import os
|
2
3
|
from typing import Optional
|
3
4
|
from typing import TYPE_CHECKING
|
4
5
|
|
6
|
+
import tenacity as tx
|
7
|
+
from httpx import AsyncClient
|
5
8
|
from playwright.async_api import ProxySettings
|
6
9
|
|
7
10
|
if TYPE_CHECKING:
|
@@ -19,3 +22,22 @@ def get_proxy_env() -> Optional["ProxySettings"]:
|
|
19
22
|
"username": username,
|
20
23
|
"password": password,
|
21
24
|
}
|
25
|
+
|
26
|
+
|
27
|
+
def get_local_cdp_address(port: int) -> str:
|
28
|
+
return f"http://localhost:{port}"
|
29
|
+
|
30
|
+
|
31
|
+
async def wait_on_cdp_address(cdp_address: str) -> None:
|
32
|
+
cdp_address_without_protocol = (
|
33
|
+
cdp_address.replace("http://", "").replace("https://", "").replace("localhost", "127.0.0.1")
|
34
|
+
)
|
35
|
+
|
36
|
+
@tx.retry(stop=tx.stop_after_delay(5), wait=tx.wait_fixed(0.1))
|
37
|
+
async def _check_cdp() -> None:
|
38
|
+
async with AsyncClient(timeout=1) as client:
|
39
|
+
response = await client.get(f"http://{cdp_address_without_protocol}/json/version")
|
40
|
+
response.raise_for_status()
|
41
|
+
|
42
|
+
await asyncio.sleep(0.1)
|
43
|
+
await _check_cdp()
|