diff --git a/synapse/handlers/auth.py b/synapse/handlers/auth.py
index 57a6854b1e..cf12b55d21 100644
--- a/synapse/handlers/auth.py
+++ b/synapse/handlers/auth.py
@@ -201,7 +201,7 @@ class AuthHandler:
for auth_checker_class in INTERACTIVE_AUTH_CHECKERS:
inst = auth_checker_class(hs)
if inst.is_enabled():
- self.checkers[inst.AUTH_TYPE] = inst # type: ignore
+ self.checkers[inst.AUTH_TYPE] = inst
self.bcrypt_rounds = hs.config.registration.bcrypt_rounds
diff --git a/synapse/handlers/ui_auth/checkers.py b/synapse/handlers/ui_auth/checkers.py
index 332edcca24..78a75bfed6 100644
--- a/synapse/handlers/ui_auth/checkers.py
+++ b/synapse/handlers/ui_auth/checkers.py
@@ -13,7 +13,8 @@
# limitations under the License.
import logging
-from typing import TYPE_CHECKING, Any
+from abc import ABC, abstractmethod
+from typing import TYPE_CHECKING, Any, ClassVar, Sequence, Type
from twisted.web.client import PartialDownloadError
@@ -27,19 +28,28 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
-class UserInteractiveAuthChecker:
+class UserInteractiveAuthChecker(ABC):
"""Abstract base class for an interactive auth checker"""
- def __init__(self, hs: "HomeServer"):
+ # This should really be an "abstract class property", i.e. it should
+ # be an error to instantiate a subclass that doesn't specify an AUTH_TYPE.
+ # But calling this a `ClassVar` is simpler than a decorator stack of
+ # @property @abstractmethod and @classmethod (if that's even the right order).
+ AUTH_TYPE: ClassVar[str]
+
+ def __init__(self, hs: "HomeServer"): # noqa: B027
pass
+ @abstractmethod
def is_enabled(self) -> bool:
"""Check if the configuration of the homeserver allows this checker to work
Returns:
True if this login type is enabled.
"""
+ raise NotImplementedError()
+ @abstractmethod
async def check_auth(self, authdict: dict, clientip: str) -> Any:
"""Given the authentication dict from the client, attempt to check this step
@@ -304,7 +314,7 @@ class RegistrationTokenAuthChecker(UserInteractiveAuthChecker):
)
-INTERACTIVE_AUTH_CHECKERS = [
+INTERACTIVE_AUTH_CHECKERS: Sequence[Type[UserInteractiveAuthChecker]] = [
DummyAuthChecker,
TermsAuthChecker,
RecaptchaAuthChecker,
diff --git a/synapse/http/matrixfederationclient.py b/synapse/http/matrixfederationclient.py
index b92f1d3d1a..312aab4dcc 100644
--- a/synapse/http/matrixfederationclient.py
+++ b/synapse/http/matrixfederationclient.py
@@ -1267,7 +1267,7 @@ class MatrixFederationHttpClient:
def _flatten_response_never_received(e: BaseException) -> str:
if hasattr(e, "reasons"):
reasons = ", ".join(
- _flatten_response_never_received(f.value) for f in e.reasons # type: ignore[attr-defined]
+ _flatten_response_never_received(f.value) for f in e.reasons
)
return "%s:[%s]" % (type(e).__name__, reasons)
diff --git a/synapse/logging/opentracing.py b/synapse/logging/opentracing.py
index 6c7cf1b294..5aed71262f 100644
--- a/synapse/logging/opentracing.py
+++ b/synapse/logging/opentracing.py
@@ -188,7 +188,7 @@ from typing import (
)
import attr
-from typing_extensions import ParamSpec
+from typing_extensions import Concatenate, ParamSpec
from twisted.internet import defer
from twisted.web.http import Request
@@ -445,7 +445,7 @@ def init_tracer(hs: "HomeServer") -> None:
opentracing = None # type: ignore[assignment]
return
- if not opentracing or not JaegerConfig:
+ if opentracing is None or JaegerConfig is None:
raise ConfigError(
"The server has been configured to use opentracing but opentracing is not "
"installed."
@@ -872,7 +872,7 @@ def extract_text_map(carrier: Dict[str, str]) -> Optional["opentracing.SpanConte
def _custom_sync_async_decorator(
func: Callable[P, R],
- wrapping_logic: Callable[[Callable[P, R], Any, Any], ContextManager[None]],
+ wrapping_logic: Callable[Concatenate[Callable[P, R], P], ContextManager[None]],
) -> Callable[P, R]:
"""
Decorates a function that is sync or async (coroutines), or that returns a Twisted
@@ -902,10 +902,14 @@ def _custom_sync_async_decorator(
"""
if inspect.iscoroutinefunction(func):
-
+ # In this branch, R = Awaitable[RInner], for some other type RInner
@wraps(func)
- async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
+ async def _wrapper(
+ *args: P.args, **kwargs: P.kwargs
+ ) -> Any: # Return type is RInner
with wrapping_logic(func, *args, **kwargs):
+ # type-ignore: func() returns R, but mypy doesn't know that R is
+ # Awaitable here.
return await func(*args, **kwargs) # type: ignore[misc]
else:
@@ -972,7 +976,11 @@ def trace_with_opname(
if not opentracing:
return func
- return _custom_sync_async_decorator(func, _wrapping_logic)
+ # type-ignore: mypy seems to be confused by the ParamSpecs here.
+ # I think the problem is https://github.com/python/mypy/issues/12909
+ return _custom_sync_async_decorator(
+ func, _wrapping_logic # type: ignore[arg-type]
+ )
return _decorator
@@ -1018,7 +1026,9 @@ def tag_args(func: Callable[P, R]) -> Callable[P, R]:
set_tag(SynapseTags.FUNC_KWARGS, str(kwargs))
yield
- return _custom_sync_async_decorator(func, _wrapping_logic)
+ # type-ignore: mypy seems to be confused by the ParamSpecs here.
+ # I think the problem is https://github.com/python/mypy/issues/12909
+ return _custom_sync_async_decorator(func, _wrapping_logic) # type: ignore[arg-type]
@contextlib.contextmanager
diff --git a/synapse/rest/media/v1/_base.py b/synapse/rest/media/v1/_base.py
index d30878f704..6e035afcce 100644
--- a/synapse/rest/media/v1/_base.py
+++ b/synapse/rest/media/v1/_base.py
@@ -16,6 +16,7 @@
import logging
import os
import urllib
+from abc import ABC, abstractmethod
from types import TracebackType
from typing import Awaitable, Dict, Generator, List, Optional, Tuple, Type
@@ -284,13 +285,14 @@ async def respond_with_responder(
finish_request(request)
-class Responder:
+class Responder(ABC):
"""Represents a response that can be streamed to the requester.
Responder is a context manager which *must* be used, so that any resources
held can be cleaned up.
"""
+ @abstractmethod
def write_to_consumer(self, consumer: IConsumer) -> Awaitable:
"""Stream response into consumer
@@ -300,11 +302,12 @@ class Responder:
Returns:
Resolves once the response has finished being written
"""
+ raise NotImplementedError()
- def __enter__(self) -> None:
+ def __enter__(self) -> None: # noqa: B027
pass
- def __exit__(
+ def __exit__( # noqa: B027
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
diff --git a/synapse/storage/engines/__init__.py b/synapse/storage/engines/__init__.py
index a182e8a098..d1ccb7390a 100644
--- a/synapse/storage/engines/__init__.py
+++ b/synapse/storage/engines/__init__.py
@@ -25,7 +25,7 @@ try:
except ImportError:
class PostgresEngine(BaseDatabaseEngine): # type: ignore[no-redef]
- def __new__(cls, *args: object, **kwargs: object) -> NoReturn: # type: ignore[misc]
+ def __new__(cls, *args: object, **kwargs: object) -> NoReturn:
raise RuntimeError(
f"Cannot create {cls.__name__} -- psycopg2 module is not installed"
)
@@ -36,7 +36,7 @@ try:
except ImportError:
class Sqlite3Engine(BaseDatabaseEngine): # type: ignore[no-redef]
- def __new__(cls, *args: object, **kwargs: object) -> NoReturn: # type: ignore[misc]
+ def __new__(cls, *args: object, **kwargs: object) -> NoReturn:
raise RuntimeError(
f"Cannot create {cls.__name__} -- sqlite3 module is not installed"
)
diff --git a/synapse/storage/types.py b/synapse/storage/types.py
index 0031df1e06..56a0048539 100644
--- a/synapse/storage/types.py
+++ b/synapse/storage/types.py
@@ -12,7 +12,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from types import TracebackType
-from typing import Any, Iterator, List, Mapping, Optional, Sequence, Tuple, Type, Union
+from typing import (
+ Any,
+ Callable,
+ Iterator,
+ List,
+ Mapping,
+ Optional,
+ Sequence,
+ Tuple,
+ Type,
+ Union,
+)
from typing_extensions import Protocol
@@ -112,15 +123,35 @@ class DBAPI2Module(Protocol):
# extends from this hierarchy. See
# https://docs.python.org/3/library/sqlite3.html?highlight=sqlite3#exceptions
# https://www.postgresql.org/docs/current/errcodes-appendix.html#ERRCODES-TABLE
- Warning: Type[Exception]
- Error: Type[Exception]
+ #
+ # Note: rather than
+ # x: T
+ # we write
+ # @property
+ # def x(self) -> T: ...
+ # which expresses that the protocol attribute `x` is read-only. The mypy docs
+ # https://mypy.readthedocs.io/en/latest/common_issues.html#covariant-subtyping-of-mutable-protocol-members-is-rejected
+ # explain why this is necessary for safety. TL;DR: we shouldn't be able to write
+ # to `x`, only read from it. See also https://github.com/python/mypy/issues/6002 .
+ @property
+ def Warning(self) -> Type[Exception]:
+ ...
+
+ @property
+ def Error(self) -> Type[Exception]:
+ ...
# Errors are divided into `InterfaceError`s (something went wrong in the database
# driver) and `DatabaseError`s (something went wrong in the database). These are
# both subclasses of `Error`, but we can't currently express this in type
# annotations due to https://github.com/python/mypy/issues/8397
- InterfaceError: Type[Exception]
- DatabaseError: Type[Exception]
+ @property
+ def InterfaceError(self) -> Type[Exception]:
+ ...
+
+ @property
+ def DatabaseError(self) -> Type[Exception]:
+ ...
# Everything below is a subclass of `DatabaseError`.
@@ -128,7 +159,9 @@ class DBAPI2Module(Protocol):
# - An integer was too big for its data type.
# - An invalid date time was provided.
# - A string contained a null code point.
- DataError: Type[Exception]
+ @property
+ def DataError(self) -> Type[Exception]:
+ ...
# Roughly: something went wrong in the database, but it's not within the application
# programmer's control. Examples:
@@ -138,28 +171,45 @@ class DBAPI2Module(Protocol):
# - A serialisation failure occurred.
# - The database ran out of resources, such as storage, memory, connections, etc.
# - The database encountered an error from the operating system.
- OperationalError: Type[Exception]
+ @property
+ def OperationalError(self) -> Type[Exception]:
+ ...
# Roughly: we've given the database data which breaks a rule we asked it to enforce.
# Examples:
# - Stop, criminal scum! You violated the foreign key constraint
# - Also check constraints, non-null constraints, etc.
- IntegrityError: Type[Exception]
+ @property
+ def IntegrityError(self) -> Type[Exception]:
+ ...
# Roughly: something went wrong within the database server itself.
- InternalError: Type[Exception]
+ @property
+ def InternalError(self) -> Type[Exception]:
+ ...
# Roughly: the application did something silly that needs to be fixed. Examples:
# - We don't have permissions to do something.
# - We tried to create a table with duplicate column names.
# - We tried to use a reserved name.
# - We referred to a column that doesn't exist.
- ProgrammingError: Type[Exception]
+ @property
+ def ProgrammingError(self) -> Type[Exception]:
+ ...
# Roughly: we've tried to do something that this database doesn't support.
- NotSupportedError: Type[Exception]
+ @property
+ def NotSupportedError(self) -> Type[Exception]:
+ ...
- def connect(self, **parameters: object) -> Connection:
+ # We originally wrote
+ # def connect(self, *args, **kwargs) -> Connection: ...
+ # But mypy doesn't seem to like that because sqlite3.connect takes a mandatory
+ # positional argument. We can't make that part of the signature though, because
+ # psycopg2.connect doesn't have a mandatory positional argument. Instead, we use
+ # the following slightly unusual workaround.
+ @property
+ def connect(self) -> Callable[..., Connection]:
...
diff --git a/synapse/streams/__init__.py b/synapse/streams/__init__.py
index c6c8a0315c..8a48ffc48d 100644
--- a/synapse/streams/__init__.py
+++ b/synapse/streams/__init__.py
@@ -11,7 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
+from abc import ABC, abstractmethod
from typing import Generic, List, Optional, Tuple, TypeVar
from synapse.types import StrCollection, UserID
@@ -22,7 +22,8 @@ K = TypeVar("K")
R = TypeVar("R")
-class EventSource(Generic[K, R]):
+class EventSource(ABC, Generic[K, R]):
+ @abstractmethod
async def get_new_events(
self,
user: UserID,
@@ -32,4 +33,4 @@ class EventSource(Generic[K, R]):
is_guest: bool,
explicit_room_id: Optional[str] = None,
) -> Tuple[List[R], K]:
- ...
+ raise NotImplementedError()
|