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 2 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.
38 changes: 34 additions & 4 deletions synapse/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,16 @@
import abc
import functools
import logging
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, TypeVar, cast
from typing import (
TYPE_CHECKING,
Callable,
Dict,
List,
Optional,
TypeAlias,
TypeVar,
cast,
)

from twisted.internet.interfaces import IOpenSSLContextFactory
from twisted.internet.tcp import Port
Expand Down Expand Up @@ -142,10 +151,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 +213,7 @@ def _get(self: "HomeServer") -> T:

return dep

return _get
return cast(F, _get)


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