Skip to content

Commit

Permalink
Add an initial set of type hints
Browse files Browse the repository at this point in the history
These focus on the public API with the private aspect omitted. This is
because these are hard to type, and may not be required with modern
python. However, this should still be useful if adopted for code
that uses blinker.
  • Loading branch information
pgjones committed Apr 2, 2023
1 parent 5ed9c95 commit fae6164
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 48 deletions.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
include CHANGES.rst
include tox.ini
include src/blinker/py.typed
graft docs
prune docs/_build
graft tests
Expand Down
22 changes: 22 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ classifiers = [
]
requires-python = ">= 3.7"
dynamic = ["version"]
dependencies = ["typing-extensions"]

[project.urls]
Homepage = "https://blinker.readthedocs.io"
Expand All @@ -36,6 +37,27 @@ Chat = "https://discord.gg/pallets"
file = "README.rst"
content-type = "text/x-rst"

[tool.mypy]
python_version = "3.7"
files = ["src/blinker"]
show_error_codes = true
pretty = true
#strict = true
allow_redefinition = true
disallow_subclassing_any = true
#disallow_untyped_calls = true
#disallow_untyped_defs = true
disallow_incomplete_defs = true
no_implicit_optional = true
local_partial_types = true
no_implicit_reexport = true
strict_equality = true
warn_redundant_casts = true
warn_unused_configs = true
warn_unused_ignores = true
warn_return_any = true
#warn_unreachable = True

[tool.setuptools]
license-files = ["LICENSE.rst"]
include-package-data = false
Expand Down
1 change: 1 addition & 0 deletions requirements/typing.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mypy
13 changes: 13 additions & 0 deletions requirements/typing.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# SHA1:7983aaa01d64547827c20395d77e248c41b2572f
#
# This file is autogenerated by pip-compile-multi
# To update, run:
#
# pip-compile-multi
#
mypy==1.0.1
# via -r requirements/typing.in
mypy-extensions==1.0.0
# via mypy
typing-extensions==4.5.0
# via mypy
2 changes: 1 addition & 1 deletion src/blinker/_saferef.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class BoundMethodWeakref:
produce the same BoundMethodWeakref instance.
"""

_all_instances = weakref.WeakValueDictionary()
_all_instances = weakref.WeakValueDictionary() # type: ignore

def __new__(cls, target, on_delete=None, *arguments, **named):
"""Create new instance or return current instance.
Expand Down
27 changes: 17 additions & 10 deletions src/blinker/_utilities.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import asyncio
import inspect
import sys
import typing as t
from functools import partial
from typing import Any
from weakref import ref

from blinker._saferef import BoundMethodWeakref

IdentityType = t.Union[t.Tuple[int, int], str, int]


class _symbol:
def __init__(self, name):
Expand Down Expand Up @@ -38,7 +40,7 @@ class symbol:
"""

symbols = {}
symbols = {} # type: ignore

def __new__(cls, name):
try:
Expand All @@ -47,11 +49,11 @@ def __new__(cls, name):
return cls.symbols.setdefault(name, _symbol(name))


def hashable_identity(obj):
def hashable_identity(obj: object) -> IdentityType:
if hasattr(obj, "__func__"):
return (id(obj.__func__), id(obj.__self__))
return (id(obj.__func__), id(obj.__self__)) # type: ignore
elif hasattr(obj, "im_func"):
return (id(obj.im_func), id(obj.im_self))
return (id(obj.im_func), id(obj.im_self)) # type: ignore
elif isinstance(obj, (int, str)):
return obj
else:
Expand All @@ -64,16 +66,21 @@ def hashable_identity(obj):
class annotatable_weakref(ref):
"""A weakref.ref that supports custom instance attributes."""

receiver_id: t.Optional[IdentityType]
sender_id: t.Optional[IdentityType]


def reference(object, callback=None, **annotations):
def reference( # type: ignore
object, callback=None, **annotations
) -> annotatable_weakref:
"""Return an annotated weak ref."""
if callable(object):
weak = callable_reference(object, callback)
else:
weak = annotatable_weakref(object, callback)
for key, value in annotations.items():
setattr(weak, key, value)
return weak
return weak # type: ignore


def callable_reference(object, callback=None):
Expand All @@ -100,7 +107,7 @@ def __get__(self, obj, cls):
return value


def is_coroutine_function(func: Any) -> bool:
def is_coroutine_function(func: t.Any) -> bool:
# Python < 3.8 does not correctly determine partially wrapped
# coroutine functions are coroutine functions, hence the need for
# this to exist. Code taken from CPython.
Expand All @@ -111,7 +118,7 @@ def is_coroutine_function(func: Any) -> bool:
# such that it isn't determined as a coroutine function
# without an explicit check.
try:
from unittest.mock import AsyncMock
from unittest.mock import AsyncMock # type: ignore

if isinstance(func, AsyncMock):
return True
Expand All @@ -128,5 +135,5 @@ def is_coroutine_function(func: Any) -> bool:
result = bool(func.__code__.co_flags & inspect.CO_COROUTINE)
return (
result
or getattr(func, "_is_coroutine", None) is asyncio.coroutines._is_coroutine
or getattr(func, "_is_coroutine", None) is asyncio.coroutines._is_coroutine # type: ignore # noqa: B950
)
Loading

0 comments on commit fae6164

Please sign in to comment.