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

Commit e9329dc

Browse files
author
David Robertson
committed
1 parent 29dd442 commit e9329dc

File tree

3 files changed

+67
-21
lines changed

3 files changed

+67
-21
lines changed

synapse/storage/engines/postgres.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
IncorrectDatabaseSetup,
2323
IsolationLevel,
2424
)
25-
from synapse.storage.types import Cursor, DBAPI2Module
25+
from synapse.storage.types import Cursor
2626

2727
if TYPE_CHECKING:
2828
from synapse.storage.database import LoggingDatabaseConnection
@@ -35,9 +35,7 @@ class PostgresEngine(
3535
BaseDatabaseEngine[psycopg2.extensions.connection, psycopg2.extensions.cursor]
3636
):
3737
def __init__(self, database_config: Mapping[str, Any]):
38-
# Cast: mypy 1.0.0 doesn't seem to think that the module implements the protocol.
39-
# AFAICS this is a false positive.
40-
super().__init__(cast(DBAPI2Module, psycopg2), database_config)
38+
super().__init__(psycopg2, database_config)
4139
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
4240

4341
# Disables passing `bytes` to txn.execute, c.f. #6186. If you do

synapse/storage/engines/sqlite.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,18 @@
1515
import sqlite3
1616
import struct
1717
import threading
18-
from typing import TYPE_CHECKING, Any, List, Mapping, Optional, cast
18+
from typing import TYPE_CHECKING, Any, List, Mapping, Optional
1919

2020
from synapse.storage.engines import BaseDatabaseEngine
21-
from synapse.storage.types import Cursor, DBAPI2Module
21+
from synapse.storage.types import Cursor
2222

2323
if TYPE_CHECKING:
2424
from synapse.storage.database import LoggingDatabaseConnection
2525

2626

2727
class Sqlite3Engine(BaseDatabaseEngine[sqlite3.Connection, sqlite3.Cursor]):
2828
def __init__(self, database_config: Mapping[str, Any]):
29-
# Cast: mypy 1.0.0 doesn't seem to think that the module implements the protocol.
30-
# AFAICS this is a false positive.
31-
super().__init__(cast(DBAPI2Module, sqlite3), database_config)
29+
super().__init__(sqlite3, database_config)
3230

3331
database = database_config.get("args", {}).get("database")
3432
self._is_in_memory = database in (

synapse/storage/types.py

+62-12
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,18 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
from types import TracebackType
15-
from typing import Any, Iterator, List, Mapping, Optional, Sequence, Tuple, Type, Union
15+
from typing import (
16+
Any,
17+
Callable,
18+
Iterator,
19+
List,
20+
Mapping,
21+
Optional,
22+
Sequence,
23+
Tuple,
24+
Type,
25+
Union,
26+
)
1627

1728
from typing_extensions import Protocol
1829

@@ -112,23 +123,45 @@ class DBAPI2Module(Protocol):
112123
# extends from this hierarchy. See
113124
# https://docs.python.org/3/library/sqlite3.html?highlight=sqlite3#exceptions
114125
# https://www.postgresql.org/docs/current/errcodes-appendix.html#ERRCODES-TABLE
115-
Warning: Type[Exception]
116-
Error: Type[Exception]
126+
#
127+
# Note: rather than
128+
# x: T
129+
# we write
130+
# @property
131+
# def x(self) -> T: ...
132+
# which expresses that the protocol attribute `x` is read-only. The mypy docs
133+
# https://mypy.readthedocs.io/en/latest/common_issues.html#covariant-subtyping-of-mutable-protocol-members-is-rejected
134+
# explain why this is necessary for safety. TL;DR: we shouldn't be able to write
135+
# to `x`, only read from it. See also https://github.com/python/mypy/issues/6002 .
136+
@property
137+
def Warning(self) -> Type[Exception]:
138+
...
139+
140+
@property
141+
def Error(self) -> Type[Exception]:
142+
...
117143

118144
# Errors are divided into `InterfaceError`s (something went wrong in the database
119145
# driver) and `DatabaseError`s (something went wrong in the database). These are
120146
# both subclasses of `Error`, but we can't currently express this in type
121147
# annotations due to https://github.com/python/mypy/issues/8397
122-
InterfaceError: Type[Exception]
123-
DatabaseError: Type[Exception]
148+
@property
149+
def InterfaceError(self) -> Type[Exception]:
150+
...
151+
152+
@property
153+
def DatabaseError(self) -> Type[Exception]:
154+
...
124155

125156
# Everything below is a subclass of `DatabaseError`.
126157

127158
# Roughly: the database rejected a nonsensical value. Examples:
128159
# - An integer was too big for its data type.
129160
# - An invalid date time was provided.
130161
# - A string contained a null code point.
131-
DataError: Type[Exception]
162+
@property
163+
def DataError(self) -> Type[Exception]:
164+
...
132165

133166
# Roughly: something went wrong in the database, but it's not within the application
134167
# programmer's control. Examples:
@@ -138,28 +171,45 @@ class DBAPI2Module(Protocol):
138171
# - A serialisation failure occurred.
139172
# - The database ran out of resources, such as storage, memory, connections, etc.
140173
# - The database encountered an error from the operating system.
141-
OperationalError: Type[Exception]
174+
@property
175+
def OperationalError(self) -> Type[Exception]:
176+
...
142177

143178
# Roughly: we've given the database data which breaks a rule we asked it to enforce.
144179
# Examples:
145180
# - Stop, criminal scum! You violated the foreign key constraint
146181
# - Also check constraints, non-null constraints, etc.
147-
IntegrityError: Type[Exception]
182+
@property
183+
def IntegrityError(self) -> Type[Exception]:
184+
...
148185

149186
# Roughly: something went wrong within the database server itself.
150-
InternalError: Type[Exception]
187+
@property
188+
def InternalError(self) -> Type[Exception]:
189+
...
151190

152191
# Roughly: the application did something silly that needs to be fixed. Examples:
153192
# - We don't have permissions to do something.
154193
# - We tried to create a table with duplicate column names.
155194
# - We tried to use a reserved name.
156195
# - We referred to a column that doesn't exist.
157-
ProgrammingError: Type[Exception]
196+
@property
197+
def ProgrammingError(self) -> Type[Exception]:
198+
...
158199

159200
# Roughly: we've tried to do something that this database doesn't support.
160-
NotSupportedError: Type[Exception]
201+
@property
202+
def NotSupportedError(self) -> Type[Exception]:
203+
...
161204

162-
def connect(self, **parameters: object) -> Connection:
205+
# We originally wrote
206+
# def connect(self, *args, **kwargs) -> Connection: ...
207+
# But mypy doesn't seem to like that because sqlite3.connect takes a mandatory
208+
# positional argument. We can't make that part of the signature though, because
209+
# psycopg2.connect doesn't have a mandatory positional argument. Instead, we use
210+
# the following slightly unusual workaround.
211+
@property
212+
def connect(self) -> Callable[..., Connection]:
163213
...
164214

165215

0 commit comments

Comments
 (0)