| import ast | |
| import python_pyi | |
| w = python_pyi.Widget(42) | |
| if w.getId() != 42: | |
| raise RuntimeError("Widget.getId() returned the wrong value") | |
| if python_pyi.Widget.create(7).getId() != 7: | |
| raise RuntimeError("Widget.create() returned the wrong value") | |
| if python_pyi.OverloadedWidget.create(7).getId() != 7: | |
| raise RuntimeError("OverloadedWidget.create() returned the wrong value") | |
| if python_pyi.OverloadedWidget.create(7, 3).getId() != 10: | |
| raise RuntimeError("OverloadedWidget.create() returned the wrong value for the second overload") | |
| with open("python_pyi.pyi") as f: | |
| source = f.read() | |
| # Must always be syntactically valid Python. | |
| tree = ast.parse(source, filename="python_pyi.pyi") | |
| names = set() | |
| for node in ast.walk(tree): | |
| if isinstance(node, (ast.FunctionDef, ast.ClassDef)): | |
| names.add(node.name) | |
| elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name): | |
| names.add(node.target.id) | |
| # Everything expected from a -builtin class should appear in the stub. | |
| expected = {"Empty", "SizedCollection", "Widget", "__init__", "getId", "create", "id", "make_unwrapped", "SWIGTYPE_p_Unwrapped"} | |
| missing = expected - names | |
| if missing: | |
| raise RuntimeError("python_pyi.pyi is missing expected declarations: %s" % sorted(missing)) | |
| # An overloaded method is declared with **kwargs as well as *args, so that it stays assignable to the one it hides. | |
| for node in ast.walk(tree): | |
| if isinstance(node, ast.ClassDef) and node.name == "OverloadedWidget": | |
| for method in node.body: | |
| if isinstance(method, ast.FunctionDef) and method.name == "create" and method.args.kwarg is None: | |
| raise RuntimeError("python_pyi.pyi should declare OverloadedWidget.create with **kwargs") | |
| # The opaque type wrapper class is only ever generated into the .pyi file. | |
| classes = {node.name for node in ast.walk(tree) if isinstance(node, ast.ClassDef)} | |
| if "SWIGTYPE_p_Unwrapped" not in classes: | |
| raise RuntimeError("python_pyi.pyi should declare the SWIGTYPE_p_Unwrapped class") | |
| # %pythonstubcode inserted the import needed by the collections.abc base class. | |
| if "import collections.abc" not in source: | |
| raise RuntimeError("python_pyi.pyi is missing the collections.abc import") | |
| # %pythonstubbegin inserted its code at the top of the stub, before any real code. | |
| marker = "# Generated by %pythonstubbegin" | |
| if marker not in source: | |
| raise RuntimeError("python_pyi.pyi is missing the %pythonstubbegin code") | |
| if source.index(marker) > source.index("import typing"): | |
| raise RuntimeError("python_pyi.pyi has the %pythonstubbegin code after the import typing statement") | |
| # TYPE_CHECKING is meaningless in a .pyi (it is never executed, only read by | |
| # a type checker), so SWIGTYPE_p_Unwrapped must not be guarded by it there. | |
| if "TYPE_CHECKING" in source: | |
| raise RuntimeError("python_pyi.pyi should not guard anything with typing.TYPE_CHECKING") | |
| # The low-level module stub declares everything the module exports, so it must not be marked incomplete. | |
| with open("_python_pyi.pyi") as f: | |
| if "__getattr__" in f.read(): | |
| raise RuntimeError("_python_pyi.pyi should declare every export instead of a catch-all __getattr__") | |
| with open("python_pyi.py") as f: | |
| py_source = f.read() | |
| py_tree = ast.parse(py_source, filename="python_pyi.py") | |
| # The .py file keeps just the guarded 'this' declaration, which __disown__ needs to type check. | |
| # There are no proxy classes at all to declare it in when -builtin is used. | |
| py_classes = [node for node in ast.walk(py_tree) if isinstance(node, ast.ClassDef)] | |
| if py_classes and "if typing.TYPE_CHECKING:" not in py_source: | |
| raise RuntimeError("python_pyi.py should declare 'this' guarded by typing.TYPE_CHECKING") | |
| for node in ast.walk(py_tree): | |
| if isinstance(node, ast.AnnAssign) and getattr(node.target, "id", None) != "this": | |
| raise RuntimeError("python_pyi.py should have no variable annotations except 'this' when -pyi is used") | |
| # _swig_dispatch is type checker plumbing for the overload dispatchers in this file, not an annotation of the wrapped API. | |
| if isinstance(node, ast.FunctionDef) and node.name != "_swig_dispatch" and (node.returns is not None or any(a.annotation is not None for a in node.args.args)): | |
| raise RuntimeError("python_pyi.py should have no function annotations when -pyi is used") | |
| # The type wrapper classes only exist to give an annotation a name, and the annotations are in the stub. | |
| if "SWIGTYPE_p_Unwrapped" in py_source: | |
| raise RuntimeError("python_pyi.py should not declare SWIGTYPE_p_Unwrapped when -pyi is used") |