Protocol intersection for mypy
Project description
typing-protocol-intersection
A tiny Python 3 package providing exactly one class - ProtocolIntersection (for Protocols themselves see PEP 544).
Along with a mypy plugin this class allows to say that a function takes a parameter which implements multiple protocols
or returns an object implementing multiple protocols without explicitly creating a new protocol class that inherits them.
See the examples section below.
Installation
pip install typing-protocol-intersection
Configuration
> cat mypy.ini
[mypy]
plugins = typing_protocol_intersection.mypy_plugin
Examples
Make sure to check the Recommended usage section after you familiarize yourself with the examples.
Simple example
from typing import Protocol
from typing_protocol_intersection import ProtocolIntersection
class HasX(Protocol):
x: str
class HasY(Protocol):
y: str
def foo(xy: ProtocolIntersection[HasX, HasY]) -> None:
print(xy.x, xy.y)
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
class HasX(Protocol):
x: str
class HasY(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[ProtocolIntersection[T, HasX]]":
self._d["x"] = "X"
return self # type: ignore
def with_y(self) -> "Builder[ProtocolIntersection[T, HasY]]":
self._d["y"] = "Y"
return self # type: ignore
def build(self) -> T:
return SimpleNamespace(**self._d) # type: ignore
class DesiredObject(HasX, HasY, Protocol):
pass
def get_x_y_1(o: DesiredObject) -> None:
print(f"{o.x=}; {o.y=}")
def get_x_y_2(o: ProtocolIntersection[HasX, HasY]) -> 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()
> # without plugin
> mypy example.py
example.py:18:25: error: "ProtocolIntersection" expects no type arguments, but 2 given [type-arg]
example.py:22:25: error: "ProtocolIntersection" expects no type arguments, but 2 given [type-arg]
example.py:35:18: error: "ProtocolIntersection" expects no type arguments, but 2 given [type-arg]
example.py:36:11: error: "ProtocolIntersection" has no attribute "x" [attr-defined]
example.py:36:11: error: "ProtocolIntersection" has no attribute "y" [attr-defined]
example.py:40:15: error: Argument 1 to "get_x_y_1" has incompatible type "ProtocolIntersection"; expected "DesiredObject" [arg-type]
Found 6 errors in 1 file (checked 1 source file)
> # with plugin
> mypy example.py
Success: no issues found in 1 source file
Invalid program
And here's how would the plugin help if you forgot to include one of the protocols:
from types import SimpleNamespace
from typing import Protocol, Generic, TypeVar, Dict
from typing_protocol_intersection import ProtocolIntersection
class HasX(Protocol):
x: str
class HasY(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[ProtocolIntersection[T, HasX]]":
self._d["x"] = "X"
return self # type: ignore
def with_y(self) -> "Builder[ProtocolIntersection[T, HasY]]":
self._d["y"] = "Y"
return self # type: ignore
def build(self) -> T:
return SimpleNamespace(**self._d) # type: ignore
class DesiredObject(HasX, HasY, Protocol):
pass
def get_x_y_1(o: DesiredObject) -> None:
print(f"{o.x=}; {o.y=}")
def get_x_y_2(o: ProtocolIntersection[HasX, HasY]) -> 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()
> mypy example.py
example.py:40:15: error: Argument 1 to "get_x_y_1" has incompatible type "ProtocolIntersection[HasX]"; 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[HasX]"; expected "typing_protocol_intersection.types.ProtocolIntersection[HasY, HasX]" [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.
from typing_protocol_intersection import ProtocolIntersection as Has
The simple example would translate to
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:
print(xy.x, xy.y)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file typing-protocol-intersection-0.2.2.tar.gz.
File metadata
- Download URL: typing-protocol-intersection-0.2.2.tar.gz
- Upload date:
- Size: 8.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.10.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
50f6b1b45ee5ef60de132e27cd188b964572ab4165ae22fc4a68e7da96b6a1c8
|
|
| MD5 |
f2bcaa1bf27ec17ed75b289196f5b2a9
|
|
| BLAKE2b-256 |
88a8024aa7c499736a7597a55b59eb15e4dc1871f5271f53d71bbe2bbd6bc5d6
|
File details
Details for the file typing_protocol_intersection-0.2.2-py3-none-any.whl.
File metadata
- Download URL: typing_protocol_intersection-0.2.2-py3-none-any.whl
- Upload date:
- Size: 7.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.10.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9598d16aaa65678056830d2c63465da7f2b70199f87df6f733ee4802c0aa390a
|
|
| MD5 |
c2d3ca8c40628e5d665d076b2e4fe92b
|
|
| BLAKE2b-256 |
e8c1cdbafb84dc012d970510d7060b8e17aaadfbb8c210a15083f462301bfb21
|