-
-
Notifications
You must be signed in to change notification settings - Fork 107
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #199 from jodal/proxy-typing
Add helpers for typing proxies
- Loading branch information
Showing
5 changed files
with
196 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
========== | ||
Type hints | ||
========== | ||
|
||
.. automodule:: pykka.typing | ||
:members: proxy_field, proxy_method | ||
|
||
.. versionadded:: 4.0 | ||
|
||
.. autoclass:: pykka.typing.ActorMemberMixin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -68,6 +68,7 @@ Project resources | |
api/messages | ||
api/logging | ||
api/debug | ||
api/typing | ||
|
||
|
||
License | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
"""The :mod:`pykka.typing` module contains helpers to improve type hints. | ||
Since Pykka 4.0, Pykka has complete type hints for the public API, tested using | ||
both `Mypy <https://www.mypy-lang.org/>`_ and `Pyright | ||
<https://github.com/microsoft/pyright>`_. | ||
Due to the dynamic nature of :class:`~pykka.ActorProxy` objects, it is not | ||
possible to automatically type them correctly. This module contains helpers to | ||
manually create additional classes that correctly describe the type hints for | ||
the proxy objects. In cases where a proxy objects is used a lot, this might be | ||
worth the extra effort to increase development speed and catch bugs earlier. | ||
Example usage:: | ||
from typing import cast | ||
from pykka import ActorProxy, ThreadingActor | ||
from pykka.typing import ActorMemberMixin, proxy_field, proxy_method | ||
class CircleActor(ThreadingActor): | ||
pi = 3.14 | ||
def area(self, radius: float) -> float: | ||
return self.pi * radius**2 | ||
class CircleProxy(ActorMemberMixin, ActorProxy[CircleActor]): | ||
pi = proxy_field(CircleActor.pi) | ||
area = proxy_method(CircleActor.area) | ||
proxy = cast(CircleProxy, CircleActor.start().proxy()) | ||
reveal_type(proxy.stop) | ||
# Revealed type is 'Callable[[], pykka.Future[None]]' | ||
reveal_type(proxy.pi) | ||
# Revealed type is 'pykka.Future[float]' | ||
reveal_type(proxy.area)) | ||
# Revealed type is 'Callable[[float], pykka.Future[float]]' | ||
""" | ||
|
||
from __future__ import annotations | ||
|
||
import sys | ||
from typing import TYPE_CHECKING, Any, Callable, Generic, Protocol, TypeVar | ||
|
||
from pykka import Actor | ||
|
||
if TYPE_CHECKING: | ||
from pykka import Future | ||
|
||
if sys.version_info >= (3, 10): | ||
from typing import ( | ||
Concatenate, | ||
ParamSpec, | ||
) | ||
else: | ||
from typing_extensions import ( | ||
Concatenate, | ||
ParamSpec, | ||
) | ||
|
||
__all__ = [ | ||
"ActorMemberMixin", | ||
"proxy_field", | ||
"proxy_method", | ||
] | ||
|
||
|
||
T = TypeVar("T") | ||
P = ParamSpec("P") | ||
R = TypeVar("R", covariant=True) | ||
|
||
|
||
class Method(Protocol, Generic[P, R]): | ||
def __get__(self, instance: Any, owner: type | None = None) -> Callable[P, R]: | ||
... | ||
|
||
def __call__(self, obj: Any, *args: P.args, **kwargs: P.kwargs) -> R: | ||
... | ||
|
||
|
||
def proxy_field(field: T) -> Future[T]: | ||
"""Type a field on an actor proxy. | ||
.. versionadded:: 4.0 | ||
""" | ||
return field # type: ignore[return-value] | ||
|
||
|
||
def proxy_method( | ||
field: Callable[Concatenate[Any, P], T], | ||
) -> Method[P, Future[T]]: | ||
"""Type a method on an actor proxy. | ||
.. versionadded:: 4.0 | ||
""" | ||
return field # type: ignore[return-value] | ||
|
||
|
||
class ActorMemberMixin: | ||
"""Mixin class for typing actor members accessible through a proxy. | ||
.. versionadded:: 4.0 | ||
""" | ||
|
||
stop = proxy_method(Actor.stop) | ||
on_start = proxy_method(Actor.on_start) | ||
on_stop = proxy_method(Actor.on_stop) | ||
on_failure = proxy_method(Actor.on_failure) | ||
on_receive = proxy_method(Actor.on_receive) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
from __future__ import annotations | ||
|
||
from dataclasses import dataclass | ||
from typing import TYPE_CHECKING, Iterator, cast | ||
|
||
import pytest | ||
|
||
import pykka | ||
from pykka import Actor, ActorProxy | ||
from pykka.typing import ActorMemberMixin, proxy_field, proxy_method | ||
|
||
if TYPE_CHECKING: | ||
from tests.types import Runtime | ||
|
||
|
||
@dataclass | ||
class Constants: | ||
pi: float | ||
|
||
|
||
class CircleActor(Actor): | ||
constants = pykka.traversable(Constants(pi=3.14)) | ||
text: str = "The fox crossed the road." | ||
|
||
def area(self, radius: float) -> float: | ||
return self.constants.pi * radius**2 | ||
|
||
|
||
class ConstantsProxy: | ||
pi = proxy_field(CircleActor.constants.pi) | ||
|
||
|
||
class FooProxy(ActorMemberMixin, ActorProxy[CircleActor]): | ||
numbers: ConstantsProxy | ||
text = proxy_field(CircleActor.text) | ||
area = proxy_method(CircleActor.area) | ||
|
||
|
||
@pytest.fixture(scope="module") | ||
def actor_class(runtime: Runtime) -> type[CircleActor]: | ||
class FooActorImpl(CircleActor, runtime.actor_class): # type: ignore[name-defined] # noqa: E501 | ||
pass | ||
|
||
return FooActorImpl | ||
|
||
|
||
@pytest.fixture() | ||
def proxy( | ||
actor_class: type[CircleActor], | ||
) -> Iterator[FooProxy]: | ||
proxy = cast(FooProxy, actor_class.start().proxy()) | ||
yield proxy | ||
proxy.stop() | ||
|
||
|
||
def test_proxy_field(proxy: FooProxy) -> None: | ||
assert proxy.text.get() == "The fox crossed the road." | ||
|
||
|
||
def test_proxy_traversable_object_field(proxy: FooProxy) -> None: | ||
assert proxy.constants.pi.get() == 3.14 | ||
|
||
|
||
def test_proxy_method(proxy: FooProxy) -> None: | ||
assert proxy.area(2.0).get() == 12.56 | ||
|
||
|
||
def test_proxy_to_actor_methods(proxy: FooProxy) -> None: | ||
assert proxy.stop().get() is None |