361 lines
13 KiB
Python
361 lines
13 KiB
Python
# Copyright (c) Microsoft Corporation.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import re
|
|
import sys
|
|
from types import FunctionType
|
|
from typing import Any, Dict, List, Match, Optional, Union, cast, get_args, get_origin
|
|
from typing import get_type_hints as typing_get_type_hints
|
|
|
|
from playwright._impl._assertions import (
|
|
APIResponseAssertions,
|
|
LocatorAssertions,
|
|
PageAssertions,
|
|
)
|
|
from playwright._impl._browser import Browser
|
|
from playwright._impl._browser_context import BrowserContext
|
|
from playwright._impl._browser_type import BrowserType
|
|
from playwright._impl._cdp_session import CDPSession
|
|
from playwright._impl._clock import Clock
|
|
from playwright._impl._console_message import ConsoleMessage
|
|
from playwright._impl._debugger import Debugger
|
|
from playwright._impl._dialog import Dialog
|
|
from playwright._impl._disposable import Disposable
|
|
from playwright._impl._download import Download
|
|
from playwright._impl._element_handle import ElementHandle
|
|
from playwright._impl._fetch import APIRequest, APIRequestContext, APIResponse
|
|
from playwright._impl._file_chooser import FileChooser
|
|
from playwright._impl._frame import Frame
|
|
from playwright._impl._helper import Error, to_snake_case
|
|
from playwright._impl._input import Keyboard, Mouse, Touchscreen
|
|
from playwright._impl._js_handle import JSHandle, Serializable
|
|
from playwright._impl._locator import FrameLocator, Locator
|
|
from playwright._impl._network import (
|
|
Request,
|
|
Response,
|
|
Route,
|
|
WebSocket,
|
|
WebSocketRoute,
|
|
)
|
|
from playwright._impl._page import Page, Worker
|
|
from playwright._impl._playwright import Playwright
|
|
from playwright._impl._screencast import Screencast
|
|
from playwright._impl._selectors import Selectors
|
|
from playwright._impl._tracing import Tracing
|
|
from playwright._impl._video import Video
|
|
from playwright._impl._web_error import WebError
|
|
|
|
SYNC_API = False
|
|
|
|
|
|
def process_type(value: Any, param: bool = False) -> str:
|
|
value = str(value)
|
|
value = re.sub("pathlib._local.Path", "pathlib.Path", value)
|
|
value = re.sub(r"<class '([^']+)'>", r"\1", value)
|
|
value = re.sub(r"NoneType", "None", value)
|
|
value = re.sub(r"playwright\._impl\._api_structures.([\w]+)", r"\1", value)
|
|
value = re.sub(r"playwright\._impl\.[\w]+\.([\w]+)", r'"\1"', value)
|
|
value = re.sub(r"typing.Literal", "Literal", value)
|
|
if SYNC_API:
|
|
# Sync API does not accept awaitable callbacks; collapse
|
|
# Union[X, Awaitable[X]] (used for predicates the async API also
|
|
# accepts as `async def`) down to just X.
|
|
value = re.sub(
|
|
r"typing\.Union\[([^\[\],]+),\s*typing\.Awaitable\[\1\]\]",
|
|
r"\1",
|
|
value,
|
|
)
|
|
if param:
|
|
value = re.sub(r"^typing.Union\[([^,]+), None\]$", r"\1 = None", value)
|
|
value = re.sub(
|
|
r"typing.Union\[(Literal\[[^\]]+\]), None\]", r"\1 = None", value
|
|
)
|
|
value = re.sub(
|
|
r"^typing.Union\[(.+), None\]$", r"typing.Union[\1] = None", value
|
|
)
|
|
value = re.sub(
|
|
r"^typing.Optional\[(.+)\]$", r"typing.Optional[\1] = None", value
|
|
)
|
|
if not re.match(r"typing.Optional\[.*\] = None", value):
|
|
value = re.sub(r"(.*) = None", r"typing.Optional[\1] = None", value)
|
|
return value
|
|
|
|
|
|
positional_exceptions = [
|
|
r"abort\.errorCode",
|
|
r"accept\.promptText",
|
|
r"add_init_script\.script",
|
|
r"cookies\.urls",
|
|
r"dispatch_event\.eventInit",
|
|
r"eval.*\.arg",
|
|
r"expect_.*\.predicate",
|
|
r"evaluate_handle\.arg",
|
|
r"frame.*\.name",
|
|
r"register\.script",
|
|
r"select_option\.value",
|
|
r"send\.params",
|
|
r"set_geolocation\.geolocation",
|
|
r"wait_for_.*\.predicate",
|
|
r"wait_for_load_state\.state",
|
|
r"unroute\.handler",
|
|
]
|
|
|
|
|
|
def is_positional_exception(key: str) -> bool:
|
|
for pattern in positional_exceptions:
|
|
if re.match(pattern, key):
|
|
return True
|
|
return False
|
|
|
|
|
|
def signature(func: FunctionType, indent: int) -> str:
|
|
hints = get_type_hints(func, globals())
|
|
tokens = ["self"]
|
|
split = ",\n" + " " * indent
|
|
|
|
saw_optional = False
|
|
for [name, value] in hints.items():
|
|
if name == "return":
|
|
continue
|
|
positional_exception = is_positional_exception(f"{func.__name__}.{name}")
|
|
if saw_optional and positional_exception:
|
|
raise Exception(
|
|
"Positional exception is not first in the list "
|
|
+ f"{func.__name__}.{name}"
|
|
)
|
|
processed = process_type(value, True)
|
|
if name == "timeout":
|
|
processed = re.sub(
|
|
r"\bfloat\b",
|
|
"typing.Union[float, datetime.timedelta]",
|
|
processed,
|
|
)
|
|
if (
|
|
not positional_exception
|
|
and not saw_optional
|
|
and processed.startswith("typing.Optional")
|
|
):
|
|
saw_optional = True
|
|
tokens.append("*")
|
|
tokens.append(f"{to_snake_case(name)}: {processed}")
|
|
return split.join(tokens)
|
|
|
|
|
|
def arguments(func: FunctionType, indent: int) -> str:
|
|
hints = get_type_hints(func, globals())
|
|
tokens = []
|
|
split = ",\n" + " " * indent
|
|
for [name, value] in hints.items():
|
|
value_str = str(value)
|
|
if name == "return":
|
|
continue
|
|
assert (
|
|
"_" not in name
|
|
), f"Underscore in impl classes is not allowed, use camel case, func={func}, name={name}"
|
|
if "Callable" in value_str:
|
|
tokens.append(f"{name}=self._wrap_handler({to_snake_case(name)})")
|
|
elif name == "timeout" and "float" in value_str:
|
|
tokens.append(f"{name}=to_milliseconds({to_snake_case(name)})")
|
|
elif (
|
|
"typing.Any" in value_str
|
|
or "typing.Dict" in value_str
|
|
or "typing.Sequence" in value_str
|
|
or "Handle" in value_str
|
|
):
|
|
tokens.append(f"{name}=mapping.to_impl({to_snake_case(name)})")
|
|
elif (
|
|
re.match(r"<class 'playwright\._impl\.[\w]+\.[\w]+", value_str)
|
|
and "_api_structures" not in value_str
|
|
):
|
|
tokens.append(f"{name}={to_snake_case(name)}._impl_obj")
|
|
elif (
|
|
re.match(r"typing\.Optional\[playwright\._impl\.[\w]+\.[\w]+\]", value_str)
|
|
and "_api_structures" not in value_str
|
|
):
|
|
tokens.append(
|
|
f"{name}={to_snake_case(name)}._impl_obj if {to_snake_case(name)} else None"
|
|
)
|
|
else:
|
|
tokens.append(f"{name}={to_snake_case(name)}")
|
|
return split.join(tokens)
|
|
|
|
|
|
def return_type(func: FunctionType) -> str:
|
|
value = get_type_hints(func, globals())["return"]
|
|
return process_type(value)
|
|
|
|
|
|
def short_name(t: Any) -> str:
|
|
match = cast(
|
|
Match[str], re.compile(r"playwright\._impl\.[^.]+\.([^']+)").search(str(t))
|
|
)
|
|
return match.group(1)
|
|
|
|
|
|
def return_value(value: Any) -> List[str]:
|
|
value_str = str(value)
|
|
if "playwright" not in value_str:
|
|
return ["mapping.from_maybe_impl(", ")"]
|
|
if (
|
|
get_origin(value) == Union
|
|
and len(get_args(value)) == 2
|
|
and str(get_args(value)[1]) == "<class 'NoneType'>"
|
|
):
|
|
return ["mapping.from_impl_nullable(", ")"]
|
|
if str(get_origin(value)) in [
|
|
"<class 'list'>",
|
|
"<class 'collections.abc.Sequence'>",
|
|
]:
|
|
return ["mapping.from_impl_list(", ")"]
|
|
if str(get_origin(value)) == "<class 'dict'>":
|
|
return ["mapping.from_impl_dict(", ")"]
|
|
return ["mapping.from_impl(", ")"]
|
|
|
|
|
|
header = """
|
|
# Copyright (c) Microsoft Corporation.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
|
|
import typing
|
|
import pathlib
|
|
import datetime
|
|
|
|
from typing import Literal
|
|
|
|
|
|
from playwright._impl._api_structures import Cookie, SetCookieParam, FloatRect, FilePayload, Geolocation, HttpCredentials, PdfMargins, Position, ProxySettings, ResourceTiming, SourceLocation, StorageState, ClientCertificate, ViewportSize, RemoteAddr, SecurityDetails, RequestSizes, NameValue, TracingGroupLocation, DebuggerLocation, DebuggerPausedDetails, ScreencastFrame, BrowserBindResult, WebErrorLocation, DropPayload
|
|
from playwright._impl._browser import Browser as BrowserImpl
|
|
from playwright._impl._browser_context import BrowserContext as BrowserContextImpl
|
|
from playwright._impl._browser_type import BrowserType as BrowserTypeImpl
|
|
from playwright._impl._clock import Clock as ClockImpl
|
|
from playwright._impl._cdp_session import CDPSession as CDPSessionImpl
|
|
from playwright._impl._console_message import ConsoleMessage as ConsoleMessageImpl
|
|
from playwright._impl._debugger import Debugger as DebuggerImpl
|
|
from playwright._impl._dialog import Dialog as DialogImpl
|
|
from playwright._impl._disposable import Disposable as DisposableImpl
|
|
from playwright._impl._download import Download as DownloadImpl
|
|
from playwright._impl._element_handle import ElementHandle as ElementHandleImpl
|
|
from playwright._impl._file_chooser import FileChooser as FileChooserImpl
|
|
from playwright._impl._frame import Frame as FrameImpl
|
|
from playwright._impl._input import Keyboard as KeyboardImpl, Mouse as MouseImpl, Touchscreen as TouchscreenImpl
|
|
from playwright._impl._js_handle import JSHandle as JSHandleImpl
|
|
from playwright._impl._network import Request as RequestImpl, Response as ResponseImpl, Route as RouteImpl, WebSocket as WebSocketImpl, WebSocketRoute as WebSocketRouteImpl
|
|
from playwright._impl._page import Page as PageImpl, Worker as WorkerImpl
|
|
from playwright._impl._web_error import WebError as WebErrorImpl
|
|
from playwright._impl._playwright import Playwright as PlaywrightImpl
|
|
from playwright._impl._selectors import Selectors as SelectorsImpl
|
|
from playwright._impl._screencast import Screencast as ScreencastImpl
|
|
from playwright._impl._video import Video as VideoImpl
|
|
from playwright._impl._tracing import Tracing as TracingImpl
|
|
from playwright._impl._locator import Locator as LocatorImpl, FrameLocator as FrameLocatorImpl
|
|
from playwright._impl._errors import Error
|
|
from playwright._impl._form_data import FormData
|
|
from playwright._impl._helper import to_milliseconds
|
|
from playwright._impl._fetch import APIRequest as APIRequestImpl, APIResponse as APIResponseImpl, APIRequestContext as APIRequestContextImpl
|
|
from playwright._impl._assertions import PageAssertions as PageAssertionsImpl, LocatorAssertions as LocatorAssertionsImpl, APIResponseAssertions as APIResponseAssertionsImpl
|
|
"""
|
|
|
|
|
|
generated_types = [
|
|
Request,
|
|
Response,
|
|
Route,
|
|
WebSocket,
|
|
WebSocketRoute,
|
|
Keyboard,
|
|
Mouse,
|
|
Touchscreen,
|
|
JSHandle,
|
|
ElementHandle,
|
|
FileChooser,
|
|
Frame,
|
|
FrameLocator,
|
|
Worker,
|
|
Selectors,
|
|
Clock,
|
|
ConsoleMessage,
|
|
Debugger,
|
|
Dialog,
|
|
Download,
|
|
Screencast,
|
|
Video,
|
|
Page,
|
|
WebError,
|
|
BrowserContext,
|
|
CDPSession,
|
|
Browser,
|
|
BrowserType,
|
|
Playwright,
|
|
Tracing,
|
|
Locator,
|
|
APIResponse,
|
|
APIRequestContext,
|
|
APIRequest,
|
|
PageAssertions,
|
|
LocatorAssertions,
|
|
APIResponseAssertions,
|
|
Disposable,
|
|
]
|
|
|
|
all_types = generated_types + [
|
|
Error,
|
|
]
|
|
|
|
api_globals = globals()
|
|
assert Serializable
|
|
|
|
|
|
# Python 3.11+ does not treat default args with None as Optional anymore, this wrapper will still wrap them.
|
|
# https://github.com/python/cpython/issues/90353
|
|
def get_type_hints(func: Any, globalns: Any) -> Dict[str, Any]:
|
|
original_value = typing_get_type_hints(func, globalns)
|
|
if sys.version_info < (3, 11):
|
|
return original_value
|
|
for key, value in _get_defaults(func).items():
|
|
if value is None and original_value[key] is not Optional:
|
|
original_value[key] = Optional[original_value[key]]
|
|
return original_value
|
|
|
|
|
|
def _get_defaults(func: Any) -> Dict[str, Any]:
|
|
"""Internal helper to extract the default arguments, by name."""
|
|
try:
|
|
code = func.__code__
|
|
except AttributeError:
|
|
# Some built-in functions don't have __code__, __defaults__, etc.
|
|
return {}
|
|
pos_count = code.co_argcount
|
|
arg_names = code.co_varnames
|
|
arg_names = arg_names[:pos_count]
|
|
defaults = func.__defaults__ or ()
|
|
kwdefaults = func.__kwdefaults__
|
|
res = dict(kwdefaults) if kwdefaults else {}
|
|
pos_offset = pos_count - len(defaults)
|
|
for name, value in zip(arg_names[pos_offset:], defaults):
|
|
assert name not in res
|
|
res[name] = value
|
|
return res
|