plain.models 0.49.1__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 +23 -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 +34 -25
- 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.1.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.1.dist-info/RECORD +0 -122
- {plain_models-0.49.1.dist-info → plain_models-0.50.0.dist-info}/WHEEL +0 -0
- {plain_models-0.49.1.dist-info → plain_models-0.50.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.49.1.dist-info → plain_models-0.50.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,13 +1,22 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING, Any
|
4
|
+
|
5
|
+
if TYPE_CHECKING:
|
6
|
+
from plain.models.backends.base.base import BaseDatabaseWrapper
|
7
|
+
from plain.models.fields import Field
|
8
|
+
|
9
|
+
|
1
10
|
class BaseDatabaseValidation:
|
2
11
|
"""Encapsulate backend-specific validation."""
|
3
12
|
|
4
|
-
def __init__(self, connection):
|
13
|
+
def __init__(self, connection: BaseDatabaseWrapper) -> None:
|
5
14
|
self.connection = connection
|
6
15
|
|
7
|
-
def preflight(self):
|
16
|
+
def preflight(self) -> list[Any]:
|
8
17
|
return []
|
9
18
|
|
10
|
-
def check_field(self, field, **kwargs):
|
19
|
+
def check_field(self, field: Field, **kwargs: Any) -> list[Any]:
|
11
20
|
errors = []
|
12
21
|
# Backends may implement a check_field_type() method.
|
13
22
|
if (
|
@@ -3,40 +3,49 @@ Helpers to manipulate deferred DDL statements that might need to be adjusted or
|
|
3
3
|
discarded within when executing a migration.
|
4
4
|
"""
|
5
5
|
|
6
|
+
from __future__ import annotations
|
7
|
+
|
8
|
+
from collections.abc import Callable
|
6
9
|
from copy import deepcopy
|
10
|
+
from typing import TYPE_CHECKING, Any
|
11
|
+
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from plain.models.sql.compiler import SQLCompiler
|
7
14
|
|
8
15
|
|
9
16
|
class Reference:
|
10
17
|
"""Base class that defines the reference interface."""
|
11
18
|
|
12
|
-
def references_table(self, table):
|
19
|
+
def references_table(self, table: str) -> bool:
|
13
20
|
"""
|
14
21
|
Return whether or not this instance references the specified table.
|
15
22
|
"""
|
16
23
|
return False
|
17
24
|
|
18
|
-
def references_column(self, table, column):
|
25
|
+
def references_column(self, table: str, column: str) -> bool:
|
19
26
|
"""
|
20
27
|
Return whether or not this instance references the specified column.
|
21
28
|
"""
|
22
29
|
return False
|
23
30
|
|
24
|
-
def rename_table_references(self, old_table, new_table):
|
31
|
+
def rename_table_references(self, old_table: str, new_table: str) -> None:
|
25
32
|
"""
|
26
33
|
Rename all references to the old_name to the new_table.
|
27
34
|
"""
|
28
35
|
pass
|
29
36
|
|
30
|
-
def rename_column_references(
|
37
|
+
def rename_column_references(
|
38
|
+
self, table: str, old_column: str, new_column: str
|
39
|
+
) -> None:
|
31
40
|
"""
|
32
41
|
Rename all references to the old_column to the new_column.
|
33
42
|
"""
|
34
43
|
pass
|
35
44
|
|
36
|
-
def __repr__(self):
|
45
|
+
def __repr__(self) -> str:
|
37
46
|
return f"<{self.__class__.__name__} {str(self)!r}>"
|
38
47
|
|
39
|
-
def __str__(self):
|
48
|
+
def __str__(self) -> str:
|
40
49
|
raise NotImplementedError(
|
41
50
|
"Subclasses must define how they should be converted to string."
|
42
51
|
)
|
@@ -45,32 +54,34 @@ class Reference:
|
|
45
54
|
class Table(Reference):
|
46
55
|
"""Hold a reference to a table."""
|
47
56
|
|
48
|
-
def __init__(self, table, quote_name):
|
57
|
+
def __init__(self, table: str, quote_name: Callable[[str], str]) -> None:
|
49
58
|
self.table = table
|
50
59
|
self.quote_name = quote_name
|
51
60
|
|
52
|
-
def references_table(self, table):
|
61
|
+
def references_table(self, table: str) -> bool:
|
53
62
|
return self.table == table
|
54
63
|
|
55
|
-
def rename_table_references(self, old_table, new_table):
|
64
|
+
def rename_table_references(self, old_table: str, new_table: str) -> None:
|
56
65
|
if self.table == old_table:
|
57
66
|
self.table = new_table
|
58
67
|
|
59
|
-
def __str__(self):
|
68
|
+
def __str__(self) -> str:
|
60
69
|
return self.quote_name(self.table)
|
61
70
|
|
62
71
|
|
63
72
|
class TableColumns(Table):
|
64
73
|
"""Base class for references to multiple columns of a table."""
|
65
74
|
|
66
|
-
def __init__(self, table, columns):
|
75
|
+
def __init__(self, table: str, columns: list[str]) -> None:
|
67
76
|
self.table = table
|
68
77
|
self.columns = columns
|
69
78
|
|
70
|
-
def references_column(self, table, column):
|
79
|
+
def references_column(self, table: str, column: str) -> bool:
|
71
80
|
return self.table == table and column in self.columns
|
72
81
|
|
73
|
-
def rename_column_references(
|
82
|
+
def rename_column_references(
|
83
|
+
self, table: str, old_column: str, new_column: str
|
84
|
+
) -> None:
|
74
85
|
if self.table == table:
|
75
86
|
for index, column in enumerate(self.columns):
|
76
87
|
if column == old_column:
|
@@ -80,13 +91,19 @@ class TableColumns(Table):
|
|
80
91
|
class Columns(TableColumns):
|
81
92
|
"""Hold a reference to one or many columns."""
|
82
93
|
|
83
|
-
def __init__(
|
94
|
+
def __init__(
|
95
|
+
self,
|
96
|
+
table: str,
|
97
|
+
columns: list[str],
|
98
|
+
quote_name: Callable[[str], str],
|
99
|
+
col_suffixes: tuple[str, ...] = (),
|
100
|
+
) -> None:
|
84
101
|
self.quote_name = quote_name
|
85
102
|
self.col_suffixes = col_suffixes
|
86
103
|
super().__init__(table, columns)
|
87
104
|
|
88
|
-
def __str__(self):
|
89
|
-
def col_str(column, idx):
|
105
|
+
def __str__(self) -> str:
|
106
|
+
def col_str(column: str, idx: int) -> str:
|
90
107
|
col = self.quote_name(column)
|
91
108
|
try:
|
92
109
|
suffix = self.col_suffixes[idx]
|
@@ -104,22 +121,35 @@ class Columns(TableColumns):
|
|
104
121
|
class IndexName(TableColumns):
|
105
122
|
"""Hold a reference to an index name."""
|
106
123
|
|
107
|
-
def __init__(
|
124
|
+
def __init__(
|
125
|
+
self,
|
126
|
+
table: str,
|
127
|
+
columns: list[str],
|
128
|
+
suffix: str,
|
129
|
+
create_index_name: Callable[[str, list[str], str], str],
|
130
|
+
) -> None:
|
108
131
|
self.suffix = suffix
|
109
132
|
self.create_index_name = create_index_name
|
110
133
|
super().__init__(table, columns)
|
111
134
|
|
112
|
-
def __str__(self):
|
135
|
+
def __str__(self) -> str:
|
113
136
|
return self.create_index_name(self.table, self.columns, self.suffix)
|
114
137
|
|
115
138
|
|
116
139
|
class IndexColumns(Columns):
|
117
|
-
def __init__(
|
140
|
+
def __init__(
|
141
|
+
self,
|
142
|
+
table: str,
|
143
|
+
columns: list[str],
|
144
|
+
quote_name: Callable[[str], str],
|
145
|
+
col_suffixes: tuple[str, ...] = (),
|
146
|
+
opclasses: tuple[str, ...] = (),
|
147
|
+
) -> None:
|
118
148
|
self.opclasses = opclasses
|
119
149
|
super().__init__(table, columns, quote_name, col_suffixes)
|
120
150
|
|
121
|
-
def __str__(self):
|
122
|
-
def col_str(column, idx):
|
151
|
+
def __str__(self) -> str:
|
152
|
+
def col_str(column: str, idx: int) -> str:
|
123
153
|
# Index.__init__() guarantees that self.opclasses is the same
|
124
154
|
# length as self.columns.
|
125
155
|
col = f"{self.quote_name(column)} {self.opclasses[idx]}"
|
@@ -141,13 +171,13 @@ class ForeignKeyName(TableColumns):
|
|
141
171
|
|
142
172
|
def __init__(
|
143
173
|
self,
|
144
|
-
from_table,
|
145
|
-
from_columns,
|
146
|
-
to_table,
|
147
|
-
to_columns,
|
148
|
-
suffix_template,
|
149
|
-
create_fk_name,
|
150
|
-
):
|
174
|
+
from_table: str,
|
175
|
+
from_columns: list[str],
|
176
|
+
to_table: str,
|
177
|
+
to_columns: list[str],
|
178
|
+
suffix_template: str,
|
179
|
+
create_fk_name: Callable[[str, list[str], str], str],
|
180
|
+
) -> None:
|
151
181
|
self.to_reference = TableColumns(to_table, to_columns)
|
152
182
|
self.suffix_template = suffix_template
|
153
183
|
self.create_fk_name = create_fk_name
|
@@ -156,25 +186,27 @@ class ForeignKeyName(TableColumns):
|
|
156
186
|
from_columns,
|
157
187
|
)
|
158
188
|
|
159
|
-
def references_table(self, table):
|
189
|
+
def references_table(self, table: str) -> bool:
|
160
190
|
return super().references_table(table) or self.to_reference.references_table(
|
161
191
|
table
|
162
192
|
)
|
163
193
|
|
164
|
-
def references_column(self, table, column):
|
194
|
+
def references_column(self, table: str, column: str) -> bool:
|
165
195
|
return super().references_column(
|
166
196
|
table, column
|
167
197
|
) or self.to_reference.references_column(table, column)
|
168
198
|
|
169
|
-
def rename_table_references(self, old_table, new_table):
|
199
|
+
def rename_table_references(self, old_table: str, new_table: str) -> None:
|
170
200
|
super().rename_table_references(old_table, new_table)
|
171
201
|
self.to_reference.rename_table_references(old_table, new_table)
|
172
202
|
|
173
|
-
def rename_column_references(
|
203
|
+
def rename_column_references(
|
204
|
+
self, table: str, old_column: str, new_column: str
|
205
|
+
) -> None:
|
174
206
|
super().rename_column_references(table, old_column, new_column)
|
175
207
|
self.to_reference.rename_column_references(table, old_column, new_column)
|
176
208
|
|
177
|
-
def __str__(self):
|
209
|
+
def __str__(self) -> str:
|
178
210
|
suffix = self.suffix_template % {
|
179
211
|
"to_table": self.to_reference.table,
|
180
212
|
"to_column": self.to_reference.columns[0],
|
@@ -191,38 +223,46 @@ class Statement(Reference):
|
|
191
223
|
that is removed
|
192
224
|
"""
|
193
225
|
|
194
|
-
def __init__(self, template, **parts):
|
226
|
+
def __init__(self, template: str, **parts: Any) -> None:
|
195
227
|
self.template = template
|
196
228
|
self.parts = parts
|
197
229
|
|
198
|
-
def references_table(self, table):
|
230
|
+
def references_table(self, table: str) -> bool:
|
199
231
|
return any(
|
200
232
|
hasattr(part, "references_table") and part.references_table(table)
|
201
233
|
for part in self.parts.values()
|
202
234
|
)
|
203
235
|
|
204
|
-
def references_column(self, table, column):
|
236
|
+
def references_column(self, table: str, column: str) -> bool:
|
205
237
|
return any(
|
206
238
|
hasattr(part, "references_column") and part.references_column(table, column)
|
207
239
|
for part in self.parts.values()
|
208
240
|
)
|
209
241
|
|
210
|
-
def rename_table_references(self, old_table, new_table):
|
242
|
+
def rename_table_references(self, old_table: str, new_table: str) -> None:
|
211
243
|
for part in self.parts.values():
|
212
244
|
if hasattr(part, "rename_table_references"):
|
213
245
|
part.rename_table_references(old_table, new_table)
|
214
246
|
|
215
|
-
def rename_column_references(
|
247
|
+
def rename_column_references(
|
248
|
+
self, table: str, old_column: str, new_column: str
|
249
|
+
) -> None:
|
216
250
|
for part in self.parts.values():
|
217
251
|
if hasattr(part, "rename_column_references"):
|
218
252
|
part.rename_column_references(table, old_column, new_column)
|
219
253
|
|
220
|
-
def __str__(self):
|
254
|
+
def __str__(self) -> str:
|
221
255
|
return self.template % self.parts
|
222
256
|
|
223
257
|
|
224
258
|
class Expressions(TableColumns):
|
225
|
-
def __init__(
|
259
|
+
def __init__(
|
260
|
+
self,
|
261
|
+
table: str,
|
262
|
+
expressions: Any,
|
263
|
+
compiler: SQLCompiler,
|
264
|
+
quote_value: Callable[[Any], str],
|
265
|
+
) -> None:
|
226
266
|
self.compiler = compiler
|
227
267
|
self.expressions = expressions
|
228
268
|
self.quote_value = quote_value
|
@@ -232,13 +272,15 @@ class Expressions(TableColumns):
|
|
232
272
|
]
|
233
273
|
super().__init__(table, columns)
|
234
274
|
|
235
|
-
def rename_table_references(self, old_table, new_table):
|
275
|
+
def rename_table_references(self, old_table: str, new_table: str) -> None:
|
236
276
|
if self.table != old_table:
|
237
277
|
return
|
238
278
|
self.expressions = self.expressions.relabeled_clone({old_table: new_table})
|
239
279
|
super().rename_table_references(old_table, new_table)
|
240
280
|
|
241
|
-
def rename_column_references(
|
281
|
+
def rename_column_references(
|
282
|
+
self, table: str, old_column: str, new_column: str
|
283
|
+
) -> None:
|
242
284
|
if self.table != table:
|
243
285
|
return
|
244
286
|
expressions = deepcopy(self.expressions)
|
@@ -249,7 +291,7 @@ class Expressions(TableColumns):
|
|
249
291
|
self.columns.append(col.target.column)
|
250
292
|
self.expressions = expressions
|
251
293
|
|
252
|
-
def __str__(self):
|
294
|
+
def __str__(self) -> str:
|
253
295
|
sql, params = self.compiler.compile(self.expressions)
|
254
296
|
params = map(self.quote_value, params)
|
255
297
|
return sql % tuple(params)
|
@@ -4,11 +4,14 @@ MySQL database backend for Plain.
|
|
4
4
|
Requires mysqlclient: https://pypi.org/project/mysqlclient/
|
5
5
|
"""
|
6
6
|
|
7
|
+
from __future__ import annotations
|
8
|
+
|
7
9
|
from functools import cached_property
|
10
|
+
from typing import Any
|
8
11
|
|
9
|
-
import MySQLdb as Database
|
10
|
-
from MySQLdb.constants import CLIENT, FIELD_TYPE
|
11
|
-
from MySQLdb.converters import conversions
|
12
|
+
import MySQLdb as Database # type: ignore[import-untyped]
|
13
|
+
from MySQLdb.constants import CLIENT, FIELD_TYPE # type: ignore[import-untyped]
|
14
|
+
from MySQLdb.converters import conversions # type: ignore[import-untyped]
|
12
15
|
|
13
16
|
from plain.exceptions import ImproperlyConfigured
|
14
17
|
from plain.models.backends import utils as backend_utils
|
@@ -53,10 +56,10 @@ class CursorWrapper:
|
|
53
56
|
4025, # CHECK constraint failed
|
54
57
|
)
|
55
58
|
|
56
|
-
def __init__(self, cursor):
|
59
|
+
def __init__(self, cursor: Any) -> None:
|
57
60
|
self.cursor = cursor
|
58
61
|
|
59
|
-
def execute(self, query, args=None):
|
62
|
+
def execute(self, query: str, args: Any = None) -> int:
|
60
63
|
try:
|
61
64
|
# args is None means no string interpolation
|
62
65
|
return self.cursor.execute(query, args)
|
@@ -67,7 +70,7 @@ class CursorWrapper:
|
|
67
70
|
raise IntegrityError(*tuple(e.args))
|
68
71
|
raise
|
69
72
|
|
70
|
-
def executemany(self, query, args):
|
73
|
+
def executemany(self, query: str, args: Any) -> int:
|
71
74
|
try:
|
72
75
|
return self.cursor.executemany(query, args)
|
73
76
|
except Database.OperationalError as e:
|
@@ -77,14 +80,14 @@ class CursorWrapper:
|
|
77
80
|
raise IntegrityError(*tuple(e.args))
|
78
81
|
raise
|
79
82
|
|
80
|
-
def __getattr__(self, attr):
|
83
|
+
def __getattr__(self, attr: str) -> Any:
|
81
84
|
return getattr(self.cursor, attr)
|
82
85
|
|
83
|
-
def __iter__(self):
|
86
|
+
def __iter__(self) -> Any:
|
84
87
|
return iter(self.cursor)
|
85
88
|
|
86
89
|
|
87
|
-
class
|
90
|
+
class MySQLDatabaseWrapper(BaseDatabaseWrapper):
|
88
91
|
vendor = "mysql"
|
89
92
|
# This dictionary maps Field objects to their associated MySQL column
|
90
93
|
# types, as strings. Column-type strings can contain format strings; they'll
|
@@ -180,11 +183,11 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|
180
183
|
ops_class = DatabaseOperations
|
181
184
|
validation_class = DatabaseValidation
|
182
185
|
|
183
|
-
def get_database_version(self):
|
186
|
+
def get_database_version(self) -> tuple[int, int, int]:
|
184
187
|
return self.mysql_version
|
185
188
|
|
186
|
-
def get_connection_params(self):
|
187
|
-
kwargs = {
|
189
|
+
def get_connection_params(self) -> dict[str, Any]:
|
190
|
+
kwargs: dict[str, Any] = {
|
188
191
|
"conv": plain_conversions,
|
189
192
|
"charset": "utf8",
|
190
193
|
}
|
@@ -221,7 +224,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|
221
224
|
kwargs.update(options)
|
222
225
|
return kwargs
|
223
226
|
|
224
|
-
def get_new_connection(self, conn_params):
|
227
|
+
def get_new_connection(self, conn_params: dict[str, Any]) -> Any:
|
225
228
|
connection = Database.connect(**conn_params)
|
226
229
|
# bytes encoder in mysqlclient doesn't work and was added only to
|
227
230
|
# prevent KeyErrors in Plain < 2.0. We can remove this workaround when
|
@@ -231,7 +234,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|
231
234
|
connection.encoders.pop(bytes)
|
232
235
|
return connection
|
233
236
|
|
234
|
-
def init_connection_state(self):
|
237
|
+
def init_connection_state(self) -> None:
|
235
238
|
super().init_connection_state()
|
236
239
|
assignments = []
|
237
240
|
if self.features.is_sql_auto_is_null_enabled:
|
@@ -250,21 +253,21 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|
250
253
|
with self.cursor() as cursor:
|
251
254
|
cursor.execute("; ".join(assignments))
|
252
255
|
|
253
|
-
def create_cursor(self, name=None):
|
256
|
+
def create_cursor(self, name: str | None = None) -> CursorWrapper:
|
254
257
|
cursor = self.connection.cursor()
|
255
258
|
return CursorWrapper(cursor)
|
256
259
|
|
257
|
-
def _rollback(self):
|
260
|
+
def _rollback(self) -> None:
|
258
261
|
try:
|
259
262
|
BaseDatabaseWrapper._rollback(self)
|
260
263
|
except Database.NotSupportedError:
|
261
264
|
pass
|
262
265
|
|
263
|
-
def _set_autocommit(self, autocommit):
|
266
|
+
def _set_autocommit(self, autocommit: bool) -> None:
|
264
267
|
with self.wrap_database_errors:
|
265
268
|
self.connection.autocommit(autocommit)
|
266
269
|
|
267
|
-
def check_constraints(self, table_names=None):
|
270
|
+
def check_constraints(self, table_names: list[str] | None = None) -> None:
|
268
271
|
"""Check ``table_names`` for rows with invalid foreign key references."""
|
269
272
|
with self.cursor() as cursor:
|
270
273
|
if table_names is None:
|
@@ -295,7 +298,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|
295
298
|
f"does not have a corresponding value in {referenced_table_name}.{referenced_column_name}."
|
296
299
|
)
|
297
300
|
|
298
|
-
def is_usable(self):
|
301
|
+
def is_usable(self) -> bool:
|
299
302
|
try:
|
300
303
|
self.connection.ping()
|
301
304
|
except Database.Error:
|
@@ -304,11 +307,11 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|
304
307
|
return True
|
305
308
|
|
306
309
|
@cached_property
|
307
|
-
def display_name(self):
|
310
|
+
def display_name(self) -> str:
|
308
311
|
return "MariaDB" if self.mysql_is_mariadb else "MySQL"
|
309
312
|
|
310
313
|
@cached_property
|
311
|
-
def data_type_check_constraints(self):
|
314
|
+
def data_type_check_constraints(self) -> dict[str, str]:
|
312
315
|
if self.features.supports_column_check_constraints:
|
313
316
|
check_constraints = {
|
314
317
|
"PositiveBigIntegerField": "`%(column)s` >= 0",
|
@@ -323,7 +326,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|
323
326
|
return {}
|
324
327
|
|
325
328
|
@cached_property
|
326
|
-
def mysql_server_data(self):
|
329
|
+
def mysql_server_data(self) -> dict[str, Any]:
|
327
330
|
with self.temporary_connection() as cursor:
|
328
331
|
# Select some server variables and test if the time zone
|
329
332
|
# definitions are installed. CONVERT_TZ returns NULL if 'UTC'
|
@@ -349,11 +352,11 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|
349
352
|
}
|
350
353
|
|
351
354
|
@cached_property
|
352
|
-
def mysql_server_info(self):
|
355
|
+
def mysql_server_info(self) -> str:
|
353
356
|
return self.mysql_server_data["version"]
|
354
357
|
|
355
358
|
@cached_property
|
356
|
-
def mysql_version(self):
|
359
|
+
def mysql_version(self) -> tuple[int, int, int]:
|
357
360
|
match = server_version_re.match(self.mysql_server_info)
|
358
361
|
if not match:
|
359
362
|
raise Exception(
|
@@ -362,10 +365,10 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|
362
365
|
return tuple(int(x) for x in match.groups())
|
363
366
|
|
364
367
|
@cached_property
|
365
|
-
def mysql_is_mariadb(self):
|
368
|
+
def mysql_is_mariadb(self) -> bool:
|
366
369
|
return "mariadb" in self.mysql_server_info.lower()
|
367
370
|
|
368
371
|
@cached_property
|
369
|
-
def sql_mode(self):
|
372
|
+
def sql_mode(self) -> set[str]:
|
370
373
|
sql_mode = self.mysql_server_data["sql_mode"]
|
371
374
|
return set(sql_mode.split(",") if sql_mode else ())
|
@@ -1,4 +1,7 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import signal
|
4
|
+
from typing import Any
|
2
5
|
|
3
6
|
from plain.models.backends.base.client import BaseDatabaseClient
|
4
7
|
|
@@ -7,7 +10,9 @@ class DatabaseClient(BaseDatabaseClient):
|
|
7
10
|
executable_name = "mysql"
|
8
11
|
|
9
12
|
@classmethod
|
10
|
-
def settings_to_cmd_args_env(
|
13
|
+
def settings_to_cmd_args_env(
|
14
|
+
cls, settings_dict: dict[str, Any], parameters: list[str]
|
15
|
+
) -> tuple[list[str], dict[str, str] | None]:
|
11
16
|
args = [cls.executable_name]
|
12
17
|
env = None
|
13
18
|
database = settings_dict["OPTIONS"].get(
|
@@ -61,7 +66,7 @@ class DatabaseClient(BaseDatabaseClient):
|
|
61
66
|
args.extend(parameters)
|
62
67
|
return args, env
|
63
68
|
|
64
|
-
def runshell(self, parameters):
|
69
|
+
def runshell(self, parameters: list[str]) -> None:
|
65
70
|
sigint_handler = signal.getsignal(signal.SIGINT)
|
66
71
|
try:
|
67
72
|
# Allow SIGINT to pass to mysql to abort queries.
|
@@ -1,10 +1,19 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING, Any
|
4
|
+
|
1
5
|
from plain.models.exceptions import FieldError, FullResultSet
|
2
6
|
from plain.models.expressions import Col
|
3
7
|
from plain.models.sql import compiler
|
4
8
|
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from plain.models.sql.compiler import SQLCompiler as BaseSQLCompiler
|
11
|
+
|
5
12
|
|
6
13
|
class SQLCompiler(compiler.SQLCompiler):
|
7
|
-
def as_subquery_condition(
|
14
|
+
def as_subquery_condition(
|
15
|
+
self, alias: str, columns: list[str], compiler: BaseSQLCompiler
|
16
|
+
) -> tuple[str, tuple[Any, ...]]:
|
8
17
|
qn = compiler.quote_name_unless_alias
|
9
18
|
qn2 = self.connection.ops.quote_name
|
10
19
|
sql, params = self.as_sql()
|
@@ -22,7 +31,7 @@ class SQLInsertCompiler(compiler.SQLInsertCompiler, SQLCompiler):
|
|
22
31
|
|
23
32
|
|
24
33
|
class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler):
|
25
|
-
def as_sql(self):
|
34
|
+
def as_sql(self) -> tuple[str, tuple[Any, ...]]:
|
26
35
|
# Prefer the non-standard DELETE FROM syntax over the SQL generated by
|
27
36
|
# the SQLDeleteCompiler's default implementation when multiple tables
|
28
37
|
# are involved since MySQL/MariaDB will generate a more efficient query
|
@@ -51,7 +60,7 @@ class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler):
|
|
51
60
|
|
52
61
|
|
53
62
|
class SQLUpdateCompiler(compiler.SQLUpdateCompiler, SQLCompiler):
|
54
|
-
def as_sql(self):
|
63
|
+
def as_sql(self) -> tuple[str, tuple[Any, ...]]:
|
55
64
|
update_query, update_params = super().as_sql()
|
56
65
|
# MySQL and MariaDB support UPDATE ... ORDER BY syntax.
|
57
66
|
if self.query.order_by:
|
@@ -1,10 +1,13 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import sys
|
4
|
+
from typing import Any
|
2
5
|
|
3
6
|
from plain.models.backends.base.creation import BaseDatabaseCreation
|
4
7
|
|
5
8
|
|
6
9
|
class DatabaseCreation(BaseDatabaseCreation):
|
7
|
-
def sql_table_creation_suffix(self):
|
10
|
+
def sql_table_creation_suffix(self) -> str:
|
8
11
|
suffix = []
|
9
12
|
test_settings = self.connection.settings_dict["TEST"]
|
10
13
|
if test_settings["CHARSET"]:
|
@@ -13,7 +16,7 @@ class DatabaseCreation(BaseDatabaseCreation):
|
|
13
16
|
suffix.append("COLLATE {}".format(test_settings["COLLATION"]))
|
14
17
|
return " ".join(suffix)
|
15
18
|
|
16
|
-
def _execute_create_test_db(self, cursor, parameters):
|
19
|
+
def _execute_create_test_db(self, cursor: Any, parameters: dict[str, Any]) -> None:
|
17
20
|
try:
|
18
21
|
super()._execute_create_test_db(cursor, parameters)
|
19
22
|
except Exception as e:
|