intuned-runtime 1.0.0__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 (58) hide show
  1. cli/__init__.py +45 -0
  2. cli/commands/__init__.py +25 -0
  3. cli/commands/ai_source/__init__.py +4 -0
  4. cli/commands/ai_source/ai_source.py +10 -0
  5. cli/commands/ai_source/deploy.py +64 -0
  6. cli/commands/browser/__init__.py +3 -0
  7. cli/commands/browser/save_state.py +32 -0
  8. cli/commands/init.py +127 -0
  9. cli/commands/project/__init__.py +20 -0
  10. cli/commands/project/auth_session/__init__.py +5 -0
  11. cli/commands/project/auth_session/check.py +118 -0
  12. cli/commands/project/auth_session/create.py +96 -0
  13. cli/commands/project/auth_session/load.py +39 -0
  14. cli/commands/project/project.py +10 -0
  15. cli/commands/project/run.py +340 -0
  16. cli/commands/project/run_interface.py +265 -0
  17. cli/commands/project/type_check.py +86 -0
  18. cli/commands/project/upgrade.py +92 -0
  19. cli/commands/publish_packages.py +264 -0
  20. cli/logger.py +19 -0
  21. cli/utils/ai_source_project.py +31 -0
  22. cli/utils/code_tree.py +83 -0
  23. cli/utils/run_apis.py +147 -0
  24. cli/utils/unix_socket.py +55 -0
  25. intuned_runtime-1.0.0.dist-info/LICENSE +42 -0
  26. intuned_runtime-1.0.0.dist-info/METADATA +113 -0
  27. intuned_runtime-1.0.0.dist-info/RECORD +58 -0
  28. intuned_runtime-1.0.0.dist-info/WHEEL +4 -0
  29. intuned_runtime-1.0.0.dist-info/entry_points.txt +3 -0
  30. runtime/__init__.py +3 -0
  31. runtime/backend_functions/__init__.py +5 -0
  32. runtime/backend_functions/_call_backend_function.py +86 -0
  33. runtime/backend_functions/get_auth_session_parameters.py +30 -0
  34. runtime/browser/__init__.py +3 -0
  35. runtime/browser/launch_chromium.py +212 -0
  36. runtime/browser/storage_state.py +106 -0
  37. runtime/context/__init__.py +5 -0
  38. runtime/context/context.py +51 -0
  39. runtime/env.py +13 -0
  40. runtime/errors/__init__.py +21 -0
  41. runtime/errors/auth_session_errors.py +9 -0
  42. runtime/errors/run_api_errors.py +120 -0
  43. runtime/errors/trace_errors.py +3 -0
  44. runtime/helpers/__init__.py +5 -0
  45. runtime/helpers/extend_payload.py +9 -0
  46. runtime/helpers/extend_timeout.py +13 -0
  47. runtime/helpers/get_auth_session_parameters.py +14 -0
  48. runtime/py.typed +0 -0
  49. runtime/run/__init__.py +3 -0
  50. runtime/run/intuned_settings.py +38 -0
  51. runtime/run/playwright_constructs.py +19 -0
  52. runtime/run/run_api.py +233 -0
  53. runtime/run/traces.py +36 -0
  54. runtime/types/__init__.py +15 -0
  55. runtime/types/payload.py +7 -0
  56. runtime/types/run_types.py +177 -0
  57. runtime_helpers/__init__.py +5 -0
  58. runtime_helpers/py.typed +0 -0
@@ -0,0 +1,42 @@
1
+ Acceptance
2
+ By using the software, you agree to all of the terms and conditions below.
3
+
4
+ Copyright License
5
+ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations and conditions below.
6
+
7
+ Limitations
8
+ You may not provide the software to third parties as a hosted or managed service, where the service provides users with access to any substantial set of the features or functionality of the software.
9
+
10
+ You may not move, change, disable, or circumvent the license key functionality in the software, and you may not remove or obscure any functionality in the software that is protected by the license key.
11
+
12
+ You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensor’s trademarks is subject to applicable law.
13
+
14
+ Patents
15
+ The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
16
+
17
+ Notices
18
+ You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms.
19
+
20
+ If you modify the software, you must include in any modified copies of the software prominent notices stating that you have modified the software.
21
+
22
+ No Other Rights
23
+ These terms do not imply any licenses other than those expressly granted in these terms.
24
+
25
+ Termination
26
+ If you use the software in violation of these terms, such use is not licensed, and your licenses will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your licenses will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your licenses to terminate automatically and permanently.
27
+
28
+ No Liability
29
+ As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.
30
+
31
+ Definitions
32
+ The licensor is the entity offering these terms, and the software is the software the licensor makes available under these terms, including any portion of it.
33
+
34
+ you refers to the individual or entity agreeing to these terms.
35
+
36
+ your company is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. control means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
37
+
38
+ your licenses are all the licenses granted to you for the software under these terms.
39
+
40
+ use means anything you do with the software requiring one of your licenses.
41
+
42
+ trademark means trademarks, service marks, and similar rights.
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.3
2
+ Name: intuned-runtime
3
+ Version: 1.0.0
4
+ Summary: Runtime commands for Intuned platform Python scrapers
5
+ License: Elastic-2.0
6
+ Keywords: runtime,intuned
7
+ Author: Intuned Developers
8
+ Author-email: engineering@intunedhq.com
9
+ Requires-Python: >=3.9,<4.0
10
+ Classifier: License :: Other/Proprietary License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
19
+ Requires-Dist: arguably (>=1.3.0,<2.0.0)
20
+ Requires-Dist: gitpython (>=3.1.43,<4.0.0)
21
+ Requires-Dist: httpx (>=0.23.0,<1)
22
+ Requires-Dist: more-termcolor (>=1.1.3,<2.0.0)
23
+ Requires-Dist: pathspec (>=0.12.1,<0.13.0)
24
+ Requires-Dist: pydantic (>=2.10.6,<3.0.0)
25
+ Requires-Dist: pyright (>=1.1.387,<2.0.0)
26
+ Requires-Dist: python-dotenv (==1.0.1)
27
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
28
+ Requires-Dist: ruff (>=0.7.2,<0.8.0)
29
+ Requires-Dist: semver (>=3.0.4,<4.0.0)
30
+ Requires-Dist: tenacity (>=8.5.0,<9.0.0)
31
+ Requires-Dist: toml (>=0.10.2,<0.11.0)
32
+ Requires-Dist: waitress (>=3.0.1,<4.0.0)
33
+ Description-Content-Type: text/markdown
34
+
35
+ # Intuned Python Runtime
36
+
37
+ Runtime commands for Intuned platform Python automations.
38
+
39
+ ## Dependencies
40
+
41
+ - Requires Python 3.12 or higher.
42
+ - Install poetry: `pip install poetry`
43
+ - Install dependencies: `poetry install`
44
+ - Activate virtual environment: `poetry shell`
45
+ - Now you have access to `intuned` cli from within project.
46
+
47
+ ## Install globally
48
+
49
+ - This project can be installed globally on the system to use `intuned` cli anywhere
50
+ - Make sure you are not in a virtual environment. `which python` should point to system python.
51
+ - If you are, `deactivate` to exit virtual environment. Or open in an external terminal (from outside vscode if it doesn't work)
52
+ - Run `pip install -e .` from the root of the project.
53
+
54
+ ## Commands
55
+
56
+ All commands have `-h` flag to show help.
57
+
58
+ ### `intuned init`
59
+
60
+ - Initializes a project. Creates `pyproject.toml`, `Intuned.json` and `README.md` files.
61
+ - Prompts for confirmation for each file that already exists.
62
+ - Options:
63
+ - `--yes/-y` flag to overwrite all files.
64
+ - `--no/-n` flag to not overwrite any files.
65
+
66
+ ### `intuned publish-packages`
67
+
68
+ - Publishes packages to `python-packages` repository.
69
+ - Options:
70
+ - `--sdk` flag to publish SDK package. Creates `sdk-<version>` and `sdk-latest` tags for the published version.
71
+ - `--runtime` flag to publish runtime package. Creates `runtime-<version>` and `runtime-latest` tags for the published version.
72
+ - `--overwrite` flag to overwrite the existing version if it exists.
73
+ - `--show-diff` flag to show the diff of the package before publishing. You need to configure a diff tool to be used for `git difftool` command in your git config. [How to configure VS Code as a diff tool](https://www.roboleary.net/vscode/2020/09/15/vscode-git.html#tldr).
74
+ - `--no-latest` flag to not release `latest` tag for the published version.
75
+ - Uses the version specified in `pyproject.toml` of each package respectively.
76
+ - Uses WebApp directory specified in `WEBAPP_REPO` environment variable or tries to resolve it (only works if installed globally with `-e` flag).
77
+ - Uses `python-packages` directory to be sister to WebApp directory `<webapp path>/../python-packages`.
78
+ - These packages are used on deployed apps.
79
+
80
+ ### `intuned project run`
81
+
82
+ - Runs the project.
83
+ - `--mode` to specify the mode to run. Default is `sample`.
84
+ - `--mode sample` extends a sample of payloads to run.
85
+ - `--mode full` runs all extended payloads.
86
+ - `--mode single` runs the initial API only.
87
+ - `--api-name <name>` to specify the initial API to run. Defaults to `default`
88
+ - `--params <params json>` to specify the parameters to the initial API.
89
+ - `--sample-config-str '{<api name>: <sample size>, ...}` to specify the sample config. Only used with `--mode sample`.
90
+ - `--no-headless` to disable headless mode.
91
+
92
+ ### `intuned project deploy`
93
+
94
+ - Deploys a project and starts a default job.
95
+ - Options:
96
+ - `--workspace-info '{"environment_url": <>, "workspace_id": <>, "api_key": <>}'` to specify the workspace info.
97
+ - `--workspace-info-path` to specify the path to a JSON file containing workspace info.
98
+ - `-y/--yes` to skip confirmation.
99
+ - `--project-name` to specify the project name. Resolves the name if not provided.
100
+ - Resolves `.gitignore` from current/parent directories to decide what to deploy.
101
+ - Resolves `.env` from current/parent directories to get environment variables to deploy.
102
+ - Resolves project name from the current/parent directory name if not provided.
103
+
104
+ ### `intuned project serve`
105
+
106
+ - Serves the project as an HTTP server.
107
+ - Options:
108
+ - `--env development/production` to specify the environment to run the server.
109
+ - Development runs using Flask's development server.
110
+ - Production runs using Waitress.
111
+ - `--debug` to run the development server in debug mode. Not supported in production.
112
+ - This is used on deployed apps.
113
+
@@ -0,0 +1,58 @@
1
+ cli/__init__.py,sha256=QvTM82L3VLVOFGpu_bziWupgwjsAgv07u6o_gUwO3z0,1314
2
+ cli/commands/__init__.py,sha256=QF-zy-iOZXY-9m-1O-JjWnugjulgJtsODtnkhvlNKq8,944
3
+ cli/commands/ai_source/__init__.py,sha256=lg7owgcK8owNn2a4VBUP9RKxzFsLruhtnnQV0F_z6pc,149
4
+ cli/commands/ai_source/ai_source.py,sha256=2woQtCmhxKvLfEz832eUoCT9gMsuSvEE6rMnHSYXC7w,138
5
+ cli/commands/ai_source/deploy.py,sha256=XVN_alilwIaoVniuSpE8RkAveuaB3MaUtsRjIcpQTXo,2440
6
+ cli/commands/browser/__init__.py,sha256=AuVbvh7aSBTFKYvewXZyPoIvfBTe2uHiPcnaAkzapas,95
7
+ cli/commands/browser/save_state.py,sha256=eHKfvBfeFR_U9VQbsjOnIZjWepyDjNn9NL3naDYWP2s,841
8
+ cli/commands/init.py,sha256=8rWBenWZfwNtLxOBqhEMbOATyQNEnmDUmrFJ1xBGyxI,4384
9
+ cli/commands/project/__init__.py,sha256=t97wvhSenerYRdbSeCKXqHASA6EWA3lc1hnRhF9baOE,734
10
+ cli/commands/project/auth_session/__init__.py,sha256=gt7mlaW6xmqAc_4-pfF_FiecsR51C6fqCaq_NFbcbwA,300
11
+ cli/commands/project/auth_session/check.py,sha256=AFILp7m34nAO_RD3IfRpuJm5Zh0wnCRtBXqIrerdbwo,4565
12
+ cli/commands/project/auth_session/create.py,sha256=r-eYu3uLUo2mzF836CbVgu4oBzcIIDekzzFwwekxmg0,3374
13
+ cli/commands/project/auth_session/load.py,sha256=UUvg9Vyj15xiR44XlJzojLoFm5esv-o4E3qA3JqnBsE,1201
14
+ cli/commands/project/project.py,sha256=_MSh6Xor2Cbh-ItifwgOPq_BP8UDuKB7S6w796FULKQ,137
15
+ cli/commands/project/run.py,sha256=FDYYkU24aURYbljyYLFo8wLF-nvb86EVr9gMEjAfeE0,12274
16
+ cli/commands/project/run_interface.py,sha256=4RyR8WZriIF7Va4z1wt-q6zZDQOI31n62Ho2dyimzUY,8717
17
+ cli/commands/project/type_check.py,sha256=3BKMqTzJ73_J-RYrjba7Yv2mM_VzHR0gnXQEg_Us7JA,3384
18
+ cli/commands/project/upgrade.py,sha256=XmkZLflM4O-mwvhwcswlZpazRotwi3xesLgE0Zz8fTI,3061
19
+ cli/commands/publish_packages.py,sha256=sijkaG7_s0I1EWgLekGy1qm8Aqi_gYY8poXbMX0B6Yw,10505
20
+ cli/logger.py,sha256=bZK3q-KUdGxk_qzDb6pn-n0LOhKJvi6a9p8oSwZtq3s,594
21
+ cli/utils/ai_source_project.py,sha256=xUCM6p3i1XN4bJbuQz8LCzeI4BwqAdSvCl_vwDAEi0k,831
22
+ cli/utils/code_tree.py,sha256=1wfxZoQ5kRCfqs2SEPAicbAIPTiD6P1LxSuwYu_eeaI,2790
23
+ cli/utils/run_apis.py,sha256=Zee4zkgt9R8XY1XCGzj2Nc4zJ3jlRz1xnO493wotuWw,4690
24
+ cli/utils/unix_socket.py,sha256=UISmkJMHrir5iBLUm6vxC3uzTGIFyOk_wa0C9LUw4Cc,1889
25
+ runtime/__init__.py,sha256=wgS7k3f10LBbjSYJdjKeKUptizNw6OrBgNx-ECT5Ox8,124
26
+ runtime/backend_functions/__init__.py,sha256=j2EaK4FK8bmdFtqc5FxtFwx1KhIn_7qKPChrrAhJI3s,119
27
+ runtime/backend_functions/_call_backend_function.py,sha256=sER0op3vE9EVEB5-ZNwpGraxrqkFh5ZgOIV2B5fgmg4,3028
28
+ runtime/backend_functions/get_auth_session_parameters.py,sha256=pOvB7XiWpphEuBpazdKALw9EWgBU1PeY3gkzBfVLpkc,869
29
+ runtime/browser/__init__.py,sha256=9Nqf-80l1F7KGjy2pYBC5S5SeEXJ4YJ4Dl71hi_GWdI,132
30
+ runtime/browser/launch_chromium.py,sha256=sML_XpjmlMf5QpuqMGeaS42D43pAXWKswm2q-3a2jAM,7459
31
+ runtime/browser/storage_state.py,sha256=AmiHMfWbFVr7XUEYXoaWWZH4pXGdPv4wTNPXunm19rA,3591
32
+ runtime/context/__init__.py,sha256=hg8ejm4bJy4tNkwmZ9lKgYJx6bU7OgOdBS684Uv5XGg,73
33
+ runtime/context/context.py,sha256=pl_0x77_d5CiAznz1qGSk6o9cW-msNvlCt-2eFoMKlA,1739
34
+ runtime/env.py,sha256=h4BJI-XVSZKgtTxjkzj2HsyN3DlY5Ml9GZqeH4CKDE8,238
35
+ runtime/errors/__init__.py,sha256=oqiBSvT_yFLQ3hG0AbCUA3WYFaxkTDVkDMSy59xvBCo,688
36
+ runtime/errors/auth_session_errors.py,sha256=6b4XTI8UCDHDPX4jEA8_HyrNUp4VZ1TrEA8DRh6Z3rM,228
37
+ runtime/errors/run_api_errors.py,sha256=etqS18ZxDzr30qUezG6D8gcH0Yc1XNtQ8GMkUZx3Rmw,3400
38
+ runtime/errors/trace_errors.py,sha256=Lzfo0sH3zGaWz1kn5DHcAXQMn3aR2y2bnauj6xP1LYE,110
39
+ runtime/helpers/__init__.py,sha256=jozYPKHgZJ7Na5U1Wjt83egzjPATMZ_OMInEI6swSbY,234
40
+ runtime/helpers/extend_payload.py,sha256=towZF08WTpTTDBL4AV1bUU3XpKAQHEB66kGUfTICDe0,246
41
+ runtime/helpers/extend_timeout.py,sha256=KjfSLEUrqoz7v00rhnPAKq2OmUzEzcv-eQ3M8c2U46s,348
42
+ runtime/helpers/get_auth_session_parameters.py,sha256=7bopGhJ7vjKAn_UxnHSAah-k2rVOPbq0zi9FQOOCFds,472
43
+ runtime/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
+ runtime/run/__init__.py,sha256=zxMYVb7hn147YTrhMLsrcX6-KTd71HLrYHstJOWeWXQ,52
45
+ runtime/run/intuned_settings.py,sha256=vy2-ktEzUfUp5Z90dp3l7jPKHNjgB-8GSMDgAY-rYaU,1074
46
+ runtime/run/playwright_constructs.py,sha256=kOa95d4m4DTcIDKq_QajBs-9FkPz5yZFcsafLtx5288,519
47
+ runtime/run/run_api.py,sha256=88u91YS5-m_aGwQnNfeN7Mszm1MRfOmciCpzuAXjdoI,8853
48
+ runtime/run/traces.py,sha256=fKzh11LqV47ujgq_9I2tdp-dgld566wffWaHwU_4gis,1123
49
+ runtime/types/__init__.py,sha256=IJkDfqsau8F8R_j8TO6j-JwW4ElQr6aU6LNaWRehg5U,401
50
+ runtime/types/payload.py,sha256=sty8HgDEn3nJbZrwEOMCXyuG7_ICGDwlBIIWSON5ABY,124
51
+ runtime/types/run_types.py,sha256=_ZoW0rv4VnjqVwVxJBWsdh0S5toppNL3AfL456rn5yM,4076
52
+ runtime_helpers/__init__.py,sha256=XBrEiE9yNC8Lgn8NgIkqNXbI6e4ap237E83Zj_nlhCQ,249
53
+ runtime_helpers/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
+ intuned_runtime-1.0.0.dist-info/LICENSE,sha256=9LIjQdgyU_ptzNIfItNCR7VmEHqYnrY1f1XwOreKFI0,3714
55
+ intuned_runtime-1.0.0.dist-info/METADATA,sha256=i6-dbJpG78FttN_tw7HgA3qd_fE99cHW7bkASh2z16o,5134
56
+ intuned_runtime-1.0.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
57
+ intuned_runtime-1.0.0.dist-info/entry_points.txt,sha256=4VXmTtVfaolAgt2owC9QF1bQpzCVv6Qh_cqW6ZGpftw,35
58
+ intuned_runtime-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.1.3
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ intuned=cli:run
3
+
runtime/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .browser import launch_chromium, dangerous_launch_chromium
2
+
3
+ __all__ = ["launch_chromium", "dangerous_launch_chromium"]
@@ -0,0 +1,5 @@
1
+ from .get_auth_session_parameters import get_auth_session_parameters
2
+
3
+ __all__ = [
4
+ "get_auth_session_parameters",
5
+ ]
@@ -0,0 +1,86 @@
1
+ import json
2
+ from typing import Any
3
+ from typing import Literal
4
+
5
+ from httpx import AsyncClient
6
+ from pydantic import BaseModel
7
+
8
+ from runtime.context.context import IntunedContext
9
+ from runtime.env import get_functions_domain
10
+ from runtime.env import get_project_id
11
+ from runtime.env import get_workspace_id
12
+
13
+
14
+ async def call_backend_function[T: BaseModel](
15
+ name: str,
16
+ validation_model: type[T],
17
+ *,
18
+ method: Literal["GET", "POST"] = "GET",
19
+ params: BaseModel | None = None,
20
+ ) -> T:
21
+ """
22
+ Get the auth session parameters from the IntunedContext.
23
+ """
24
+ functions_domain, workspace_id, project_id = get_functions_domain(), get_workspace_id(), get_project_id()
25
+
26
+ if functions_domain is None or workspace_id is None or project_id is None:
27
+ # todo
28
+ raise Exception("No workspace ID or project ID found.")
29
+
30
+ context = IntunedContext.current()
31
+ if context.run_context is None:
32
+ # todo
33
+ raise Exception("No run context found.")
34
+
35
+ auth_session_id = context.run_context.auth_session_id
36
+ if auth_session_id is None:
37
+ # todo
38
+ raise Exception("No auth session ID found.")
39
+
40
+ async with AsyncClient() as client:
41
+ if context.functions_token:
42
+ client.headers["Authorization"] = f"Bearer {context.functions_token}"
43
+ if params:
44
+ client.headers["Content-Type"] = "application/json"
45
+ path = f"{functions_domain}/api/{workspace_id}/functions/{project_id}/{name}"
46
+ body = params.model_dump() if params else None
47
+ print("Calling backend function", method, path, context.functions_token, json.dumps(body, indent=2))
48
+ response = await client.request(
49
+ method,
50
+ path,
51
+ json=body,
52
+ )
53
+ try:
54
+ response_json = response.json()
55
+ except json.JSONDecodeError as e:
56
+ raise CallBackendException(
57
+ response.status_code,
58
+ f"Expected JSON response, but got: {response.text}",
59
+ ) from e
60
+ if not isinstance(response_json, dict):
61
+ raise CallBackendException(
62
+ response.status_code,
63
+ f"Expected JSON object, but got: {response_json}",
64
+ )
65
+ if 200 <= response.status_code < 300:
66
+ return validation_model.model_validate(response_json)
67
+ raise CallBackendException(
68
+ response.status_code,
69
+ f"Calling backend function errored with status {response.status_code}: {response_json}",
70
+ )
71
+
72
+
73
+ class CallBackendException(Exception):
74
+ def __init__(self, status_code: int, body: str | dict[str, Any]):
75
+ message = "Unknown error"
76
+ if isinstance(body, str):
77
+ message = body
78
+ else:
79
+ body_message = body.get("message") or body.get("error")
80
+ if body_message:
81
+ message = str(body_message)
82
+ else:
83
+ message = json.dumps(body)
84
+ super().__init__(message)
85
+ self.status_code = status_code
86
+ self.body = body
@@ -0,0 +1,30 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from runtime.context.context import IntunedContext
6
+
7
+ from ._call_backend_function import call_backend_function
8
+
9
+
10
+ class AuthSessionParameters(BaseModel):
11
+ parameters: dict[str, Any]
12
+
13
+
14
+ async def get_auth_session_parameters() -> dict[str, Any]:
15
+ """
16
+ Get the auth session parameters from backend.
17
+ """
18
+
19
+ context = IntunedContext.current()
20
+ if context.run_context is None:
21
+ raise Exception("get_auth_session_parameters failed due to an internal error (context was not found).")
22
+ if context.run_context.auth_session_id is None:
23
+ raise Exception("Auth sessions are not enabled")
24
+
25
+ result = await call_backend_function(
26
+ name=f"auth-session/{context.run_context.auth_session_id}/parameters",
27
+ validation_model=AuthSessionParameters,
28
+ )
29
+
30
+ return result.parameters
@@ -0,0 +1,3 @@
1
+ from .launch_chromium import launch_chromium, dangerous_launch_chromium
2
+
3
+ __all__ = ["launch_chromium", "dangerous_launch_chromium"]
@@ -0,0 +1,212 @@
1
+ import json
2
+ import os
3
+ import tempfile
4
+ from contextlib import asynccontextmanager
5
+ from os.path import join
6
+ from typing import Any
7
+ from typing import Optional
8
+ from typing import Literal
9
+ import logging
10
+ import aiofiles
11
+ from playwright.async_api import async_playwright
12
+ from playwright.async_api import Browser
13
+ from playwright.async_api import ProxySettings
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ def get_proxy_env() -> Optional[ProxySettings]:
19
+ server = os.getenv("PROXY_SERVER")
20
+ username = os.getenv("PROXY_USERNAME")
21
+ password = os.getenv("PROXY_PASSWORD")
22
+ if server is None or username is None or password is None:
23
+ return None
24
+ return {
25
+ "server": server,
26
+ "username": username,
27
+ "password": password,
28
+ }
29
+
30
+
31
+ chromium_launch_args_to_ignore = [
32
+ "--disable-field-trial-config",
33
+ "--disable-background-networking",
34
+ "--enable-features=NetworkService,NetworkServiceInProcess",
35
+ "--disable-background-timer-throttling",
36
+ "--disable-backgrounding-occluded-windows",
37
+ "--disable-back-forward-cache",
38
+ "--disable-breakpad",
39
+ "--disable-client-side-phishing-detection",
40
+ "--disable-component-extensions-with-background-pages",
41
+ "--disable-component-update",
42
+ "--no-default-browser-check",
43
+ "--disable-default-apps",
44
+ "--disable-dev-shm-usage",
45
+ "--disable-extensions",
46
+ "--disable-features=ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate,TranslateUI",
47
+ "--allow-pre-commit-input",
48
+ "--disable-hang-monitor",
49
+ "--disable-ipc-flooding-protection",
50
+ "--disable-prompt-on-repost",
51
+ "--disable-renderer-backgrounding",
52
+ "--force-color-profile=srgb",
53
+ "--metrics-recording-only",
54
+ "--no-first-run",
55
+ "--enable-automation",
56
+ "--password-store=basic",
57
+ "--use-mock-keychain",
58
+ "--no-service-autorun",
59
+ "--export-tagged-pdf",
60
+ "--enable-use-zoom-for-dsf=false",
61
+ "--disable-popup-blocking",
62
+ ]
63
+
64
+
65
+ async def create_user_dir_with_preferences():
66
+ # Create a temporary directory
67
+ playwright_temp_dir = tempfile.mkdtemp(prefix="pw-")
68
+ user_dir = join(playwright_temp_dir, "userdir")
69
+ default_dir = join(user_dir, "Default")
70
+
71
+ # Create the default directory recursively
72
+ os.makedirs(default_dir, exist_ok=True)
73
+
74
+ # Preferences data
75
+ preferences = {
76
+ "plugins": {
77
+ "always_open_pdf_externally": True,
78
+ }
79
+ }
80
+
81
+ # Write preferences to file
82
+ async with aiofiles.open(join(default_dir, "Preferences"), mode="w") as f:
83
+ await f.write(json.dumps(preferences))
84
+
85
+ return os.path.abspath(user_dir)
86
+
87
+
88
+ extra_args = [
89
+ "--no-first-run",
90
+ "--disable-sync",
91
+ "--disable-translate",
92
+ "--disable-features=TranslateUI",
93
+ "--disable-features=NetworkService",
94
+ "--lang=en",
95
+ "--disable-blink-features=AutomationControlled",
96
+ ]
97
+
98
+ default_user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
99
+
100
+
101
+ @asynccontextmanager
102
+ async def launch_chromium(
103
+ headless: bool = True,
104
+ timeout: int = 10,
105
+ cdp_address: str | None = None,
106
+ **kwargs: Any,
107
+ ):
108
+ async with async_playwright() as playwright:
109
+ if cdp_address is not None:
110
+ browser: Browser = await playwright.chromium.connect_over_cdp(cdp_address)
111
+ context = browser.contexts[0]
112
+ dir = None
113
+ else:
114
+ dir = await create_user_dir_with_preferences()
115
+ if kwargs.get("proxy") is None:
116
+ proxy_env = get_proxy_env()
117
+ else:
118
+ proxy_env = kwargs.get("proxy")
119
+ # Remove proxy from kwargs if it exists
120
+ kwargs.pop("proxy", None)
121
+ viewport = kwargs.get("viewport", {"width": 1280, "height": 800})
122
+ kwargs.pop("viewport", None)
123
+
124
+ if headless:
125
+ chromium_launch_args_to_ignore.append("--headless")
126
+ extra_args.append("--headless=new")
127
+
128
+ context = await playwright.chromium.launch_persistent_context(
129
+ dir,
130
+ headless=headless,
131
+ viewport=viewport,
132
+ proxy=proxy_env,
133
+ # ignore_default_args=chromium_launch_args_to_ignore,
134
+ user_agent=os.environ.get("USER_AGENT", default_user_agent),
135
+ # args=extra_args,
136
+ **kwargs,
137
+ )
138
+ context.set_default_timeout(timeout * 1000)
139
+
140
+ async def remove_dir_after_close(*_: Any, **__: Any) -> None:
141
+ if not dir:
142
+ return
143
+ os.system(f"rm -rf {os.path.realpath(dir)}")
144
+
145
+ context.once("close", remove_dir_after_close)
146
+ yield context, context.pages[0]
147
+
148
+
149
+ async def dangerous_launch_chromium(
150
+ headless: bool = True,
151
+ timeout: int = 10,
152
+ web_socket: str | None = None,
153
+ cdp_url: str | None = None,
154
+ connection_method: Literal["ws", "cdp"] | None = None,
155
+ **kwargs: Any,
156
+ ):
157
+ playwright = await async_playwright().start()
158
+ if web_socket is not None and connection_method == "ws":
159
+ logging.info(f"Connecting to ws: {web_socket}")
160
+ browser: Browser = await playwright.chromium.connect(web_socket)
161
+ browser.on("disconnected", lambda: logging.info("Browser Session disconnected"))
162
+ await browser.new_context(
163
+ viewport={"width": 1280, "height": 800}, user_agent=default_user_agent
164
+ )
165
+ context = browser.contexts[0]
166
+ dir = None
167
+ elif cdp_url is not None and connection_method == "cdp":
168
+ logging.info(f"Connecting to cdp: {cdp_url}")
169
+ browser: Browser = await playwright.chromium.connect_over_cdp(cdp_url)
170
+ browser.on("disconnected", lambda: logging.info("Browser Session disconnected"))
171
+ context = browser.contexts[0]
172
+ dir = None
173
+ elif web_socket is None and cdp_url is None and connection_method is None:
174
+ logging.info("Launching local browser")
175
+ dir = await create_user_dir_with_preferences()
176
+ logging.info(f"Using user data directory: {dir}")
177
+ if kwargs.get("proxy") is None:
178
+ proxy_env = get_proxy_env()
179
+ else:
180
+ proxy_env = kwargs.get("proxy")
181
+ # Remove proxy from kwargs if it exists
182
+ kwargs.pop("proxy", None)
183
+ viewport = kwargs.get("viewport", {"width": 1280, "height": 800})
184
+ kwargs.pop("viewport", None)
185
+
186
+ if headless:
187
+ chromium_launch_args_to_ignore.append("--headless")
188
+ extra_args.append("--headless=new")
189
+
190
+ context = await playwright.chromium.launch_persistent_context(
191
+ dir,
192
+ headless=headless,
193
+ viewport=viewport,
194
+ proxy=proxy_env,
195
+ ignore_default_args=chromium_launch_args_to_ignore,
196
+ user_agent=os.environ.get("USER_AGENT", default_user_agent),
197
+ args=extra_args,
198
+ **kwargs,
199
+ )
200
+ else:
201
+ raise ValueError(
202
+ "You have to provide method if you are launching a remote browser with ws or cdp"
203
+ )
204
+ context.set_default_timeout(timeout * 1000)
205
+
206
+ async def remove_dir_after_close(*_: Any, **__: Any) -> None:
207
+ if not dir:
208
+ return
209
+ os.system(f"rm -rf {os.path.realpath(dir)}")
210
+
211
+ context.once("close", remove_dir_after_close)
212
+ return playwright, context