pybiolib 0.2.951__py3-none-any.whl → 1.2.1890__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.
- biolib/__init__.py +357 -11
- biolib/_data_record/data_record.py +380 -0
- biolib/_index/__init__.py +0 -0
- biolib/_index/index.py +55 -0
- biolib/_index/query_result.py +103 -0
- biolib/_internal/__init__.py +0 -0
- biolib/_internal/add_copilot_prompts.py +58 -0
- biolib/_internal/add_gui_files.py +81 -0
- biolib/_internal/data_record/__init__.py +1 -0
- biolib/_internal/data_record/data_record.py +85 -0
- biolib/_internal/data_record/push_data.py +116 -0
- biolib/_internal/data_record/remote_storage_endpoint.py +43 -0
- biolib/_internal/errors.py +5 -0
- biolib/_internal/file_utils.py +125 -0
- biolib/_internal/fuse_mount/__init__.py +1 -0
- biolib/_internal/fuse_mount/experiment_fuse_mount.py +209 -0
- biolib/_internal/http_client.py +159 -0
- biolib/_internal/lfs/__init__.py +1 -0
- biolib/_internal/lfs/cache.py +51 -0
- biolib/_internal/libs/__init__.py +1 -0
- biolib/_internal/libs/fusepy/__init__.py +1257 -0
- biolib/_internal/push_application.py +488 -0
- biolib/_internal/runtime.py +22 -0
- biolib/_internal/string_utils.py +13 -0
- biolib/_internal/templates/__init__.py +1 -0
- biolib/_internal/templates/copilot_template/.github/instructions/general-app-knowledge.instructions.md +10 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-general.instructions.md +20 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-python.instructions.md +16 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-react-ts.instructions.md +47 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_app_inputs.prompt.md +11 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_onboard_repo.prompt.md +19 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_run_apps.prompt.md +12 -0
- biolib/_internal/templates/dashboard_template/.biolib/config.yml +5 -0
- biolib/_internal/templates/github_workflow_template/.github/workflows/biolib.yml +21 -0
- biolib/_internal/templates/gitignore_template/.gitignore +10 -0
- biolib/_internal/templates/gui_template/.yarnrc.yml +1 -0
- biolib/_internal/templates/gui_template/App.tsx +53 -0
- biolib/_internal/templates/gui_template/Dockerfile +27 -0
- biolib/_internal/templates/gui_template/biolib-sdk.ts +82 -0
- biolib/_internal/templates/gui_template/dev-data/output.json +7 -0
- biolib/_internal/templates/gui_template/index.css +5 -0
- biolib/_internal/templates/gui_template/index.html +13 -0
- biolib/_internal/templates/gui_template/index.tsx +10 -0
- biolib/_internal/templates/gui_template/package.json +27 -0
- biolib/_internal/templates/gui_template/tsconfig.json +24 -0
- biolib/_internal/templates/gui_template/vite-plugin-dev-data.ts +50 -0
- biolib/_internal/templates/gui_template/vite.config.mts +10 -0
- biolib/_internal/templates/init_template/.biolib/config.yml +19 -0
- biolib/_internal/templates/init_template/Dockerfile +14 -0
- biolib/_internal/templates/init_template/requirements.txt +1 -0
- biolib/_internal/templates/init_template/run.py +12 -0
- biolib/_internal/templates/init_template/run.sh +4 -0
- biolib/_internal/templates/templates.py +25 -0
- biolib/_internal/tree_utils.py +106 -0
- biolib/_internal/utils/__init__.py +65 -0
- biolib/_internal/utils/auth.py +46 -0
- biolib/_internal/utils/job_url.py +33 -0
- biolib/_internal/utils/multinode.py +263 -0
- biolib/_runtime/runtime.py +157 -0
- biolib/_session/session.py +44 -0
- biolib/_shared/__init__.py +0 -0
- biolib/_shared/types/__init__.py +74 -0
- biolib/_shared/types/account.py +12 -0
- biolib/_shared/types/account_member.py +8 -0
- biolib/_shared/types/app.py +9 -0
- biolib/_shared/types/data_record.py +40 -0
- biolib/_shared/types/experiment.py +32 -0
- biolib/_shared/types/file_node.py +17 -0
- biolib/_shared/types/push.py +6 -0
- biolib/_shared/types/resource.py +37 -0
- biolib/_shared/types/resource_deploy_key.py +11 -0
- biolib/_shared/types/resource_permission.py +14 -0
- biolib/_shared/types/resource_version.py +19 -0
- biolib/_shared/types/result.py +14 -0
- biolib/_shared/types/typing.py +10 -0
- biolib/_shared/types/user.py +19 -0
- biolib/_shared/utils/__init__.py +7 -0
- biolib/_shared/utils/resource_uri.py +75 -0
- biolib/api/__init__.py +6 -0
- biolib/api/client.py +168 -0
- biolib/app/app.py +252 -49
- biolib/app/search_apps.py +45 -0
- biolib/biolib_api_client/api_client.py +126 -31
- biolib/biolib_api_client/app_types.py +24 -4
- biolib/biolib_api_client/auth.py +31 -8
- biolib/biolib_api_client/biolib_app_api.py +147 -52
- biolib/biolib_api_client/biolib_job_api.py +161 -141
- biolib/biolib_api_client/job_types.py +21 -5
- biolib/biolib_api_client/lfs_types.py +7 -23
- biolib/biolib_api_client/user_state.py +56 -0
- biolib/biolib_binary_format/__init__.py +1 -4
- biolib/biolib_binary_format/file_in_container.py +105 -0
- biolib/biolib_binary_format/module_input.py +24 -7
- biolib/biolib_binary_format/module_output_v2.py +149 -0
- biolib/biolib_binary_format/remote_endpoints.py +34 -0
- biolib/biolib_binary_format/remote_stream_seeker.py +59 -0
- biolib/biolib_binary_format/saved_job.py +3 -2
- biolib/biolib_binary_format/{attestation_document.py → stdout_and_stderr.py} +8 -8
- biolib/biolib_binary_format/system_status_update.py +3 -2
- biolib/biolib_binary_format/utils.py +175 -0
- biolib/biolib_docker_client/__init__.py +11 -2
- biolib/biolib_errors.py +36 -0
- biolib/biolib_logging.py +27 -10
- biolib/cli/__init__.py +38 -0
- biolib/cli/auth.py +46 -0
- biolib/cli/data_record.py +164 -0
- biolib/cli/index.py +32 -0
- biolib/cli/init.py +421 -0
- biolib/cli/lfs.py +101 -0
- biolib/cli/push.py +50 -0
- biolib/cli/run.py +63 -0
- biolib/cli/runtime.py +14 -0
- biolib/cli/sdk.py +16 -0
- biolib/cli/start.py +56 -0
- biolib/compute_node/cloud_utils/cloud_utils.py +110 -161
- biolib/compute_node/job_worker/cache_state.py +66 -88
- biolib/compute_node/job_worker/cache_types.py +1 -6
- biolib/compute_node/job_worker/docker_image_cache.py +112 -37
- biolib/compute_node/job_worker/executors/__init__.py +0 -3
- biolib/compute_node/job_worker/executors/docker_executor.py +532 -199
- biolib/compute_node/job_worker/executors/docker_types.py +9 -1
- biolib/compute_node/job_worker/executors/types.py +19 -9
- biolib/compute_node/job_worker/job_legacy_input_wait_timeout_thread.py +30 -0
- biolib/compute_node/job_worker/job_max_runtime_timer_thread.py +3 -5
- biolib/compute_node/job_worker/job_storage.py +108 -0
- biolib/compute_node/job_worker/job_worker.py +397 -212
- biolib/compute_node/job_worker/large_file_system.py +87 -38
- biolib/compute_node/job_worker/network_alloc.py +99 -0
- biolib/compute_node/job_worker/network_buffer.py +240 -0
- biolib/compute_node/job_worker/utilization_reporter_thread.py +197 -0
- biolib/compute_node/job_worker/utils.py +9 -24
- biolib/compute_node/remote_host_proxy.py +400 -98
- biolib/compute_node/utils.py +31 -9
- biolib/compute_node/webserver/compute_node_results_proxy.py +189 -0
- biolib/compute_node/webserver/proxy_utils.py +28 -0
- biolib/compute_node/webserver/webserver.py +130 -44
- biolib/compute_node/webserver/webserver_types.py +2 -6
- biolib/compute_node/webserver/webserver_utils.py +77 -12
- biolib/compute_node/webserver/worker_thread.py +183 -42
- biolib/experiments/__init__.py +0 -0
- biolib/experiments/experiment.py +356 -0
- biolib/jobs/__init__.py +1 -0
- biolib/jobs/job.py +741 -0
- biolib/jobs/job_result.py +185 -0
- biolib/jobs/types.py +50 -0
- biolib/py.typed +0 -0
- biolib/runtime/__init__.py +14 -0
- biolib/sdk/__init__.py +91 -0
- biolib/tables.py +34 -0
- biolib/typing_utils.py +2 -7
- biolib/user/__init__.py +1 -0
- biolib/user/sign_in.py +54 -0
- biolib/utils/__init__.py +162 -0
- biolib/utils/cache_state.py +94 -0
- biolib/utils/multipart_uploader.py +194 -0
- biolib/utils/seq_util.py +150 -0
- biolib/utils/zip/remote_zip.py +640 -0
- pybiolib-1.2.1890.dist-info/METADATA +41 -0
- pybiolib-1.2.1890.dist-info/RECORD +177 -0
- {pybiolib-0.2.951.dist-info → pybiolib-1.2.1890.dist-info}/WHEEL +1 -1
- pybiolib-1.2.1890.dist-info/entry_points.txt +2 -0
- README.md +0 -17
- biolib/app/app_result.py +0 -68
- biolib/app/utils.py +0 -62
- biolib/biolib-js/0-biolib.worker.js +0 -1
- biolib/biolib-js/1-biolib.worker.js +0 -1
- biolib/biolib-js/2-biolib.worker.js +0 -1
- biolib/biolib-js/3-biolib.worker.js +0 -1
- biolib/biolib-js/4-biolib.worker.js +0 -1
- biolib/biolib-js/5-biolib.worker.js +0 -1
- biolib/biolib-js/6-biolib.worker.js +0 -1
- biolib/biolib-js/index.html +0 -10
- biolib/biolib-js/main-biolib.js +0 -1
- biolib/biolib_api_client/biolib_account_api.py +0 -21
- biolib/biolib_api_client/biolib_large_file_system_api.py +0 -108
- biolib/biolib_binary_format/aes_encrypted_package.py +0 -42
- biolib/biolib_binary_format/module_output.py +0 -58
- biolib/biolib_binary_format/rsa_encrypted_aes_package.py +0 -57
- biolib/biolib_push.py +0 -114
- biolib/cli.py +0 -203
- biolib/cli_utils.py +0 -273
- biolib/compute_node/cloud_utils/enclave_parent_types.py +0 -7
- biolib/compute_node/enclave/__init__.py +0 -2
- biolib/compute_node/enclave/enclave_remote_hosts.py +0 -53
- biolib/compute_node/enclave/nitro_secure_module_utils.py +0 -64
- biolib/compute_node/job_worker/executors/base_executor.py +0 -18
- biolib/compute_node/job_worker/executors/pyppeteer_executor.py +0 -173
- biolib/compute_node/job_worker/executors/remote/__init__.py +0 -1
- biolib/compute_node/job_worker/executors/remote/nitro_enclave_utils.py +0 -81
- biolib/compute_node/job_worker/executors/remote/remote_executor.py +0 -51
- biolib/lfs.py +0 -196
- biolib/pyppeteer/.circleci/config.yml +0 -100
- biolib/pyppeteer/.coveragerc +0 -3
- biolib/pyppeteer/.gitignore +0 -89
- biolib/pyppeteer/.pre-commit-config.yaml +0 -28
- biolib/pyppeteer/CHANGES.md +0 -253
- biolib/pyppeteer/CONTRIBUTING.md +0 -26
- biolib/pyppeteer/LICENSE +0 -12
- biolib/pyppeteer/README.md +0 -137
- biolib/pyppeteer/docs/Makefile +0 -177
- biolib/pyppeteer/docs/_static/custom.css +0 -28
- biolib/pyppeteer/docs/_templates/layout.html +0 -10
- biolib/pyppeteer/docs/changes.md +0 -1
- biolib/pyppeteer/docs/conf.py +0 -299
- biolib/pyppeteer/docs/index.md +0 -21
- biolib/pyppeteer/docs/make.bat +0 -242
- biolib/pyppeteer/docs/reference.md +0 -211
- biolib/pyppeteer/docs/server.py +0 -60
- biolib/pyppeteer/poetry.lock +0 -1699
- biolib/pyppeteer/pyppeteer/__init__.py +0 -135
- biolib/pyppeteer/pyppeteer/accessibility.py +0 -286
- biolib/pyppeteer/pyppeteer/browser.py +0 -401
- biolib/pyppeteer/pyppeteer/browser_fetcher.py +0 -194
- biolib/pyppeteer/pyppeteer/command.py +0 -22
- biolib/pyppeteer/pyppeteer/connection/__init__.py +0 -242
- biolib/pyppeteer/pyppeteer/connection/cdpsession.py +0 -101
- biolib/pyppeteer/pyppeteer/coverage.py +0 -346
- biolib/pyppeteer/pyppeteer/device_descriptors.py +0 -787
- biolib/pyppeteer/pyppeteer/dialog.py +0 -79
- biolib/pyppeteer/pyppeteer/domworld.py +0 -597
- biolib/pyppeteer/pyppeteer/emulation_manager.py +0 -53
- biolib/pyppeteer/pyppeteer/errors.py +0 -48
- biolib/pyppeteer/pyppeteer/events.py +0 -63
- biolib/pyppeteer/pyppeteer/execution_context.py +0 -156
- biolib/pyppeteer/pyppeteer/frame/__init__.py +0 -299
- biolib/pyppeteer/pyppeteer/frame/frame_manager.py +0 -306
- biolib/pyppeteer/pyppeteer/helpers.py +0 -245
- biolib/pyppeteer/pyppeteer/input.py +0 -371
- biolib/pyppeteer/pyppeteer/jshandle.py +0 -598
- biolib/pyppeteer/pyppeteer/launcher.py +0 -683
- biolib/pyppeteer/pyppeteer/lifecycle_watcher.py +0 -169
- biolib/pyppeteer/pyppeteer/models/__init__.py +0 -103
- biolib/pyppeteer/pyppeteer/models/_protocol.py +0 -12460
- biolib/pyppeteer/pyppeteer/multimap.py +0 -82
- biolib/pyppeteer/pyppeteer/network_manager.py +0 -678
- biolib/pyppeteer/pyppeteer/options.py +0 -8
- biolib/pyppeteer/pyppeteer/page.py +0 -1728
- biolib/pyppeteer/pyppeteer/pipe_transport.py +0 -59
- biolib/pyppeteer/pyppeteer/target.py +0 -147
- biolib/pyppeteer/pyppeteer/task_queue.py +0 -24
- biolib/pyppeteer/pyppeteer/timeout_settings.py +0 -36
- biolib/pyppeteer/pyppeteer/tracing.py +0 -93
- biolib/pyppeteer/pyppeteer/us_keyboard_layout.py +0 -305
- biolib/pyppeteer/pyppeteer/util.py +0 -18
- biolib/pyppeteer/pyppeteer/websocket_transport.py +0 -47
- biolib/pyppeteer/pyppeteer/worker.py +0 -101
- biolib/pyppeteer/pyproject.toml +0 -97
- biolib/pyppeteer/spell.txt +0 -137
- biolib/pyppeteer/tox.ini +0 -72
- biolib/pyppeteer/utils/generate_protocol_types.py +0 -603
- biolib/start_cli.py +0 -7
- biolib/utils.py +0 -47
- biolib/validators/validate_app_version.py +0 -183
- biolib/validators/validate_argument.py +0 -134
- biolib/validators/validate_module.py +0 -323
- biolib/validators/validate_zip_file.py +0 -40
- biolib/validators/validator_utils.py +0 -103
- pybiolib-0.2.951.dist-info/LICENSE +0 -21
- pybiolib-0.2.951.dist-info/METADATA +0 -61
- pybiolib-0.2.951.dist-info/RECORD +0 -153
- pybiolib-0.2.951.dist-info/entry_points.txt +0 -3
- /LICENSE → /pybiolib-1.2.1890.dist-info/licenses/LICENSE +0 -0
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
import logging
|
|
4
|
-
import os
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import Any, List, Sequence, Union
|
|
7
|
-
|
|
8
|
-
from appdirs import AppDirs
|
|
9
|
-
|
|
10
|
-
__author__ = 'Hiroyuki Takagi, Bernardas Ališauskas, Matt Marcus'
|
|
11
|
-
__email__ = 'pyppeteer@protonmail.com'
|
|
12
|
-
__version__ = '0.2.2'
|
|
13
|
-
__chromium_revision__ = '722234'
|
|
14
|
-
__base_puppeteer_version__ = 'v2.1.1'
|
|
15
|
-
__pyppeteer_home__ = os.environ.get('PYPPETEER_HOME', AppDirs('pyppeteer').user_data_dir) # type: str
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
from biolib.pyppeteer.pyppeteer.browser import Browser # isort:skip
|
|
19
|
-
from biolib.pyppeteer.pyppeteer.browser_fetcher import BrowserFetcher, Platform # isort:skip
|
|
20
|
-
from biolib.pyppeteer.pyppeteer.device_descriptors import devices # isort:skip
|
|
21
|
-
from biolib.pyppeteer.pyppeteer.launcher import ChromeLauncher, FirefoxLauncher, launcher # isort:skip
|
|
22
|
-
from biolib.pyppeteer.pyppeteer.models import BrowserOptions, ChromeArgOptions, Devices, LaunchOptions, Protocol # isort:skip
|
|
23
|
-
from biolib.pyppeteer.pyppeteer.websocket_transport import WebsocketTransport # isort:skip
|
|
24
|
-
|
|
25
|
-
# Setup root logger
|
|
26
|
-
_logger = logging.getLogger(__name__)
|
|
27
|
-
_logger.setLevel(logging.DEBUG)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class Pyppeteer:
|
|
31
|
-
def __init__(self, projectRoot: str = None, preferredRevision: str = None):
|
|
32
|
-
self._projectRoot = projectRoot
|
|
33
|
-
self._preferredRevision = preferredRevision
|
|
34
|
-
self._lazyLauncher = None
|
|
35
|
-
self.productName = None
|
|
36
|
-
|
|
37
|
-
@property
|
|
38
|
-
def executablePath(self) -> Union[str, Path]:
|
|
39
|
-
return self._launcher.executablePath
|
|
40
|
-
|
|
41
|
-
@property
|
|
42
|
-
def product(self) -> str:
|
|
43
|
-
return self._launcher.product
|
|
44
|
-
|
|
45
|
-
@property
|
|
46
|
-
def devices(self) -> Devices:
|
|
47
|
-
return devices
|
|
48
|
-
|
|
49
|
-
async def launch(self, **kwargs: Union[LaunchOptions, ChromeArgOptions, BrowserOptions]) -> Browser:
|
|
50
|
-
if not self.productName and kwargs:
|
|
51
|
-
self.productName = kwargs.get('product')
|
|
52
|
-
return await self._launcher.launch(**kwargs)
|
|
53
|
-
|
|
54
|
-
async def connect(
|
|
55
|
-
self,
|
|
56
|
-
browserWSEndpoint: str = None,
|
|
57
|
-
browserURL: str = None,
|
|
58
|
-
transport: WebsocketTransport = None,
|
|
59
|
-
ignoreHTTPSErrors: bool = False,
|
|
60
|
-
slowMo: float = 0,
|
|
61
|
-
defaultViewport: Protocol.Page.Viewport = None,
|
|
62
|
-
) -> Browser:
|
|
63
|
-
return await self._launcher.connect(
|
|
64
|
-
browserWSEndpoint=browserWSEndpoint,
|
|
65
|
-
browserURL=browserURL,
|
|
66
|
-
ignoreHTTPSErrors=ignoreHTTPSErrors,
|
|
67
|
-
transport=transport,
|
|
68
|
-
slowMo=slowMo,
|
|
69
|
-
defaultViewport=defaultViewport,
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
@property
|
|
73
|
-
def _launcher(self) -> Union[FirefoxLauncher, ChromeLauncher]:
|
|
74
|
-
if not self._lazyLauncher:
|
|
75
|
-
self._lazyLauncher = launcher(
|
|
76
|
-
projectRoot=self._projectRoot, preferredRevision=self._preferredRevision, product=self.productName
|
|
77
|
-
)
|
|
78
|
-
return self._lazyLauncher
|
|
79
|
-
|
|
80
|
-
async def defaultArgs(
|
|
81
|
-
self,
|
|
82
|
-
args: Sequence[str] = None,
|
|
83
|
-
devtools: bool = False,
|
|
84
|
-
headless: bool = None,
|
|
85
|
-
userDataDir: str = None,
|
|
86
|
-
**_: Any,
|
|
87
|
-
) -> List[str]:
|
|
88
|
-
return self._launcher.default_args(args=args, devtools=devtools, headless=headless, userDataDir=userDataDir)
|
|
89
|
-
|
|
90
|
-
def createBrowserFetcher(self, platform: Platform = None, host: str = None,) -> BrowserFetcher:
|
|
91
|
-
return BrowserFetcher(projectRoot=self._projectRoot, platform=platform, host=host)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
# shortcut methods
|
|
95
|
-
async def launch(
|
|
96
|
-
projectRoot: Union[Path, str] = None,
|
|
97
|
-
preferredRevision: str = None,
|
|
98
|
-
**kwargs: Union[LaunchOptions, ChromeArgOptions, BrowserOptions],
|
|
99
|
-
) -> Browser:
|
|
100
|
-
return await Pyppeteer(projectRoot, preferredRevision).launch(**kwargs)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
async def connect(
|
|
104
|
-
projectRoot: Union[Path, str] = None,
|
|
105
|
-
preferredRevision: str = None,
|
|
106
|
-
browserWSEndpoint: str = None,
|
|
107
|
-
browserURL: str = None,
|
|
108
|
-
transport: WebsocketTransport = None,
|
|
109
|
-
ignoreHTTPSErrors: bool = False,
|
|
110
|
-
slowMo: float = 0,
|
|
111
|
-
defaultViewport: Protocol.Page.Viewport = None,
|
|
112
|
-
) -> Browser:
|
|
113
|
-
return await Pyppeteer(projectRoot, preferredRevision).connect(
|
|
114
|
-
browserWSEndpoint=browserWSEndpoint,
|
|
115
|
-
browserURL=browserURL,
|
|
116
|
-
transport=transport,
|
|
117
|
-
ignoreHTTPSErrors=ignoreHTTPSErrors,
|
|
118
|
-
slowMo=slowMo,
|
|
119
|
-
defaultViewport=defaultViewport,
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
version = __version__
|
|
124
|
-
version_info = tuple(int(i) for i in version.split('.'))
|
|
125
|
-
|
|
126
|
-
__all__ = [
|
|
127
|
-
'__chromium_revision__',
|
|
128
|
-
'__pyppeteer_home__',
|
|
129
|
-
'version',
|
|
130
|
-
'version_info',
|
|
131
|
-
'devices',
|
|
132
|
-
'launch',
|
|
133
|
-
'connect',
|
|
134
|
-
'Pyppeteer',
|
|
135
|
-
]
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING, Dict, List, Set, Union
|
|
2
|
-
|
|
3
|
-
if TYPE_CHECKING:
|
|
4
|
-
from pyppeteer.connection.cdpsession import CDPSession
|
|
5
|
-
from pyppeteer.jshandle import ElementHandle
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class Accessibility:
|
|
9
|
-
def __init__(self, client: 'CDPSession'):
|
|
10
|
-
self._client = client
|
|
11
|
-
|
|
12
|
-
async def snapshot(self, interestingOnly: bool = True, root: 'ElementHandle' = None):
|
|
13
|
-
nodes = (await self._client.send('Accessibility.getFullAXTree'))['nodes']
|
|
14
|
-
backendNodeId = None
|
|
15
|
-
if root:
|
|
16
|
-
node = (await self._client.send('DOM.describeNode', {'objectId': root._remoteObject['objectId']}))['node']
|
|
17
|
-
backendNodeId = node['backendNodeId']
|
|
18
|
-
defaultRoot = AXNode.createTree(nodes)
|
|
19
|
-
needle = defaultRoot
|
|
20
|
-
if backendNodeId:
|
|
21
|
-
needle = defaultRoot.find(lambda _node: _node._payload['backendDOMNodeId'] == backendNodeId)
|
|
22
|
-
if not needle:
|
|
23
|
-
return
|
|
24
|
-
if not interestingOnly:
|
|
25
|
-
return serializeTree(needle)[0]
|
|
26
|
-
|
|
27
|
-
interestingNodes = set()
|
|
28
|
-
collectInterestingNodes(interestingNodes, defaultRoot, False)
|
|
29
|
-
if needle not in interestingNodes:
|
|
30
|
-
return None
|
|
31
|
-
return serializeTree(needle, interestingNodes)[0]
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class AXNode:
|
|
35
|
-
def __init__(self, payload: Dict):
|
|
36
|
-
"""
|
|
37
|
-
:param payload: dict with keys nodeId, name, role, properties
|
|
38
|
-
"""
|
|
39
|
-
self._payload = payload
|
|
40
|
-
self._children: List[AXNode] = []
|
|
41
|
-
self._richlyEditable = False
|
|
42
|
-
self._editable = False
|
|
43
|
-
self._focusable = False
|
|
44
|
-
self._expanded = False
|
|
45
|
-
self._hidden = False
|
|
46
|
-
self._name = payload.get('name', {}).get('value', '')
|
|
47
|
-
self._role = payload.get('role', {}).get('value', 'Unknown')
|
|
48
|
-
self._cacheHasFocusableChild = None
|
|
49
|
-
|
|
50
|
-
for property in payload.get('properties', []):
|
|
51
|
-
_name = property['name']
|
|
52
|
-
_value = property.get('value', {}).get('value', None)
|
|
53
|
-
if _name == 'editable':
|
|
54
|
-
self._richlyEditable = _value == 'richtext'
|
|
55
|
-
self._editable = True
|
|
56
|
-
if _name == 'focusable':
|
|
57
|
-
self._focusable = _value
|
|
58
|
-
if _name == 'expanded':
|
|
59
|
-
self._expanded = _value
|
|
60
|
-
if _name == 'hidden':
|
|
61
|
-
self._hidden = _value
|
|
62
|
-
|
|
63
|
-
@property
|
|
64
|
-
def _isPlainTextField(self):
|
|
65
|
-
if self._richlyEditable:
|
|
66
|
-
return False
|
|
67
|
-
if self._editable:
|
|
68
|
-
return True
|
|
69
|
-
return self._role in ['textbox', 'ComboBox', 'searchbox']
|
|
70
|
-
|
|
71
|
-
@property
|
|
72
|
-
def _isTextOnlyObject(self):
|
|
73
|
-
return self._role in ['LineBreak', 'text', 'InlineTextBox']
|
|
74
|
-
|
|
75
|
-
@property
|
|
76
|
-
def _hasFocusableChild(self):
|
|
77
|
-
if self._cacheHasFocusableChild is None:
|
|
78
|
-
self._cacheHasFocusableChild = False
|
|
79
|
-
for child in self._children:
|
|
80
|
-
if child._focusable or child._hasFocusableChild:
|
|
81
|
-
self._cacheHasFocusableChild = True
|
|
82
|
-
break
|
|
83
|
-
return self._cacheHasFocusableChild
|
|
84
|
-
|
|
85
|
-
def find(self, predicate):
|
|
86
|
-
if predicate(self):
|
|
87
|
-
return self
|
|
88
|
-
for child in self._children:
|
|
89
|
-
result = child.find(predicate)
|
|
90
|
-
if result:
|
|
91
|
-
return result
|
|
92
|
-
|
|
93
|
-
def isLeafNode(self):
|
|
94
|
-
if not self._children:
|
|
95
|
-
return True
|
|
96
|
-
|
|
97
|
-
# These types of objects may have children that we use as internal
|
|
98
|
-
# implementation details, but we want to expose them as leaves to platform
|
|
99
|
-
# accessibility APIs because screen readers might be confused if they find
|
|
100
|
-
# any children.
|
|
101
|
-
if self._isPlainTextField or self._isTextOnlyObject:
|
|
102
|
-
return True
|
|
103
|
-
# Roles whose children are only presentational according to the ARIA and
|
|
104
|
-
# HTML5 Specs should be hidden from screen readers.
|
|
105
|
-
# (Note that whilst ARIA buttons can have only presentational children, HTML5
|
|
106
|
-
# buttons are allowed to have content.)
|
|
107
|
-
if self._role in [
|
|
108
|
-
'doc-cover',
|
|
109
|
-
'graphics-symbol',
|
|
110
|
-
'img',
|
|
111
|
-
'Meter',
|
|
112
|
-
'scrollbar',
|
|
113
|
-
'slider',
|
|
114
|
-
'separator',
|
|
115
|
-
'progressbar',
|
|
116
|
-
]:
|
|
117
|
-
return True
|
|
118
|
-
|
|
119
|
-
# here and below: android heuristics
|
|
120
|
-
if self._hasFocusableChild:
|
|
121
|
-
return False
|
|
122
|
-
if self._focusable and self._name:
|
|
123
|
-
return True
|
|
124
|
-
if self._role == 'heading' and self._name:
|
|
125
|
-
return True
|
|
126
|
-
return False
|
|
127
|
-
|
|
128
|
-
@property
|
|
129
|
-
def isControl(self):
|
|
130
|
-
return self._role in [
|
|
131
|
-
'button',
|
|
132
|
-
'checkbox',
|
|
133
|
-
'ColorWell',
|
|
134
|
-
'combobox',
|
|
135
|
-
'DisclosureTriangle',
|
|
136
|
-
'listbox',
|
|
137
|
-
'menu',
|
|
138
|
-
'menubar',
|
|
139
|
-
'menuitem',
|
|
140
|
-
'menuitemcheckbox',
|
|
141
|
-
'menuitemradio',
|
|
142
|
-
'radio',
|
|
143
|
-
'scrollbar',
|
|
144
|
-
'searchbox',
|
|
145
|
-
'slider',
|
|
146
|
-
'spinbutton',
|
|
147
|
-
'switch',
|
|
148
|
-
'tab',
|
|
149
|
-
'textbox',
|
|
150
|
-
'tree',
|
|
151
|
-
]
|
|
152
|
-
|
|
153
|
-
def isInteresting(self, insideControl: bool) -> bool:
|
|
154
|
-
role = self._role
|
|
155
|
-
if role == 'Ignored' or self._hidden:
|
|
156
|
-
return False
|
|
157
|
-
if self._focusable or self._richlyEditable:
|
|
158
|
-
return True
|
|
159
|
-
|
|
160
|
-
# If it's not focusable but has a control role, then it's interesting.
|
|
161
|
-
if self.isControl:
|
|
162
|
-
return True
|
|
163
|
-
|
|
164
|
-
# A non focusable child of a control is not interesting
|
|
165
|
-
if insideControl:
|
|
166
|
-
return False
|
|
167
|
-
return self.isLeafNode() and bool(self._name)
|
|
168
|
-
|
|
169
|
-
def serialize(self): # noqa C901
|
|
170
|
-
properties: Dict[str, Union[str, float, bool]] = {}
|
|
171
|
-
for property_ in self._payload.get('properties', []):
|
|
172
|
-
properties[property_['name'].lower()] = property_['value'].get('value')
|
|
173
|
-
if self._payload.get('name'):
|
|
174
|
-
properties['name'] = self._payload['name']['value']
|
|
175
|
-
if self._payload.get('value'):
|
|
176
|
-
properties['value'] = self._payload['value']['value']
|
|
177
|
-
if self._payload.get('description'):
|
|
178
|
-
properties['description'] = self._payload['description']['value']
|
|
179
|
-
|
|
180
|
-
node = {'role': self._role}
|
|
181
|
-
userStringProperties = [
|
|
182
|
-
'name',
|
|
183
|
-
'value',
|
|
184
|
-
'description',
|
|
185
|
-
'keyshortcuts',
|
|
186
|
-
'roledescription',
|
|
187
|
-
'valuetext',
|
|
188
|
-
]
|
|
189
|
-
for prop in userStringProperties:
|
|
190
|
-
if prop not in properties:
|
|
191
|
-
continue
|
|
192
|
-
node[prop] = properties[prop]
|
|
193
|
-
booleanProperties = [
|
|
194
|
-
'disabled',
|
|
195
|
-
'expanded',
|
|
196
|
-
'focused',
|
|
197
|
-
'modal',
|
|
198
|
-
'multiline',
|
|
199
|
-
'multiselectable',
|
|
200
|
-
'readonly',
|
|
201
|
-
'required',
|
|
202
|
-
'selected',
|
|
203
|
-
]
|
|
204
|
-
for prop in booleanProperties:
|
|
205
|
-
# WebArea's treat focus differently than other nodes. They report whether their frame has focus,
|
|
206
|
-
# not whether focus is specifically on the root node.
|
|
207
|
-
if prop == 'focused' and self._role == 'WebArea':
|
|
208
|
-
continue
|
|
209
|
-
value = properties.get(prop)
|
|
210
|
-
if value:
|
|
211
|
-
node[prop] = value
|
|
212
|
-
|
|
213
|
-
tristateProperties = [
|
|
214
|
-
'checked',
|
|
215
|
-
'pressed',
|
|
216
|
-
]
|
|
217
|
-
for prop in tristateProperties:
|
|
218
|
-
if prop not in properties:
|
|
219
|
-
continue
|
|
220
|
-
value = properties.get(prop)
|
|
221
|
-
if value != 'mixed':
|
|
222
|
-
if value == 'true':
|
|
223
|
-
value = True
|
|
224
|
-
else:
|
|
225
|
-
value = False
|
|
226
|
-
node[prop] = value
|
|
227
|
-
|
|
228
|
-
numericProperties = [
|
|
229
|
-
'level',
|
|
230
|
-
'valuemax',
|
|
231
|
-
'valuemin',
|
|
232
|
-
]
|
|
233
|
-
for prop in numericProperties:
|
|
234
|
-
if prop not in properties:
|
|
235
|
-
continue
|
|
236
|
-
node[prop] = properties.get(prop)
|
|
237
|
-
|
|
238
|
-
tokenProperties = [
|
|
239
|
-
'autocomplete',
|
|
240
|
-
'haspopup',
|
|
241
|
-
'invalid',
|
|
242
|
-
'orientation',
|
|
243
|
-
]
|
|
244
|
-
for prop in tokenProperties:
|
|
245
|
-
value = properties.get(prop)
|
|
246
|
-
if not value or value == 'false':
|
|
247
|
-
continue
|
|
248
|
-
node[prop] = value
|
|
249
|
-
return node
|
|
250
|
-
|
|
251
|
-
@staticmethod
|
|
252
|
-
def createTree(payloads: List[dict]) -> 'AXNode':
|
|
253
|
-
"""
|
|
254
|
-
|
|
255
|
-
:param payloads: List of dictionaries of AXNode kwargs
|
|
256
|
-
:return:
|
|
257
|
-
"""
|
|
258
|
-
nodeById = {}
|
|
259
|
-
for payload in payloads:
|
|
260
|
-
nodeById[payload['nodeId']] = AXNode(payload)
|
|
261
|
-
for node in nodeById.values():
|
|
262
|
-
for childId in node._payload.get('childIds', []):
|
|
263
|
-
node._children.append(nodeById[childId])
|
|
264
|
-
return [*nodeById.values()][0]
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
def collectInterestingNodes(collection: Set[AXNode], node: AXNode, insideControl: bool):
|
|
268
|
-
if node.isInteresting(insideControl):
|
|
269
|
-
collection.add(node)
|
|
270
|
-
if node.isLeafNode():
|
|
271
|
-
return
|
|
272
|
-
insideControl = insideControl or node.isControl
|
|
273
|
-
for child in node._children:
|
|
274
|
-
collectInterestingNodes(collection, child, insideControl)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
def serializeTree(node: 'AXNode', whitelistedNodes: Set[AXNode] = None):
|
|
278
|
-
children = []
|
|
279
|
-
for child in node._children:
|
|
280
|
-
children.extend(serializeTree(child, whitelistedNodes))
|
|
281
|
-
if whitelistedNodes and node not in whitelistedNodes:
|
|
282
|
-
return children
|
|
283
|
-
serializedNode = node.serialize()
|
|
284
|
-
if children:
|
|
285
|
-
serializedNode['children'] = children
|
|
286
|
-
return [serializedNode]
|