plain.models 0.49.2__py3-none-any.whl → 0.51.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.
Files changed (108) hide show
  1. plain/models/CHANGELOG.md +27 -0
  2. plain/models/README.md +26 -42
  3. plain/models/__init__.py +2 -0
  4. plain/models/aggregates.py +42 -19
  5. plain/models/backends/base/base.py +125 -105
  6. plain/models/backends/base/client.py +11 -3
  7. plain/models/backends/base/creation.py +24 -14
  8. plain/models/backends/base/features.py +10 -4
  9. plain/models/backends/base/introspection.py +37 -20
  10. plain/models/backends/base/operations.py +187 -91
  11. plain/models/backends/base/schema.py +338 -218
  12. plain/models/backends/base/validation.py +13 -4
  13. plain/models/backends/ddl_references.py +85 -43
  14. plain/models/backends/mysql/base.py +29 -26
  15. plain/models/backends/mysql/client.py +7 -2
  16. plain/models/backends/mysql/compiler.py +13 -4
  17. plain/models/backends/mysql/creation.py +5 -2
  18. plain/models/backends/mysql/features.py +24 -22
  19. plain/models/backends/mysql/introspection.py +22 -13
  20. plain/models/backends/mysql/operations.py +107 -40
  21. plain/models/backends/mysql/schema.py +52 -28
  22. plain/models/backends/mysql/validation.py +13 -6
  23. plain/models/backends/postgresql/base.py +41 -34
  24. plain/models/backends/postgresql/client.py +7 -2
  25. plain/models/backends/postgresql/creation.py +10 -5
  26. plain/models/backends/postgresql/introspection.py +15 -8
  27. plain/models/backends/postgresql/operations.py +110 -43
  28. plain/models/backends/postgresql/schema.py +88 -49
  29. plain/models/backends/sqlite3/_functions.py +151 -115
  30. plain/models/backends/sqlite3/base.py +37 -23
  31. plain/models/backends/sqlite3/client.py +7 -1
  32. plain/models/backends/sqlite3/creation.py +9 -5
  33. plain/models/backends/sqlite3/features.py +5 -3
  34. plain/models/backends/sqlite3/introspection.py +32 -16
  35. plain/models/backends/sqlite3/operations.py +126 -43
  36. plain/models/backends/sqlite3/schema.py +127 -92
  37. plain/models/backends/utils.py +52 -29
  38. plain/models/backups/cli.py +8 -6
  39. plain/models/backups/clients.py +16 -7
  40. plain/models/backups/core.py +24 -13
  41. plain/models/base.py +221 -229
  42. plain/models/cli.py +98 -67
  43. plain/models/config.py +1 -1
  44. plain/models/connections.py +23 -7
  45. plain/models/constraints.py +79 -56
  46. plain/models/database_url.py +1 -1
  47. plain/models/db.py +6 -2
  48. plain/models/deletion.py +80 -56
  49. plain/models/entrypoints.py +1 -1
  50. plain/models/enums.py +22 -11
  51. plain/models/exceptions.py +23 -8
  52. plain/models/expressions.py +441 -258
  53. plain/models/fields/__init__.py +272 -217
  54. plain/models/fields/json.py +123 -57
  55. plain/models/fields/mixins.py +12 -8
  56. plain/models/fields/related.py +324 -290
  57. plain/models/fields/related_descriptors.py +33 -24
  58. plain/models/fields/related_lookups.py +24 -12
  59. plain/models/fields/related_managers.py +102 -79
  60. plain/models/fields/reverse_related.py +66 -63
  61. plain/models/forms.py +101 -75
  62. plain/models/functions/comparison.py +71 -18
  63. plain/models/functions/datetime.py +79 -29
  64. plain/models/functions/math.py +43 -10
  65. plain/models/functions/mixins.py +24 -7
  66. plain/models/functions/text.py +104 -25
  67. plain/models/functions/window.py +12 -6
  68. plain/models/indexes.py +57 -32
  69. plain/models/lookups.py +228 -153
  70. plain/models/meta.py +505 -0
  71. plain/models/migrations/autodetector.py +86 -43
  72. plain/models/migrations/exceptions.py +7 -3
  73. plain/models/migrations/executor.py +33 -7
  74. plain/models/migrations/graph.py +79 -50
  75. plain/models/migrations/loader.py +45 -22
  76. plain/models/migrations/migration.py +23 -18
  77. plain/models/migrations/operations/base.py +38 -20
  78. plain/models/migrations/operations/fields.py +95 -48
  79. plain/models/migrations/operations/models.py +246 -142
  80. plain/models/migrations/operations/special.py +82 -25
  81. plain/models/migrations/optimizer.py +7 -2
  82. plain/models/migrations/questioner.py +58 -31
  83. plain/models/migrations/recorder.py +27 -16
  84. plain/models/migrations/serializer.py +50 -39
  85. plain/models/migrations/state.py +232 -156
  86. plain/models/migrations/utils.py +30 -14
  87. plain/models/migrations/writer.py +17 -14
  88. plain/models/options.py +189 -518
  89. plain/models/otel.py +16 -6
  90. plain/models/preflight.py +42 -17
  91. plain/models/query.py +400 -251
  92. plain/models/query_utils.py +109 -69
  93. plain/models/registry.py +40 -21
  94. plain/models/sql/compiler.py +190 -127
  95. plain/models/sql/datastructures.py +38 -25
  96. plain/models/sql/query.py +320 -225
  97. plain/models/sql/subqueries.py +36 -25
  98. plain/models/sql/where.py +54 -29
  99. plain/models/test/pytest.py +15 -11
  100. plain/models/test/utils.py +4 -2
  101. plain/models/transaction.py +20 -7
  102. plain/models/utils.py +17 -6
  103. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/METADATA +27 -43
  104. plain_models-0.51.0.dist-info/RECORD +123 -0
  105. plain_models-0.49.2.dist-info/RECORD +0 -122
  106. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/WHEEL +0 -0
  107. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/entry_points.txt +0 -0
  108. {plain_models-0.49.2.dist-info → plain_models-0.51.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 (
@@ -19,7 +28,7 @@ class BaseDatabaseValidation:
19
28
  # Ignore fields with unsupported features.
20
29
  db_supports_all_required_features = all(
21
30
  getattr(self.connection.features, feature, False)
22
- for feature in field.model._meta.required_db_features
31
+ for feature in field.model.model_options.required_db_features
23
32
  )
24
33
  if db_supports_all_required_features:
25
34
  field_type = field.db_type(self.connection)
@@ -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(self, table, old_column, new_column):
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(self, table, old_column, new_column):
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__(self, table, columns, quote_name, col_suffixes=()):
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__(self, table, columns, suffix, create_index_name):
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__(self, table, columns, quote_name, col_suffixes=(), opclasses=()):
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(self, table, old_column, new_column):
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(self, table, old_column, new_column):
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__(self, table, expressions, compiler, quote_value):
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(self, table, old_column, new_column):
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 DatabaseWrapper(BaseDatabaseWrapper):
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(cls, settings_dict, parameters):
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(self, alias, columns, compiler):
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,13 +60,13 @@ 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:
58
67
  order_by_sql = []
59
68
  order_by_params = []
60
- db_table = self.query.get_meta().db_table
69
+ db_table = self.query.get_model_meta().db_table
61
70
  try:
62
71
  for resolved, (sql, params, _) in self.get_order_by():
63
72
  if (
@@ -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: