mypy plugin

We provide a custom mypy plugin to fix existing issues, provide new awesome features, 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 (setup.cfg or mypy.ini):

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

Or in pyproject.toml:

[tool.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

  • do-notation feature allows using Do Notation

Further reading

API Reference

Plugin definition

TYPED_PARTIAL_FUNCTION: Final = 'returns.curry.partial'

Used for typed partial function.

TYPED_CURRY_FUNCTION: Final = 'returns.curry.curry'

Used for typed curry decorator.

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

Used for typed flow call.

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

Used for typed pipe call.

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

Used for HKT emulation.

DO_NOTATION_METHODS: Final = ('returns.io.IO.do', 'returns.maybe.Maybe.do', 'returns.future.Future.do', 'returns.result.Result.do', 'returns.io.IOResult.do', 'returns.future.FutureResult.do')

Used for Do Notation.

classDiagram 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

classDiagram Enum <|-- _KindErrors str <|-- _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

classDiagram
analyze(ctx)[source]

Returns proper type for curried functions.

Parameters:

ctx (FunctionContext) –

Return type:

Type

Partial

classDiagram
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

classDiagram
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

classDiagram

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

Do notation

classDiagram
analyze(ctx)[source]

Used to handle validation and error types in Do Notation.

What it does?

  1. For all types we ensure that only a single container type is used in a single do-notation. We don’t allow mixing them.

  2. For types with error types (like Result), it inferes what possible errors types can we have. The result is a Union of all possible errors.

  3. Ensures that expression passed into .do method is literal.

  4. Checks that default value is provided if generator expression has if conditions inside.

Parameters:

ctx (MethodContext) –

Return type:

Type