Skip to content

Commit 462dc22

Browse files
committed
Type-check entire code base with mypy
Lots of `# type: ignore` in the internals, but the types should be decent enough for consumption.
1 parent 784e7a1 commit 462dc22

33 files changed

+315
-126
lines changed

.circleci/config.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ jobs:
119119
steps:
120120
- checkout
121121
- install-dependencies:
122-
extra: analysis
122+
extra: analysis, test
123123
- run:
124124
name: Verify
125125
command: python setup.py verify

psqlextra/backend/base.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import logging
22

3+
from typing import TYPE_CHECKING
4+
35
from django.conf import settings
46
from django.db import ProgrammingError
57

@@ -8,17 +10,31 @@
810
from .operations import PostgresOperations
911
from .schema import PostgresSchemaEditor
1012

13+
from django.db.backends.postgresql.base import ( # isort:skip
14+
DatabaseWrapper as PostgresDatabaseWrapper,
15+
)
16+
17+
1118
logger = logging.getLogger(__name__)
1219

1320

14-
class DatabaseWrapper(base_impl.backend()):
21+
if TYPE_CHECKING:
22+
23+
class Wrapper(PostgresDatabaseWrapper):
24+
pass
25+
26+
else:
27+
Wrapper = base_impl.backend()
28+
29+
30+
class DatabaseWrapper(Wrapper):
1531
"""Wraps the standard PostgreSQL database back-end.
1632
1733
Overrides the schema editor with our custom schema editor and makes
1834
sure the `hstore` extension is enabled.
1935
"""
2036

21-
SchemaEditorClass = PostgresSchemaEditor
37+
SchemaEditorClass = PostgresSchemaEditor # type: ignore[assignment]
2238
introspection_class = PostgresIntrospection
2339
ops_class = PostgresOperations
2440

psqlextra/backend/base_impl.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
from django.conf import settings
44
from django.core.exceptions import ImproperlyConfigured
55
from django.db import DEFAULT_DB_ALIAS, connections
6+
from django.db.backends.postgresql.base import DatabaseWrapper
7+
from django.db.backends.postgresql.introspection import ( # type: ignore[import]
8+
DatabaseIntrospection,
9+
)
10+
from django.db.backends.postgresql.operations import DatabaseOperations
11+
from django.db.backends.postgresql.schema import ( # type: ignore[import]
12+
DatabaseSchemaEditor,
13+
)
614

715
from django.db.backends.postgresql.base import ( # isort:skip
816
DatabaseWrapper as Psycopg2DatabaseWrapper,
@@ -68,13 +76,13 @@ def base_backend_instance():
6876
return base_instance
6977

7078

71-
def backend():
79+
def backend() -> DatabaseWrapper:
7280
"""Gets the base class for the database back-end."""
7381

7482
return base_backend_instance().__class__
7583

7684

77-
def schema_editor():
85+
def schema_editor() -> DatabaseSchemaEditor:
7886
"""Gets the base class for the schema editor.
7987
8088
We have to use the configured base back-end's schema editor for
@@ -84,7 +92,7 @@ def schema_editor():
8492
return base_backend_instance().SchemaEditorClass
8593

8694

87-
def introspection():
95+
def introspection() -> DatabaseIntrospection:
8896
"""Gets the base class for the introspection class.
8997
9098
We have to use the configured base back-end's introspection class
@@ -94,7 +102,7 @@ def introspection():
94102
return base_backend_instance().introspection.__class__
95103

96104

97-
def operations():
105+
def operations() -> DatabaseOperations:
98106
"""Gets the base class for the operations class.
99107
100108
We have to use the configured base back-end's operations class for

psqlextra/backend/introspection.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
from dataclasses import dataclass
2-
from typing import Dict, List, Optional, Tuple
2+
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
3+
4+
from django.db.backends.postgresql.introspection import ( # type: ignore[import]
5+
DatabaseIntrospection,
6+
)
37

48
from psqlextra.types import PostgresPartitioningMethod
59

@@ -45,7 +49,16 @@ def partition_by_name(
4549
)
4650

4751

48-
class PostgresIntrospection(base_impl.introspection()):
52+
if TYPE_CHECKING:
53+
54+
class Introspection(DatabaseIntrospection):
55+
pass
56+
57+
else:
58+
Introspection = base_impl.introspection()
59+
60+
61+
class PostgresIntrospection(Introspection):
4962
"""Adds introspection features specific to PostgreSQL."""
5063

5164
# TODO: This class is a mess, both here and in the
@@ -66,7 +79,7 @@ class PostgresIntrospection(base_impl.introspection()):
6679

6780
def get_partitioned_tables(
6881
self, cursor
69-
) -> PostgresIntrospectedPartitonedTable:
82+
) -> List[PostgresIntrospectedPartitonedTable]:
7083
"""Gets a list of partitioned tables."""
7184

7285
cursor.execute(

psqlextra/backend/migrations/patched_autodetector.py

+16-9
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
RenameField,
1313
)
1414
from django.db.migrations.autodetector import MigrationAutodetector
15-
from django.db.migrations.operations.base import Operation
15+
from django.db.migrations.operations.fields import FieldOperation
1616

1717
from psqlextra.models import (
1818
PostgresMaterializedViewModel,
@@ -83,7 +83,7 @@ def rename_field(self, operation: RenameField):
8383

8484
return self._transform_view_field_operations(operation)
8585

86-
def _transform_view_field_operations(self, operation: Operation):
86+
def _transform_view_field_operations(self, operation: FieldOperation):
8787
"""Transforms operations on fields on a (materialized) view into state
8888
only operations.
8989
@@ -199,9 +199,15 @@ def add_create_partitioned_model(self, operation: CreateModel):
199199
)
200200
)
201201

202+
partitioned_kwargs = {
203+
**kwargs,
204+
"partitioning_options": partitioning_options,
205+
}
206+
202207
self.add(
203208
operations.PostgresCreatePartitionedModel(
204-
*args, **kwargs, partitioning_options=partitioning_options
209+
*args,
210+
**partitioned_kwargs,
205211
)
206212
)
207213

@@ -231,11 +237,9 @@ def add_create_view_model(self, operation: CreateModel):
231237

232238
_, args, kwargs = operation.deconstruct()
233239

234-
self.add(
235-
operations.PostgresCreateViewModel(
236-
*args, **kwargs, view_options=view_options
237-
)
238-
)
240+
view_kwargs = {**kwargs, "view_options": view_options}
241+
242+
self.add(operations.PostgresCreateViewModel(*args, **view_kwargs))
239243

240244
def add_delete_view_model(self, operation: DeleteModel):
241245
"""Adds a :see:PostgresDeleteViewModel operation to the list of
@@ -261,9 +265,12 @@ def add_create_materialized_view_model(self, operation: CreateModel):
261265

262266
_, args, kwargs = operation.deconstruct()
263267

268+
view_kwargs = {**kwargs, "view_options": view_options}
269+
264270
self.add(
265271
operations.PostgresCreateMaterializedViewModel(
266-
*args, **kwargs, view_options=view_options
272+
*args,
273+
**view_kwargs,
267274
)
268275
)
269276

psqlextra/backend/migrations/state/model.py

+15-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from collections.abc import Mapping
2-
from typing import Type
2+
from typing import Tuple, Type, cast
33

44
from django.db.migrations.state import ModelState
55
from django.db.models import Model
@@ -17,8 +17,8 @@ class PostgresModelState(ModelState):
1717
"""
1818

1919
@classmethod
20-
def from_model(
21-
cls, model: PostgresModel, *args, **kwargs
20+
def from_model( # type: ignore[override]
21+
cls, model: Type[PostgresModel], *args, **kwargs
2222
) -> "PostgresModelState":
2323
"""Creates a new :see:PostgresModelState object from the specified
2424
model.
@@ -29,28 +29,32 @@ def from_model(
2929
We also need to patch up the base class for the model.
3030
"""
3131

32-
model_state = super().from_model(model, *args, **kwargs)
33-
model_state = cls._pre_new(model, model_state)
32+
model_state = super().from_model(
33+
cast(Type[Model], model), *args, **kwargs
34+
)
35+
model_state = cls._pre_new(
36+
model, cast("PostgresModelState", model_state)
37+
)
3438

3539
# django does not add abstract bases as a base in migrations
3640
# because it assumes the base does not add anything important
3741
# in a migration.. but it does, so we replace the Model
3842
# base with the actual base
39-
bases = tuple()
43+
bases: Tuple[Type[Model], ...] = tuple()
4044
for base in model_state.bases:
4145
if issubclass(base, Model):
4246
bases += (cls._get_base_model_class(),)
4347
else:
4448
bases += (base,)
4549

46-
model_state.bases = bases
50+
model_state.bases = cast(Tuple[Type[Model]], bases)
4751
return model_state
4852

4953
def clone(self) -> "PostgresModelState":
5054
"""Gets an exact copy of this :see:PostgresModelState."""
5155

5256
model_state = super().clone()
53-
return self._pre_clone(model_state)
57+
return self._pre_clone(cast(PostgresModelState, model_state))
5458

5559
def render(self, apps):
5660
"""Renders this state into an actual model."""
@@ -95,7 +99,9 @@ def render(self, apps):
9599

96100
@classmethod
97101
def _pre_new(
98-
cls, model: PostgresModel, model_state: "PostgresModelState"
102+
cls,
103+
model: Type[PostgresModel],
104+
model_state: "PostgresModelState",
99105
) -> "PostgresModelState":
100106
"""Called when a new model state is created from the specified
101107
model."""

psqlextra/backend/migrations/state/partitioning.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def delete_partition(self, name: str):
9494
del self.partitions[name]
9595

9696
@classmethod
97-
def _pre_new(
97+
def _pre_new( # type: ignore[override]
9898
cls,
9999
model: PostgresPartitionedModel,
100100
model_state: "PostgresPartitionedModelState",
@@ -108,7 +108,7 @@ def _pre_new(
108108
)
109109
return model_state
110110

111-
def _pre_clone(
111+
def _pre_clone( # type: ignore[override]
112112
self, model_state: "PostgresPartitionedModelState"
113113
) -> "PostgresPartitionedModelState":
114114
"""Called when this model state is cloned."""

psqlextra/backend/migrations/state/view.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,18 @@ def __init__(self, *args, view_options={}, **kwargs):
2222
self.view_options = dict(view_options)
2323

2424
@classmethod
25-
def _pre_new(
26-
cls, model: PostgresViewModel, model_state: "PostgresViewModelState"
25+
def _pre_new( # type: ignore[override]
26+
cls,
27+
model: Type[PostgresViewModel],
28+
model_state: "PostgresViewModelState",
2729
) -> "PostgresViewModelState":
2830
"""Called when a new model state is created from the specified
2931
model."""
3032

3133
model_state.view_options = dict(model._view_meta.original_attrs)
3234
return model_state
3335

34-
def _pre_clone(
36+
def _pre_clone( # type: ignore[override]
3537
self, model_state: "PostgresViewModelState"
3638
) -> "PostgresViewModelState":
3739
"""Called when this model state is cloned."""

psqlextra/backend/operations.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from . import base_impl
1010

1111

12-
class PostgresOperations(base_impl.operations()):
12+
class PostgresOperations(base_impl.operations()): # type: ignore[misc]
1313
"""Simple operations specific to PostgreSQL."""
1414

1515
compiler_module = "psqlextra.compiler"

0 commit comments

Comments
 (0)