Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve types through typing all tests #195

Merged
merged 6 commits into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 2 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ toml = "^0.10.2"
mypy = "^1.3.0"

[tool.poetry.group.pyright.dependencies]
pyright = "^1.1.311"
pyright = "^1.1.313"

[tool.poetry.group.ruff.dependencies]
ruff = "^0.0.270"
Expand All @@ -48,18 +48,14 @@ pytest-mock = "^3.10.0"
target-version = ["py38", "py39", "py310", "py311"]

[tool.mypy]
disallow_untyped_defs = true
no_implicit_optional = true
strict_equality = true
warn_return_any = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_unused_configs = true

[[tool.mypy.overrides]]
# Options that only applies to src, and not tests
module = "pykka.*"
disallow_untyped_defs = true

[tool.ruff]
select = [
"A", # flake8-builtins
Expand Down
20 changes: 14 additions & 6 deletions src/pykka/_actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import sys
import threading
import uuid
from typing import TYPE_CHECKING, Any, Optional, Protocol, Sequence
from typing import TYPE_CHECKING, Any, Optional, Protocol, Sequence, TypeVar

from pykka import ActorDeadError, ActorRef, ActorRegistry, messages

Expand All @@ -19,6 +19,9 @@
logger = logging.getLogger("pykka")


A = TypeVar("A", bound="Actor")


class ActorInbox(Protocol):
def put(self, envelope: Envelope[Any], /) -> None:
...
Expand Down Expand Up @@ -75,10 +78,10 @@ def a_method(self, ...):

@classmethod
def start(
cls,
cls: type[A],
*args: Any,
**kwargs: Any,
) -> ActorRef:
) -> ActorRef[A]:
"""Start an actor.

Starting an actor also registers it in the
Expand Down Expand Up @@ -151,8 +154,13 @@ def _start_actor_loop(self) -> None:
#: friends to put messages in the inbox.
actor_inbox: ActorInbox

#: The actor's :class:`ActorRef` instance.
actor_ref: ActorRef
_actor_ref: ActorRef[Any]

@property
def actor_ref(self: A) -> ActorRef[A]:
"""The actor's :class:`ActorRef` instance."""
# This property only exists to improve the typing of the ActorRef.
return self._actor_ref

#: A :class:`threading.Event` representing whether or not the actor should
#: continue processing messages. Use :meth:`stop` to change it.
Expand Down Expand Up @@ -184,7 +192,7 @@ def __init__(
self.actor_inbox = self._create_actor_inbox()
self.actor_stopped = threading.Event()

self.actor_ref = ActorRef(self)
self._actor_ref = ActorRef(self)

def __str__(self) -> str:
return f"{self.__class__.__name__} ({self.actor_urn})"
Expand Down
8 changes: 5 additions & 3 deletions src/pykka/_future.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@

T = TypeVar("T")
J = TypeVar("J") # For when T is Iterable[J]

M = TypeVar("M") # For Future.map()
R = TypeVar("R") # For Future.reduce()
M = TypeVar("M") # Result of Future.map()
R = TypeVar("R") # Result of Future.reduce()

GetHookFunc: TypeAlias = Callable[[Optional[float]], T]

Expand All @@ -47,6 +46,9 @@ def __init__(self) -> None:
self._get_hook = None
self._get_hook_result = None

def __repr__(self) -> str:
return "<pykka.Future>"

def get(
self,
timeout: Optional[float] = None,
Expand Down
35 changes: 18 additions & 17 deletions src/pykka/_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
from typing import (
TYPE_CHECKING,
Any,
Generic,
NamedTuple,
Optional,
Sequence,
TypeVar,
Union,
)

from pykka import ActorDeadError, messages
Expand All @@ -23,7 +23,8 @@
logger = logging.getLogger("pykka")


_T = TypeVar("_T")
T = TypeVar("T")
A = TypeVar("A", bound="Actor")

AttrPath: TypeAlias = Sequence[str]

Expand All @@ -33,7 +34,7 @@ class AttrInfo(NamedTuple):
traversable: bool


class ActorProxy:
class ActorProxy(Generic[A]):
"""An :class:`ActorProxy` wraps an :class:`ActorRef <pykka.ActorRef>` instance.

The proxy allows the referenced actor to be used through regular
Expand Down Expand Up @@ -132,17 +133,17 @@ def do_more_work(self):
"""

#: The actor's :class:`pykka.ActorRef` instance.
actor_ref: ActorRef
actor_ref: ActorRef[A]

_actor: Actor
_actor: A
_attr_path: AttrPath
_known_attrs: dict[AttrPath, AttrInfo]
_actor_proxies: dict[AttrPath, ActorProxy]
_callable_proxies: dict[AttrPath, CallableProxy]
_actor_proxies: dict[AttrPath, ActorProxy[A]]
_callable_proxies: dict[AttrPath, CallableProxy[A]]

def __init__(
self,
actor_ref: ActorRef,
actor_ref: ActorRef[A],
attr_path: Optional[AttrPath] = None,
) -> None:
if not actor_ref.is_alive():
Expand Down Expand Up @@ -229,7 +230,10 @@ def __eq__(
) -> bool:
if not isinstance(other, ActorProxy):
return False
if self._actor != other._actor: # noqa: SLF001
if (
self._actor
!= other._actor # noqa: SLF001 # pyright: ignore[reportUnknownMemberType]
):
return False
if self._attr_path != other._attr_path: # noqa: SLF001
return False
Expand All @@ -248,10 +252,7 @@ def __dir__(self) -> list[str]:
result += [attr_path[0] for attr_path in list(self._known_attrs.keys())]
return sorted(result)

def __getattr__(
self,
name: str,
) -> Union[CallableProxy, ActorProxy, Future[Any]]:
def __getattr__(self, name: str) -> Any:
"""Get a field or callable from the actor."""
attr_path: AttrPath = (*self._attr_path, name)

Expand Down Expand Up @@ -294,7 +295,7 @@ def __setattr__(
return None


class CallableProxy:
class CallableProxy(Generic[A]):
"""Proxy to a single method.

:class:`CallableProxy` instances are returned when accessing methods on a
Expand All @@ -311,12 +312,12 @@ class CallableProxy:
proxy.do_work.defer()
"""

actor_ref: ActorRef
actor_ref: ActorRef[A]
_attr_path: AttrPath

def __init__(
self,
actor_ref: ActorRef,
actor_ref: ActorRef[A],
attr_path: AttrPath,
) -> None:
self.actor_ref = actor_ref
Expand Down Expand Up @@ -362,7 +363,7 @@ def defer(
self.actor_ref.tell(message)


def traversable(obj: _T) -> _T:
def traversable(obj: T) -> T:
"""Mark an actor attribute as traversable.

The traversable marker makes the actor attribute's own methods and
Expand Down
28 changes: 20 additions & 8 deletions src/pykka/_ref.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Literal, Optional, Union, overload
from typing import (
TYPE_CHECKING,
Any,
Generic,
Literal,
Optional,
TypeVar,
Union,
overload,
)

from pykka import ActorDeadError, ActorProxy
from pykka._envelope import Envelope
Expand All @@ -9,13 +18,16 @@
if TYPE_CHECKING:
from threading import Event

from pykka import Future
from pykka._actor import Actor, ActorInbox
from pykka import Actor, Future
from pykka._actor import ActorInbox

Check warning on line 22 in src/pykka/_ref.py

View check run for this annotation

Codecov / codecov/patch

src/pykka/_ref.py#L21-L22

Added lines #L21 - L22 were not covered by tests

__all__ = ["ActorRef"]


class ActorRef:
A = TypeVar("A", bound="Actor")


class ActorRef(Generic[A]):
"""Reference to a running actor which may safely be passed around.

:class:`ActorRef` instances are returned by :meth:`Actor.start` and the
Expand All @@ -27,7 +39,7 @@
"""

#: The class of the referenced actor.
actor_class: type[Actor]
actor_class: type[A]

#: See :attr:`Actor.actor_urn`.
actor_urn: str
Expand All @@ -39,8 +51,8 @@
actor_stopped: Event

def __init__(
self,
actor: Actor,
self: ActorRef[A],
actor: A,
) -> None:
self._actor = actor
self.actor_class = actor.__class__
Expand Down Expand Up @@ -223,7 +235,7 @@

return converted_future

def proxy(self) -> ActorProxy:
def proxy(self: ActorRef[A]) -> ActorProxy[A]:
"""Wrap the :class:`ActorRef` in an :class:`ActorProxy <pykka.ActorProxy>`.

Using this method like this::
Expand Down
32 changes: 21 additions & 11 deletions src/pykka/_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,25 @@

import logging
import threading
from typing import TYPE_CHECKING, Any, ClassVar, Literal, Optional, Union, overload
from typing import (
TYPE_CHECKING,
Any,
ClassVar,
Literal,
Optional,
TypeVar,
Union,
overload,
)

if TYPE_CHECKING:
from pykka import Actor, ActorRef, Future

logger = logging.getLogger("pykka")
__all__ = ["ActorRegistry"]

logger = logging.getLogger("pykka")

__all__ = ["ActorRegistry"]
A = TypeVar("A", bound="Actor")


class ActorRegistry:
Expand All @@ -19,7 +29,7 @@ class ActorRegistry:
Contains global state, but should be thread-safe.
"""

_actor_refs: ClassVar[list[ActorRef]] = []
_actor_refs: ClassVar[list[ActorRef[Any]]] = []
_actor_refs_lock: ClassVar[threading.RLock] = threading.RLock()

@classmethod
Expand Down Expand Up @@ -49,7 +59,7 @@ def broadcast(
ref.tell(message)

@classmethod
def get_all(cls) -> list[ActorRef]:
def get_all(cls) -> list[ActorRef[Any]]:
"""Get all running actors.

:returns: list of :class:`pykka.ActorRef`
Expand All @@ -60,8 +70,8 @@ def get_all(cls) -> list[ActorRef]:
@classmethod
def get_by_class(
cls,
actor_class: type[Actor],
) -> list[ActorRef]:
actor_class: type[A],
) -> list[ActorRef[A]]:
"""Get all running actors of the given class or a subclass.

:param actor_class: actor class, or any superclass of the actor
Expand All @@ -80,7 +90,7 @@ def get_by_class(
def get_by_class_name(
cls,
actor_class_name: str,
) -> list[ActorRef]:
) -> list[ActorRef[Any]]:
"""Get all running actors of the given class name.

:param actor_class_name: actor class name
Expand All @@ -99,7 +109,7 @@ def get_by_class_name(
def get_by_urn(
cls,
actor_urn: str,
) -> Optional[ActorRef]:
) -> Optional[ActorRef[Any]]:
"""Get an actor by its universally unique URN.

:param actor_urn: actor URN
Expand All @@ -116,7 +126,7 @@ def get_by_urn(
@classmethod
def register(
cls,
actor_ref: ActorRef,
actor_ref: ActorRef[Any],
) -> None:
"""Register an :class:`ActorRef` in the registry.

Expand Down Expand Up @@ -188,7 +198,7 @@ def stop_all(
@classmethod
def unregister(
cls,
actor_ref: ActorRef,
actor_ref: ActorRef[A],
) -> None:
"""Remove an :class:`ActorRef <pykka.ActorRef>` from the registry.

Expand Down
4 changes: 2 additions & 2 deletions src/pykka/_threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ class ThreadingFutureResult(NamedTuple):
exc_info: Optional[OptExcInfo] = None


F = TypeVar("F")
T = TypeVar("T")


class ThreadingFuture(Future[F]):
class ThreadingFuture(Future[T]):
"""Implementation of :class:`Future` for use with regular Python threads`.

The future is implemented using a :class:`queue.Queue`.
Expand Down