rustwright 0.1.0__cp38-abi3-win_amd64.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.
- rustwright/__init__.py +56 -0
- rustwright/__main__.py +5 -0
- rustwright/_backend.py +58 -0
- rustwright/_compat/__init__.py +83 -0
- rustwright/_compat/cloakbrowser/__init__.py +92 -0
- rustwright/_compat/patchright/__init__.py +2 -0
- rustwright/_compat/patchright/__main__.py +5 -0
- rustwright/_compat/patchright/_impl/__init__.py +1 -0
- rustwright/_compat/patchright/_impl/_errors.py +4 -0
- rustwright/_compat/patchright/async_api.py +2 -0
- rustwright/_compat/patchright/pytest_plugin.py +2 -0
- rustwright/_compat/patchright/sync_api.py +2 -0
- rustwright/_compat/playwright/__init__.py +2 -0
- rustwright/_compat/playwright/__main__.py +5 -0
- rustwright/_compat/playwright/_impl/__init__.py +2 -0
- rustwright/_compat/playwright/_impl/_errors.py +4 -0
- rustwright/_compat/playwright/async_api.py +2 -0
- rustwright/_compat/playwright/pytest_plugin.py +1 -0
- rustwright/_compat/playwright/sync_api.py +2 -0
- rustwright/_compat/pytest_playwright/__init__.py +3 -0
- rustwright/_compat/pytest_playwright/py.typed +1 -0
- rustwright/_compat/pytest_playwright/pytest_playwright.py +57 -0
- rustwright/_devices.py +1034 -0
- rustwright/_rustwright.pyd +0 -0
- rustwright/async_api.py +5745 -0
- rustwright/cli.py +1538 -0
- rustwright/pytest_plugin.py +409 -0
- rustwright/sync_api.py +29198 -0
- rustwright-0.1.0.dist-info/METADATA +239 -0
- rustwright-0.1.0.dist-info/RECORD +33 -0
- rustwright-0.1.0.dist-info/WHEEL +4 -0
- rustwright-0.1.0.dist-info/entry_points.txt +4 -0
- rustwright-0.1.0.dist-info/licenses/LICENSE +21 -0
rustwright/__init__.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Rust-backed browser automation with a Playwright-shaped Python API."""
|
|
2
|
+
|
|
3
|
+
from ._compat import disable_playwright_compat, enable_playwright_compat
|
|
4
|
+
from .sync_api import (
|
|
5
|
+
Browser,
|
|
6
|
+
BrowserContext,
|
|
7
|
+
BrowserType,
|
|
8
|
+
BackendMarker,
|
|
9
|
+
ConsoleMessage,
|
|
10
|
+
ElementHandle,
|
|
11
|
+
Error,
|
|
12
|
+
Download,
|
|
13
|
+
FileChooser,
|
|
14
|
+
Frame,
|
|
15
|
+
FrameLocator,
|
|
16
|
+
JSHandle,
|
|
17
|
+
Locator,
|
|
18
|
+
Page,
|
|
19
|
+
Playwright,
|
|
20
|
+
Response,
|
|
21
|
+
TargetClosedError,
|
|
22
|
+
TimeoutError,
|
|
23
|
+
Video,
|
|
24
|
+
backend_marker,
|
|
25
|
+
expect,
|
|
26
|
+
sync_playwright,
|
|
27
|
+
)
|
|
28
|
+
from .async_api import async_playwright
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"Browser",
|
|
32
|
+
"BrowserContext",
|
|
33
|
+
"BrowserType",
|
|
34
|
+
"BackendMarker",
|
|
35
|
+
"ConsoleMessage",
|
|
36
|
+
"Download",
|
|
37
|
+
"ElementHandle",
|
|
38
|
+
"Error",
|
|
39
|
+
"FileChooser",
|
|
40
|
+
"Frame",
|
|
41
|
+
"FrameLocator",
|
|
42
|
+
"JSHandle",
|
|
43
|
+
"Locator",
|
|
44
|
+
"Page",
|
|
45
|
+
"Playwright",
|
|
46
|
+
"Response",
|
|
47
|
+
"TargetClosedError",
|
|
48
|
+
"TimeoutError",
|
|
49
|
+
"Video",
|
|
50
|
+
"backend_marker",
|
|
51
|
+
"disable_playwright_compat",
|
|
52
|
+
"enable_playwright_compat",
|
|
53
|
+
"expect",
|
|
54
|
+
"async_playwright",
|
|
55
|
+
"sync_playwright",
|
|
56
|
+
]
|
rustwright/__main__.py
ADDED
rustwright/_backend.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from importlib import metadata
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional, TypedDict
|
|
6
|
+
|
|
7
|
+
from . import _rustwright
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BackendMarker(TypedDict):
|
|
11
|
+
implementation: str
|
|
12
|
+
package: str
|
|
13
|
+
version: str
|
|
14
|
+
api_package: str
|
|
15
|
+
api_module: str
|
|
16
|
+
replacement_backend: bool
|
|
17
|
+
runtime: str
|
|
18
|
+
runtime_module: str
|
|
19
|
+
runtime_module_file: Optional[str]
|
|
20
|
+
transport: str
|
|
21
|
+
transport_protocol: str
|
|
22
|
+
cdp_first: bool
|
|
23
|
+
python_playwright_driver: bool
|
|
24
|
+
playwright_driver: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _version() -> str:
|
|
28
|
+
try:
|
|
29
|
+
return metadata.version("rustwright")
|
|
30
|
+
except metadata.PackageNotFoundError:
|
|
31
|
+
return "0.1.0+local"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def backend_marker(api_module: str | None = None) -> BackendMarker:
|
|
35
|
+
"""Return JSON-safe evidence that this API is backed by Rustwright CDP."""
|
|
36
|
+
|
|
37
|
+
module_name = api_module or "rustwright"
|
|
38
|
+
api_package = module_name.split(".", 1)[0]
|
|
39
|
+
runtime_file = getattr(_rustwright, "__file__", None)
|
|
40
|
+
return {
|
|
41
|
+
"implementation": "rustwright",
|
|
42
|
+
"package": "rustwright",
|
|
43
|
+
"version": _version(),
|
|
44
|
+
"api_package": api_package,
|
|
45
|
+
"api_module": module_name,
|
|
46
|
+
"replacement_backend": True,
|
|
47
|
+
"runtime": "rust-pyo3-extension",
|
|
48
|
+
"runtime_module": _rustwright.__name__,
|
|
49
|
+
"runtime_module_file": str(Path(runtime_file).resolve()) if runtime_file else None,
|
|
50
|
+
"transport": "raw-cdp",
|
|
51
|
+
"transport_protocol": "Chrome DevTools Protocol",
|
|
52
|
+
"cdp_first": True,
|
|
53
|
+
"python_playwright_driver": False,
|
|
54
|
+
"playwright_driver": "none",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
__all__ = ["BackendMarker", "backend_marker"]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Explicit opt-in Playwright/Patchright/Cloakbrowser import compatibility."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import sys
|
|
7
|
+
from types import ModuleType
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_ALIASES = (
|
|
12
|
+
("playwright", "rustwright._compat.playwright"),
|
|
13
|
+
("playwright.__main__", "rustwright._compat.playwright.__main__"),
|
|
14
|
+
("playwright._impl", "rustwright._compat.playwright._impl"),
|
|
15
|
+
("playwright._impl._errors", "rustwright._compat.playwright._impl._errors"),
|
|
16
|
+
("playwright.async_api", "rustwright._compat.playwright.async_api"),
|
|
17
|
+
("playwright.pytest_plugin", "rustwright._compat.playwright.pytest_plugin"),
|
|
18
|
+
("playwright.sync_api", "rustwright._compat.playwright.sync_api"),
|
|
19
|
+
("patchright", "rustwright._compat.patchright"),
|
|
20
|
+
("patchright.__main__", "rustwright._compat.patchright.__main__"),
|
|
21
|
+
("patchright._impl", "rustwright._compat.patchright._impl"),
|
|
22
|
+
("patchright._impl._errors", "rustwright._compat.patchright._impl._errors"),
|
|
23
|
+
("patchright.async_api", "rustwright._compat.patchright.async_api"),
|
|
24
|
+
("patchright.pytest_plugin", "rustwright._compat.patchright.pytest_plugin"),
|
|
25
|
+
("patchright.sync_api", "rustwright._compat.patchright.sync_api"),
|
|
26
|
+
("cloakbrowser", "rustwright._compat.cloakbrowser"),
|
|
27
|
+
("pytest_playwright", "rustwright._compat.pytest_playwright"),
|
|
28
|
+
("pytest_playwright.pytest_playwright", "rustwright._compat.pytest_playwright.pytest_playwright"),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
_PREVIOUS_MODULES: dict[str, Optional[ModuleType]] = {}
|
|
32
|
+
_ENABLED = False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _set_parent_attribute(module_name: str, module: ModuleType) -> None:
|
|
36
|
+
parent_name, _, child_name = module_name.rpartition(".")
|
|
37
|
+
if not parent_name:
|
|
38
|
+
return
|
|
39
|
+
parent = sys.modules.get(parent_name)
|
|
40
|
+
if parent is not None:
|
|
41
|
+
setattr(parent, child_name, module)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def enable_playwright_compat() -> None:
|
|
45
|
+
"""Enable legacy Playwright-compatible import names for this Python process.
|
|
46
|
+
|
|
47
|
+
After this is called, subsequent imports such as ``playwright.sync_api`` or
|
|
48
|
+
``patchright.async_api`` resolve to Rustwright's compatibility shims.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
global _ENABLED
|
|
52
|
+
if _ENABLED:
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
loaded_modules = [(alias_name, importlib.import_module(target_name)) for alias_name, target_name in _ALIASES]
|
|
56
|
+
for alias_name, module in loaded_modules:
|
|
57
|
+
_PREVIOUS_MODULES[alias_name] = sys.modules.get(alias_name)
|
|
58
|
+
sys.modules[alias_name] = module
|
|
59
|
+
_set_parent_attribute(alias_name, module)
|
|
60
|
+
|
|
61
|
+
_ENABLED = True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def disable_playwright_compat() -> None:
|
|
65
|
+
"""Undo aliases installed by :func:`enable_playwright_compat`."""
|
|
66
|
+
|
|
67
|
+
global _ENABLED
|
|
68
|
+
if not _ENABLED:
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
for alias_name, _target_name in _ALIASES:
|
|
72
|
+
previous = _PREVIOUS_MODULES.get(alias_name)
|
|
73
|
+
if previous is None:
|
|
74
|
+
sys.modules.pop(alias_name, None)
|
|
75
|
+
else:
|
|
76
|
+
sys.modules[alias_name] = previous
|
|
77
|
+
_set_parent_attribute(alias_name, previous)
|
|
78
|
+
|
|
79
|
+
_PREVIOUS_MODULES.clear()
|
|
80
|
+
_ENABLED = False
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
__all__ = ["disable_playwright_compat", "enable_playwright_compat"]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from rustwright.async_api import async_playwright
|
|
8
|
+
from rustwright.sync_api import sync_playwright
|
|
9
|
+
|
|
10
|
+
_VALID_BACKENDS = {"playwright", "patchright"}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _resolve_backend(backend: Any = None) -> str:
|
|
14
|
+
value = backend if backend is not None else os.environ.get("CLOAKBROWSER_BACKEND", "playwright")
|
|
15
|
+
if not isinstance(value, str):
|
|
16
|
+
raise ValueError(f"Invalid cloakbrowser backend type: {type(value).__name__}")
|
|
17
|
+
normalized = value.strip().lower()
|
|
18
|
+
if normalized not in _VALID_BACKENDS:
|
|
19
|
+
raise ValueError(f"Invalid cloakbrowser backend: {value!r}. Must be one of {sorted(_VALID_BACKENDS)}")
|
|
20
|
+
return normalized
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _normalize_proxy(proxy: Any) -> Any:
|
|
24
|
+
if isinstance(proxy, str):
|
|
25
|
+
return {"server": proxy}
|
|
26
|
+
return proxy
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _normalize_common_options(options: dict[str, Any], *, persistent: bool) -> dict[str, Any]:
|
|
30
|
+
normalized = dict(options)
|
|
31
|
+
_resolve_backend(normalized.pop("backend", None))
|
|
32
|
+
normalized.pop("geoip", None)
|
|
33
|
+
normalized.pop("humanize", None)
|
|
34
|
+
|
|
35
|
+
if "proxy" in normalized:
|
|
36
|
+
normalized["proxy"] = _normalize_proxy(normalized["proxy"])
|
|
37
|
+
|
|
38
|
+
if persistent and "timezone" in normalized and "timezone_id" not in normalized:
|
|
39
|
+
normalized["timezone_id"] = normalized.pop("timezone")
|
|
40
|
+
else:
|
|
41
|
+
normalized.pop("timezone", None)
|
|
42
|
+
|
|
43
|
+
return normalized
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def ensure_binary(*, force: bool = False, **_: Any) -> str:
|
|
47
|
+
from rustwright.cli import _chromium_executable_path, _download_chromium
|
|
48
|
+
|
|
49
|
+
executable = _chromium_executable_path()
|
|
50
|
+
if executable and not force:
|
|
51
|
+
return executable
|
|
52
|
+
result = _download_chromium(force=force, dry_run=False)
|
|
53
|
+
return str(result["executable"])
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def launch(**kwargs: Any) -> Any:
|
|
57
|
+
manager = sync_playwright()
|
|
58
|
+
playwright = manager.start()
|
|
59
|
+
return playwright.chromium.launch(**_normalize_common_options(kwargs, persistent=False))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def launch_async(**kwargs: Any) -> Any:
|
|
63
|
+
manager = async_playwright()
|
|
64
|
+
playwright = await manager.start()
|
|
65
|
+
return await playwright.chromium.launch(**_normalize_common_options(kwargs, persistent=False))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def launch_persistent_context(user_data_dir: str | Path, **kwargs: Any) -> Any:
|
|
69
|
+
manager = sync_playwright()
|
|
70
|
+
playwright = manager.start()
|
|
71
|
+
return playwright.chromium.launch_persistent_context(
|
|
72
|
+
user_data_dir,
|
|
73
|
+
**_normalize_common_options(kwargs, persistent=True),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def launch_persistent_context_async(user_data_dir: str | Path, **kwargs: Any) -> Any:
|
|
78
|
+
manager = async_playwright()
|
|
79
|
+
playwright = await manager.start()
|
|
80
|
+
return await playwright.chromium.launch_persistent_context(
|
|
81
|
+
user_data_dir,
|
|
82
|
+
**_normalize_common_options(kwargs, persistent=True),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
__all__ = [
|
|
87
|
+
"ensure_binary",
|
|
88
|
+
"launch",
|
|
89
|
+
"launch_async",
|
|
90
|
+
"launch_persistent_context",
|
|
91
|
+
"launch_persistent_context_async",
|
|
92
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from rustwright.pytest_plugin import * # noqa: F401,F403
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Dict, Literal, Optional, Pattern, Protocol, Sequence, Union
|
|
5
|
+
|
|
6
|
+
from rustwright.pytest_plugin import * # noqa: F401,F403
|
|
7
|
+
from rustwright.sync_api import (
|
|
8
|
+
BrowserContext,
|
|
9
|
+
Geolocation,
|
|
10
|
+
HttpCredentials,
|
|
11
|
+
ProxySettings,
|
|
12
|
+
StorageState,
|
|
13
|
+
ViewportSize,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CreateContextCallback(Protocol):
|
|
18
|
+
def __call__(
|
|
19
|
+
self,
|
|
20
|
+
viewport: Optional[ViewportSize] = None,
|
|
21
|
+
screen: Optional[ViewportSize] = None,
|
|
22
|
+
no_viewport: Optional[bool] = None,
|
|
23
|
+
ignore_https_errors: Optional[bool] = None,
|
|
24
|
+
java_script_enabled: Optional[bool] = None,
|
|
25
|
+
bypass_csp: Optional[bool] = None,
|
|
26
|
+
user_agent: Optional[str] = None,
|
|
27
|
+
locale: Optional[str] = None,
|
|
28
|
+
timezone_id: Optional[str] = None,
|
|
29
|
+
geolocation: Optional[Geolocation] = None,
|
|
30
|
+
permissions: Optional[Sequence[str]] = None,
|
|
31
|
+
extra_http_headers: Optional[Dict[str, str]] = None,
|
|
32
|
+
offline: Optional[bool] = None,
|
|
33
|
+
http_credentials: Optional[HttpCredentials] = None,
|
|
34
|
+
device_scale_factor: Optional[float] = None,
|
|
35
|
+
is_mobile: Optional[bool] = None,
|
|
36
|
+
has_touch: Optional[bool] = None,
|
|
37
|
+
color_scheme: Optional[Literal["dark", "light", "no-preference", "no-override", "null"]] = None,
|
|
38
|
+
reduced_motion: Optional[Literal["reduce", "no-preference", "no-override", "null"]] = None,
|
|
39
|
+
forced_colors: Optional[Literal["active", "none", "no-override", "null"]] = None,
|
|
40
|
+
contrast: Optional[Literal["no-preference", "more", "no-override"]] = None,
|
|
41
|
+
accept_downloads: Optional[bool] = None,
|
|
42
|
+
default_browser_type: Optional[str] = None,
|
|
43
|
+
proxy: Optional[ProxySettings] = None,
|
|
44
|
+
record_har_path: Optional[Union[str, Path]] = None,
|
|
45
|
+
record_har_omit_content: Optional[bool] = None,
|
|
46
|
+
record_video_dir: Optional[Union[str, Path]] = None,
|
|
47
|
+
record_video_size: Optional[ViewportSize] = None,
|
|
48
|
+
storage_state: Optional[Union[StorageState, str, Path]] = None,
|
|
49
|
+
base_url: Optional[str] = None,
|
|
50
|
+
strict_selectors: Optional[bool] = None,
|
|
51
|
+
service_workers: Optional[Literal["allow", "block"]] = None,
|
|
52
|
+
record_har_url_filter: Optional[Union[str, Pattern[str]]] = None,
|
|
53
|
+
record_har_mode: Optional[Literal["full", "minimal"]] = None,
|
|
54
|
+
record_har_content: Optional[Literal["attach", "embed", "omit"]] = None,
|
|
55
|
+
client_certificates: Optional[list[Any]] = None,
|
|
56
|
+
) -> BrowserContext:
|
|
57
|
+
...
|