def _patch_case(self) -> None: self.classes = {} for clazz in self.api: members = {} self.classes[clazz["name"]] = clazz for member in clazz["members"]: if member["kind"] == "event": continue method_name = member["name"] new_name = to_snake_case(method_name) alias = (member["langs"].get("aliases").get("python") if member["langs"].get("aliases") else None) if alias: new_name = alias members[new_name] = member member["name"] = new_name if "args" in member: args = {} for arg in member["args"]: arg_name = arg["name"] new_name = to_snake_case(arg_name) expand_type = None expand_as_optional = False if arg_name == "options": expand_type = arg["type"] expand_as_optional = True if (method_name == "setViewportSize" and arg_name == "viewportSize"): expand_type = arg["type"] if arg_name == "frameSelector": expand_type = arg["type"]["union"][1] if expand_type: for opt_property in expand_type["properties"]: opt_name = opt_property["name"] if opt_name == "recordHar" or opt_name == "recordVideo": for sub_property in opt_property["type"][ "properties"]: sub_name = sub_property["name"] new_sub_name = to_snake_case( opt_name + sub_name[0:1].upper() + sub_name[1:]) args[new_sub_name] = sub_property sub_property["name"] = new_sub_name sub_property["required"] = False else: args[to_snake_case( opt_name)] = opt_property opt_property["name"] = to_snake_case( opt_name) if expand_as_optional: opt_property["required"] = False else: args[new_name] = arg arg["name"] = new_name member["args"] = args clazz["members"] = members
def _patch_descriptions(self) -> None: map: Dict[str, str] = {} for class_name in self.api: clazz = self.api[class_name] js_class = "" if class_name.startswith("JS"): js_class = "jsHandle" if class_name.startswith("CDP"): js_class = "cdpSession" else: js_class = class_name[0:1].lower() + class_name[1:] for method_name in clazz["methods"]: camel_case = js_class + "." + method_name snake_case = ( to_snake_case(class_name) + "." + to_snake_case(method_name) ) map[camel_case] = snake_case for [name, value] in map.items(): for class_name in self.api: clazz = self.api[class_name] for method_name in clazz["methods"]: method = clazz["methods"][method_name] if "comment" in method: method["comment"] = method["comment"].replace(name, value) if "args" in method: for _, arg in method["args"].items(): if "comment" in arg: arg["comment"] = arg["comment"].replace(name, value)
def _patch_case(self) -> None: self.classes = {} for clazz in self.api: if not works_for_python(clazz): continue members = {} self.classes[clazz["name"]] = clazz events = [] for member in clazz["members"]: if not works_for_python(member): continue member_name = member["name"] new_name = name_or_alias(member) self._add_link(member["kind"], clazz["name"], member_name, new_name) if member["kind"] == "event": events.append(member) else: new_name = to_snake_case(new_name) member["name"] = new_name members[new_name] = member apply_type_or_override(member) if "args" in member: args = {} for arg in member["args"]: if not works_for_python(arg): continue if arg["name"] == "options": for option in arg["type"]["properties"]: if not works_for_python(option): continue option = self_or_override(option) option_name = to_snake_case( name_or_alias(option)) option["name"] = option_name option["required"] = False args[option_name] = option else: arg = self_or_override(arg) arg_name = to_snake_case(name_or_alias(arg)) arg["name"] = arg_name args[arg_name] = arg member["args"] = args clazz["members"] = members clazz["events"] = events
def _add_link(self, kind: str, clazz: str, member: str, alias: str) -> None: match = re.match(r"(JS|CDP|[A-Z])([^.]+)", clazz) if not match: raise Exception("Invalid class " + clazz) var_name = to_snake_case(f"{match.group(1).lower()}{match.group(2)}") new_name = to_snake_case(alias) if kind == "event": new_name = new_name.lower() self.links[ f"[`event: {clazz}.{member}`]" ] = f"`{var_name}.on('{new_name}')`" elif kind == "property": self.links[f"[`property: {clazz}.{member}`]"] = f"`{var_name}.{new_name}`" else: self.links[f"[`method: {clazz}.{member}`]"] = f"`{var_name}.{new_name}()`"
def inner_serialize_doc_type(self, type: Any) -> str: if type["name"] == "Promise": type = type["templates"][0] if "union" in type: ll = [self.serialize_doc_type(t) for t in type["union"]] ll.sort(key=lambda item: "}" if item == "NoneType" else item) return f"Union[{', '.join(ll)}]" type_name = type["name"] if type_name == "path": return "Union[pathlib.Path, str]" if type_name == "function" and "args" not in type: return "Callable" if type_name == "function": return_type = "Any" if type.get("returnType"): return_type = self.serialize_doc_type(type["returnType"]) return f"Callable[[{', '.join(self.serialize_doc_type(t) for t in type['args'])}], {return_type}]" if "templates" in type: base = type_name if type_name == "Array": base = "List" if type_name == "Object" or type_name == "Map": base = "Dict" return f"{base}[{', '.join(self.serialize_doc_type(t) for t in type['templates'])}]" if type_name == "Object" and "properties" in type: items = [] for p in type["properties"]: items.append( to_snake_case(p["name"]) + ": " + (self.serialize_doc_type(p["type"]) if p["required"] else self.make_optional(self.serialize_doc_type(p["type"])))) return f"{{{', '.join(items)}}}" if type_name == "boolean": return "bool" if type_name == "string": return "str" if type_name == "Object" or type_name == "Serializable": return "Any" if type_name == "Buffer": return "bytes" if type_name == "URL": return "str" if type_name == "RegExp": return "Pattern" if type_name == "null": return "NoneType" if type_name == "EvaluationArgument": return "Dict" return type["name"]
def _patch_case(self) -> None: self.classes = {} for clazz in self.api: members = {} self.classes[clazz["name"]] = clazz for member in clazz["members"]: member_name = member["name"] alias = (member["langs"].get("aliases").get("python") if member["langs"].get("aliases") else None) new_name = member_name if alias: new_name = alias self._add_link(member["kind"], clazz["name"], member_name, new_name) if member["kind"] == "event": continue new_name = to_snake_case(new_name) member["name"] = new_name members[new_name] = member if member["langs"].get( "types") and member["langs"]["types"].get("python"): member["type"] = member["langs"]["types"]["python"] if "args" in member: args = {} for arg in member["args"]: arg_name = arg["name"] new_name = to_snake_case(arg_name) if arg_name == "options": for opt_property in arg["type"]["properties"]: opt_name = opt_property["name"] args[to_snake_case(opt_name)] = opt_property opt_property["name"] = to_snake_case(opt_name) opt_property["required"] = False else: args[new_name] = arg arg["name"] = new_name member["args"] = args clazz["members"] = members
def generate(t: Any) -> None: print("") class_name = short_name(t) base_class = t.__bases__[0].__name__ base_sync_class = ("SyncBase" if base_class == "ChannelOwner" or base_class == "object" else base_class) print(f"class {class_name}({base_sync_class}):") print("") print(f" def __init__(self, obj: {class_name}Impl):") print(" super().__init__(obj)") for [name, type] in get_type_hints(t, api_globals).items(): print("") print(" @property") print(f" def {to_snake_case(name)}(self) -> {process_type(type)}:") documentation_provider.print_entry(class_name, to_snake_case(name), {"return": type}) [prefix, suffix] = return_value(type) prefix = " return " + prefix + f"self._impl_obj.{name}" print(f"{prefix}{suffix}") for [name, value] in t.__dict__.items(): if name.startswith("_"): continue if not name.startswith("_") and str(value).startswith("<property"): value = value.fget print("") print(" @property") print( f" def {to_snake_case(name)}({signature(value, len(name) + 9)}) -> {return_type(value)}:" ) documentation_provider.print_entry( class_name, to_snake_case(name), get_type_hints(value, api_globals)) [prefix, suffix ] = return_value(get_type_hints(value, api_globals)["return"]) prefix = " return " + prefix + f"self._impl_obj.{name}" print(f"{prefix}{arguments(value, len(prefix))}{suffix}") for [name, value] in t.__dict__.items(): if name in ["expect_dialog"]: continue if (not name.startswith("_") and isinstance(value, FunctionType) and "expect_" not in name and "remove_listener" != name): is_async = inspect.iscoroutinefunction(value) print("") print( f" def {to_snake_case(name)}({signature(value, len(name) + 9)}) -> {return_type(value)}:" ) documentation_provider.print_entry( class_name, to_snake_case(name), get_type_hints(value, api_globals)) [prefix, suffix ] = return_value(get_type_hints(value, api_globals)["return"]) if is_async: prefix = prefix + f"self._sync(self._impl_obj.{name}(" suffix = "))" + suffix else: prefix = prefix + f"self._impl_obj.{name}(" suffix = ")" + suffix print(f""" try: log_api("=> {to_snake_case(class_name)}.{to_snake_case(name)} started") result = {prefix}{arguments(value, len(prefix))}{suffix} log_api("<= {to_snake_case(class_name)}.{to_snake_case(name)} succeded") return result except Exception as e: log_api("<= {to_snake_case(class_name)}.{to_snake_case(name)} failed") raise e""") if "expect_" in name: print("") return_type_value = return_type(value) return_type_value = re.sub(r"\"([^\"]+)Impl\"", r"\1", return_type_value) event_name = re.sub(r"expect_(.*)", r"\1", name) event_name = re.sub(r"_", "", event_name) event_name = re.sub(r"consolemessage", "console", event_name) print( f""" def {name}({signature(value, len(name) + 9)}) -> {return_type_value}: \"\"\"{class_name}.{name} Returns context manager that waits for ``event`` to fire upon exit. It passes event's value into the ``predicate`` function and waits for the predicate to return a truthy value. Will throw an error if the page is closed before the ``event`` is fired. with page.expect_{event_name}() as event_info: page.click("button") value = event_info.value Parameters ---------- predicate : Optional[typing.Callable[[Any], bool]] Predicate receiving event data. timeout : Optional[int] Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. \"\"\"""") wait_for_method = "waitForEvent(event, predicate, timeout)" if event_name == "request": wait_for_method = "waitForRequest(url_or_predicate, timeout)" elif event_name == "response": wait_for_method = "waitForResponse(url_or_predicate, timeout)" elif event_name == "loadstate": wait_for_method = "waitForLoadState(state, timeout)" elif event_name == "navigation": wait_for_method = "waitForNavigation(url, wait_until, timeout)" elif event_name != "event": print(f' event = "{event_name}"') print( f" return EventContextManager(self, self._impl_obj.{wait_for_method})" ) print("") print(f"mapping.register({class_name}Impl, {class_name})")
def print_entry( self, class_name: str, method_name: str, signature: Dict[str, Any] = None, is_property: bool = False, ) -> None: if class_name in ["BindingCall"] or method_name in [ "pid", "_add_event_handler", "remove_listener", ]: return original_method_name = method_name self.printed_entries.append(f"{class_name}.{method_name}") clazz = self.classes[class_name] method = clazz["members"].get(method_name) if not method and "extends" in clazz: superclass = self.classes.get(clazz["extends"]) if superclass: method = superclass["members"].get(method_name) fqname = f"{class_name}.{method_name}" if not method: self.errors.add(f"Method not documented: {fqname}") return doc_is_property = (not method.get("async") and not len(method["args"]) and "type" in method) if method["name"].startswith("is_") or method["name"].startswith( "as_"): doc_is_property = False if doc_is_property != is_property: self.errors.add(f"Method vs property mismatch: {fqname}") return indent = " " * 8 print(f'{indent}"""{class_name}.{to_snake_case(original_method_name)}') if method.get("comment"): print( f"{indent}{self.beautify_method_comment(method['comment'], indent)}" ) signature_no_return = {**signature} if signature else None if signature_no_return and "return" in signature_no_return: del signature_no_return["return"] # Collect a list of all names, flatten options. args = method["args"] if signature and signature_no_return: print("") print(" Parameters") print(" ----------") for [name, value] in signature.items(): name = to_snake_case(name) if name == "return": continue original_name = name doc_value = args.get(name) if name in args: del args[name] if not doc_value: self.errors.add( f"Parameter not documented: {fqname}({name}=)") else: code_type = self.serialize_python_type(value) print( f"{indent}{to_snake_case(original_name)} : {code_type}" ) if doc_value.get("comment"): print( f"{indent} {self.indent_paragraph(self.render_links(doc_value['comment']), f'{indent} ')}" ) self.compare_types(code_type, doc_value, f"{fqname}({name}=)", "in") if (signature and "return" in signature and str(signature["return"]) != "<class 'NoneType'>"): value = signature["return"] doc_value = method self.compare_types(value, doc_value, f"{fqname}(return=)", "out") print("") print(" Returns") print(" -------") print(f" {self.serialize_python_type(value)}") print(f'{indent}"""') for name in args: if args[name].get("deprecated"): continue self.errors.add( f"Parameter not implemented: {class_name}.{method_name}({name}=)" )
def _patch_case(self) -> None: self.classes = {} for clazz in self.api: members = {} self.classes[clazz["name"]] = clazz for member in clazz["members"]: if member["kind"] == "event": continue method_name = member["name"] new_name = to_snake_case(method_name) if method_name == "continue": new_name = "continue_" if method_name == "$eval": new_name = "eval_on_selector" if method_name == "$$eval": new_name = "eval_on_selector_all" if method_name == "$": new_name = "query_selector" if method_name == "$$": new_name = "query_selector_all" members[new_name] = member member["name"] = new_name if "args" in member: args = {} for arg in member["args"]: arg_name = arg["name"] new_name = to_snake_case(arg_name) if arg_name == "pageFunction": new_name = "expression" expand_type = None expand_as_optional = False if arg_name == "options": expand_type = arg["type"] expand_as_optional = True if arg_name == "optionsOrPredicate": expand_type = arg["type"]["union"][1] expand_as_optional = True if arg_name == "params" and "properties" in arg["type"]: expand_type = arg["type"] if method_name == "emulateMedia" and arg_name == "params": expand_type = arg["type"] if method_name == "fulfill" and arg_name == "response": expand_type = arg["type"] if method_name == "continue" and arg_name == "overrides": expand_type = arg["type"] if (method_name == "setViewportSize" and arg_name == "viewportSize"): expand_type = arg["type"] if arg_name == "geolocation": expand_type = arg["type"]["union"][1] if arg_name == "frameSelector": expand_type = arg["type"]["union"][1] if expand_type: for opt_property in expand_type["properties"]: opt_name = opt_property["name"] if opt_name == "recordHar" or opt_name == "recordVideo": for sub_property in opt_property["type"][ "properties"]: sub_name = sub_property["name"] new_sub_name = to_snake_case( opt_name + sub_name[0:1].upper() + sub_name[1:]) args[new_sub_name] = sub_property sub_property["name"] = new_sub_name sub_property["required"] = False else: args[to_snake_case( opt_name)] = opt_property opt_property["name"] = to_snake_case( opt_name) if expand_as_optional: opt_property["required"] = False else: args[new_name] = arg arg["name"] = new_name member["args"] = args clazz["members"] = members
def print_entry(self, class_name: str, method_name: str, signature: Dict[str, Any] = None) -> None: if class_name in ["BindingCall"] or method_name in [ "pid", "_add_event_handler", "remove_listener", ]: return original_method_name = method_name self.printed_entries.append(f"{class_name}.{method_name}") if class_name == "JSHandle": self.printed_entries.append(f"ElementHandle.{method_name}") clazz = self.classes[class_name] method = clazz["members"].get(method_name) if not method and "extends" in clazz: superclass = self.classes.get(clazz["extends"]) if superclass: method = superclass["members"].get(method_name) fqname = f"{class_name}.{method_name}" if not method: self.errors.add(f"Method not documented: {fqname}") return indent = " " * 8 print(f'{indent}"""{class_name}.{to_snake_case(original_method_name)}') if method.get("comment"): print( f"{indent}{self.beautify_method_comment(method['comment'], indent)}" ) signature_no_return = {**signature} if signature else None if signature_no_return and "return" in signature_no_return: del signature_no_return["return"] # Collect a list of all names, flatten options. args = method["args"] if signature and signature_no_return: print("") print(" Parameters") print(" ----------") for [name, value] in signature.items(): name = to_snake_case(name) if name == "return": continue if name == "force_expr": continue original_name = name doc_value = args.get(name) if name in args: del args[name] if not doc_value: self.errors.add( f"Parameter not documented: {fqname}({name}=)") else: code_type = self.serialize_python_type(value) print( f"{indent}{to_snake_case(original_name)} : {code_type}" ) if doc_value.get("comment"): print( f"{indent} {self.indent_paragraph(self.render_links(doc_value['comment']), f'{indent} ')}" ) if original_name == "expression": print(f"{indent}force_expr : bool") print( f"{indent} Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function" ) self.compare_types(code_type, doc_value, f"{fqname}({name}=)") if (signature and "return" in signature and str(signature["return"]) != "<class 'NoneType'>"): value = signature["return"] doc_value = method self.compare_types(value, doc_value, f"{fqname}(return=)") print("") print(" Returns") print(" -------") print(f" {self.serialize_python_type(value)}") print(f'{indent}"""') for name in args: self.errors.add( f"Parameter not implemented: {class_name}.{method_name}({name}=)" )