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
|