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.
Files changed (262) hide show
  1. biolib/__init__.py +357 -11
  2. biolib/_data_record/data_record.py +380 -0
  3. biolib/_index/__init__.py +0 -0
  4. biolib/_index/index.py +55 -0
  5. biolib/_index/query_result.py +103 -0
  6. biolib/_internal/__init__.py +0 -0
  7. biolib/_internal/add_copilot_prompts.py +58 -0
  8. biolib/_internal/add_gui_files.py +81 -0
  9. biolib/_internal/data_record/__init__.py +1 -0
  10. biolib/_internal/data_record/data_record.py +85 -0
  11. biolib/_internal/data_record/push_data.py +116 -0
  12. biolib/_internal/data_record/remote_storage_endpoint.py +43 -0
  13. biolib/_internal/errors.py +5 -0
  14. biolib/_internal/file_utils.py +125 -0
  15. biolib/_internal/fuse_mount/__init__.py +1 -0
  16. biolib/_internal/fuse_mount/experiment_fuse_mount.py +209 -0
  17. biolib/_internal/http_client.py +159 -0
  18. biolib/_internal/lfs/__init__.py +1 -0
  19. biolib/_internal/lfs/cache.py +51 -0
  20. biolib/_internal/libs/__init__.py +1 -0
  21. biolib/_internal/libs/fusepy/__init__.py +1257 -0
  22. biolib/_internal/push_application.py +488 -0
  23. biolib/_internal/runtime.py +22 -0
  24. biolib/_internal/string_utils.py +13 -0
  25. biolib/_internal/templates/__init__.py +1 -0
  26. biolib/_internal/templates/copilot_template/.github/instructions/general-app-knowledge.instructions.md +10 -0
  27. biolib/_internal/templates/copilot_template/.github/instructions/style-general.instructions.md +20 -0
  28. biolib/_internal/templates/copilot_template/.github/instructions/style-python.instructions.md +16 -0
  29. biolib/_internal/templates/copilot_template/.github/instructions/style-react-ts.instructions.md +47 -0
  30. biolib/_internal/templates/copilot_template/.github/prompts/biolib_app_inputs.prompt.md +11 -0
  31. biolib/_internal/templates/copilot_template/.github/prompts/biolib_onboard_repo.prompt.md +19 -0
  32. biolib/_internal/templates/copilot_template/.github/prompts/biolib_run_apps.prompt.md +12 -0
  33. biolib/_internal/templates/dashboard_template/.biolib/config.yml +5 -0
  34. biolib/_internal/templates/github_workflow_template/.github/workflows/biolib.yml +21 -0
  35. biolib/_internal/templates/gitignore_template/.gitignore +10 -0
  36. biolib/_internal/templates/gui_template/.yarnrc.yml +1 -0
  37. biolib/_internal/templates/gui_template/App.tsx +53 -0
  38. biolib/_internal/templates/gui_template/Dockerfile +27 -0
  39. biolib/_internal/templates/gui_template/biolib-sdk.ts +82 -0
  40. biolib/_internal/templates/gui_template/dev-data/output.json +7 -0
  41. biolib/_internal/templates/gui_template/index.css +5 -0
  42. biolib/_internal/templates/gui_template/index.html +13 -0
  43. biolib/_internal/templates/gui_template/index.tsx +10 -0
  44. biolib/_internal/templates/gui_template/package.json +27 -0
  45. biolib/_internal/templates/gui_template/tsconfig.json +24 -0
  46. biolib/_internal/templates/gui_template/vite-plugin-dev-data.ts +50 -0
  47. biolib/_internal/templates/gui_template/vite.config.mts +10 -0
  48. biolib/_internal/templates/init_template/.biolib/config.yml +19 -0
  49. biolib/_internal/templates/init_template/Dockerfile +14 -0
  50. biolib/_internal/templates/init_template/requirements.txt +1 -0
  51. biolib/_internal/templates/init_template/run.py +12 -0
  52. biolib/_internal/templates/init_template/run.sh +4 -0
  53. biolib/_internal/templates/templates.py +25 -0
  54. biolib/_internal/tree_utils.py +106 -0
  55. biolib/_internal/utils/__init__.py +65 -0
  56. biolib/_internal/utils/auth.py +46 -0
  57. biolib/_internal/utils/job_url.py +33 -0
  58. biolib/_internal/utils/multinode.py +263 -0
  59. biolib/_runtime/runtime.py +157 -0
  60. biolib/_session/session.py +44 -0
  61. biolib/_shared/__init__.py +0 -0
  62. biolib/_shared/types/__init__.py +74 -0
  63. biolib/_shared/types/account.py +12 -0
  64. biolib/_shared/types/account_member.py +8 -0
  65. biolib/_shared/types/app.py +9 -0
  66. biolib/_shared/types/data_record.py +40 -0
  67. biolib/_shared/types/experiment.py +32 -0
  68. biolib/_shared/types/file_node.py +17 -0
  69. biolib/_shared/types/push.py +6 -0
  70. biolib/_shared/types/resource.py +37 -0
  71. biolib/_shared/types/resource_deploy_key.py +11 -0
  72. biolib/_shared/types/resource_permission.py +14 -0
  73. biolib/_shared/types/resource_version.py +19 -0
  74. biolib/_shared/types/result.py +14 -0
  75. biolib/_shared/types/typing.py +10 -0
  76. biolib/_shared/types/user.py +19 -0
  77. biolib/_shared/utils/__init__.py +7 -0
  78. biolib/_shared/utils/resource_uri.py +75 -0
  79. biolib/api/__init__.py +6 -0
  80. biolib/api/client.py +168 -0
  81. biolib/app/app.py +252 -49
  82. biolib/app/search_apps.py +45 -0
  83. biolib/biolib_api_client/api_client.py +126 -31
  84. biolib/biolib_api_client/app_types.py +24 -4
  85. biolib/biolib_api_client/auth.py +31 -8
  86. biolib/biolib_api_client/biolib_app_api.py +147 -52
  87. biolib/biolib_api_client/biolib_job_api.py +161 -141
  88. biolib/biolib_api_client/job_types.py +21 -5
  89. biolib/biolib_api_client/lfs_types.py +7 -23
  90. biolib/biolib_api_client/user_state.py +56 -0
  91. biolib/biolib_binary_format/__init__.py +1 -4
  92. biolib/biolib_binary_format/file_in_container.py +105 -0
  93. biolib/biolib_binary_format/module_input.py +24 -7
  94. biolib/biolib_binary_format/module_output_v2.py +149 -0
  95. biolib/biolib_binary_format/remote_endpoints.py +34 -0
  96. biolib/biolib_binary_format/remote_stream_seeker.py +59 -0
  97. biolib/biolib_binary_format/saved_job.py +3 -2
  98. biolib/biolib_binary_format/{attestation_document.py → stdout_and_stderr.py} +8 -8
  99. biolib/biolib_binary_format/system_status_update.py +3 -2
  100. biolib/biolib_binary_format/utils.py +175 -0
  101. biolib/biolib_docker_client/__init__.py +11 -2
  102. biolib/biolib_errors.py +36 -0
  103. biolib/biolib_logging.py +27 -10
  104. biolib/cli/__init__.py +38 -0
  105. biolib/cli/auth.py +46 -0
  106. biolib/cli/data_record.py +164 -0
  107. biolib/cli/index.py +32 -0
  108. biolib/cli/init.py +421 -0
  109. biolib/cli/lfs.py +101 -0
  110. biolib/cli/push.py +50 -0
  111. biolib/cli/run.py +63 -0
  112. biolib/cli/runtime.py +14 -0
  113. biolib/cli/sdk.py +16 -0
  114. biolib/cli/start.py +56 -0
  115. biolib/compute_node/cloud_utils/cloud_utils.py +110 -161
  116. biolib/compute_node/job_worker/cache_state.py +66 -88
  117. biolib/compute_node/job_worker/cache_types.py +1 -6
  118. biolib/compute_node/job_worker/docker_image_cache.py +112 -37
  119. biolib/compute_node/job_worker/executors/__init__.py +0 -3
  120. biolib/compute_node/job_worker/executors/docker_executor.py +532 -199
  121. biolib/compute_node/job_worker/executors/docker_types.py +9 -1
  122. biolib/compute_node/job_worker/executors/types.py +19 -9
  123. biolib/compute_node/job_worker/job_legacy_input_wait_timeout_thread.py +30 -0
  124. biolib/compute_node/job_worker/job_max_runtime_timer_thread.py +3 -5
  125. biolib/compute_node/job_worker/job_storage.py +108 -0
  126. biolib/compute_node/job_worker/job_worker.py +397 -212
  127. biolib/compute_node/job_worker/large_file_system.py +87 -38
  128. biolib/compute_node/job_worker/network_alloc.py +99 -0
  129. biolib/compute_node/job_worker/network_buffer.py +240 -0
  130. biolib/compute_node/job_worker/utilization_reporter_thread.py +197 -0
  131. biolib/compute_node/job_worker/utils.py +9 -24
  132. biolib/compute_node/remote_host_proxy.py +400 -98
  133. biolib/compute_node/utils.py +31 -9
  134. biolib/compute_node/webserver/compute_node_results_proxy.py +189 -0
  135. biolib/compute_node/webserver/proxy_utils.py +28 -0
  136. biolib/compute_node/webserver/webserver.py +130 -44
  137. biolib/compute_node/webserver/webserver_types.py +2 -6
  138. biolib/compute_node/webserver/webserver_utils.py +77 -12
  139. biolib/compute_node/webserver/worker_thread.py +183 -42
  140. biolib/experiments/__init__.py +0 -0
  141. biolib/experiments/experiment.py +356 -0
  142. biolib/jobs/__init__.py +1 -0
  143. biolib/jobs/job.py +741 -0
  144. biolib/jobs/job_result.py +185 -0
  145. biolib/jobs/types.py +50 -0
  146. biolib/py.typed +0 -0
  147. biolib/runtime/__init__.py +14 -0
  148. biolib/sdk/__init__.py +91 -0
  149. biolib/tables.py +34 -0
  150. biolib/typing_utils.py +2 -7
  151. biolib/user/__init__.py +1 -0
  152. biolib/user/sign_in.py +54 -0
  153. biolib/utils/__init__.py +162 -0
  154. biolib/utils/cache_state.py +94 -0
  155. biolib/utils/multipart_uploader.py +194 -0
  156. biolib/utils/seq_util.py +150 -0
  157. biolib/utils/zip/remote_zip.py +640 -0
  158. pybiolib-1.2.1890.dist-info/METADATA +41 -0
  159. pybiolib-1.2.1890.dist-info/RECORD +177 -0
  160. {pybiolib-0.2.951.dist-info → pybiolib-1.2.1890.dist-info}/WHEEL +1 -1
  161. pybiolib-1.2.1890.dist-info/entry_points.txt +2 -0
  162. README.md +0 -17
  163. biolib/app/app_result.py +0 -68
  164. biolib/app/utils.py +0 -62
  165. biolib/biolib-js/0-biolib.worker.js +0 -1
  166. biolib/biolib-js/1-biolib.worker.js +0 -1
  167. biolib/biolib-js/2-biolib.worker.js +0 -1
  168. biolib/biolib-js/3-biolib.worker.js +0 -1
  169. biolib/biolib-js/4-biolib.worker.js +0 -1
  170. biolib/biolib-js/5-biolib.worker.js +0 -1
  171. biolib/biolib-js/6-biolib.worker.js +0 -1
  172. biolib/biolib-js/index.html +0 -10
  173. biolib/biolib-js/main-biolib.js +0 -1
  174. biolib/biolib_api_client/biolib_account_api.py +0 -21
  175. biolib/biolib_api_client/biolib_large_file_system_api.py +0 -108
  176. biolib/biolib_binary_format/aes_encrypted_package.py +0 -42
  177. biolib/biolib_binary_format/module_output.py +0 -58
  178. biolib/biolib_binary_format/rsa_encrypted_aes_package.py +0 -57
  179. biolib/biolib_push.py +0 -114
  180. biolib/cli.py +0 -203
  181. biolib/cli_utils.py +0 -273
  182. biolib/compute_node/cloud_utils/enclave_parent_types.py +0 -7
  183. biolib/compute_node/enclave/__init__.py +0 -2
  184. biolib/compute_node/enclave/enclave_remote_hosts.py +0 -53
  185. biolib/compute_node/enclave/nitro_secure_module_utils.py +0 -64
  186. biolib/compute_node/job_worker/executors/base_executor.py +0 -18
  187. biolib/compute_node/job_worker/executors/pyppeteer_executor.py +0 -173
  188. biolib/compute_node/job_worker/executors/remote/__init__.py +0 -1
  189. biolib/compute_node/job_worker/executors/remote/nitro_enclave_utils.py +0 -81
  190. biolib/compute_node/job_worker/executors/remote/remote_executor.py +0 -51
  191. biolib/lfs.py +0 -196
  192. biolib/pyppeteer/.circleci/config.yml +0 -100
  193. biolib/pyppeteer/.coveragerc +0 -3
  194. biolib/pyppeteer/.gitignore +0 -89
  195. biolib/pyppeteer/.pre-commit-config.yaml +0 -28
  196. biolib/pyppeteer/CHANGES.md +0 -253
  197. biolib/pyppeteer/CONTRIBUTING.md +0 -26
  198. biolib/pyppeteer/LICENSE +0 -12
  199. biolib/pyppeteer/README.md +0 -137
  200. biolib/pyppeteer/docs/Makefile +0 -177
  201. biolib/pyppeteer/docs/_static/custom.css +0 -28
  202. biolib/pyppeteer/docs/_templates/layout.html +0 -10
  203. biolib/pyppeteer/docs/changes.md +0 -1
  204. biolib/pyppeteer/docs/conf.py +0 -299
  205. biolib/pyppeteer/docs/index.md +0 -21
  206. biolib/pyppeteer/docs/make.bat +0 -242
  207. biolib/pyppeteer/docs/reference.md +0 -211
  208. biolib/pyppeteer/docs/server.py +0 -60
  209. biolib/pyppeteer/poetry.lock +0 -1699
  210. biolib/pyppeteer/pyppeteer/__init__.py +0 -135
  211. biolib/pyppeteer/pyppeteer/accessibility.py +0 -286
  212. biolib/pyppeteer/pyppeteer/browser.py +0 -401
  213. biolib/pyppeteer/pyppeteer/browser_fetcher.py +0 -194
  214. biolib/pyppeteer/pyppeteer/command.py +0 -22
  215. biolib/pyppeteer/pyppeteer/connection/__init__.py +0 -242
  216. biolib/pyppeteer/pyppeteer/connection/cdpsession.py +0 -101
  217. biolib/pyppeteer/pyppeteer/coverage.py +0 -346
  218. biolib/pyppeteer/pyppeteer/device_descriptors.py +0 -787
  219. biolib/pyppeteer/pyppeteer/dialog.py +0 -79
  220. biolib/pyppeteer/pyppeteer/domworld.py +0 -597
  221. biolib/pyppeteer/pyppeteer/emulation_manager.py +0 -53
  222. biolib/pyppeteer/pyppeteer/errors.py +0 -48
  223. biolib/pyppeteer/pyppeteer/events.py +0 -63
  224. biolib/pyppeteer/pyppeteer/execution_context.py +0 -156
  225. biolib/pyppeteer/pyppeteer/frame/__init__.py +0 -299
  226. biolib/pyppeteer/pyppeteer/frame/frame_manager.py +0 -306
  227. biolib/pyppeteer/pyppeteer/helpers.py +0 -245
  228. biolib/pyppeteer/pyppeteer/input.py +0 -371
  229. biolib/pyppeteer/pyppeteer/jshandle.py +0 -598
  230. biolib/pyppeteer/pyppeteer/launcher.py +0 -683
  231. biolib/pyppeteer/pyppeteer/lifecycle_watcher.py +0 -169
  232. biolib/pyppeteer/pyppeteer/models/__init__.py +0 -103
  233. biolib/pyppeteer/pyppeteer/models/_protocol.py +0 -12460
  234. biolib/pyppeteer/pyppeteer/multimap.py +0 -82
  235. biolib/pyppeteer/pyppeteer/network_manager.py +0 -678
  236. biolib/pyppeteer/pyppeteer/options.py +0 -8
  237. biolib/pyppeteer/pyppeteer/page.py +0 -1728
  238. biolib/pyppeteer/pyppeteer/pipe_transport.py +0 -59
  239. biolib/pyppeteer/pyppeteer/target.py +0 -147
  240. biolib/pyppeteer/pyppeteer/task_queue.py +0 -24
  241. biolib/pyppeteer/pyppeteer/timeout_settings.py +0 -36
  242. biolib/pyppeteer/pyppeteer/tracing.py +0 -93
  243. biolib/pyppeteer/pyppeteer/us_keyboard_layout.py +0 -305
  244. biolib/pyppeteer/pyppeteer/util.py +0 -18
  245. biolib/pyppeteer/pyppeteer/websocket_transport.py +0 -47
  246. biolib/pyppeteer/pyppeteer/worker.py +0 -101
  247. biolib/pyppeteer/pyproject.toml +0 -97
  248. biolib/pyppeteer/spell.txt +0 -137
  249. biolib/pyppeteer/tox.ini +0 -72
  250. biolib/pyppeteer/utils/generate_protocol_types.py +0 -603
  251. biolib/start_cli.py +0 -7
  252. biolib/utils.py +0 -47
  253. biolib/validators/validate_app_version.py +0 -183
  254. biolib/validators/validate_argument.py +0 -134
  255. biolib/validators/validate_module.py +0 -323
  256. biolib/validators/validate_zip_file.py +0 -40
  257. biolib/validators/validator_utils.py +0 -103
  258. pybiolib-0.2.951.dist-info/LICENSE +0 -21
  259. pybiolib-0.2.951.dist-info/METADATA +0 -61
  260. pybiolib-0.2.951.dist-info/RECORD +0 -153
  261. pybiolib-0.2.951.dist-info/entry_points.txt +0 -3
  262. /LICENSE → /pybiolib-1.2.1890.dist-info/licenses/LICENSE +0 -0
@@ -1,598 +0,0 @@
1
- import asyncio
2
- import copy
3
- import logging
4
- import math
5
- import os
6
- from pathlib import Path
7
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
8
-
9
- from biolib.pyppeteer.pyppeteer import helpers
10
- from biolib.pyppeteer.pyppeteer.connection import CDPSession
11
- from biolib.pyppeteer.pyppeteer.errors import BrowserError, ElementHandleError, NetworkError
12
- from biolib.pyppeteer.pyppeteer.models import JSFunctionArg, MouseButton, Protocol
13
-
14
- if TYPE_CHECKING:
15
- from pyppeteer.page import Page
16
- from pyppeteer.frame import FrameManager
17
- from pyppeteer.execution_context import ExecutionContext
18
-
19
-
20
- logger = logging.getLogger(__name__)
21
-
22
-
23
- def createJSHandle(context, remoteObject) -> Union['JSHandle', 'ElementHandle']:
24
- frame = context.frame
25
- if remoteObject.get('subtype') == 'node' and frame:
26
- frameManager = frame._frameManager
27
- return ElementHandle(context, context._client, remoteObject, frameManager.page, frameManager)
28
- return JSHandle(context, context._client, remoteObject,)
29
-
30
-
31
- class JSHandle:
32
- """JSHandle class.
33
-
34
- JSHandle represents an in-page JavaScript object. JSHandle can be created
35
- with the :meth:`~pyppeteer.page.Page.evaluateHandle` method.
36
- """
37
-
38
- def __init__(self, context: 'ExecutionContext', client: 'CDPSession', remoteObject: Protocol.Runtime.RemoteObject):
39
- self._context = context
40
- self._client = client
41
- self._remoteObject = remoteObject
42
- self._disposed = False
43
-
44
- @property
45
- def executionContext(self):
46
- """Get execution context of this handle."""
47
- return self._context
48
-
49
- async def evaluate(self, pageFunction: str, *args: JSFunctionArg):
50
- return await self.executionContext.evaluate(pageFunction, self, *args)
51
-
52
- async def evaluateHandle(self, pageFunction: str, *args: JSFunctionArg):
53
- return await self.executionContext.evaluateHandle(pageFunction, self, *args)
54
-
55
- async def getProperty(self, propertyName: str) -> 'JSHandle':
56
- """Get property value of ``propertyName``."""
57
- objectHandle = await self._context.evaluateHandle(
58
- '''(object, propertyName) => {
59
- const result = {__proto__: null};
60
- result[propertyName] = object[propertyName];
61
- return result;
62
- }''',
63
- self,
64
- propertyName,
65
- )
66
- properties = await objectHandle.getProperties()
67
- result = properties[propertyName]
68
- await objectHandle.dispose()
69
- return result
70
-
71
- async def getProperties(self) -> Dict[str, 'JSHandle']:
72
- """Get all properties of this handle."""
73
- response = await self._client.send(
74
- 'Runtime.getProperties', {'objectId': self._remoteObject.get('objectId', ''), 'ownProperties': True,}
75
- )
76
- result = {}
77
- for prop in response['result']:
78
- if not prop.get('enumerable'):
79
- continue
80
- result[prop['name']] = createJSHandle(self._context, prop['value'])
81
- return result
82
-
83
- async def jsonValue(self) -> Dict:
84
- """Get Jsonized value of this object."""
85
- objectId = self._remoteObject.get('objectId')
86
- if objectId:
87
- response = await self._client.send(
88
- 'Runtime.callFunctionOn',
89
- {
90
- 'functionDeclaration': 'function() { return this; }',
91
- 'objectId': objectId,
92
- 'returnByValue': True,
93
- 'awaitPromise': True,
94
- },
95
- )
96
- return helpers.valueFromRemoteObject(response['result'])
97
- return helpers.valueFromRemoteObject(self._remoteObject)
98
-
99
- def asElement(self) -> 'ElementHandle':
100
- ...
101
-
102
- async def dispose(self) -> None:
103
- """Stop referencing the handle."""
104
- if self._disposed:
105
- return
106
- self._disposed = True
107
- await helpers.releaseObject(self._client, self._remoteObject)
108
-
109
- def toString(self) -> str:
110
- """Get string representation."""
111
- if self._remoteObject.get('objectId'):
112
- _type = self._remoteObject.get('subtype') or self._remoteObject.get('type')
113
- return f'JSHandle@{_type}'
114
- return 'JSHandle:{}'.format(helpers.valueFromRemoteObject(self._remoteObject))
115
-
116
-
117
- class ElementHandle(JSHandle):
118
- def __init__(
119
- self,
120
- context: 'ExecutionContext',
121
- client: CDPSession,
122
- remoteObject: Protocol.Runtime.RemoteObject,
123
- page: 'Page',
124
- frameManager: 'FrameManager',
125
- ):
126
- super().__init__(context, client, remoteObject)
127
- self._page = page
128
- self._frameManager = frameManager
129
- self._disposed = False
130
-
131
- # Aliases for query methods:
132
- self.J = self.querySelector
133
- self.Jx = self.xpath
134
- self.Jeval = self.querySelectorEval
135
- self.JJ = self.querySelectorAll
136
- self.JJeval = self.querySelectorAllEval
137
-
138
- def asElement(self) -> Optional['ElementHandle']:
139
- return self
140
-
141
- async def contentFrame(self):
142
- nodeInfo = await self._client.send('DOM.describeNode', {'objectId': self._remoteObject.get('objectId')})
143
- frameId = nodeInfo.get('node', {}).get('frameId')
144
- if isinstance(frameId, str):
145
- return self._frameManager.frame(frameId)
146
-
147
- async def _scrollIntoViewIfNeeded(self):
148
- error = await self.evaluate(
149
- """
150
- async(element, pageJavascriptEnabled) => {
151
- if (!element.isConnected)
152
- return 'Node is detached from document';
153
- if (element.nodeType !== Node.ELEMENT_NODE)
154
- return 'Node is not of type HTMLElement';
155
- // force-scroll if page's javascript is disabled.
156
- if (!pageJavascriptEnabled) {
157
- element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
158
- return false;
159
- }
160
- const visibleRatio = await new Promise(resolve => {
161
- const observer = new IntersectionObserver(entries => {
162
- resolve(entries[0].intersectionRatio);
163
- observer.disconnect();
164
- });
165
- observer.observe(element);
166
- });
167
- if (visibleRatio !== 1.0)
168
- element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
169
- return false;
170
- }
171
- """,
172
- self._page._javascriptEnabled,
173
- )
174
- if error:
175
- raise BrowserError(error)
176
-
177
- async def _clickablePoint(self):
178
- # swallow any errors to get a better error message
179
- # before: Protocol error (DOM.getContentQuads): Could not compute content quads.
180
- # after swallowing: Node is either invisible or not an HTMLElement
181
- async def silent_get_quads():
182
- try:
183
- return await self._client.send('DOM.getContentQuads', {'objectId': self._remoteObject['objectId']})
184
- except NetworkError:
185
- logger.exception('Failed to retrieve content quads')
186
- return None
187
-
188
- result, layoutMetrics = await asyncio.gather(silent_get_quads(), self._client.send('Page.getLayoutMetrics'),)
189
- if not result or not result.get('quads'):
190
- raise BrowserError('Node is either not visible or not an HTMLElement')
191
- clientWidth = layoutMetrics['layoutViewport']['clientWidth']
192
- clientHeight = layoutMetrics['layoutViewport']['clientHeight']
193
- quads = []
194
- for quad in result['quads']:
195
- quad = self._intersectQuadWithViewport(self._fromProtocolQuad(quad), clientWidth, clientHeight)
196
- if computeQuadArea(quad) > 1:
197
- quads.append(quad)
198
- if not quads:
199
- raise BrowserError('Node is either not visible or not an HTMLElement')
200
- # return middle of quad[0]
201
- quad = quads[0]
202
- x, y = 0, 0
203
- for point in quad:
204
- x += point['x']
205
- y += point['y']
206
- return {
207
- 'x': x / 4,
208
- 'y': y / 4,
209
- }
210
-
211
- async def _getBoxModel(self):
212
- try:
213
- return await self._client.send('DOM.getBoxModel', {'objectId': self._remoteObject['objectId']})
214
- except Exception as e:
215
- logger.error(f'An exception occurred: {e}')
216
-
217
- def _fromProtocolQuad(self, quad):
218
- return [
219
- {'x': quad[0], 'y': quad[1]},
220
- {'x': quad[2], 'y': quad[3]},
221
- {'x': quad[4], 'y': quad[5]},
222
- {'x': quad[6], 'y': quad[7]},
223
- ]
224
-
225
- def _intersectQuadWithViewport(self, quad: List[Dict[str, float]], width: float, height: float):
226
- return [{'x': min(max(point['x'], 0), width), 'y': min(max(point['y'], 0), height)} for point in quad]
227
-
228
- async def hover(self) -> None:
229
- """Move mouse over to center of this element.
230
-
231
- If needed, this method scrolls element into view. If this element is
232
- detached from DOM tree, the method raises an ``ElementHandleError``.
233
- """
234
- await self._scrollIntoViewIfNeeded()
235
- obj = await self._clickablePoint()
236
- x = obj.get('x', 0)
237
- y = obj.get('y', 0)
238
- await self._page.mouse.move(x, y)
239
-
240
- async def click(self, button: MouseButton = 'left', clickCount: int = 1, delay: float = 0) -> None:
241
- """Click the center of this element.
242
-
243
- If needed, this method scrolls element into view. If the element is
244
- detached from DOM, the method raises ``ElementHandleError``.
245
-
246
- ``options`` can contain the following fields:
247
-
248
- * ``button`` (str): ``left``, ``right``, of ``middle``, defaults to
249
- ``left``.
250
- * ``clickCount`` (int): Defaults to 1.
251
- * ``delay`` (int|float): Time to wait between ``mousedown`` and
252
- ``mouseup`` in milliseconds. Defaults to 0.
253
- """
254
- await self._scrollIntoViewIfNeeded()
255
- point = await self._clickablePoint()
256
- x = point.get('x', 0)
257
- y = point.get('y', 0)
258
- await self._page.mouse.click(x, y, button=button, clickCount=clickCount, delay=delay)
259
-
260
- async def select(self, *values: str) -> List[str]:
261
- for val in values:
262
- if not isinstance(val, str):
263
- raise ValueError(f'value "{val}" needs to be of type str, but found value of type {type(val)}')
264
- return await self.evaluate(
265
- """(element, values) => {
266
- if (element.nodeName.toLowerCase() !== 'select')
267
- throw new Error('Element is not a <select> element.');
268
-
269
- const options = Array.from(element.options);
270
- element.value = undefined;
271
- for (const option of options) {
272
- option.selected = values.includes(option.value);
273
- if (option.selected && !element.multiple)
274
- break;
275
- }
276
- element.dispatchEvent(new Event('input', { bubbles: true }));
277
- element.dispatchEvent(new Event('change', { bubbles: true }));
278
- return options.filter(option => option.selected).map(option => option.value);
279
- }
280
- """,
281
- values,
282
- )
283
-
284
- async def uploadFile(self, *filePaths: Union[Path, str]) -> dict:
285
- """Upload files."""
286
- # TODO port this
287
- files = [os.path.abspath(p) for p in filePaths]
288
- objectId = self._remoteObject.get('objectId')
289
- return await self._client.send('DOM.setFileInputFiles', {'objectId': objectId, 'files': files})
290
-
291
- async def tap(self) -> None:
292
- """Tap the center of this element.
293
-
294
- If needed, this method scrolls element into view. If the element is
295
- detached from DOM, the method raises ``ElementHandleError``.
296
- """
297
- await self._scrollIntoViewIfNeeded()
298
- center = await self._clickablePoint()
299
- x = center.get('x', 0)
300
- y = center.get('y', 0)
301
- await self._page.touchscreen.tap(x, y)
302
-
303
- async def focus(self) -> None:
304
- """Focus on this element."""
305
- await self.executionContext.evaluate('element => element.focus()', self)
306
-
307
- async def type(self, text: str, delay: float = 0) -> None:
308
- """Focus the element and then type text.
309
-
310
- Details see :meth:`pyppeteer.input.Keyboard.type` method.
311
- """
312
- await self.focus()
313
- await self._page.keyboard.type(text, delay)
314
-
315
- async def press(self, key: str, text: str = None, delay: float = 0) -> None:
316
- """Press ``key`` onto the element.
317
-
318
- This method focuses the element, and then uses
319
- :meth:`pyppeteer.input.keyboard.down` and
320
- :meth:`pyppeteer.input.keyboard.up`.
321
-
322
- :arg str key: Name of key to press, such as ``ArrowLeft``.
323
-
324
- This method accepts the following options:
325
-
326
- * ``text`` (str): If specified, generates an input event with this
327
- text.
328
- * ``delay`` (int|float): Time to wait between ``keydown`` and
329
- ``keyup``. Defaults to 0.
330
- """
331
- await self.focus()
332
- await self._page.keyboard.press(key, text=text, delay=delay)
333
-
334
- async def boundingBox(self) -> Optional[Dict[str, float]]:
335
- """Return bounding box of this element.
336
-
337
- If the element is not visible, return ``None``.
338
-
339
- This method returns dictionary of bounding box, which contains:
340
-
341
- * ``x`` (int): The X coordinate of the element in pixels.
342
- * ``y`` (int): The Y coordinate of the element in pixels.
343
- * ``width`` (int): The width of the element in pixels.
344
- * ``height`` (int): The height of the element in pixels.
345
- """
346
- result = await self._getBoxModel()
347
-
348
- if not result:
349
- return None
350
-
351
- quad = result['model']['border']
352
- x = min(quad[0], quad[2], quad[4], quad[6])
353
- y = min(quad[1], quad[3], quad[5], quad[7])
354
- width = max(quad[0], quad[2], quad[4], quad[6]) - x
355
- height = max(quad[1], quad[3], quad[5], quad[7]) - y
356
- return {'x': x, 'y': y, 'width': width, 'height': height}
357
-
358
- async def boxModel(self) -> Optional[Dict]:
359
- """Return boxes of element.
360
-
361
- Return ``None`` if element is not visible. Boxes are represented as an
362
- list of points; each Point is a dictionary ``{x, y}``. Box points are
363
- sorted clock-wise.
364
-
365
- Returned value is a dictionary with the following fields:
366
-
367
- * ``content`` (List[Dict]): Content box.
368
- * ``padding`` (List[Dict]): Padding box.
369
- * ``border`` (List[Dict]): Border box.
370
- * ``margin`` (List[Dict]): Margin box.
371
- * ``width`` (int): Element's width.
372
- * ``height`` (int): Element's height.
373
- """
374
- result = await self._getBoxModel()
375
-
376
- if not result:
377
- return None
378
-
379
- model = result.get('model', {})
380
- return {
381
- 'content': self._fromProtocolQuad(model.get('content')),
382
- 'padding': self._fromProtocolQuad(model.get('padding')),
383
- 'border': self._fromProtocolQuad(model.get('border')),
384
- 'margin': self._fromProtocolQuad(model.get('margin')),
385
- 'width': model.get('width'),
386
- 'height': model.get('height'),
387
- }
388
-
389
- async def screenshot(
390
- self,
391
- path: Union[str, Path] = None,
392
- type_: str = 'png', # png or jpeg
393
- quality: int = None, # 0 to 100
394
- fullPage: bool = False,
395
- omitBackground: bool = False,
396
- encoding: str = 'binary',
397
- ) -> bytes:
398
- """Take a screenshot of this element.
399
-
400
- If the element is detached from DOM, this method raises an
401
- ``ElementHandleError``.
402
-
403
- Available options are same as :meth:`pyppeteer.page.Page.screenshot`.
404
- """
405
-
406
- needsViewportReset = False
407
- boundingBox = await self.boundingBox()
408
- if not boundingBox:
409
- raise ElementHandleError('Node is either not visible or not an HTMLElement')
410
-
411
- original_viewport = copy.deepcopy(self._page.viewport)
412
-
413
- if boundingBox['width'] > original_viewport['width'] or boundingBox['height'] > original_viewport['height']:
414
- newViewport = {
415
- 'width': max(original_viewport['width'], math.ceil(boundingBox['width'])),
416
- 'height': max(original_viewport['height'], math.ceil(boundingBox['height'])),
417
- }
418
- new_viewport = copy.deepcopy(original_viewport)
419
- new_viewport.update(newViewport)
420
- await self._page.setViewport(new_viewport)
421
- needsViewportReset = True
422
-
423
- await self._scrollIntoViewIfNeeded()
424
- boundingBox = await self.boundingBox()
425
- if not boundingBox:
426
- raise ElementHandleError('Node is either not visible or not an HTMLElement')
427
-
428
- _obj = await self._client.send('Page.getLayoutMetrics')
429
- pageX = _obj['layoutViewport']['pageX']
430
- pageY = _obj['layoutViewport']['pageY']
431
-
432
- clip = {}
433
- clip.update(boundingBox)
434
- clip['x'] = clip['x'] + pageX
435
- clip['y'] = clip['y'] + pageY
436
-
437
- imageData = await self._page.screenshot(
438
- path=path,
439
- type_=type_,
440
- quality=quality,
441
- fullPage=fullPage,
442
- clip=clip,
443
- omitBackground=omitBackground,
444
- encoding=encoding,
445
- )
446
-
447
- if needsViewportReset:
448
- await self._page.setViewport(original_viewport)
449
-
450
- return imageData
451
-
452
- async def querySelector(self, selector: str) -> Optional['ElementHandle']:
453
- """Return first element which matches ``selector`` under this element.
454
-
455
- If no element matches the ``selector``, returns ``None``.
456
- """
457
- handle = await self.evaluateHandle('(element, selector) => element.querySelector(selector)', selector,)
458
- element = handle.asElement()
459
- if element:
460
- return element
461
- await handle.dispose()
462
- return None
463
-
464
- async def querySelectorAll(self, selector: str) -> List['ElementHandle']:
465
- """Return all elements which match ``selector`` under this element.
466
-
467
- If no element matches the ``selector``, returns empty list (``[]``).
468
- """
469
- arrayHandle = await self.executionContext.evaluateHandle(
470
- '(element, selector) => element.querySelectorAll(selector)', self, selector,
471
- )
472
- properties = await arrayHandle.getProperties()
473
- await arrayHandle.dispose()
474
- result = []
475
- for prop in properties.values():
476
- elementHandle = prop.asElement()
477
- if elementHandle:
478
- result.append(elementHandle)
479
- return result # type: ignore
480
-
481
- async def querySelectorEval(self, selector: str, pageFunction: str, *args: JSFunctionArg) -> Any:
482
- """Run ``Page.querySelectorEval`` within the element.
483
-
484
- This method runs ``document.querySelector`` within the element and
485
- passes it as the first argument to ``pageFunction``. If there is no
486
- element matching ``selector``, the method raises
487
- ``ElementHandleError``.
488
-
489
- If ``pageFunction`` returns a promise, then wait for the promise to
490
- resolve and return its value.
491
-
492
- ``ElementHandle.Jeval`` is a shortcut of this method.
493
-
494
- Example:
495
-
496
- .. code:: python
497
-
498
- tweetHandle = await page.querySelector('.tweet')
499
- assert (await tweetHandle.querySelectorEval('.like', 'node => node.innerText')) == 100
500
- assert (await tweetHandle.Jeval('.retweets', 'node => node.innerText')) == 10
501
- """
502
- elementHandle = await self.querySelector(selector)
503
- if not elementHandle:
504
- raise ElementHandleError(f'Error: failed to find element matching selector "{selector}"')
505
- result = await self.executionContext.evaluate(pageFunction, elementHandle, *args)
506
- await elementHandle.dispose()
507
- return result
508
-
509
- async def querySelectorAllEval(self, selector: str, pageFunction: str, *args: JSFunctionArg) -> Any:
510
- """Run ``Page.querySelectorAllEval`` within the element.
511
-
512
- This method runs ``Array.from(document.querySelectorAll)`` within the
513
- element and passes it as the first argument to ``pageFunction``. If
514
- there is no element matching ``selector``, the method raises
515
- ``ElementHandleError``.
516
-
517
- If ``pageFunction`` returns a promise, then wait for the promise to
518
- resolve and return its value.
519
-
520
- Example:
521
-
522
- .. code:: html
523
-
524
- <div class="feed">
525
- <div class="tweet">Hello!</div>
526
- <div class="tweet">Hi!</div>
527
- </div>
528
-
529
- .. code:: python
530
-
531
- feedHandle = await page.J('.feed')
532
- assert (await feedHandle.JJeval('.tweet', '(nodes => nodes.map(n => n.innerText))')) == ['Hello!', 'Hi!']
533
- """
534
- arrayHandle = await self.executionContext.evaluateHandle(
535
- '(element, selector) => Array.from(element.querySelectorAll(selector))', self, selector
536
- )
537
- result = await self.executionContext.evaluate(pageFunction, arrayHandle, *args)
538
- await arrayHandle.dispose()
539
- return result
540
-
541
- async def xpath(self, expression: str) -> List['ElementHandle']:
542
- """Evaluate the XPath expression relative to this elementHandle.
543
-
544
- If there are no such elements, return an empty list.
545
-
546
- :arg str expression: XPath string to be evaluated.
547
- """
548
- arrayHandle = await self.executionContext.evaluateHandle(
549
- '''(element, expression) => {
550
- const document = element.ownerDocument || element;
551
- const iterator = document.evaluate(expression, element, null,
552
- XPathResult.ORDERED_NODE_ITERATOR_TYPE);
553
- const array = [];
554
- let item;
555
- while ((item = iterator.iterateNext()))
556
- array.push(item);
557
- return array;
558
-
559
- }''',
560
- self,
561
- expression,
562
- )
563
- properties = await arrayHandle.getProperties()
564
- await arrayHandle.dispose()
565
- result = []
566
- for property in properties.values():
567
- elementHandle = property.asElement()
568
- if elementHandle:
569
- result.append(elementHandle)
570
- return result
571
-
572
- #: alias to :meth:`xpath`
573
- Jx = xpath
574
-
575
- async def isIntersectingViewport(self) -> bool:
576
- """Return ``True`` if the element is visible in the viewport."""
577
- return await self.executionContext.evaluate(
578
- '''async element => {
579
- const visibleRatio = await new Promise(resolve => {
580
- const observer = new IntersectionObserver(entries => {
581
- resolve(entries[0].intersectionRatio);
582
- observer.disconnect();
583
- });
584
- observer.observe(element);
585
- });
586
- return visibleRatio > 0;
587
- }''',
588
- self,
589
- )
590
-
591
-
592
- def computeQuadArea(quad: List[Dict]) -> float:
593
- area = 0
594
- for i in range(len(quad)):
595
- p1 = quad[i]
596
- p2 = quad[(i + 1) % len(quad)]
597
- area += (p1['x'] * p2['y'] - p2['x'] * p1['y']) / 2
598
- return abs(area)