plain.models 0.49.2__py3-none-any.whl → 0.50.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plain/models/CHANGELOG.md +13 -0
- plain/models/aggregates.py +42 -19
- plain/models/backends/base/base.py +125 -105
- plain/models/backends/base/client.py +11 -3
- plain/models/backends/base/creation.py +22 -12
- plain/models/backends/base/features.py +10 -4
- plain/models/backends/base/introspection.py +29 -16
- plain/models/backends/base/operations.py +187 -91
- plain/models/backends/base/schema.py +267 -165
- plain/models/backends/base/validation.py +12 -3
- plain/models/backends/ddl_references.py +85 -43
- plain/models/backends/mysql/base.py +29 -26
- plain/models/backends/mysql/client.py +7 -2
- plain/models/backends/mysql/compiler.py +12 -3
- plain/models/backends/mysql/creation.py +5 -2
- plain/models/backends/mysql/features.py +24 -22
- plain/models/backends/mysql/introspection.py +22 -13
- plain/models/backends/mysql/operations.py +106 -39
- plain/models/backends/mysql/schema.py +48 -24
- plain/models/backends/mysql/validation.py +13 -6
- plain/models/backends/postgresql/base.py +41 -34
- plain/models/backends/postgresql/client.py +7 -2
- plain/models/backends/postgresql/creation.py +10 -5
- plain/models/backends/postgresql/introspection.py +15 -8
- plain/models/backends/postgresql/operations.py +109 -42
- plain/models/backends/postgresql/schema.py +85 -46
- plain/models/backends/sqlite3/_functions.py +151 -115
- plain/models/backends/sqlite3/base.py +37 -23
- plain/models/backends/sqlite3/client.py +7 -1
- plain/models/backends/sqlite3/creation.py +9 -5
- plain/models/backends/sqlite3/features.py +5 -3
- plain/models/backends/sqlite3/introspection.py +32 -16
- plain/models/backends/sqlite3/operations.py +125 -42
- plain/models/backends/sqlite3/schema.py +82 -58
- plain/models/backends/utils.py +52 -29
- plain/models/backups/cli.py +8 -6
- plain/models/backups/clients.py +16 -7
- plain/models/backups/core.py +24 -13
- plain/models/base.py +113 -74
- plain/models/cli.py +94 -63
- plain/models/config.py +1 -1
- plain/models/connections.py +23 -7
- plain/models/constraints.py +65 -47
- plain/models/database_url.py +1 -1
- plain/models/db.py +6 -2
- plain/models/deletion.py +66 -43
- plain/models/entrypoints.py +1 -1
- plain/models/enums.py +22 -11
- plain/models/exceptions.py +23 -8
- plain/models/expressions.py +440 -257
- plain/models/fields/__init__.py +253 -202
- plain/models/fields/json.py +120 -54
- plain/models/fields/mixins.py +12 -8
- plain/models/fields/related.py +284 -252
- plain/models/fields/related_descriptors.py +31 -22
- plain/models/fields/related_lookups.py +23 -11
- plain/models/fields/related_managers.py +81 -47
- plain/models/fields/reverse_related.py +58 -55
- plain/models/forms.py +89 -63
- plain/models/functions/comparison.py +71 -18
- plain/models/functions/datetime.py +79 -29
- plain/models/functions/math.py +43 -10
- plain/models/functions/mixins.py +24 -7
- plain/models/functions/text.py +104 -25
- plain/models/functions/window.py +12 -6
- plain/models/indexes.py +52 -28
- plain/models/lookups.py +228 -153
- plain/models/migrations/autodetector.py +86 -43
- plain/models/migrations/exceptions.py +7 -3
- plain/models/migrations/executor.py +33 -7
- plain/models/migrations/graph.py +79 -50
- plain/models/migrations/loader.py +45 -22
- plain/models/migrations/migration.py +23 -18
- plain/models/migrations/operations/base.py +37 -19
- plain/models/migrations/operations/fields.py +89 -42
- plain/models/migrations/operations/models.py +245 -143
- plain/models/migrations/operations/special.py +82 -25
- plain/models/migrations/optimizer.py +7 -2
- plain/models/migrations/questioner.py +58 -31
- plain/models/migrations/recorder.py +18 -11
- plain/models/migrations/serializer.py +50 -39
- plain/models/migrations/state.py +220 -133
- plain/models/migrations/utils.py +29 -13
- plain/models/migrations/writer.py +17 -14
- plain/models/options.py +63 -56
- plain/models/otel.py +16 -6
- plain/models/preflight.py +35 -12
- plain/models/query.py +323 -228
- plain/models/query_utils.py +93 -58
- plain/models/registry.py +34 -16
- plain/models/sql/compiler.py +146 -97
- plain/models/sql/datastructures.py +38 -25
- plain/models/sql/query.py +255 -169
- plain/models/sql/subqueries.py +32 -21
- plain/models/sql/where.py +54 -29
- plain/models/test/pytest.py +15 -11
- plain/models/test/utils.py +4 -2
- plain/models/transaction.py +20 -7
- plain/models/utils.py +13 -5
- {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/METADATA +1 -1
- plain_models-0.50.0.dist-info/RECORD +122 -0
- plain_models-0.49.2.dist-info/RECORD +0 -122
- {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/WHEEL +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,14 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from collections.abc import Callable
|
4
|
+
from typing import TYPE_CHECKING, Any, cast
|
5
|
+
|
1
6
|
from .base import Operation
|
2
7
|
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from plain.models.backends.base.schema import BaseDatabaseSchemaEditor
|
10
|
+
from plain.models.migrations.state import ProjectState
|
11
|
+
|
3
12
|
|
4
13
|
class SeparateDatabaseAndState(Operation):
|
5
14
|
"""
|
@@ -11,23 +20,33 @@ class SeparateDatabaseAndState(Operation):
|
|
11
20
|
|
12
21
|
serialization_expand_args = ["database_operations", "state_operations"]
|
13
22
|
|
14
|
-
def __init__(
|
23
|
+
def __init__(
|
24
|
+
self,
|
25
|
+
database_operations: list[Operation] | None = None,
|
26
|
+
state_operations: list[Operation] | None = None,
|
27
|
+
) -> None:
|
15
28
|
self.database_operations = database_operations or []
|
16
29
|
self.state_operations = state_operations or []
|
17
30
|
|
18
|
-
def deconstruct(self):
|
19
|
-
kwargs = {}
|
31
|
+
def deconstruct(self) -> tuple[str, list[Any], dict[str, list[Operation]]]:
|
32
|
+
kwargs: dict[str, list[Operation]] = {}
|
20
33
|
if self.database_operations:
|
21
34
|
kwargs["database_operations"] = self.database_operations
|
22
35
|
if self.state_operations:
|
23
36
|
kwargs["state_operations"] = self.state_operations
|
24
37
|
return (self.__class__.__qualname__, [], kwargs)
|
25
38
|
|
26
|
-
def state_forwards(self, package_label, state):
|
39
|
+
def state_forwards(self, package_label: str, state: ProjectState) -> None:
|
27
40
|
for state_operation in self.state_operations:
|
28
41
|
state_operation.state_forwards(package_label, state)
|
29
42
|
|
30
|
-
def database_forwards(
|
43
|
+
def database_forwards(
|
44
|
+
self,
|
45
|
+
package_label: str,
|
46
|
+
schema_editor: BaseDatabaseSchemaEditor,
|
47
|
+
from_state: ProjectState,
|
48
|
+
to_state: ProjectState,
|
49
|
+
) -> None:
|
31
50
|
# We calculate state separately in here since our state functions aren't useful
|
32
51
|
for database_operation in self.database_operations:
|
33
52
|
to_state = from_state.clone()
|
@@ -37,7 +56,7 @@ class SeparateDatabaseAndState(Operation):
|
|
37
56
|
)
|
38
57
|
from_state = to_state
|
39
58
|
|
40
|
-
def describe(self):
|
59
|
+
def describe(self) -> str:
|
41
60
|
return "Custom state/database change combination"
|
42
61
|
|
43
62
|
|
@@ -49,42 +68,68 @@ class RunSQL(Operation):
|
|
49
68
|
by this SQL change, in case it's custom column/table creation/deletion.
|
50
69
|
"""
|
51
70
|
|
52
|
-
def __init__(
|
71
|
+
def __init__(
|
72
|
+
self,
|
73
|
+
sql: str
|
74
|
+
| list[str | tuple[str, list[Any]]]
|
75
|
+
| tuple[str | tuple[str, list[Any]], ...],
|
76
|
+
*,
|
77
|
+
state_operations: list[Operation] | None = None,
|
78
|
+
elidable: bool = False,
|
79
|
+
) -> None:
|
53
80
|
self.sql = sql
|
54
81
|
self.state_operations = state_operations or []
|
55
82
|
self.elidable = elidable
|
56
83
|
|
57
|
-
def deconstruct(self):
|
58
|
-
kwargs = {
|
84
|
+
def deconstruct(self) -> tuple[str, list[Any], dict[str, Any]]:
|
85
|
+
kwargs: dict[str, Any] = {
|
59
86
|
"sql": self.sql,
|
60
87
|
}
|
61
88
|
if self.state_operations:
|
62
89
|
kwargs["state_operations"] = self.state_operations
|
63
90
|
return (self.__class__.__qualname__, [], kwargs)
|
64
91
|
|
65
|
-
def state_forwards(self, package_label, state):
|
92
|
+
def state_forwards(self, package_label: str, state: ProjectState) -> None:
|
66
93
|
for state_operation in self.state_operations:
|
67
94
|
state_operation.state_forwards(package_label, state)
|
68
95
|
|
69
|
-
def database_forwards(
|
96
|
+
def database_forwards(
|
97
|
+
self,
|
98
|
+
package_label: str,
|
99
|
+
schema_editor: BaseDatabaseSchemaEditor,
|
100
|
+
from_state: ProjectState,
|
101
|
+
to_state: ProjectState,
|
102
|
+
) -> None:
|
70
103
|
self._run_sql(schema_editor, self.sql)
|
71
104
|
|
72
|
-
def describe(self):
|
105
|
+
def describe(self) -> str:
|
73
106
|
return "Raw SQL operation"
|
74
107
|
|
75
|
-
def _run_sql(
|
108
|
+
def _run_sql(
|
109
|
+
self,
|
110
|
+
schema_editor: BaseDatabaseSchemaEditor,
|
111
|
+
sqls: str
|
112
|
+
| list[str | tuple[str, list[Any]]]
|
113
|
+
| tuple[str | tuple[str, list[Any]], ...],
|
114
|
+
) -> None:
|
76
115
|
if isinstance(sqls, list | tuple):
|
77
|
-
for
|
78
|
-
params = None
|
79
|
-
|
80
|
-
|
116
|
+
for sql_item in sqls:
|
117
|
+
params: list[Any] | None = None
|
118
|
+
sql: str
|
119
|
+
if isinstance(sql_item, list | tuple):
|
120
|
+
elements = len(sql_item)
|
81
121
|
if elements == 2:
|
82
|
-
sql, params =
|
122
|
+
sql, params = sql_item # type: ignore[misc]
|
83
123
|
else:
|
84
124
|
raise ValueError("Expected a 2-tuple but got %d" % elements) # noqa: UP031
|
125
|
+
else:
|
126
|
+
sql = sql_item
|
85
127
|
schema_editor.execute(sql, params=params)
|
86
128
|
else:
|
87
|
-
|
129
|
+
# sqls is a str in this branch
|
130
|
+
statements = schema_editor.connection.ops.prepare_sql_script(
|
131
|
+
cast(str, sqls)
|
132
|
+
)
|
88
133
|
for statement in statements:
|
89
134
|
schema_editor.execute(statement, params=None)
|
90
135
|
|
@@ -96,7 +141,13 @@ class RunPython(Operation):
|
|
96
141
|
|
97
142
|
reduces_to_sql = False
|
98
143
|
|
99
|
-
def __init__(
|
144
|
+
def __init__(
|
145
|
+
self,
|
146
|
+
code: Callable[..., Any],
|
147
|
+
*,
|
148
|
+
atomic: bool | None = None,
|
149
|
+
elidable: bool = False,
|
150
|
+
) -> None:
|
100
151
|
self.atomic = atomic
|
101
152
|
# Forwards code
|
102
153
|
if not callable(code):
|
@@ -104,20 +155,26 @@ class RunPython(Operation):
|
|
104
155
|
self.code = code
|
105
156
|
self.elidable = elidable
|
106
157
|
|
107
|
-
def deconstruct(self):
|
108
|
-
kwargs = {
|
158
|
+
def deconstruct(self) -> tuple[str, list[Any], dict[str, Any]]:
|
159
|
+
kwargs: dict[str, Any] = {
|
109
160
|
"code": self.code,
|
110
161
|
}
|
111
162
|
if self.atomic is not None:
|
112
163
|
kwargs["atomic"] = self.atomic
|
113
164
|
return (self.__class__.__qualname__, [], kwargs)
|
114
165
|
|
115
|
-
def state_forwards(self, package_label, state):
|
166
|
+
def state_forwards(self, package_label: str, state: Any) -> None:
|
116
167
|
# RunPython objects have no state effect. To add some, combine this
|
117
168
|
# with SeparateDatabaseAndState.
|
118
169
|
pass
|
119
170
|
|
120
|
-
def database_forwards(
|
171
|
+
def database_forwards(
|
172
|
+
self,
|
173
|
+
package_label: str,
|
174
|
+
schema_editor: BaseDatabaseSchemaEditor,
|
175
|
+
from_state: ProjectState,
|
176
|
+
to_state: ProjectState,
|
177
|
+
) -> None:
|
121
178
|
# RunPython has access to all models. Ensure that all models are
|
122
179
|
# reloaded in case any are delayed.
|
123
180
|
from_state.clear_delayed_models_cache()
|
@@ -127,5 +184,5 @@ class RunPython(Operation):
|
|
127
184
|
# use direct imports, so we go with a documentation approach instead.
|
128
185
|
self.code(from_state.models_registry, schema_editor)
|
129
186
|
|
130
|
-
def describe(self):
|
187
|
+
def describe(self) -> str:
|
131
188
|
return "Raw Python operation"
|
@@ -1,3 +1,8 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
|
1
6
|
class MigrationOptimizer:
|
2
7
|
"""
|
3
8
|
Power the optimization process, where you provide a list of Operations
|
@@ -9,7 +14,7 @@ class MigrationOptimizer:
|
|
9
14
|
nothing.
|
10
15
|
"""
|
11
16
|
|
12
|
-
def optimize(self, operations, package_label):
|
17
|
+
def optimize(self, operations: list[Any], package_label: str) -> list[Any]:
|
13
18
|
"""
|
14
19
|
Main optimization entry point. Pass in a list of Operation instances,
|
15
20
|
get out a new list of Operation instances.
|
@@ -35,7 +40,7 @@ class MigrationOptimizer:
|
|
35
40
|
return result
|
36
41
|
operations = result
|
37
42
|
|
38
|
-
def optimize_inner(self, operations, package_label):
|
43
|
+
def optimize_inner(self, operations: list[Any], package_label: str) -> list[Any]:
|
39
44
|
"""Inner optimization loop."""
|
40
45
|
new_operations = []
|
41
46
|
for i, operation in enumerate(operations):
|
@@ -1,7 +1,11 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import datetime
|
2
4
|
import importlib
|
3
5
|
import os
|
4
6
|
import sys
|
7
|
+
from collections.abc import Callable
|
8
|
+
from typing import TYPE_CHECKING, Any
|
5
9
|
|
6
10
|
import click
|
7
11
|
|
@@ -11,6 +15,9 @@ from plain.utils import timezone
|
|
11
15
|
|
12
16
|
from .loader import MigrationLoader
|
13
17
|
|
18
|
+
if TYPE_CHECKING:
|
19
|
+
from plain.models.fields import Field
|
20
|
+
|
14
21
|
|
15
22
|
class MigrationQuestioner:
|
16
23
|
"""
|
@@ -19,12 +26,17 @@ class MigrationQuestioner:
|
|
19
26
|
interactive subclass is what the command-line arguments will use.
|
20
27
|
"""
|
21
28
|
|
22
|
-
def __init__(
|
29
|
+
def __init__(
|
30
|
+
self,
|
31
|
+
defaults: dict[str, Any] | None = None,
|
32
|
+
specified_packages: set[str] | None = None,
|
33
|
+
dry_run: bool | None = None,
|
34
|
+
) -> None:
|
23
35
|
self.defaults = defaults or {}
|
24
36
|
self.specified_packages = specified_packages or set()
|
25
37
|
self.dry_run = dry_run
|
26
38
|
|
27
|
-
def ask_initial(self, package_label):
|
39
|
+
def ask_initial(self, package_label: str) -> bool:
|
28
40
|
"""Should we create an initial migration for the app?"""
|
29
41
|
# If it was specified on the command line, definitely true
|
30
42
|
if package_label in self.specified_packages:
|
@@ -49,52 +61,61 @@ class MigrationQuestioner:
|
|
49
61
|
return self.defaults.get("ask_initial", False)
|
50
62
|
else:
|
51
63
|
if getattr(migrations_module, "__file__", None):
|
52
|
-
filenames = os.listdir(os.path.dirname(migrations_module.__file__))
|
64
|
+
filenames = os.listdir(os.path.dirname(migrations_module.__file__)) # type: ignore[arg-type]
|
53
65
|
elif hasattr(migrations_module, "__path__"):
|
54
66
|
if len(migrations_module.__path__) > 1:
|
55
67
|
return False
|
56
68
|
filenames = os.listdir(list(migrations_module.__path__)[0])
|
57
69
|
return not any(x.endswith(".py") for x in filenames if x != "__init__.py")
|
58
70
|
|
59
|
-
def ask_not_null_addition(self, field_name, model_name):
|
71
|
+
def ask_not_null_addition(self, field_name: str, model_name: str) -> Any:
|
60
72
|
"""Adding a NOT NULL field to a model."""
|
61
73
|
# None means quit
|
62
74
|
return None
|
63
75
|
|
64
|
-
def ask_not_null_alteration(self, field_name, model_name):
|
76
|
+
def ask_not_null_alteration(self, field_name: str, model_name: str) -> Any:
|
65
77
|
"""Changing a NULL field to NOT NULL."""
|
66
78
|
# None means quit
|
67
79
|
return None
|
68
80
|
|
69
|
-
def ask_rename(
|
81
|
+
def ask_rename(
|
82
|
+
self, model_name: str, old_name: str, new_name: str, field_instance: Field
|
83
|
+
) -> bool:
|
70
84
|
"""Was this field really renamed?"""
|
71
85
|
return self.defaults.get("ask_rename", False)
|
72
86
|
|
73
|
-
def ask_rename_model(self, old_model_state, new_model_state):
|
87
|
+
def ask_rename_model(self, old_model_state: Any, new_model_state: Any) -> bool:
|
74
88
|
"""Was this model really renamed?"""
|
75
89
|
return self.defaults.get("ask_rename_model", False)
|
76
90
|
|
77
|
-
def ask_auto_now_add_addition(self, field_name, model_name):
|
91
|
+
def ask_auto_now_add_addition(self, field_name: str, model_name: str) -> Any:
|
78
92
|
"""Adding an auto_now_add field to a model."""
|
79
93
|
# None means quit
|
80
94
|
return None
|
81
95
|
|
82
|
-
def ask_unique_callable_default_addition(
|
96
|
+
def ask_unique_callable_default_addition(
|
97
|
+
self, field_name: str, model_name: str
|
98
|
+
) -> Any:
|
83
99
|
"""Adding a unique field with a callable default."""
|
84
100
|
# None means continue.
|
85
101
|
return None
|
86
102
|
|
87
103
|
|
88
104
|
class InteractiveMigrationQuestioner(MigrationQuestioner):
|
89
|
-
def __init__(
|
105
|
+
def __init__(
|
106
|
+
self,
|
107
|
+
defaults: dict[str, Any] | None = None,
|
108
|
+
specified_packages: set[str] | None = None,
|
109
|
+
dry_run: bool | None = None,
|
110
|
+
) -> None:
|
90
111
|
super().__init__(
|
91
112
|
defaults=defaults, specified_packages=specified_packages, dry_run=dry_run
|
92
113
|
)
|
93
114
|
|
94
|
-
def _boolean_input(self, question, default=None):
|
115
|
+
def _boolean_input(self, question: str, default: bool | None = None) -> bool:
|
95
116
|
return click.confirm(question, default=default)
|
96
117
|
|
97
|
-
def _choice_input(self, question, choices):
|
118
|
+
def _choice_input(self, question: str, choices: list[str]) -> int:
|
98
119
|
choice_map = {str(i + 1): choice for i, choice in enumerate(choices)}
|
99
120
|
choice_map_str = "\n".join(
|
100
121
|
[f"{i}) {choice}" for i, choice in choice_map.items()]
|
@@ -105,7 +126,7 @@ class InteractiveMigrationQuestioner(MigrationQuestioner):
|
|
105
126
|
)
|
106
127
|
return int(choice)
|
107
128
|
|
108
|
-
def _ask_default(self, default=""):
|
129
|
+
def _ask_default(self, default: str = "") -> Any:
|
109
130
|
"""
|
110
131
|
Prompt for a default value.
|
111
132
|
|
@@ -144,7 +165,7 @@ class InteractiveMigrationQuestioner(MigrationQuestioner):
|
|
144
165
|
except (SyntaxError, NameError) as e:
|
145
166
|
click.echo(f"Invalid input: {e}")
|
146
167
|
|
147
|
-
def ask_not_null_addition(self, field_name, model_name):
|
168
|
+
def ask_not_null_addition(self, field_name: str, model_name: str) -> Any:
|
148
169
|
"""Adding a NOT NULL field to a model."""
|
149
170
|
if not self.dry_run:
|
150
171
|
choice = self._choice_input(
|
@@ -167,7 +188,7 @@ class InteractiveMigrationQuestioner(MigrationQuestioner):
|
|
167
188
|
return self._ask_default()
|
168
189
|
return None
|
169
190
|
|
170
|
-
def ask_not_null_alteration(self, field_name, model_name):
|
191
|
+
def ask_not_null_alteration(self, field_name: str, model_name: str) -> Any:
|
171
192
|
"""Changing a NULL field to NOT NULL."""
|
172
193
|
if not self.dry_run:
|
173
194
|
choice = self._choice_input(
|
@@ -195,7 +216,9 @@ class InteractiveMigrationQuestioner(MigrationQuestioner):
|
|
195
216
|
return self._ask_default()
|
196
217
|
return None
|
197
218
|
|
198
|
-
def ask_rename(
|
219
|
+
def ask_rename(
|
220
|
+
self, model_name: str, old_name: str, new_name: str, field_instance: Field
|
221
|
+
) -> bool:
|
199
222
|
"""Was this field really renamed?"""
|
200
223
|
msg = "Was %s.%s renamed to %s.%s (a %s)?"
|
201
224
|
return self._boolean_input(
|
@@ -210,7 +233,7 @@ class InteractiveMigrationQuestioner(MigrationQuestioner):
|
|
210
233
|
default=False,
|
211
234
|
)
|
212
235
|
|
213
|
-
def ask_rename_model(self, old_model_state, new_model_state):
|
236
|
+
def ask_rename_model(self, old_model_state: Any, new_model_state: Any) -> bool:
|
214
237
|
"""Was this model really renamed?"""
|
215
238
|
msg = "Was the model %s.%s renamed to %s?"
|
216
239
|
return self._boolean_input(
|
@@ -223,7 +246,7 @@ class InteractiveMigrationQuestioner(MigrationQuestioner):
|
|
223
246
|
default=False,
|
224
247
|
)
|
225
248
|
|
226
|
-
def ask_auto_now_add_addition(self, field_name, model_name):
|
249
|
+
def ask_auto_now_add_addition(self, field_name: str, model_name: str) -> Any:
|
227
250
|
"""Adding an auto_now_add field to a model."""
|
228
251
|
if not self.dry_run:
|
229
252
|
choice = self._choice_input(
|
@@ -243,7 +266,9 @@ class InteractiveMigrationQuestioner(MigrationQuestioner):
|
|
243
266
|
return self._ask_default(default="timezone.now")
|
244
267
|
return None
|
245
268
|
|
246
|
-
def ask_unique_callable_default_addition(
|
269
|
+
def ask_unique_callable_default_addition(
|
270
|
+
self, field_name: str, model_name: str
|
271
|
+
) -> Any:
|
247
272
|
"""Adding a unique field with a callable default."""
|
248
273
|
if not self.dry_run:
|
249
274
|
choice = self._choice_input(
|
@@ -264,12 +289,12 @@ class InteractiveMigrationQuestioner(MigrationQuestioner):
|
|
264
289
|
class NonInteractiveMigrationQuestioner(MigrationQuestioner):
|
265
290
|
def __init__(
|
266
291
|
self,
|
267
|
-
defaults=None,
|
268
|
-
specified_packages=None,
|
269
|
-
dry_run=None,
|
270
|
-
verbosity=1,
|
271
|
-
log=None,
|
272
|
-
):
|
292
|
+
defaults: dict[str, Any] | None = None,
|
293
|
+
specified_packages: set[str] | None = None,
|
294
|
+
dry_run: bool | None = None,
|
295
|
+
verbosity: int = 1,
|
296
|
+
log: Callable[[str], Any] | None = None,
|
297
|
+
) -> None:
|
273
298
|
self.verbosity = verbosity
|
274
299
|
self.log = log
|
275
300
|
super().__init__(
|
@@ -278,13 +303,15 @@ class NonInteractiveMigrationQuestioner(MigrationQuestioner):
|
|
278
303
|
dry_run=dry_run,
|
279
304
|
)
|
280
305
|
|
281
|
-
def log_lack_of_migration(
|
306
|
+
def log_lack_of_migration(
|
307
|
+
self, field_name: str, model_name: str, reason: str
|
308
|
+
) -> None:
|
282
309
|
if self.verbosity > 0:
|
283
|
-
self.log(
|
310
|
+
self.log( # type: ignore[misc]
|
284
311
|
f"Field '{field_name}' on model '{model_name}' not migrated: {reason}."
|
285
312
|
)
|
286
313
|
|
287
|
-
def ask_not_null_addition(self, field_name, model_name):
|
314
|
+
def ask_not_null_addition(self, field_name: str, model_name: str) -> Any:
|
288
315
|
# We can't ask the user, so act like the user aborted.
|
289
316
|
self.log_lack_of_migration(
|
290
317
|
field_name,
|
@@ -293,15 +320,15 @@ class NonInteractiveMigrationQuestioner(MigrationQuestioner):
|
|
293
320
|
)
|
294
321
|
sys.exit(3)
|
295
322
|
|
296
|
-
def ask_not_null_alteration(self, field_name, model_name):
|
323
|
+
def ask_not_null_alteration(self, field_name: str, model_name: str) -> Any:
|
297
324
|
# We can't ask the user, so set as not provided.
|
298
|
-
self.log(
|
325
|
+
self.log( # type: ignore[misc]
|
299
326
|
f"Field '{field_name}' on model '{model_name}' given a default of "
|
300
327
|
f"NOT PROVIDED and must be corrected."
|
301
328
|
)
|
302
329
|
return NOT_PROVIDED
|
303
330
|
|
304
|
-
def ask_auto_now_add_addition(self, field_name, model_name):
|
331
|
+
def ask_auto_now_add_addition(self, field_name: str, model_name: str) -> Any:
|
305
332
|
# We can't ask the user, so act like the user aborted.
|
306
333
|
self.log_lack_of_migration(
|
307
334
|
field_name,
|
@@ -1,3 +1,7 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING, Any
|
4
|
+
|
1
5
|
from plain import models
|
2
6
|
from plain.models.db import DatabaseError
|
3
7
|
from plain.models.registry import ModelsRegistry
|
@@ -6,6 +10,9 @@ from plain.utils.timezone import now
|
|
6
10
|
|
7
11
|
from .exceptions import MigrationSchemaMissing
|
8
12
|
|
13
|
+
if TYPE_CHECKING:
|
14
|
+
from plain.models.backends.base.base import BaseDatabaseWrapper
|
15
|
+
|
9
16
|
|
10
17
|
class MigrationRecorder:
|
11
18
|
"""
|
@@ -20,10 +27,10 @@ class MigrationRecorder:
|
|
20
27
|
a row in the table always means a migration is applied.
|
21
28
|
"""
|
22
29
|
|
23
|
-
_migration_class = None
|
30
|
+
_migration_class: type[models.Model] | None = None
|
24
31
|
|
25
32
|
@classproperty
|
26
|
-
def Migration(cls):
|
33
|
+
def Migration(cls) -> type[models.Model]: # type: ignore[misc]
|
27
34
|
"""
|
28
35
|
Lazy load to avoid PackageRegistryNotReady if installed packages import
|
29
36
|
MigrationRecorder.
|
@@ -42,26 +49,26 @@ class MigrationRecorder:
|
|
42
49
|
package_label = "migrations"
|
43
50
|
db_table = "plainmigrations"
|
44
51
|
|
45
|
-
def __str__(self):
|
52
|
+
def __str__(self) -> str:
|
46
53
|
return f"Migration {self.name} for {self.app}"
|
47
54
|
|
48
55
|
cls._migration_class = Migration
|
49
56
|
return cls._migration_class
|
50
57
|
|
51
|
-
def __init__(self, connection):
|
58
|
+
def __init__(self, connection: BaseDatabaseWrapper) -> None:
|
52
59
|
self.connection = connection
|
53
60
|
|
54
61
|
@property
|
55
|
-
def migration_qs(self):
|
62
|
+
def migration_qs(self) -> Any:
|
56
63
|
return self.Migration.query.all()
|
57
64
|
|
58
|
-
def has_table(self):
|
65
|
+
def has_table(self) -> bool:
|
59
66
|
"""Return True if the plainmigrations table exists."""
|
60
67
|
with self.connection.cursor() as cursor:
|
61
68
|
tables = self.connection.introspection.table_names(cursor)
|
62
69
|
return self.Migration._meta.db_table in tables
|
63
70
|
|
64
|
-
def ensure_schema(self):
|
71
|
+
def ensure_schema(self) -> None:
|
65
72
|
"""Ensure the table exists and has the correct schema."""
|
66
73
|
# If the table's there, that's fine - we've never changed its schema
|
67
74
|
# in the codebase.
|
@@ -76,7 +83,7 @@ class MigrationRecorder:
|
|
76
83
|
f"Unable to create the plainmigrations table ({exc})"
|
77
84
|
)
|
78
85
|
|
79
|
-
def applied_migrations(self):
|
86
|
+
def applied_migrations(self) -> dict[tuple[str, str], Any]:
|
80
87
|
"""
|
81
88
|
Return a dict mapping (package_name, migration_name) to Migration instances
|
82
89
|
for all applied migrations.
|
@@ -91,16 +98,16 @@ class MigrationRecorder:
|
|
91
98
|
# are applied.
|
92
99
|
return {}
|
93
100
|
|
94
|
-
def record_applied(self, app, name):
|
101
|
+
def record_applied(self, app: str, name: str) -> None:
|
95
102
|
"""Record that a migration was applied."""
|
96
103
|
self.ensure_schema()
|
97
104
|
self.migration_qs.create(app=app, name=name)
|
98
105
|
|
99
|
-
def record_unapplied(self, app, name):
|
106
|
+
def record_unapplied(self, app: str, name: str) -> None:
|
100
107
|
"""Record that a migration was unapplied."""
|
101
108
|
self.ensure_schema()
|
102
109
|
self.migration_qs.filter(app=app, name=name).delete()
|
103
110
|
|
104
|
-
def flush(self):
|
111
|
+
def flush(self) -> None:
|
105
112
|
"""Delete all migration records. Useful for testing migrations."""
|
106
113
|
self.migration_qs.all().delete()
|