Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Give PyCharm some help with @cache_in_self #15238

Merged
merged 3 commits into from
Mar 9, 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
1 change: 1 addition & 0 deletions changelog.d/15238.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve type hints.
29 changes: 26 additions & 3 deletions synapse/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import logging
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, TypeVar, cast

from typing_extensions import TypeAlias

from twisted.internet.interfaces import IOpenSSLContextFactory
from twisted.internet.tcp import Port
from twisted.web.iweb import IPolicyForHTTPS
Expand Down Expand Up @@ -142,10 +144,31 @@
from synapse.handlers.saml import SamlHandler


T = TypeVar("T")
# The annotation for `cache_in_self` used to be
# def (builder: Callable[["HomeServer"],T]) -> Callable[["HomeServer"],T]
# which mypy was happy with.
#
# But PyCharm was confused by this. If `foo` was decorated by `@cache_in_self`, then
# an expression like `hs.foo()`
#
# - would erroneously warn that we hadn't provided a `hs` argument to foo (PyCharm
# confused about boundmethods and unbound methods?), and
# - would be considered to have type `Any`, making for a poor autocomplete and
# cross-referencing experience.
#
# Instead, use a typevar `F` to express that `@cache_in_self` returns exactly the
# same type it receives. This isn't strictly true [*], but it's more than good
# enough to keep PyCharm and mypy happy.
#
# [*]: (e.g. `builder` could be an object with a __call__ attribute rather than a
# types.FunctionType instance, whereas the return value is always a
# types.FunctionType instance.)

T: TypeAlias = object
F = TypeVar("F", bound=Callable[["HomeServer"], T])


def cache_in_self(builder: Callable[["HomeServer"], T]) -> Callable[["HomeServer"], T]:
def cache_in_self(builder: F) -> F:
"""Wraps a function called e.g. `get_foo`, checking if `self.foo` exists and
returning if so. If not, calls the given function and sets `self.foo` to it.

Expand Down Expand Up @@ -183,7 +206,7 @@ def _get(self: "HomeServer") -> T:

return dep

return _get
return cast(F, _get)


class HomeServer(metaclass=abc.ABCMeta):
Expand Down