mypy plugin

We provide several mypy plugins to fix existing issues and improve type-safety of things developers commonly use:

Installation

You will need to install mypy separately. It is not bundled with returns.

To install any mypy plugin add it to the plugins section of the config file.

[mypy]
plugins =
  returns.contrib.mypy.returns_plugin

We recommend to always add our plugin as the first one in chain.

Configuration

You can have a look at the suggested mypy configuration in our own repository.

You can also use nitpick tool to enforce the same mypy configuration for all your projects.

We recommend to use our own setup. Add this to your pyproject.toml:

[tool.nitpick]
style = "https://raw.githubusercontent.com/wemake-services/wemake-python-styleguide/master/styles/mypy.toml"

And use flake8 to lint that configuration defined in the setup matches yours. This will allow to keep them in sync with the upstream.

Supported features

  • kind feature adds Higher Kinded Types (HKT) support

  • curry feature allows to write typed curried functions

  • partial feature allows to write typed partial application

  • flow feature allows to write better typed functional pipelines with flow function

  • pipe feature allows to write better typed functional pipelines with pipe function

  • decorators allows to infer types of functions that are decorated with @safe, @maybe, @impure, etc

API Reference

Plugin defenition

TYPED_DECORATORS: typing_extensions.Final = frozenset({'returns.functions.not_', 'returns.future.asyncify', 'returns.future.future', 'returns.future.future_safe', 'returns.io.impure', 'returns.io.impure_safe', 'returns.maybe.maybe', 'returns.result.safe'})

Set of full names of our decorators.

TYPED_PARTIAL_FUNCTION: typing_extensions.Final = 'returns.curry.partial'

Used for typed partial function.

TYPED_CURRY_FUNCTION: typing_extensions.Final = 'returns.curry.curry'

Used for typed curry decorator.

TYPED_FLOW_FUNCTION: typing_extensions.Final = 'returns._internal.pipeline.flow._flow'

Used for typed flow call.

TYPED_PIPE_FUNCTION: typing_extensions.Final = 'returns._internal.pipeline.pipe._pipe'

Used for typed pipe call.

TYPED_KINDN: typing_extensions.Final = 'returns.primitives.hkt.KindN'

Used for HKT emulation.

graph TD; _ReturnsPlugin Plugin --> _ReturnsPlugin

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

plugin(version)[source]

Plugin’s public API and entrypoint.

Parameters

version (str) –

Return type

Type[Plugin]

Kind

graph TD; _KindErrors str --> _KindErrors Enum --> _KindErrors
attribute_access(ctx)[source]

Ensures that attribute access to KindN is correct.

In other words:

from typing import TypeVar
from returns.primitives.hkt import KindN
from returns.interfaces.mappable import MappableN

_MappableType = TypeVar('_MappableType', bound=MappableN)

kind: KindN[_MappableType, int, int, int]
reveal_type(kind.map)  # will work correctly!
Parameters

ctx (AttributeContext) –

Return type

Type

dekind(ctx)[source]

Infers real type behind Kind form.

Basically, it turns Kind[IO, int] into IO[int]. The only limitation is that it works with only Instance type in the first type argument position.

So, dekind(KindN[T, int]) will fail.

Parameters

ctx (FunctionContext) –

Return type

Type

kinded_signature(ctx)[source]

Returns the internal function wrapped as Kinded[def].

Works for Kinded class when __call__ magic method is used. See returns.primitives.hkt.Kinded for more information.

Parameters

ctx (MethodSigContext) –

Return type

CallableType

kinded_call(ctx)[source]

Reveals the correct return type of Kinded.__call__ method.

Turns -> KindN[I, t1, t2, t3] into -> I[t1, t2, t3].

Also strips unused type arguments for KindN, so: - KindN[IO, int, <nothing>, <nothing>] will be IO[int] - KindN[Result, int, str, <nothing>] will be Result[int, str]

It also processes nested KindN with recursive strategy.

See returns.primitives.hkt.Kinded for more information.

Parameters

ctx (MethodContext) –

Return type

Type

kinded_get_descriptor(ctx)[source]

Used to analyze @kinded method calls.

We do this due to __get__ descriptor magic.

Parameters

ctx (MethodContext) –

Return type

Type

Curry

graph TD; _CurryFunctionOverloads _ArgTree
analyze(ctx)[source]

Returns proper type for curried functions.

Parameters

ctx (FunctionContext) –

Return type

Type

Partial

graph TD; _PartialFunctionReducer _AppliedArgs
analyze(ctx)[source]

This hook is used to make typed curring a thing in returns project.

This plugin is a temporary solution to the problem. It should be later replaced with the official way of doing things. One day functions will have better API and we plan to submit this plugin into mypy core plugins, so it would not be required.

Internally we just reduce the original function’s argument count. And drop some of them from function’s signature.

Parameters

ctx (FunctionContext) –

Return type

Type

Flow

graph TD;
analyze(ctx)[source]

Helps to analyze flow function calls.

By default, mypy cannot infer and check this function call:

>>> from returns.pipeline import flow
>>> assert flow(
...     1,
...     lambda x: x + 1,
...     lambda y: y / 2,
... ) == 1.0

But, this plugin can! It knows all the types for all lambda functions in the pipeline. How?

  1. We use the first passed parameter as the first argument to the first passed function

  2. We use parameter + function to check the call and reveal types of current pipeline step

  3. We iterate through all passed function and use previous return type as a new parameter to call current function

Parameters

ctx (FunctionContext) –

Return type

Type

Pipe

graph TD;

Typing pipe functions requires several phases.

It is pretty obvious from its usage:

  1. When we pass a sequence of functions we have to reduce the final callable type, it is require to match the callable protocol. And at this point we also kinda try to check that all pipeline functions do match, but this is impossible to do 100% correctly at this point, because generic functions don’t have a type argument to infer the final result

  2. When we call the function, we need to check for two things. First, we check that passed argument fits our instance requirement. Second, we check that pipeline functions match. Now we have all arguments to do the real inference.

  3. We also need to fix generic in method signature. It might be broken, because we add new generic arguments and return type. So, it is safe to reattach generic back to the function.

Here’s when it works:

>>> from returns.pipeline import pipe

>>> def first(arg: int) -> bool:
...     return arg > 0
>>> def second(arg: bool) -> str:
...     return 'bigger' if arg else 'not bigger'

>>> pipeline = pipe(first, second)  # `analyzed` is called
>>> assert pipeline(1) == 'bigger'  # `signature and `infer` are called
>>> assert pipeline(0) == 'not bigger'  # `signature and `infer` again
analyze(ctx)[source]

This hook helps when we create the pipeline from sequence of funcs.

Parameters

ctx (FunctionContext) –

Return type

Type

infer(ctx)[source]

This hook helps when we finally call the created pipeline.

Parameters

ctx (MethodContext) –

Return type

Type

signature(ctx)[source]

Helps to fix generics in method signature.

Parameters

ctx (MethodSigContext) –

Return type

CallableType

Decorators

graph TD;
analyze(ctx)[source]

Changes a type of a decorator.

This problem appears when we try to change the return type of the function. However, currently it is impossible due to this bug: https://github.com/python/mypy/issues/3157

It uses the passed function to copy its type. We only copy arguments and return type is defined by type annotations.

Parameters

ctx (FunctionContext) –

Return type

Type