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.
Files changed (44) hide show
  1. intuned_cli/__init__.py +12 -2
  2. intuned_cli/commands/__init__.py +1 -0
  3. intuned_cli/commands/attempt_api_command.py +1 -1
  4. intuned_cli/commands/attempt_authsession_check_command.py +1 -1
  5. intuned_cli/commands/attempt_authsession_create_command.py +1 -1
  6. intuned_cli/commands/deploy_command.py +4 -4
  7. intuned_cli/commands/run_api_command.py +1 -1
  8. intuned_cli/commands/run_authsession_create_command.py +1 -1
  9. intuned_cli/commands/run_authsession_update_command.py +1 -1
  10. intuned_cli/commands/run_authsession_validate_command.py +1 -1
  11. intuned_cli/commands/save_command.py +47 -0
  12. intuned_cli/controller/__test__/test_api.py +0 -1
  13. intuned_cli/controller/api.py +0 -1
  14. intuned_cli/controller/authsession.py +0 -1
  15. intuned_cli/controller/deploy.py +8 -210
  16. intuned_cli/controller/save.py +260 -0
  17. intuned_cli/utils/backend.py +26 -0
  18. intuned_cli/utils/error.py +7 -3
  19. intuned_internal_cli/commands/project/auth_session/check.py +0 -1
  20. intuned_internal_cli/commands/project/run.py +0 -2
  21. intuned_runtime/__init__.py +2 -1
  22. {intuned_runtime-1.2.1.dist-info → intuned_runtime-1.2.2.dist-info}/METADATA +4 -2
  23. {intuned_runtime-1.2.1.dist-info → intuned_runtime-1.2.2.dist-info}/RECORD +43 -36
  24. {intuned_runtime-1.2.1.dist-info → intuned_runtime-1.2.2.dist-info}/WHEEL +1 -1
  25. runtime/backend_functions/_call_backend_function.py +18 -2
  26. runtime/browser/helpers.py +22 -0
  27. runtime/browser/launch_browser.py +43 -15
  28. runtime/browser/launch_chromium.py +26 -22
  29. runtime/constants.py +1 -0
  30. runtime/context/context.py +1 -0
  31. runtime/env.py +15 -1
  32. runtime/errors/run_api_errors.py +8 -0
  33. runtime/helpers/__init__.py +2 -1
  34. runtime/helpers/attempt_store.py +14 -0
  35. runtime/run/playwright_context.py +147 -0
  36. runtime/run/playwright_tracing.py +27 -0
  37. runtime/run/run_api.py +71 -91
  38. runtime/run/setup_context_hook.py +40 -0
  39. runtime/run/types.py +14 -0
  40. runtime/types/run_types.py +0 -1
  41. runtime_helpers/__init__.py +2 -1
  42. runtime/run/playwright_constructs.py +0 -20
  43. {intuned_runtime-1.2.1.dist-info → intuned_runtime-1.2.2.dist-info}/entry_points.txt +0 -0
  44. {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]")
@@ -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
@@ -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: AutomationError):
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
- stack_trace = traceback.format_exception(type(e.error), value=e.error, tb=e.error.__traceback__)
27
- console.print(f"[red]{''.join(stack_trace)}[/red]")
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]")
@@ -94,7 +94,6 @@ async def project__auth_session__check(
94
94
  if cdp_address is not None
95
95
  else StandaloneRunOptions(headless=not no_headless),
96
96
  auth=Auth(
97
- run_check=False,
98
97
  session=StateSession(
99
98
  state=auth_session,
100
99
  ),
@@ -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,
@@ -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.3
1
+ Metadata-Version: 2.4
2
2
  Name: intuned-runtime
3
- Version: 1.2.1
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=SNcUoVkqPTmIxjEsp05GZBKaAzgCt-rBkvkzndjpfPo,1124
2
- intuned_cli/commands/__init__.py,sha256=qndefhU1fGHYYu7f7wxcv03F27NPMIwsxUd3K-fmOD8,1236
3
- intuned_cli/commands/attempt_api_command.py,sha256=f4Oi8vjNzGLMgk_1XXO5KzOIAw9sq9N8A62d3QIZxso,1954
4
- intuned_cli/commands/attempt_authsession_check_command.py,sha256=RdKRltXIp51wnXamRB1Xr45TUBUs1AQCmAzW8tMQnxo,1334
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=ynAKlNoHuaVPgx4gv2AJquIt7ZVIe8Nh1IIHwNiDeNA,1708
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=0XG8lo5fGeorZMxMtEx12EaqLFMvmFYJ7Zu9sgJCmQ8,1540
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=Y6FkSFI9wybuoW204YqOAiQ794__hIw12Eaf9px_ovE,3013
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=SiuIZ7LTMqT3gnd8DUt-VmP3cyuA0I_i27daI3IJ2tw,2089
14
- intuned_cli/commands/run_authsession_update_command.py,sha256=Op7epDgvEhjp17qs2dIXerTckHz7wlOsZsBDp8UJLjI,2145
15
- intuned_cli/commands/run_authsession_validate_command.py,sha256=5eET02paijq1f1rzsheINvAqVYj1JGUA_iGD5Py3LVM,2008
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=8Hk3V52qKhWIqjirKFwN68Tv5er70gTxemjI35HyZiU,19295
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=1wHgatavA5zr6kYMf1BJ3lC5JR1IvlVBiab_ahOSVnY,7172
21
- intuned_cli/controller/authsession.py,sha256=z8C97T-beEORNemLFx2x_JsHr2gXRakwKGV3Rgivnu8,14309
22
- intuned_cli/controller/deploy.py,sha256=5cQgtpT2nDQ5XSUnIoeBR9xdyVoLWcY-2btuUxUD7Es,12343
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=9HmaIvyKRkybUL-lEiJYHbIclinEZEBNBFLUG0hNC0A,150
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=DmNlXBb7zfpHDRya00ouEcRgUEZuGaTqIy1SIjDWVfo,842
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=AFILp7m34nAO_RD3IfRpuJm5Zh0wnCRtBXqIrerdbwo,4565
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=FDYYkU24aURYbljyYLFo8wLF-nvb86EVr9gMEjAfeE0,12274
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=XBrEiE9yNC8Lgn8NgIkqNXbI6e4ap237E83Zj_nlhCQ,249
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=h6UUsQ2pLUTbEQNYM1TLdesc5yWWaUrPUaTbCtCIxlU,2769
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=b1Xp005adbl7ZeJrSEYgH2OPzrZEHxTPgGTsnS3OqS0,554
67
- runtime/browser/launch_browser.py,sha256=f71hLUDbIBEv5hhQVOQFTXleUm_X1kmEpp0kTx0SqZo,966
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=QY9Mxw8FImT_W23cT2zjTAeLvApzVHA_YSu0b77nfeA,7813
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=pl_0x77_d5CiAznz1qGSk6o9cW-msNvlCt-2eFoMKlA,1739
73
- runtime/env.py,sha256=OXxzLpM56AJVlX0gmG7Ph82xAfqZboW3kv2232lzXb4,306
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=Bjs_gsgdIUbVLN67E_s-804qB2lyzIljDEaAcymgwK0,3906
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=jozYPKHgZJ7Na5U1Wjt83egzjPATMZ_OMInEI6swSbY,234
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/playwright_constructs.py,sha256=EIfnRlAi1k1wRiTT2OpPFVmrWwEeeiryuORibaLD1Xw,573
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=JiO-UBpCBP7H4TZbKXmYmGZmYrkqidqEg7epaxj_3jA,10105
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=-j-XKXfRkzlyoW-Doe0og2jbqMMQWjOTIUqRFEc8lHA,4582
92
- runtime_helpers/__init__.py,sha256=XBrEiE9yNC8Lgn8NgIkqNXbI6e4ap237E83Zj_nlhCQ,249
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.1.dist-info/LICENSE,sha256=9LIjQdgyU_ptzNIfItNCR7VmEHqYnrY1f1XwOreKFI0,3714
95
- intuned_runtime-1.2.1.dist-info/METADATA,sha256=HF-_MS8G5PkAx2xWevN0giX-4Vrs9VpqWICGmdSAsYM,5299
96
- intuned_runtime-1.2.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
97
- intuned_runtime-1.2.1.dist-info/entry_points.txt,sha256=ToMS2cqDeRmF1FGkflwoeD-Xz6jJV5p1zIbw9G7IxMg,85
98
- intuned_runtime-1.2.1.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.3
2
+ Generator: poetry-core 2.2.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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
- # todo
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
- print("Calling backend function", method, path, context.functions_token, json.dumps(body, indent=2))
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}",
@@ -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()