File: returns_plugin.py

package info (click to toggle)
python-returns 0.26.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,652 kB
  • sloc: python: 11,000; makefile: 18
file content (128 lines) | stat: -rw-r--r-- 3,821 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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
"""
Custom mypy plugin to solve the temporary problem with python typing.

Important: we don't do anything ugly here.
We only solve problems of the current typing implementation.

``mypy`` API docs are here:
https://mypy.readthedocs.io/en/latest/extending_mypy.html

We use ``pytest-mypy-plugins`` to test that it works correctly, see:
https://github.com/mkurnikov/pytest-mypy-plugins
"""

from collections.abc import Callable, Mapping
from typing import ClassVar, TypeAlias, final

from mypy.plugin import (
    AttributeContext,
    FunctionContext,
    MethodContext,
    MethodSigContext,
    Plugin,
)
from mypy.types import CallableType
from mypy.types import Type as MypyType

from returns.contrib.mypy import _consts
from returns.contrib.mypy._features import (
    curry,
    do_notation,
    flow,
    kind,
    partial,
    pipe,
)

# Type aliases
# ============

#: Type for a function hook.
_FunctionCallback: TypeAlias = Callable[[FunctionContext], MypyType]

#: Type for attribute hook.
_AttributeCallback: TypeAlias = Callable[[AttributeContext], MypyType]

#: Type for a method hook.
_MethodCallback: TypeAlias = Callable[[MethodContext], MypyType]

#: Type for a method signature hook.
_MethodSigCallback: TypeAlias = Callable[[MethodSigContext], CallableType]


# Interface
# =========


@final
class _ReturnsPlugin(Plugin):
    """Our main plugin to dispatch different callbacks to specific features."""

    _function_hook_plugins: ClassVar[Mapping[str, _FunctionCallback]] = {
        _consts.TYPED_PARTIAL_FUNCTION: partial.analyze,
        _consts.TYPED_CURRY_FUNCTION: curry.analyze,
        _consts.TYPED_FLOW_FUNCTION: flow.analyze,
        _consts.TYPED_PIPE_FUNCTION: pipe.analyze,
        _consts.TYPED_KIND_DEKIND: kind.dekind,
    }

    _method_sig_hook_plugins: ClassVar[Mapping[str, _MethodSigCallback]] = {
        _consts.TYPED_PIPE_METHOD: pipe.signature,
        _consts.TYPED_KIND_KINDED_CALL: kind.kinded_signature,
    }

    _method_hook_plugins: ClassVar[Mapping[str, _MethodCallback]] = {
        _consts.TYPED_PIPE_METHOD: pipe.infer,
        _consts.TYPED_KIND_KINDED_CALL: kind.kinded_call,
        _consts.TYPED_KIND_KINDED_GET: kind.kinded_get_descriptor,
        **dict.fromkeys(_consts.DO_NOTATION_METHODS, do_notation.analyze),
    }

    def get_function_hook(
        self,
        fullname: str,
    ) -> _FunctionCallback | None:
        """
        Called for function return types from ``mypy``.

        Runs on each function call in the source code.
        We are only interested in a particular subset of all functions.
        So, we return a function handler for them.

        Otherwise, we return ``None``.
        """
        return self._function_hook_plugins.get(fullname)

    def get_attribute_hook(
        self,
        fullname: str,
    ) -> _AttributeCallback | None:
        """Called for any exiting or ``__getattr__`` attribute access."""
        if fullname.startswith(_consts.TYPED_KINDN_ACCESS):
            name_parts = fullname.split('.')
            attribute_name = name_parts[-1]
            if attribute_name.startswith('__') and attribute_name.endswith(
                '__'
            ):
                return None
            return kind.attribute_access
        return None

    def get_method_signature_hook(
        self,
        fullname: str,
    ) -> _MethodSigCallback | None:
        """Called for method signature from ``mypy``."""
        return self._method_sig_hook_plugins.get(fullname)

    def get_method_hook(
        self,
        fullname: str,
    ) -> _MethodCallback | None:
        """Called for method return types from ``mypy``."""
        return self._method_hook_plugins.get(fullname)


def plugin(version: str) -> type[Plugin]:
    """Plugin's public API and entrypoint."""
    return _ReturnsPlugin