Skip to main content

Protocol intersection for mypy

Project description

typing-protocol-intersection

tests & static analysis PyPI - Python Version

A tiny Python 3 package that introduces Protocol intersections (for Protocols themselves see PEP 544). The ProtocolIntersection type tells mypy that an object implements multiple protocols. It can be used either as a function parameter or as a return value. A mypy plugin that ships with the package is required for this to work. See the examples section below.

Supported versions

The plugin supports python 3.7, 3.8, 3.9, 3.10 and 3.11 and mypy >= 0.920 and <= 1.4.x.

Installation

The typing-protocol-intersection package is pip-installable:

pip install typing-protocol-intersection 

Configuration

Add typing_protocol_intersection.mypy_plugin to plugins in mypy configuration:

> cat mypy.ini
[mypy]
plugins = typing_protocol_intersection.mypy_plugin

Examples

Simple example

from typing import Protocol
from typing_protocol_intersection import ProtocolIntersection as Has

class X(Protocol):
    x: str

class Y(Protocol):
    y: str

def foo(xy: Has[X, Y]) -> None:
    # Note xy implements both X and Y, not just one of them
    print(xy.x, xy.y)

Complex example - valid program

Here's a more complex example showing what you can write with the help of this mypy plugin:

from types import SimpleNamespace
from typing import Protocol, Generic, TypeVar, Dict
from typing_protocol_intersection import ProtocolIntersection as Has

class X(Protocol):
    x: str

class Y(Protocol):
    y: str

T = TypeVar("T")

class Builder(Generic[T]):
    def __init__(self) -> None:
        super().__init__()
        self._d: Dict[str, str] = {}

    def with_x(self) -> "Builder[Has[T, X]]":
        self._d["x"] = "X"
        return self  # type: ignore

    def with_y(self) -> "Builder[Has[T, Y]]":
        self._d["y"] = "Y"
        return self  # type: ignore

    def build(self) -> T:
        return SimpleNamespace(**self._d)  # type: ignore

class DesiredObject(X, Y, Protocol):
    pass

def get_x_y_1(o: DesiredObject) -> None:
    print(f"{o.x=}; {o.y=}")

def get_x_y_2(o: Has[X, Y]) -> None:
    print(f"{o.x=}; {o.y=}")

def main() -> None:
    valid_o = Builder().with_x().with_y().build()
    get_x_y_1(valid_o)
    get_x_y_2(valid_o)

if __name__ == "__main__":
    main()
> # with plugin
> mypy example.py
Success: no issues found in 1 source file

Complex example - invalid program

And here's how would the plugin help if you forgot to include one of the protocols while building an object:

from types import SimpleNamespace
from typing import Protocol, Generic, TypeVar, Dict
from typing_protocol_intersection import ProtocolIntersection as Has

class X(Protocol):
    x: str

class Y(Protocol):
    y: str

T = TypeVar("T")

class Builder(Generic[T]):
    def __init__(self) -> None:
        super().__init__()
        self._d: Dict[str, str] = {}

    def with_x(self) -> "Builder[Has[T, X]]":
        self._d["x"] = "X"
        return self  # type: ignore

    def with_y(self) -> "Builder[Has[T, Y]]":
        self._d["y"] = "Y"
        return self  # type: ignore

    def build(self) -> T:
        return SimpleNamespace(**self._d)  # type: ignore

class DesiredObject(X, Y, Protocol):
    pass

def get_x_y_1(o: DesiredObject) -> None:
    print(f"{o.x=}; {o.y=}")

def get_x_y_2(o: Has[X, Y]) -> None:
    print(f"{o.x=}; {o.y=}")

def main() -> None:
    valid_o = Builder().with_x().build()  # <-- note no .with_y()
    get_x_y_1(valid_o)
    get_x_y_2(valid_o)

if __name__ == "__main__":
    main()
> # Note the real output would contain some invisible characters which were removed here.
> mypy example.py
example.py:40:15: error: Argument 1 to "get_x_y_1" has incompatible type "ProtocolIntersection[X]"; expected "DesiredObject"  [arg-type]
example.py:40:15: note: "ProtocolIntersection" is missing following "DesiredObject" protocol member:
example.py:40:15: note:     y
example.py:41:15: error: Argument 1 to "get_x_y_2" has incompatible type "typing_protocol_intersection.types.ProtocolIntersection[X]"; expected "typing_protocol_intersection.types.ProtocolIntersection[Y, X]"  [arg-type]
example.py:41:15: note: "ProtocolIntersection" is missing following "ProtocolIntersection" protocol member:
example.py:41:15: note:     y
Found 2 errors in 1 file (checked 1 source file)

Recommended usage

The ProtocolIntersection class name might seem a bit lengthy, but it's explicit, which is good. For brevity and better readability, it's recommended to use an alias when importing, as seen in the examples above.

from typing_protocol_intersection import ProtocolIntersection as Has

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

typing-protocol-intersection-0.3.4.tar.gz (10.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

typing_protocol_intersection-0.3.4-py3-none-any.whl (7.9 kB view details)

Uploaded Python 3

File details

Details for the file typing-protocol-intersection-0.3.4.tar.gz.

File metadata

File hashes

Hashes for typing-protocol-intersection-0.3.4.tar.gz
Algorithm Hash digest
SHA256 be9d00ad1add223203a40c23bba7a569f759655284724e49ac87446a522a22aa
MD5 7e3c9c662b9f89d70791476cda6b3617
BLAKE2b-256 ca07b6a7e5b23883e9e58dc8c4b49be318b023f4b6948c11b0714ee5be9c8cd7

See more details on using hashes here.

File details

Details for the file typing_protocol_intersection-0.3.4-py3-none-any.whl.

File metadata

File hashes

Hashes for typing_protocol_intersection-0.3.4-py3-none-any.whl
Algorithm Hash digest
SHA256 55f78ca84711a0949a3a6d93c2d58ee40d0eb3d8df50abfb291e5219b8b608a3
MD5 2f85ceff849d9028041d9d4b7e7ccff8
BLAKE2b-256 1ce26007fadf7ab8e804149a9ef6482a6a28979f17a773f9c11093dba2ace395

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page