summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2023-04-12 14:25:36 -0700
committerDavid Lord <davidism@gmail.com>2023-04-12 14:25:36 -0700
commitbc89c33b2061004b9f80c36f8dd580563f3b569e (patch)
tree010b8c57b24b3d7d6ad278d51079f575d63c9e2f
parent11b46cf60db28c0dede87a93d62a2578c4cdca7d (diff)
downloadblinker-bc89c33b2061004b9f80c36f8dd580563f3b569e.tar.gz
lazy annotations
-rw-r--r--CHANGES.rst8
-rw-r--r--pyproject.toml1
-rw-r--r--src/blinker/__init__.py3
-rw-r--r--src/blinker/_saferef.py2
-rw-r--r--src/blinker/_utilities.py25
-rw-r--r--src/blinker/base.py80
6 files changed, 66 insertions, 53 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index acf469f..2ab8fed 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,3 +1,11 @@
+Version 1.6.2
+-------------
+
+Unreleased
+
+- Type annotations are not evaluated at runtime. typing-extensions is not a runtime
+ dependency. :pr:`94`
+
Version 1.6.1
-------------
diff --git a/pyproject.toml b/pyproject.toml
index ca0f5af..c58c26d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -24,7 +24,6 @@ classifiers = [
]
requires-python = ">= 3.7"
dynamic = ["version"]
-dependencies = ["typing-extensions>=4.2"]
[project.urls]
Homepage = "https://blinker.readthedocs.io"
diff --git a/src/blinker/__init__.py b/src/blinker/__init__.py
index 671732f..204b1ca 100644
--- a/src/blinker/__init__.py
+++ b/src/blinker/__init__.py
@@ -16,5 +16,4 @@ __all__ = [
"signal",
]
-
-__version__ = "1.6.1"
+__version__ = "1.6.2.dev"
diff --git a/src/blinker/_saferef.py b/src/blinker/_saferef.py
index 39ac90c..dcb70c1 100644
--- a/src/blinker/_saferef.py
+++ b/src/blinker/_saferef.py
@@ -108,7 +108,7 @@ class BoundMethodWeakref:
produce the same BoundMethodWeakref instance.
"""
- _all_instances = weakref.WeakValueDictionary() # type: ignore
+ _all_instances = weakref.WeakValueDictionary() # type: ignore[var-annotated]
def __new__(cls, target, on_delete=None, *arguments, **named):
"""Create new instance or return current instance.
diff --git a/src/blinker/_utilities.py b/src/blinker/_utilities.py
index 9201046..068d94c 100644
--- a/src/blinker/_utilities.py
+++ b/src/blinker/_utilities.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import asyncio
import inspect
import sys
@@ -40,7 +42,7 @@ class symbol:
"""
- symbols = {} # type: ignore
+ symbols = {} # type: ignore[var-annotated]
def __new__(cls, name):
try:
@@ -51,9 +53,9 @@ class symbol:
def hashable_identity(obj: object) -> IdentityType:
if hasattr(obj, "__func__"):
- return (id(obj.__func__), id(obj.__self__)) # type: ignore
+ return (id(obj.__func__), id(obj.__self__)) # type: ignore[attr-defined]
elif hasattr(obj, "im_func"):
- return (id(obj.im_func), id(obj.im_self)) # type: ignore
+ return (id(obj.im_func), id(obj.im_self)) # type: ignore[attr-defined]
elif isinstance(obj, (int, str)):
return obj
else:
@@ -70,7 +72,7 @@ class annotatable_weakref(ref):
sender_id: t.Optional[IdentityType]
-def reference( # type: ignore
+def reference( # type: ignore[no-untyped-def]
object, callback=None, **annotations
) -> annotatable_weakref:
"""Return an annotated weak ref."""
@@ -80,7 +82,7 @@ def reference( # type: ignore
weak = annotatable_weakref(object, callback)
for key, value in annotations.items():
setattr(weak, key, value)
- return weak # type: ignore
+ return weak # type: ignore[no-any-return]
def callable_reference(object, callback=None):
@@ -118,7 +120,7 @@ def is_coroutine_function(func: t.Any) -> bool:
# such that it isn't determined as a coroutine function
# without an explicit check.
try:
- from unittest.mock import AsyncMock # type: ignore
+ from unittest.mock import AsyncMock # type: ignore[attr-defined]
if isinstance(func, AsyncMock):
return True
@@ -132,8 +134,9 @@ def is_coroutine_function(func: t.Any) -> bool:
func = func.func
if not inspect.isfunction(func):
return False
- result = bool(func.__code__.co_flags & inspect.CO_COROUTINE)
- return (
- result
- or getattr(func, "_is_coroutine", None) is asyncio.coroutines._is_coroutine # type: ignore # noqa: B950
- )
+
+ if func.__code__.co_flags & inspect.CO_COROUTINE:
+ return True
+
+ acic = asyncio.coroutines._is_coroutine # type: ignore[attr-defined]
+ return getattr(func, "_is_coroutine", None) is acic
diff --git a/src/blinker/base.py b/src/blinker/base.py
index 997fef5..80e24e2 100644
--- a/src/blinker/base.py
+++ b/src/blinker/base.py
@@ -7,14 +7,14 @@ each manages its own receivers and message emission.
The :func:`signal` function provides singleton behavior for named signals.
"""
+from __future__ import annotations
+
import typing as t
from collections import defaultdict
from contextlib import contextmanager
from warnings import warn
from weakref import WeakValueDictionary
-import typing_extensions as te
-
from blinker._utilities import annotatable_weakref
from blinker._utilities import hashable_identity
from blinker._utilities import IdentityType
@@ -24,18 +24,20 @@ from blinker._utilities import reference
from blinker._utilities import symbol
from blinker._utilities import WeakTypes
+if t.TYPE_CHECKING:
+ import typing_extensions as te
-ANY = symbol("ANY")
-ANY.__doc__ = 'Token for "any sender".'
-ANY_ID = 0
+ T_callable = t.TypeVar("T_callable", bound=t.Callable[..., t.Any])
-T_callable = t.TypeVar("T_callable", bound=t.Callable)
+ T = t.TypeVar("T")
+ P = te.ParamSpec("P")
-T = t.TypeVar("T")
-P = te.ParamSpec("P")
+ AsyncWrapperType = t.Callable[[t.Callable[P, T]], t.Callable[P, t.Awaitable[T]]]
+ SyncWrapperType = t.Callable[[t.Callable[P, t.Awaitable[T]]], t.Callable[P, T]]
-AsyncWrapperType = t.Callable[[t.Callable[P, T]], t.Callable[P, t.Awaitable[T]]]
-SyncWrapperType = t.Callable[[t.Callable[P, t.Awaitable[T]]], t.Callable[P, T]]
+ANY = symbol("ANY")
+ANY.__doc__ = 'Token for "any sender".'
+ANY_ID = 0
class Signal:
@@ -46,7 +48,7 @@ class Signal:
ANY = ANY
@lazy_property
- def receiver_connected(self) -> "Signal":
+ def receiver_connected(self) -> Signal:
"""Emitted after each :meth:`connect`.
The signal sender is the signal instance, and the :meth:`connect`
@@ -58,7 +60,7 @@ class Signal:
return Signal(doc="Emitted after a receiver connects.")
@lazy_property
- def receiver_disconnected(self) -> "Signal":
+ def receiver_disconnected(self) -> Signal:
"""Emitted after :meth:`disconnect`.
The sender is the signal instance, and the :meth:`disconnect` arguments
@@ -81,7 +83,7 @@ class Signal:
"""
return Signal(doc="Emitted after a receiver disconnects.")
- def __init__(self, doc: t.Optional[str] = None) -> None:
+ def __init__(self, doc: str | None = None) -> None:
"""
:param doc: optional. If provided, will be assigned to the signal's
__doc__ attribute.
@@ -95,13 +97,11 @@ class Signal:
#: internal :class:`Signal` implementation, however the boolean value
#: of the mapping is useful as an extremely efficient check to see if
#: any receivers are connected to the signal.
- self.receivers: t.Dict[
- IdentityType, t.Union[t.Callable, annotatable_weakref]
- ] = {}
+ self.receivers: dict[IdentityType, t.Callable | annotatable_weakref] = {}
self.is_muted = False
- self._by_receiver: t.Dict[IdentityType, t.Set[IdentityType]] = defaultdict(set)
- self._by_sender: t.Dict[IdentityType, t.Set[IdentityType]] = defaultdict(set)
- self._weak_senders: t.Dict[IdentityType, annotatable_weakref] = {}
+ self._by_receiver: dict[IdentityType, set[IdentityType]] = defaultdict(set)
+ self._by_sender: dict[IdentityType, set[IdentityType]] = defaultdict(set)
+ self._weak_senders: dict[IdentityType, annotatable_weakref] = {}
def connect(
self, receiver: T_callable, sender: t.Any = ANY, weak: bool = True
@@ -125,7 +125,8 @@ class Signal:
"""
receiver_id = hashable_identity(receiver)
- receiver_ref: t.Union[annotatable_weakref, T_callable]
+ receiver_ref: T_callable | annotatable_weakref
+
if weak:
receiver_ref = reference(receiver, self._cleanup_receiver)
receiver_ref.receiver_id = receiver_id
@@ -271,9 +272,9 @@ class Signal:
def send(
self,
*sender: t.Any,
- _async_wrapper: t.Optional[AsyncWrapperType] = None,
+ _async_wrapper: AsyncWrapperType | None = None,
**kwargs: t.Any,
- ) -> t.List[t.Tuple[t.Callable, t.Any]]:
+ ) -> list[tuple[t.Callable, t.Any]]:
"""Emit this signal on behalf of *sender*, passing on ``kwargs``.
Returns a list of 2-tuples, pairing receivers with their return
@@ -296,15 +297,16 @@ class Signal:
if _async_wrapper is None:
raise RuntimeError("Cannot send to a coroutine function")
receiver = _async_wrapper(receiver)
- results.append((receiver, receiver(sender, **kwargs))) # type: ignore
+ result = receiver(sender, **kwargs) # type: ignore[call-arg]
+ results.append((receiver, result))
return results
async def send_async(
self,
*sender: t.Any,
- _sync_wrapper: t.Optional[SyncWrapperType] = None,
+ _sync_wrapper: SyncWrapperType | None = None,
**kwargs: t.Any,
- ) -> t.List[t.Tuple[t.Callable, t.Any]]:
+ ) -> list[tuple[t.Callable, t.Any]]:
"""Emit this signal on behalf of *sender*, passing on ``kwargs``.
Returns a list of 2-tuples, pairing receivers with their return
@@ -326,11 +328,12 @@ class Signal:
if not is_coroutine_function(receiver):
if _sync_wrapper is None:
raise RuntimeError("Cannot send to a non-coroutine function")
- receiver = _sync_wrapper(receiver) # type: ignore
- results.append((receiver, await receiver(sender, **kwargs))) # type: ignore
+ receiver = _sync_wrapper(receiver) # type: ignore[arg-type]
+ result = await receiver(sender, **kwargs) # type: ignore[call-arg, misc]
+ results.append((receiver, result))
return results
- def _extract_sender(self, sender):
+ def _extract_sender(self, sender: t.Any) -> t.Any:
if not self.receivers:
# Ensure correct signature even on no-op sends, disable with -O
# for lowest possible cost.
@@ -371,7 +374,7 @@ class Signal:
def receivers_for(
self, sender: t.Any
- ) -> t.Generator[t.Union[t.Callable, annotatable_weakref], None, None]:
+ ) -> t.Generator[t.Callable | annotatable_weakref, None, None]:
"""Iterate all live receivers listening for *sender*."""
# TODO: test receivers_for(ANY)
if self.receivers:
@@ -390,8 +393,7 @@ class Signal:
self._disconnect(receiver_id, ANY_ID)
continue
receiver = strong
- receiver = t.cast(t.Union[t.Callable, annotatable_weakref], receiver)
- yield receiver
+ yield receiver # type: ignore[misc]
def disconnect(self, receiver: t.Callable, sender: t.Any = ANY) -> None:
"""Disconnect *receiver* from this signal's events.
@@ -495,7 +497,7 @@ call signature. This global signal is planned to be removed in 1.6.
class NamedSignal(Signal):
"""A named generic notification emitter."""
- def __init__(self, name: str, doc: t.Optional[str] = None) -> None:
+ def __init__(self, name: str, doc: str | None = None) -> None:
Signal.__init__(self, doc)
#: The name of this signal.
@@ -509,16 +511,17 @@ class NamedSignal(Signal):
class Namespace(dict):
"""A mapping of signal names to signals."""
- def signal(self, name: str, doc: t.Optional[str] = None) -> NamedSignal:
+ def signal(self, name: str, doc: str | None = None) -> NamedSignal:
"""Return the :class:`NamedSignal` *name*, creating it if required.
Repeated calls to this function will return the same signal object.
"""
try:
- return self[name] # type: ignore
+ return self[name] # type: ignore[no-any-return]
except KeyError:
- return self.setdefault(name, NamedSignal(name, doc)) # type: ignore
+ result = self.setdefault(name, NamedSignal(name, doc))
+ return result # type: ignore[no-any-return]
class WeakNamespace(WeakValueDictionary):
@@ -532,16 +535,17 @@ class WeakNamespace(WeakValueDictionary):
"""
- def signal(self, name: str, doc: t.Optional[str] = None) -> NamedSignal:
+ def signal(self, name: str, doc: str | None = None) -> NamedSignal:
"""Return the :class:`NamedSignal` *name*, creating it if required.
Repeated calls to this function will return the same signal object.
"""
try:
- return self[name] # type: ignore
+ return self[name] # type: ignore[no-any-return]
except KeyError:
- return self.setdefault(name, NamedSignal(name, doc)) # type: ignore
+ result = self.setdefault(name, NamedSignal(name, doc))
+ return result # type: ignore[no-any-return]
signal = Namespace().signal