File: mypy_plugin.py

package info (click to toggle)
python-pyvista 0.46.4-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 176,968 kB
  • sloc: python: 94,346; sh: 216; makefile: 70
file content (87 lines) | stat: -rw-r--r-- 2,860 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
"""PyVista plugin for static type-checking with mypy."""

from __future__ import annotations

__all__: list[str] = ['promote_type']

import importlib.util
from typing import TYPE_CHECKING
from typing import TypeVar

if TYPE_CHECKING:  # pragma: no cover
    from typing import Any
    from typing import Callable

    from mypy.plugin import ClassDefContext
    from mypy.types import Instance
    from typing_extensions import Self


T = TypeVar('T', bound=type)


def promote_type(*types: type[Any]) -> Callable[[T], T]:  # noqa: ARG001
    """Duck-type type-promotion decorator used by the mypy plugin.

    Apply this decorator to a class to promote its type statically.
    This tells `mypy` to treat the decorated class as though it's
    equivalent to another class.

    .. note::
        This decorator does nothing at runtime and merely passes the object through.

    Parameters
    ----------
    types : type
        Type(s) to promote the class to. The types are only used statically by mypy.

    Returns
    -------
    Callable
        Decorated class.

    """
    return lambda obj: obj


if importlib.util.find_spec('mypy'):  # pragma: no cover
    from mypy.nodes import CallExpr
    from mypy.nodes import NameExpr
    from mypy.nodes import RefExpr
    from mypy.plugin import Plugin

    def _promote_type_callback(ctx: ClassDefContext) -> None:
        """Apply the `promote_type` decorator.

        The decorated class is captured and promoted to the type(s) provided
        by the decorator's argument(s).
        """
        for decorator in ctx.cls.decorators:
            if isinstance(decorator, CallExpr):
                callee = decorator.callee
                if isinstance(callee, NameExpr):
                    name = callee.name
                    if name == promote_type.__name__:
                        decorated_type: Instance = ctx.api.named_type(ctx.cls.fullname)
                        args = decorator.args
                        for arg in args:
                            if isinstance(arg, RefExpr):
                                named_type: Instance = ctx.api.named_type(arg.fullname)
                                decorated_type.type._promote.append(named_type)

    class _PyVistaPlugin(Plugin):
        """Mypy plugin to enable static type promotions."""

        def get_class_decorator_hook(
            self: Self, fullname: str
        ) -> Callable[[ClassDefContext], None] | None:
            def _get_type_fullname(typ: Any) -> str:
                return f'{typ.__module__}.{typ.__qualname__}'

            if fullname == _get_type_fullname(promote_type):
                return _promote_type_callback
            return None

    def plugin(version: str) -> type[_PyVistaPlugin]:  # numpydoc ignore: RT01  # noqa: ARG001
        """Entry-point for mypy."""
        return _PyVistaPlugin