osn-selenium 1.0.0__py3-none-any.whl → 1.1.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.
- osn_selenium/_base_models.py +41 -0
- osn_selenium/_cdp_import.py +253 -0
- osn_selenium/abstract/executors/cdp.py +63 -0
- osn_selenium/browsers_handler/models.py +1 -1
- osn_selenium/dev_tools/_decorators.py +19 -10
- osn_selenium/dev_tools/_typehints.py +5 -2
- osn_selenium/dev_tools/_wrappers.py +0 -3
- osn_selenium/dev_tools/domains/__init__.py +2 -2
- osn_selenium/dev_tools/domains/abstract.py +1 -1
- osn_selenium/dev_tools/domains/fetch.py +1 -1
- osn_selenium/dev_tools/domains_default/fetch.py +1 -1
- osn_selenium/dev_tools/filters.py +1 -1
- osn_selenium/dev_tools/logger/main.py +0 -12
- osn_selenium/dev_tools/logger/models.py +1 -1
- osn_selenium/dev_tools/logger/target.py +42 -22
- osn_selenium/dev_tools/manager/base.py +0 -23
- osn_selenium/dev_tools/manager/lifecycle.py +1 -1
- osn_selenium/dev_tools/models.py +1 -1
- osn_selenium/dev_tools/settings.py +5 -1
- osn_selenium/dev_tools/target/base.py +12 -23
- osn_selenium/dev_tools/target/lifecycle.py +6 -1
- osn_selenium/dev_tools/target/logging.py +10 -2
- osn_selenium/exceptions/dependencies.py +60 -0
- osn_selenium/executors/sync/cdp.py +100 -0
- osn_selenium/executors/trio_threads/cdp.py +104 -0
- osn_selenium/executors/trio_threads/javascript.py +1 -1
- osn_selenium/executors/unified/javascript.py +0 -4
- osn_selenium/flags/base.py +1 -9
- osn_selenium/flags/blink.py +0 -6
- osn_selenium/flags/models/base.py +4 -4
- osn_selenium/flags/models/blink.py +1 -1
- osn_selenium/flags/models/values.py +1 -1
- osn_selenium/instances/sync/action_chains/__init__.py +6 -0
- osn_selenium/instances/sync/shadow_root.py +30 -8
- osn_selenium/instances/trio_threads/action_chains/__init__.py +6 -0
- osn_selenium/instances/trio_threads/action_chains/base.py +1 -1
- osn_selenium/instances/trio_threads/alert.py +1 -1
- osn_selenium/instances/trio_threads/browser.py +1 -1
- osn_selenium/instances/trio_threads/browsing_context.py +1 -1
- osn_selenium/instances/trio_threads/dialog.py +1 -1
- osn_selenium/instances/trio_threads/fedcm.py +1 -1
- osn_selenium/instances/trio_threads/mobile.py +1 -1
- osn_selenium/instances/trio_threads/network.py +1 -1
- osn_selenium/instances/trio_threads/permissions.py +1 -1
- osn_selenium/instances/trio_threads/script.py +1 -1
- osn_selenium/instances/trio_threads/shadow_root.py +46 -15
- osn_selenium/instances/trio_threads/storage.py +1 -1
- osn_selenium/instances/trio_threads/switch_to.py +1 -1
- osn_selenium/instances/trio_threads/web_driver_wait.py +1 -1
- osn_selenium/instances/trio_threads/web_element.py +1 -1
- osn_selenium/instances/trio_threads/web_extension.py +1 -1
- osn_selenium/javascript/fingerprint/__init__.py +1 -1
- osn_selenium/javascript/fingerprint/registry/models.py +1 -1
- osn_selenium/javascript/fingerprint/spoof/core_rules.py +1 -1
- osn_selenium/javascript/fingerprint/spoof/noise.py +1 -1
- osn_selenium/javascript/models.py +1 -1
- osn_selenium/models.py +2 -47
- osn_selenium/trio_threads_mixin.py +159 -0
- osn_selenium/webdrivers/_args_helpers.py +2 -2
- osn_selenium/webdrivers/_bridges.py +2 -2
- osn_selenium/webdrivers/_executable_tables/models.py +1 -1
- osn_selenium/webdrivers/sync/blink/__init__.py +11 -1
- osn_selenium/webdrivers/sync/blink/base.py +13 -1
- osn_selenium/webdrivers/sync/chrome/__init__.py +36 -1
- osn_selenium/webdrivers/sync/chrome/base.py +13 -1
- osn_selenium/webdrivers/sync/core/__init__.py +10 -1
- osn_selenium/webdrivers/sync/core/base.py +16 -8
- osn_selenium/webdrivers/sync/edge/__init__.py +36 -1
- osn_selenium/webdrivers/sync/edge/base.py +13 -1
- osn_selenium/webdrivers/sync/yandex/__init__.py +36 -1
- osn_selenium/webdrivers/sync/yandex/base.py +13 -1
- osn_selenium/webdrivers/trio_threads/blink/__init__.py +11 -1
- osn_selenium/webdrivers/trio_threads/blink/base.py +11 -1
- osn_selenium/webdrivers/trio_threads/blink/casting.py +1 -1
- osn_selenium/webdrivers/trio_threads/blink/features.py +1 -1
- osn_selenium/webdrivers/trio_threads/blink/logging.py +1 -1
- osn_selenium/webdrivers/trio_threads/blink/network.py +1 -1
- osn_selenium/webdrivers/trio_threads/chrome/__init__.py +34 -1
- osn_selenium/webdrivers/trio_threads/chrome/base.py +14 -2
- osn_selenium/webdrivers/trio_threads/core/__init__.py +10 -4
- osn_selenium/webdrivers/trio_threads/core/actions.py +1 -1
- osn_selenium/webdrivers/trio_threads/core/auth.py +1 -1
- osn_selenium/webdrivers/trio_threads/core/base.py +15 -7
- osn_selenium/webdrivers/trio_threads/core/capture.py +1 -1
- osn_selenium/webdrivers/trio_threads/core/comonents.py +1 -1
- osn_selenium/webdrivers/trio_threads/core/devtools.py +1 -1
- osn_selenium/webdrivers/trio_threads/core/element.py +1 -1
- osn_selenium/webdrivers/trio_threads/core/file.py +2 -2
- osn_selenium/webdrivers/trio_threads/core/lifecycle.py +1 -1
- osn_selenium/webdrivers/trio_threads/core/navigation.py +1 -1
- osn_selenium/webdrivers/trio_threads/core/script.py +1 -1
- osn_selenium/webdrivers/trio_threads/core/settings.py +1 -1
- osn_selenium/webdrivers/trio_threads/core/storage.py +1 -1
- osn_selenium/webdrivers/trio_threads/core/timeouts.py +1 -1
- osn_selenium/webdrivers/trio_threads/core/window.py +3 -3
- osn_selenium/webdrivers/trio_threads/edge/__init__.py +34 -1
- osn_selenium/webdrivers/trio_threads/edge/base.py +14 -2
- osn_selenium/webdrivers/trio_threads/yandex/__init__.py +34 -1
- osn_selenium/webdrivers/trio_threads/yandex/base.py +14 -2
- osn_selenium/webdrivers/unified/blink/base.py +5 -1
- osn_selenium/webdrivers/unified/chrome/base.py +5 -1
- osn_selenium/webdrivers/unified/core/base.py +9 -2
- osn_selenium/webdrivers/unified/edge/base.py +5 -1
- osn_selenium/webdrivers/unified/yandex/base.py +5 -1
- {osn_selenium-1.0.0.dist-info → osn_selenium-1.1.0.dist-info}/METADATA +22 -9
- {osn_selenium-1.0.0.dist-info → osn_selenium-1.1.0.dist-info}/RECORD +108 -101
- {osn_selenium-1.0.0.dist-info → osn_selenium-1.1.0.dist-info}/WHEEL +1 -1
- {osn_selenium-1.0.0.dist-info → osn_selenium-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from pydantic import (
|
|
2
|
+
BaseModel,
|
|
3
|
+
ConfigDict
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
__all__ = ["DictModel", "ExtraDictModel"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ExtraDictModel(BaseModel):
|
|
11
|
+
"""
|
|
12
|
+
Base class for Pydantic models that allows extra fields.
|
|
13
|
+
|
|
14
|
+
This configuration allows the model to accept fields that are not
|
|
15
|
+
explicitly defined in the model's schema.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
model_config = ConfigDict(
|
|
19
|
+
populate_by_name=True,
|
|
20
|
+
extra="allow",
|
|
21
|
+
use_enum_values=True,
|
|
22
|
+
str_strip_whitespace=True,
|
|
23
|
+
validate_assignment=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DictModel(BaseModel):
|
|
28
|
+
"""
|
|
29
|
+
Base class for Pydantic models with a predefined configuration.
|
|
30
|
+
|
|
31
|
+
This configuration enforces strict validation rules such as forbidding extra
|
|
32
|
+
fields and stripping whitespace from string inputs.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
model_config = ConfigDict(
|
|
36
|
+
populate_by_name=True,
|
|
37
|
+
extra="forbid",
|
|
38
|
+
use_enum_values=True,
|
|
39
|
+
str_strip_whitespace=True,
|
|
40
|
+
validate_assignment=True,
|
|
41
|
+
)
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import sys
|
|
3
|
+
import pathlib
|
|
4
|
+
import importlib.util
|
|
5
|
+
import urllib.request
|
|
6
|
+
from types import ModuleType
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
from importlib.abc import MetaPathFinder
|
|
9
|
+
from importlib.machinery import ModuleSpec
|
|
10
|
+
from osn_selenium._typehints import PATH_TYPEHINT
|
|
11
|
+
from typing import (
|
|
12
|
+
Dict,
|
|
13
|
+
Mapping,
|
|
14
|
+
Optional,
|
|
15
|
+
Sequence,
|
|
16
|
+
Tuple,
|
|
17
|
+
Union
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = ["check_cdp_version_exists_on_github", "install_cdp_hook"]
|
|
22
|
+
|
|
23
|
+
_SELENIUM_CDP_PATTERN = re.compile(r"selenium\.webdriver\.common\.devtools\.v(\d+)(.*)")
|
|
24
|
+
_INTERNAL_CDP_PATTERN = re.compile(r"osn_selenium_cdp_v(\d+)(?!\.legacy)(.*)")
|
|
25
|
+
|
|
26
|
+
_GITHUB_VERSIONS_CACHE: Dict[str, Tuple[bool, datetime]] = {}
|
|
27
|
+
_CDP_PACKAGE_ERROR = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _build_cdp_package_error(version: int) -> Exception:
|
|
31
|
+
"""
|
|
32
|
+
Creates an instance of the CDPPackageError exception.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
version (int): The version of the CDP package.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Exception: An instance of the CDPPackageError.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
global _CDP_PACKAGE_ERROR
|
|
42
|
+
|
|
43
|
+
if _CDP_PACKAGE_ERROR is None:
|
|
44
|
+
from osn_selenium.exceptions.dependencies import CDPPackageError
|
|
45
|
+
_CDP_PACKAGE_ERROR = CDPPackageError
|
|
46
|
+
|
|
47
|
+
return _CDP_PACKAGE_ERROR(version=version)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _get_external_spec(fullname: str, root_path: pathlib.Path, submodule_part: str) -> Optional[ModuleSpec]:
|
|
51
|
+
"""
|
|
52
|
+
Creates a module spec for an external path.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
fullname (str): The full name of the module.
|
|
56
|
+
root_path (pathlib.Path): The root directory where the module is located.
|
|
57
|
+
submodule_part (str): The part of the name representing submodules.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Optional[ModuleSpec]: The module spec if found, otherwise None.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
if not submodule_part:
|
|
64
|
+
target_path = root_path / "__init__.py"
|
|
65
|
+
is_package = True
|
|
66
|
+
else:
|
|
67
|
+
rel_path = submodule_part.lstrip(".").replace(".", "/")
|
|
68
|
+
target_path = root_path / f"{rel_path}.py"
|
|
69
|
+
|
|
70
|
+
if not target_path.exists():
|
|
71
|
+
target_path = root_path / rel_path / "__init__.py"
|
|
72
|
+
is_package = True
|
|
73
|
+
else:
|
|
74
|
+
is_package = False
|
|
75
|
+
|
|
76
|
+
if target_path.exists():
|
|
77
|
+
spec = importlib.util.spec_from_file_location(
|
|
78
|
+
name=fullname,
|
|
79
|
+
location=str(target_path),
|
|
80
|
+
submodule_search_locations=[str(root_path)]
|
|
81
|
+
if is_package
|
|
82
|
+
else None
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return spec
|
|
86
|
+
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class _CdpMetaPathFinder(MetaPathFinder):
|
|
91
|
+
"""
|
|
92
|
+
A custom meta path finder to redirect Selenium CDP imports.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(
|
|
96
|
+
self,
|
|
97
|
+
cdp_paths: Optional[Mapping[int, PATH_TYPEHINT]] = None,
|
|
98
|
+
ignore_package_missing: bool = True
|
|
99
|
+
) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Initializes the finder with user-defined CDP paths.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
cdp_paths (Optional[Mapping[int, PATH_TYPEHINT]]): Mapping of versions to paths.
|
|
105
|
+
ignore_package_missing (bool): Whether to ignore missing package errors.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
self._user_cdp_paths = {k: pathlib.Path(v).resolve() for k, v in (cdp_paths or {}).items()}
|
|
109
|
+
|
|
110
|
+
self._ignore_package_missing = ignore_package_missing
|
|
111
|
+
|
|
112
|
+
def _find_spec_bypassing_self(self, fullname: str, path: Optional[Sequence[str]]) -> Optional[ModuleSpec]:
|
|
113
|
+
"""
|
|
114
|
+
Attempts to find the module specification using other finders in sys.meta_path.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
fullname (str): The full name of the module.
|
|
118
|
+
path (Optional[Sequence[str]]): The search path for the module.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Optional[ModuleSpec]: The module spec if found, otherwise None.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
for finder in sys.meta_path:
|
|
125
|
+
if finder is self:
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
if hasattr(finder, "find_spec"):
|
|
130
|
+
spec = finder.find_spec(fullname, path)
|
|
131
|
+
|
|
132
|
+
if spec:
|
|
133
|
+
return spec
|
|
134
|
+
except (Exception,):
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
def find_spec(
|
|
140
|
+
self,
|
|
141
|
+
fullname: str,
|
|
142
|
+
path: Optional[Sequence[str]],
|
|
143
|
+
target: Optional[ModuleType] = None,
|
|
144
|
+
) -> Optional[ModuleSpec]:
|
|
145
|
+
"""
|
|
146
|
+
Finds the specification for the requested module if it matches the CDP pattern.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
fullname (str): The full name of the module to find.
|
|
150
|
+
path (Optional[Sequence[str]]): The search path for the module.
|
|
151
|
+
target (Optional[ModuleType]): The module object if it is being reloaded.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Optional[ModuleSpec]: The module spec if found, otherwise None.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
selenium_match = _SELENIUM_CDP_PATTERN.match(fullname)
|
|
158
|
+
internal_match = _INTERNAL_CDP_PATTERN.match(fullname)
|
|
159
|
+
|
|
160
|
+
if not (selenium_match or internal_match):
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
match = selenium_match or internal_match
|
|
164
|
+
version_part = int(match.group(1))
|
|
165
|
+
submodule_part = match.group(2)
|
|
166
|
+
|
|
167
|
+
user_cdp_path = self._user_cdp_paths.get(version_part, None)
|
|
168
|
+
if user_cdp_path is not None:
|
|
169
|
+
root = (user_cdp_path / "legacy") if selenium_match else user_cdp_path
|
|
170
|
+
return _get_external_spec(fullname=fullname, root_path=root, submodule_part=submodule_part)
|
|
171
|
+
|
|
172
|
+
spec = None
|
|
173
|
+
|
|
174
|
+
if selenium_match:
|
|
175
|
+
target_name = f"osn_selenium_cdp_v{version_part}.legacy{submodule_part}"
|
|
176
|
+
spec = importlib.util.find_spec(target_name, path)
|
|
177
|
+
|
|
178
|
+
if internal_match:
|
|
179
|
+
target_name = f"osn_selenium_cdp_v{version_part}{submodule_part}"
|
|
180
|
+
spec = self._find_spec_bypassing_self(target_name, path)
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
if spec:
|
|
184
|
+
return importlib.util.spec_from_file_location(
|
|
185
|
+
name=fullname,
|
|
186
|
+
location=spec.origin,
|
|
187
|
+
submodule_search_locations=spec.submodule_search_locations,
|
|
188
|
+
)
|
|
189
|
+
except ModuleNotFoundError:
|
|
190
|
+
if self._ignore_package_missing:
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
raise _build_cdp_package_error(version=version_part)
|
|
194
|
+
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def install_cdp_hook(
|
|
199
|
+
cdp_paths: Optional[Mapping[int, PATH_TYPEHINT]] = None,
|
|
200
|
+
ignore_package_missing: bool = True,
|
|
201
|
+
) -> None:
|
|
202
|
+
"""
|
|
203
|
+
Installs the CDP meta path finder into sys.meta_path.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
cdp_paths (Optional[Mapping[int, PATH_TYPEHINT]]): Mapping of versions to custom local paths.
|
|
207
|
+
ignore_package_missing (bool): Whether to ignore missing package errors.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
for finder in sys.meta_path:
|
|
211
|
+
if isinstance(finder, _CdpMetaPathFinder):
|
|
212
|
+
finder._user_cdp_paths.update({k: pathlib.Path(v).resolve() for k, v in (cdp_paths or {}).items()})
|
|
213
|
+
finder._ignore_package_missing = ignore_package_missing
|
|
214
|
+
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
sys.meta_path.insert(
|
|
218
|
+
0,
|
|
219
|
+
_CdpMetaPathFinder(cdp_paths=cdp_paths, ignore_package_missing=ignore_package_missing)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def check_cdp_version_exists_on_github(version: Union[int, str]) -> bool:
|
|
224
|
+
"""
|
|
225
|
+
Checks if a specific CDP version exists in the remote repository.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
version (Union[int, str]): The version of package.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
bool: True if the version exists, False otherwise.
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
branch_name = version if isinstance(version, str) else f"v{version}"
|
|
235
|
+
|
|
236
|
+
cached_answer = _GITHUB_VERSIONS_CACHE.get(branch_name, None)
|
|
237
|
+
if cached_answer and datetime.now() - cached_answer[1] < timedelta(hours=12):
|
|
238
|
+
return cached_answer[0]
|
|
239
|
+
|
|
240
|
+
repo_url = f"https://api.github.com/repos/oddshellnick/osn-selenium-cdp/branches/{branch_name}"
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
request = urllib.request.Request(url=repo_url, headers={"User-Agent": "osn-selenium"})
|
|
244
|
+
request.get_method = lambda: "HEAD"
|
|
245
|
+
|
|
246
|
+
with urllib.request.urlopen(url=request, timeout=5.0) as response:
|
|
247
|
+
existing = response.status == 200
|
|
248
|
+
_GITHUB_VERSIONS_CACHE[branch_name] = (existing, datetime.now())
|
|
249
|
+
|
|
250
|
+
return existing
|
|
251
|
+
except (Exception,):
|
|
252
|
+
_GITHUB_VERSIONS_CACHE[branch_name] = (False, datetime.now())
|
|
253
|
+
return False
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
__all__ = ["AbstractCDPExecutor"]
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from osn_selenium_cdp_v140.executors.abstract import AbstractCDP140Executor
|
|
9
|
+
from osn_selenium_cdp_v141.executors.abstract import AbstractCDP141Executor
|
|
10
|
+
from osn_selenium_cdp_v142.executors.abstract import AbstractCDP142Executor
|
|
11
|
+
from osn_selenium_cdp_v143.executors.abstract import AbstractCDP143Executor
|
|
12
|
+
from osn_selenium_cdp_v144.executors.abstract import AbstractCDP144Executor
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AbstractCDPExecutor(ABC):
|
|
16
|
+
"""
|
|
17
|
+
Global abstract interface for accessing different versions of CDP.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def v140(self) -> "AbstractCDP140Executor":
|
|
23
|
+
"""
|
|
24
|
+
Access CDP version 140 interface.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def v141(self) -> "AbstractCDP141Executor":
|
|
32
|
+
"""
|
|
33
|
+
Access CDP version 141 interface.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def v142(self) -> "AbstractCDP142Executor":
|
|
41
|
+
"""
|
|
42
|
+
Access CDP version 142 interface.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def v143(self) -> "AbstractCDP143Executor":
|
|
50
|
+
"""
|
|
51
|
+
Access CDP version 143 interface.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def v144(self) -> "AbstractCDP144Executor":
|
|
59
|
+
"""
|
|
60
|
+
Access CDP version 144 interface.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
...
|
|
@@ -10,7 +10,8 @@ from typing import (
|
|
|
10
10
|
Callable,
|
|
11
11
|
ParamSpec,
|
|
12
12
|
TYPE_CHECKING,
|
|
13
|
-
TypeVar
|
|
13
|
+
TypeVar,
|
|
14
|
+
Union
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
|
|
@@ -19,6 +20,7 @@ __all__ = ["background_task_decorator", "log_on_error", "warn_if_active"]
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
20
21
|
from osn_selenium.dev_tools.manager import DevTools
|
|
21
22
|
from osn_selenium.dev_tools.target.base import BaseMixin
|
|
23
|
+
from osn_selenium.dev_tools.target import DevToolsTarget
|
|
22
24
|
from osn_selenium.dev_tools._typehints import DEVTOOLS_BACKGROUND_FUNCTION_TYPEHINT
|
|
23
25
|
|
|
24
26
|
_METHOD_INPUT = ParamSpec("_METHOD_INPUT")
|
|
@@ -74,7 +76,7 @@ def warn_if_active(func: _METHOD) -> _METHOD:
|
|
|
74
76
|
raise NotExpectedTypeError(expected_type=["coroutine function", "function"], received_instance=func)
|
|
75
77
|
|
|
76
78
|
|
|
77
|
-
def log_on_error(func:
|
|
79
|
+
def log_on_error(func: _METHOD) -> _METHOD:
|
|
78
80
|
"""
|
|
79
81
|
Decorator that logs any `BaseException` raised by the decorated async function.
|
|
80
82
|
|
|
@@ -115,26 +117,33 @@ def log_on_error(func: Callable[_METHOD_INPUT, _METHOD_OUTPUT]) -> Callable[_MET
|
|
|
115
117
|
|
|
116
118
|
def background_task_decorator(func: "DEVTOOLS_BACKGROUND_FUNCTION_TYPEHINT") -> "DEVTOOLS_BACKGROUND_FUNCTION_TYPEHINT":
|
|
117
119
|
"""
|
|
118
|
-
Decorator for
|
|
120
|
+
Decorator for target background tasks to manage their lifecycle.
|
|
119
121
|
|
|
120
122
|
This decorator wraps a target's background task function. It ensures that
|
|
121
123
|
`target.background_task_ended` event is set when the function completes,
|
|
122
124
|
allowing the `BaseTargetMixin` to track the task's termination.
|
|
123
125
|
|
|
124
126
|
Args:
|
|
125
|
-
func ("
|
|
126
|
-
|
|
127
|
+
func ("DEVTOOLS_BACKGROUND_FUNCTION_TYPEHINT"): The asynchronous background task function
|
|
128
|
+
to be wrapped. It should accept a target instance.
|
|
127
129
|
|
|
128
130
|
Returns:
|
|
129
|
-
"
|
|
131
|
+
"DEVTOOLS_BACKGROUND_FUNCTION_TYPEHINT": The wrapped function.
|
|
130
132
|
"""
|
|
131
133
|
|
|
132
134
|
@functools.wraps(func)
|
|
133
|
-
async def wrapper(target: "BaseMixin") -> Any:
|
|
134
|
-
|
|
135
|
+
async def wrapper(target: Union["BaseMixin", "DevToolsTarget"]) -> Any:
|
|
136
|
+
if not target.about_to_stop_event.is_set():
|
|
137
|
+
with trio.CancelScope() as cancel_scope:
|
|
138
|
+
target.cancel_scopes["background_task"] = cancel_scope
|
|
135
139
|
|
|
136
|
-
|
|
140
|
+
try:
|
|
141
|
+
target.background_task_ended = trio.Event()
|
|
137
142
|
|
|
138
|
-
|
|
143
|
+
await func(target)
|
|
144
|
+
|
|
145
|
+
target.background_task_ended.set()
|
|
146
|
+
finally:
|
|
147
|
+
target.cancel_scopes.pop("background_task")
|
|
139
148
|
|
|
140
149
|
return wrapper
|
|
@@ -3,7 +3,8 @@ from typing import (
|
|
|
3
3
|
Callable,
|
|
4
4
|
Coroutine,
|
|
5
5
|
Literal,
|
|
6
|
-
TYPE_CHECKING
|
|
6
|
+
TYPE_CHECKING,
|
|
7
|
+
Union
|
|
7
8
|
)
|
|
8
9
|
|
|
9
10
|
|
|
@@ -15,10 +16,12 @@ __all__ = [
|
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
17
18
|
from osn_selenium.dev_tools.target.base import BaseMixin as BaseTargetMixin
|
|
19
|
+
from osn_selenium.dev_tools.target import DevToolsTarget
|
|
18
20
|
else:
|
|
19
21
|
BaseTargetMixin = Any
|
|
22
|
+
DevToolsTarget = Any
|
|
20
23
|
|
|
21
|
-
DEVTOOLS_BACKGROUND_FUNCTION_TYPEHINT = Callable[[BaseTargetMixin], Coroutine[Any, Any, None]]
|
|
24
|
+
DEVTOOLS_BACKGROUND_FUNCTION_TYPEHINT = Callable[[Union[BaseTargetMixin, DevToolsTarget]], Coroutine[Any, Any, None]]
|
|
22
25
|
|
|
23
26
|
CDP_LOG_LEVELS_TYPEHINT = Literal[
|
|
24
27
|
"INFO",
|
|
@@ -46,9 +46,6 @@ def _yield_package_item_way(name: Union[str, Iterable[str]]) -> Generator[str, A
|
|
|
46
46
|
class DevToolsPackage:
|
|
47
47
|
"""
|
|
48
48
|
Wrapper around the DevTools module to safely retrieve nested attributes/classes.
|
|
49
|
-
|
|
50
|
-
Attributes:
|
|
51
|
-
_package (ModuleType): The root DevTools module package.
|
|
52
49
|
"""
|
|
53
50
|
|
|
54
51
|
def __init__(self, package: ModuleType):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from osn_selenium.
|
|
1
|
+
from osn_selenium._base_models import ExtraDictModel
|
|
2
2
|
from typing import (
|
|
3
3
|
Literal,
|
|
4
4
|
Optional,
|
|
@@ -10,7 +10,7 @@ from osn_selenium.dev_tools.domains.fetch import FetchSettings
|
|
|
10
10
|
__all__ = ["DomainsSettings", "domains_classes_type", "domains_type"]
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class DomainsSettings(
|
|
13
|
+
class DomainsSettings(ExtraDictModel):
|
|
14
14
|
"""
|
|
15
15
|
A dataclass container for configuration settings across different DevTools domains.
|
|
16
16
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import trio
|
|
2
2
|
from pydantic import Field
|
|
3
|
-
from osn_selenium.
|
|
3
|
+
from osn_selenium._base_models import DictModel
|
|
4
4
|
from osn_selenium.dev_tools._functions import execute_cdp_command
|
|
5
5
|
from osn_selenium.exceptions.devtools import (
|
|
6
6
|
CDPEndExceptions,
|
|
@@ -25,18 +25,6 @@ class MainLogger:
|
|
|
25
25
|
|
|
26
26
|
This logger is responsible for writing aggregated statistics about all active
|
|
27
27
|
logging channels and target types to a designated file.
|
|
28
|
-
|
|
29
|
-
Attributes:
|
|
30
|
-
_nursery_object (trio.Nursery): The Trio nursery for managing concurrent tasks.
|
|
31
|
-
_cdp_receive_channel (Optional[trio.MemoryReceiveChannel[CDPMainLogEntry]]):
|
|
32
|
-
The receive channel for CDP main log entries.
|
|
33
|
-
_fingerprint_receive_channel (Optional[trio.MemoryReceiveChannel[FingerprintMainLogEntry]]):
|
|
34
|
-
The receive channel for fingerprint main log entries.
|
|
35
|
-
_cdp_file_writing_stopped (Optional[trio.Event]): An event set when the CDP file writing task stops.
|
|
36
|
-
_fingerprint_file_writing_stopped (Optional[trio.Event]): An event set when the fingerprint file writing task stops.
|
|
37
|
-
_is_active (bool): Flag indicating if the main logger is active.
|
|
38
|
-
_cdp_file_path (Optional[Path]): The path to the CDP main log file.
|
|
39
|
-
_fingerprint_file_path (Optional[Path]): The path to the fingerprint main log file.
|
|
40
28
|
"""
|
|
41
29
|
|
|
42
30
|
def __init__(
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import trio
|
|
2
|
-
|
|
2
|
+
import pathlib
|
|
3
|
+
from typing import (
|
|
4
|
+
Callable,
|
|
5
|
+
Optional,
|
|
6
|
+
Tuple
|
|
7
|
+
)
|
|
3
8
|
from osn_selenium.dev_tools.models import TargetData
|
|
4
9
|
from osn_selenium.dev_tools.settings import LoggerSettings
|
|
5
10
|
from osn_selenium.exceptions.devtools import TrioEndExceptions
|
|
@@ -20,15 +25,6 @@ class TargetLogger:
|
|
|
20
25
|
|
|
21
26
|
Each `TargetLogger` instance is responsible for writing log entries
|
|
22
27
|
related to its associated `TargetData` to a dedicated file.
|
|
23
|
-
|
|
24
|
-
Attributes:
|
|
25
|
-
_target_data (TargetData): The data of the browser target this logger is associated with.
|
|
26
|
-
_nursery_object (trio.Nursery): The Trio nursery for managing concurrent tasks.
|
|
27
|
-
_cdp_log_level_filter (Optional[Callable[[Any], bool]]): Filter function for CDP log levels.
|
|
28
|
-
_cdp_target_type_filter (Optional[Callable[[Any], bool]]): Filter function for CDP target types.
|
|
29
|
-
_cdp_file_writing_stopped (Optional[trio.Event]): An event set when CDP file writing task stops.
|
|
30
|
-
_cdp_file_path (Optional[Path]): The path to the target-specific CDP log file.
|
|
31
|
-
_is_active (bool): Flag indicating if the target logger is active.
|
|
32
28
|
"""
|
|
33
29
|
|
|
34
30
|
def __init__(
|
|
@@ -59,12 +55,12 @@ class TargetLogger:
|
|
|
59
55
|
self._nursery_object = nursery_object
|
|
60
56
|
self._cdp_receive_channel = cdp_receive_channel
|
|
61
57
|
self._fingerprint_receive_channel = fingerprint_receive_channel
|
|
62
|
-
self._cdp_file_path = None
|
|
63
|
-
self._cdp_log_level_filter = None
|
|
64
|
-
self._cdp_target_type_filter = None
|
|
65
|
-
self._fingerprint_file_path = None
|
|
66
|
-
self._fingerprint_log_level_filter = None
|
|
67
|
-
self._fingerprint_target_type_filter = None
|
|
58
|
+
self._cdp_file_path: Optional[pathlib.Path] = None
|
|
59
|
+
self._cdp_log_level_filter: Optional[Callable[[str], bool]] = None
|
|
60
|
+
self._cdp_target_type_filter: Optional[Callable[[str], bool]] = None
|
|
61
|
+
self._fingerprint_file_path: Optional[pathlib.Path] = None
|
|
62
|
+
self._fingerprint_log_level_filter: Optional[Callable[[str], bool]] = None
|
|
63
|
+
self._fingerprint_target_type_filter: Optional[Callable[[str], bool]] = None
|
|
68
64
|
|
|
69
65
|
if logger_settings.dir_path is None:
|
|
70
66
|
if logger_settings.cdp_settings:
|
|
@@ -106,6 +102,32 @@ class TargetLogger:
|
|
|
106
102
|
self._fingerprint_file_writing_stopped: Optional[trio.Event] = None
|
|
107
103
|
self._is_active = False
|
|
108
104
|
|
|
105
|
+
def filter_cdp_log(self, log_entry: CDPTargetLogEntry) -> bool:
|
|
106
|
+
"""
|
|
107
|
+
Applies configured filters to a CDP log entry.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
log_entry (CDPTargetLogEntry): The log entry to check.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
bool: True if the log entry passes filters, False otherwise.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
return self._cdp_log_level_filter(log_entry.level) and self._cdp_target_type_filter(log_entry.target_data.type_)
|
|
117
|
+
|
|
118
|
+
def filter_fingerprint_log(self, log_entry: FingerprintTargetLogEntry) -> bool:
|
|
119
|
+
"""
|
|
120
|
+
Applies configured filters to a fingerprint log entry.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
log_entry (FingerprintTargetLogEntry): The log entry to check.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
bool: True if the log entry passes filters, False otherwise.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
return self._fingerprint_log_level_filter(log_entry.level) and self._fingerprint_category_filter(log_entry.api)
|
|
130
|
+
|
|
109
131
|
@property
|
|
110
132
|
def is_active(self) -> bool:
|
|
111
133
|
"""
|
|
@@ -153,9 +175,8 @@ class TargetLogger:
|
|
|
153
175
|
|
|
154
176
|
async with await trio.open_file(self._fingerprint_file_path, "a+", encoding="utf-8") as file:
|
|
155
177
|
async for log_entry in self._fingerprint_receive_channel:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
await file.flush()
|
|
178
|
+
await file.write(log_entry.model_dump_json(indent=4) + end_of_entry)
|
|
179
|
+
await file.flush()
|
|
159
180
|
except* TrioEndExceptions:
|
|
160
181
|
pass
|
|
161
182
|
except* BaseException as error:
|
|
@@ -179,9 +200,8 @@ class TargetLogger:
|
|
|
179
200
|
|
|
180
201
|
async with await trio.open_file(self._cdp_file_path, "a+", encoding="utf-8") as file:
|
|
181
202
|
async for log_entry in self._cdp_receive_channel:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
await file.flush()
|
|
203
|
+
await file.write(log_entry.model_dump_json(indent=4) + end_of_entry)
|
|
204
|
+
await file.flush()
|
|
185
205
|
except* TrioEndExceptions:
|
|
186
206
|
pass
|
|
187
207
|
except* BaseException as error:
|
|
@@ -41,31 +41,8 @@ class BaseMixin:
|
|
|
41
41
|
by using an asynchronous context manager.
|
|
42
42
|
|
|
43
43
|
Attributes:
|
|
44
|
-
_webdriver (CoreWebDriver): The parent WebDriver instance associated with this DevTools instance.
|
|
45
|
-
_new_targets_filter (Optional[List[Dict[str, Any]]]): Processed filters for new targets.
|
|
46
|
-
_new_targets_buffer_size (int): Buffer size for new target events.
|
|
47
|
-
_target_background_task (Optional[devtools_background_func_type]): Optional background task for targets.
|
|
48
|
-
_logger_settings (LoggerSettings): Logging configuration for the entire DevTools manager.
|
|
49
|
-
_bidi_connection (Optional[AbstractAsyncContextManager[BidiConnection, Any]]): Asynchronous context manager for the BiDi connection.
|
|
50
|
-
_bidi_connection_object (Optional[BidiConnection]): The BiDi connection object when active.
|
|
51
|
-
_nursery (Optional[AbstractAsyncContextManager[trio.Nursery, object]]): Asynchronous context manager for the Trio nursery.
|
|
52
|
-
_nursery_object (Optional[trio.Nursery]): The Trio nursery object when active, managing concurrent tasks.
|
|
53
|
-
_domains_settings (DomainsSettings): Settings for configuring DevTools domain handlers.
|
|
54
|
-
_handling_targets (Dict[str, DevToolsTarget]): Dictionary of target IDs currently being handled by event listeners.
|
|
55
44
|
targets_lock (trio.Lock): A lock used for synchronizing access to shared resources, like the List of handled targets.
|
|
56
45
|
exit_event (Optional[trio.Event]): Trio Event to signal exiting of DevTools event handling.
|
|
57
|
-
_is_active (bool): Flag indicating if the DevTools event handler is currently active.
|
|
58
|
-
_is_closing (bool): Flag indicating if the DevTools manager is in the process of closing.
|
|
59
|
-
_num_cdp_logs (int): Total count of all CDP log entries across all targets.
|
|
60
|
-
_num_fingerprint_logs (int): Total count of all Fingerprint log entries across all targets.
|
|
61
|
-
_cdp_targets_types_stats (Dict[str, CDPTargetTypeStats]): Statistics for each target type.
|
|
62
|
-
_cdp_log_level_stats (Dict[str, CDPLogLevelStats]): Overall statistics for each log level.
|
|
63
|
-
_main_logger_cdp_send_channel (Optional[trio.MemorySendChannel[CDPMainLogEntry]]): Send channel for the main logger.
|
|
64
|
-
_fingerprint_categories_stats (Dict[str, FingerprintAPIStats]): Statistics for each API category.
|
|
65
|
-
_fingerprint_log_level_stats (Dict[str, FingerprintLogLevelStats]): Overall statistics for each log level.
|
|
66
|
-
_main_logger_fingerprint_send_channel (Optional[trio.MemorySendChannel[FingerprintMainLogEntry]]): Send channel for the main logger.
|
|
67
|
-
_main_logger (Optional[MainLogger]): The main logger instance.
|
|
68
|
-
_fingerprint_injection_script (Optional[str]): Injection script for fingerprinting.
|
|
69
46
|
"""
|
|
70
47
|
|
|
71
48
|
def __init__(
|