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.
Files changed (105) hide show
  1. plain/models/CHANGELOG.md +23 -0
  2. plain/models/aggregates.py +42 -19
  3. plain/models/backends/base/base.py +125 -105
  4. plain/models/backends/base/client.py +11 -3
  5. plain/models/backends/base/creation.py +22 -12
  6. plain/models/backends/base/features.py +10 -4
  7. plain/models/backends/base/introspection.py +29 -16
  8. plain/models/backends/base/operations.py +187 -91
  9. plain/models/backends/base/schema.py +267 -165
  10. plain/models/backends/base/validation.py +12 -3
  11. plain/models/backends/ddl_references.py +85 -43
  12. plain/models/backends/mysql/base.py +29 -26
  13. plain/models/backends/mysql/client.py +7 -2
  14. plain/models/backends/mysql/compiler.py +12 -3
  15. plain/models/backends/mysql/creation.py +5 -2
  16. plain/models/backends/mysql/features.py +24 -22
  17. plain/models/backends/mysql/introspection.py +22 -13
  18. plain/models/backends/mysql/operations.py +106 -39
  19. plain/models/backends/mysql/schema.py +48 -24
  20. plain/models/backends/mysql/validation.py +13 -6
  21. plain/models/backends/postgresql/base.py +41 -34
  22. plain/models/backends/postgresql/client.py +7 -2
  23. plain/models/backends/postgresql/creation.py +10 -5
  24. plain/models/backends/postgresql/introspection.py +15 -8
  25. plain/models/backends/postgresql/operations.py +109 -42
  26. plain/models/backends/postgresql/schema.py +85 -46
  27. plain/models/backends/sqlite3/_functions.py +151 -115
  28. plain/models/backends/sqlite3/base.py +37 -23
  29. plain/models/backends/sqlite3/client.py +7 -1
  30. plain/models/backends/sqlite3/creation.py +9 -5
  31. plain/models/backends/sqlite3/features.py +5 -3
  32. plain/models/backends/sqlite3/introspection.py +32 -16
  33. plain/models/backends/sqlite3/operations.py +125 -42
  34. plain/models/backends/sqlite3/schema.py +82 -58
  35. plain/models/backends/utils.py +52 -29
  36. plain/models/backups/cli.py +8 -6
  37. plain/models/backups/clients.py +16 -7
  38. plain/models/backups/core.py +24 -13
  39. plain/models/base.py +113 -74
  40. plain/models/cli.py +94 -63
  41. plain/models/config.py +1 -1
  42. plain/models/connections.py +23 -7
  43. plain/models/constraints.py +65 -47
  44. plain/models/database_url.py +1 -1
  45. plain/models/db.py +6 -2
  46. plain/models/deletion.py +66 -43
  47. plain/models/entrypoints.py +1 -1
  48. plain/models/enums.py +22 -11
  49. plain/models/exceptions.py +23 -8
  50. plain/models/expressions.py +440 -257
  51. plain/models/fields/__init__.py +253 -202
  52. plain/models/fields/json.py +120 -54
  53. plain/models/fields/mixins.py +12 -8
  54. plain/models/fields/related.py +284 -252
  55. plain/models/fields/related_descriptors.py +34 -25
  56. plain/models/fields/related_lookups.py +23 -11
  57. plain/models/fields/related_managers.py +81 -47
  58. plain/models/fields/reverse_related.py +58 -55
  59. plain/models/forms.py +89 -63
  60. plain/models/functions/comparison.py +71 -18
  61. plain/models/functions/datetime.py +79 -29
  62. plain/models/functions/math.py +43 -10
  63. plain/models/functions/mixins.py +24 -7
  64. plain/models/functions/text.py +104 -25
  65. plain/models/functions/window.py +12 -6
  66. plain/models/indexes.py +52 -28
  67. plain/models/lookups.py +228 -153
  68. plain/models/migrations/autodetector.py +86 -43
  69. plain/models/migrations/exceptions.py +7 -3
  70. plain/models/migrations/executor.py +33 -7
  71. plain/models/migrations/graph.py +79 -50
  72. plain/models/migrations/loader.py +45 -22
  73. plain/models/migrations/migration.py +23 -18
  74. plain/models/migrations/operations/base.py +37 -19
  75. plain/models/migrations/operations/fields.py +89 -42
  76. plain/models/migrations/operations/models.py +245 -143
  77. plain/models/migrations/operations/special.py +82 -25
  78. plain/models/migrations/optimizer.py +7 -2
  79. plain/models/migrations/questioner.py +58 -31
  80. plain/models/migrations/recorder.py +18 -11
  81. plain/models/migrations/serializer.py +50 -39
  82. plain/models/migrations/state.py +220 -133
  83. plain/models/migrations/utils.py +29 -13
  84. plain/models/migrations/writer.py +17 -14
  85. plain/models/options.py +63 -56
  86. plain/models/otel.py +16 -6
  87. plain/models/preflight.py +35 -12
  88. plain/models/query.py +323 -228
  89. plain/models/query_utils.py +93 -58
  90. plain/models/registry.py +34 -16
  91. plain/models/sql/compiler.py +146 -97
  92. plain/models/sql/datastructures.py +38 -25
  93. plain/models/sql/query.py +255 -169
  94. plain/models/sql/subqueries.py +32 -21
  95. plain/models/sql/where.py +54 -29
  96. plain/models/test/pytest.py +15 -11
  97. plain/models/test/utils.py +4 -2
  98. plain/models/transaction.py +20 -7
  99. plain/models/utils.py +13 -5
  100. {plain_models-0.49.1.dist-info → plain_models-0.50.0.dist-info}/METADATA +1 -1
  101. plain_models-0.50.0.dist-info/RECORD +122 -0
  102. plain_models-0.49.1.dist-info/RECORD +0 -122
  103. {plain_models-0.49.1.dist-info → plain_models-0.50.0.dist-info}/WHEEL +0 -0
  104. {plain_models-0.49.1.dist-info → plain_models-0.50.0.dist-info}/entry_points.txt +0 -0
  105. {plain_models-0.49.1.dist-info → plain_models-0.50.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging
2
4
  import operator
5
+ from collections.abc import Generator
3
6
  from datetime import datetime
7
+ from typing import TYPE_CHECKING, Any
4
8
 
5
9
  from plain.models.backends.ddl_references import (
6
10
  Columns,
@@ -17,10 +21,18 @@ from plain.models.sql import Query
17
21
  from plain.models.transaction import TransactionManagementError, atomic
18
22
  from plain.utils import timezone
19
23
 
24
+ if TYPE_CHECKING:
25
+ from collections.abc import Iterable
26
+
27
+ from plain.models.backends.base.base import BaseDatabaseWrapper
28
+ from plain.models.base import Model
29
+ from plain.models.constraints import BaseConstraint
30
+ from plain.models.fields import Field
31
+
20
32
  logger = logging.getLogger("plain.models.backends.schema")
21
33
 
22
34
 
23
- def _is_relevant_relation(relation, altered_field):
35
+ def _is_relevant_relation(relation: Any, altered_field: Field) -> bool:
24
36
  """
25
37
  When altering the given field, must constraints on its model from the given
26
38
  relation be temporarily dropped?
@@ -36,10 +48,10 @@ def _is_relevant_relation(relation, altered_field):
36
48
  return altered_field.name == "id"
37
49
 
38
50
 
39
- def _all_related_fields(model):
51
+ def _all_related_fields(model: type[Model]) -> list[Any]:
40
52
  # Related fields must be returned in a deterministic order.
41
53
  return sorted(
42
- model._meta._get_fields(
54
+ model._meta._get_fields( # type: ignore[attr-defined]
43
55
  forward=False,
44
56
  reverse=True,
45
57
  include_hidden=True,
@@ -48,7 +60,9 @@ def _all_related_fields(model):
48
60
  )
49
61
 
50
62
 
51
- def _related_non_m2m_objects(old_field, new_field):
63
+ def _related_non_m2m_objects(
64
+ old_field: Field, new_field: Field
65
+ ) -> Generator[tuple[Any, Any], None, None]:
52
66
  # Filter out m2m objects from reverse relations.
53
67
  # Return (old_relation, new_relation) tuples.
54
68
  related_fields = zip(
@@ -140,23 +154,28 @@ class BaseDatabaseSchemaEditor:
140
154
  sql_alter_table_comment = "COMMENT ON TABLE %(table)s IS %(comment)s"
141
155
  sql_alter_column_comment = "COMMENT ON COLUMN %(table)s.%(column)s IS %(comment)s"
142
156
 
143
- def __init__(self, connection, collect_sql=False, atomic=True):
157
+ def __init__(
158
+ self,
159
+ connection: BaseDatabaseWrapper,
160
+ collect_sql: bool = False,
161
+ atomic: bool = True,
162
+ ):
144
163
  self.connection = connection
145
164
  self.collect_sql = collect_sql
146
165
  if self.collect_sql:
147
- self.collected_sql = []
166
+ self.collected_sql: list[str] = []
148
167
  self.atomic_migration = self.connection.features.can_rollback_ddl and atomic
149
168
 
150
169
  # State-managing methods
151
170
 
152
- def __enter__(self):
153
- self.deferred_sql = []
171
+ def __enter__(self) -> BaseDatabaseSchemaEditor:
172
+ self.deferred_sql: list[Any] = []
154
173
  if self.atomic_migration:
155
174
  self.atomic = atomic()
156
175
  self.atomic.__enter__()
157
176
  return self
158
177
 
159
- def __exit__(self, exc_type, exc_value, traceback):
178
+ def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
160
179
  if exc_type is None:
161
180
  for sql in self.deferred_sql:
162
181
  self.execute(sql)
@@ -165,7 +184,9 @@ class BaseDatabaseSchemaEditor:
165
184
 
166
185
  # Core utility functions
167
186
 
168
- def execute(self, sql, params=()):
187
+ def execute(
188
+ self, sql: str | Statement, params: tuple[Any, ...] | list[Any] | None = ()
189
+ ) -> None:
169
190
  """Execute the given SQL statement, with optional parameters."""
170
191
  # Don't perform the transactional DDL check if SQL is being collected
171
192
  # as it's not going to be executed anyway.
@@ -196,10 +217,10 @@ class BaseDatabaseSchemaEditor:
196
217
  with self.connection.cursor() as cursor:
197
218
  cursor.execute(sql, params)
198
219
 
199
- def quote_name(self, name):
220
+ def quote_name(self, name: str) -> str:
200
221
  return self.connection.ops.quote_name(name)
201
222
 
202
- def table_sql(self, model):
223
+ def table_sql(self, model: type[Model]) -> tuple[str, list[Any]]:
203
224
  """Take a model and return its table definition."""
204
225
  # Create column SQL, add FK deferreds if needed.
205
226
  column_sqls = []
@@ -219,7 +240,7 @@ class BaseDatabaseSchemaEditor:
219
240
  definition += f" {col_type_suffix}"
220
241
  params.extend(extra_params)
221
242
  # FK.
222
- if field.remote_field and field.db_constraint:
243
+ if field.remote_field and field.db_constraint: # type: ignore[attr-defined]
223
244
  to_table = field.remote_field.model._meta.db_table
224
245
  to_column = field.remote_field.model._meta.get_field(
225
246
  field.remote_field.field_name
@@ -262,8 +283,14 @@ class BaseDatabaseSchemaEditor:
262
283
  # Field <-> database mapping functions
263
284
 
264
285
  def _iter_column_sql(
265
- self, column_db_type, params, model, field, field_db_params, include_default
266
- ):
286
+ self,
287
+ column_db_type: str,
288
+ params: list[Any],
289
+ model: type[Model],
290
+ field: Field,
291
+ field_db_params: dict[str, Any],
292
+ include_default: bool,
293
+ ) -> Generator[str, None, None]:
267
294
  yield column_db_type
268
295
  if collation := field_db_params.get("collation"):
269
296
  yield self._collate_sql(collation)
@@ -302,7 +329,9 @@ class BaseDatabaseSchemaEditor:
302
329
  if field.primary_key:
303
330
  yield "PRIMARY KEY"
304
331
 
305
- def column_sql(self, model, field, include_default=False):
332
+ def column_sql(
333
+ self, model: type[Model], field: Field, include_default: bool = False
334
+ ) -> tuple[str | None, list[Any] | None]:
306
335
  """
307
336
  Return the column definition for a field. The field must already have
308
337
  had set_attributes_from_name() called.
@@ -313,7 +342,7 @@ class BaseDatabaseSchemaEditor:
313
342
  # Check for fields that aren't actually columns (e.g. M2M).
314
343
  if column_db_type is None:
315
344
  return None, None
316
- params = []
345
+ params: list[Any] = []
317
346
  return (
318
347
  " ".join(
319
348
  # This appends to the params being returned.
@@ -329,21 +358,21 @@ class BaseDatabaseSchemaEditor:
329
358
  params,
330
359
  )
331
360
 
332
- def skip_default(self, field):
361
+ def skip_default(self, field: Field) -> bool:
333
362
  """
334
363
  Some backends don't accept default values for certain columns types
335
364
  (i.e. MySQL longtext and longblob).
336
365
  """
337
366
  return False
338
367
 
339
- def skip_default_on_alter(self, field):
368
+ def skip_default_on_alter(self, field: Field) -> bool:
340
369
  """
341
370
  Some backends don't accept default values for certain columns types
342
371
  (i.e. MySQL longtext and longblob) in the ALTER COLUMN statement.
343
372
  """
344
373
  return False
345
374
 
346
- def prepare_default(self, value):
375
+ def prepare_default(self, value: Any) -> str:
347
376
  """
348
377
  Only used for backends which have requires_literal_defaults feature
349
378
  """
@@ -352,7 +381,7 @@ class BaseDatabaseSchemaEditor:
352
381
  "requires_literal_defaults must provide a prepare_default() method"
353
382
  )
354
383
 
355
- def _column_default_sql(self, field):
384
+ def _column_default_sql(self, field: Field) -> str:
356
385
  """
357
386
  Return the SQL to use in a DEFAULT clause. The resulting string should
358
387
  contain a '%s' placeholder for a default value.
@@ -360,7 +389,7 @@ class BaseDatabaseSchemaEditor:
360
389
  return "%s"
361
390
 
362
391
  @staticmethod
363
- def _effective_default(field):
392
+ def _effective_default(field: Field) -> Any:
364
393
  # This method allows testing its logic without a connection.
365
394
  if field.has_default():
366
395
  default = field.get_default()
@@ -385,11 +414,11 @@ class BaseDatabaseSchemaEditor:
385
414
  default = None
386
415
  return default
387
416
 
388
- def effective_default(self, field):
417
+ def effective_default(self, field: Field) -> Any:
389
418
  """Return a field's effective database default value."""
390
419
  return field.get_db_prep_save(self._effective_default(field), self.connection)
391
420
 
392
- def quote_value(self, value):
421
+ def quote_value(self, value: Any) -> str:
393
422
  """
394
423
  Return a quoted version of the value so it's safe to use in an SQL
395
424
  string. This is not safe against injection from user code; it is
@@ -401,7 +430,7 @@ class BaseDatabaseSchemaEditor:
401
430
 
402
431
  # Actions
403
432
 
404
- def create_model(self, model):
433
+ def create_model(self, model: type[Model]) -> None:
405
434
  """
406
435
  Create a table and any accompanying indexes or unique constraints for
407
436
  the given `model`.
@@ -431,7 +460,7 @@ class BaseDatabaseSchemaEditor:
431
460
  # Add any field index (deferred as SQLite _remake_table needs it).
432
461
  self.deferred_sql.extend(self._model_indexes_sql(model))
433
462
 
434
- def delete_model(self, model):
463
+ def delete_model(self, model: type[Model]) -> None:
435
464
  """Delete a model from the database."""
436
465
 
437
466
  # Delete the table
@@ -448,7 +477,7 @@ class BaseDatabaseSchemaEditor:
448
477
  ):
449
478
  self.deferred_sql.remove(sql)
450
479
 
451
- def add_index(self, model, index):
480
+ def add_index(self, model: type[Model], index: Index) -> None:
452
481
  """Add an index on a model."""
453
482
  if (
454
483
  index.contains_expressions
@@ -459,7 +488,7 @@ class BaseDatabaseSchemaEditor:
459
488
  # necessity to avoid escaping attempts on execution.
460
489
  self.execute(index.create_sql(model, self), params=None)
461
490
 
462
- def remove_index(self, model, index):
491
+ def remove_index(self, model: type[Model], index: Index) -> None:
463
492
  """Remove an index from a model."""
464
493
  if (
465
494
  index.contains_expressions
@@ -468,7 +497,9 @@ class BaseDatabaseSchemaEditor:
468
497
  return None
469
498
  self.execute(index.remove_sql(model, self))
470
499
 
471
- def rename_index(self, model, old_index, new_index):
500
+ def rename_index(
501
+ self, model: type[Model], old_index: Index, new_index: Index
502
+ ) -> None:
472
503
  if self.connection.features.can_rename_index:
473
504
  self.execute(
474
505
  self._rename_index_sql(model, old_index.name, new_index.name),
@@ -478,7 +509,7 @@ class BaseDatabaseSchemaEditor:
478
509
  self.remove_index(model, old_index)
479
510
  self.add_index(model, new_index)
480
511
 
481
- def add_constraint(self, model, constraint):
512
+ def add_constraint(self, model: type[Model], constraint: BaseConstraint) -> None:
482
513
  """Add a constraint to a model."""
483
514
  sql = constraint.create_sql(model, self)
484
515
  if sql:
@@ -486,13 +517,15 @@ class BaseDatabaseSchemaEditor:
486
517
  # params=None a necessity to avoid escaping attempts on execution.
487
518
  self.execute(sql, params=None)
488
519
 
489
- def remove_constraint(self, model, constraint):
520
+ def remove_constraint(self, model: type[Model], constraint: BaseConstraint) -> None:
490
521
  """Remove a constraint from a model."""
491
522
  sql = constraint.remove_sql(model, self)
492
523
  if sql:
493
524
  self.execute(sql)
494
525
 
495
- def alter_db_table(self, model, old_db_table, new_db_table):
526
+ def alter_db_table(
527
+ self, model: type[Model], old_db_table: str, new_db_table: str
528
+ ) -> None:
496
529
  """Rename the table a model points to."""
497
530
  if old_db_table == new_db_table or (
498
531
  self.connection.features.ignores_table_name_case
@@ -511,7 +544,12 @@ class BaseDatabaseSchemaEditor:
511
544
  if isinstance(sql, Statement):
512
545
  sql.rename_table_references(old_db_table, new_db_table)
513
546
 
514
- def alter_db_table_comment(self, model, old_db_table_comment, new_db_table_comment):
547
+ def alter_db_table_comment(
548
+ self,
549
+ model: type[Model],
550
+ old_db_table_comment: str | None,
551
+ new_db_table_comment: str | None,
552
+ ) -> None:
515
553
  self.execute(
516
554
  self.sql_alter_table_comment
517
555
  % {
@@ -520,7 +558,7 @@ class BaseDatabaseSchemaEditor:
520
558
  }
521
559
  )
522
560
 
523
- def add_field(self, model, field):
561
+ def add_field(self, model: type[Model], field: Field) -> None:
524
562
  """
525
563
  Create a field on a model. Usually involves adding a column, but may
526
564
  involve adding a table instead (for M2M fields).
@@ -539,7 +577,7 @@ class BaseDatabaseSchemaEditor:
539
577
  if (
540
578
  field.remote_field
541
579
  and self.connection.features.supports_foreign_keys
542
- and field.db_constraint
580
+ and field.db_constraint # type: ignore[attr-defined]
543
581
  ):
544
582
  constraint_suffix = "_fk_%(to_table)s_%(to_column)s"
545
583
  # Add FK constraint inline, if supported.
@@ -601,7 +639,7 @@ class BaseDatabaseSchemaEditor:
601
639
  if self.connection.features.connection_persists_old_columns:
602
640
  self.connection.close()
603
641
 
604
- def remove_field(self, model, field):
642
+ def remove_field(self, model: type[Model], field: Field) -> None:
605
643
  """
606
644
  Remove a field from a model. Usually involves deleting a column,
607
645
  but for M2Ms may involve deleting a table.
@@ -630,7 +668,13 @@ class BaseDatabaseSchemaEditor:
630
668
  ):
631
669
  self.deferred_sql.remove(sql)
632
670
 
633
- def alter_field(self, model, old_field, new_field, strict=False):
671
+ def alter_field(
672
+ self,
673
+ model: type[Model],
674
+ old_field: Field,
675
+ new_field: Field,
676
+ strict: bool = False,
677
+ ) -> None:
634
678
  """
635
679
  Allow a field's type, uniqueness, nullability, default, column,
636
680
  constraints, etc. to be modified.
@@ -655,7 +699,7 @@ class BaseDatabaseSchemaEditor:
655
699
  elif (
656
700
  old_type is None
657
701
  and new_type is None
658
- and (old_field.remote_field.through and new_field.remote_field.through)
702
+ and (old_field.remote_field.through and new_field.remote_field.through) # type: ignore[attr-defined]
659
703
  ):
660
704
  # Both sides have through models; this is a no-op.
661
705
  return
@@ -677,7 +721,9 @@ class BaseDatabaseSchemaEditor:
677
721
  strict,
678
722
  )
679
723
 
680
- def _field_db_check(self, field, field_db_params):
724
+ def _field_db_check(
725
+ self, field: Field, field_db_params: dict[str, Any]
726
+ ) -> str | None:
681
727
  # Always check constraints with the same mocked column name to avoid
682
728
  # recreating constrains when the column is renamed.
683
729
  check_constraints = self.connection.data_type_check_constraints
@@ -690,22 +736,22 @@ class BaseDatabaseSchemaEditor:
690
736
 
691
737
  def _alter_field(
692
738
  self,
693
- model,
694
- old_field,
695
- new_field,
696
- old_type,
697
- new_type,
698
- old_db_params,
699
- new_db_params,
700
- strict=False,
701
- ):
739
+ model: type[Model],
740
+ old_field: Field,
741
+ new_field: Field,
742
+ old_type: str,
743
+ new_type: str,
744
+ old_db_params: dict[str, Any],
745
+ new_db_params: dict[str, Any],
746
+ strict: bool = False,
747
+ ) -> None:
702
748
  """Perform a "physical" (non-ManyToMany) field update."""
703
749
  # Drop any FK constraints, we'll remake them later
704
750
  fks_dropped = set()
705
751
  if (
706
752
  self.connection.features.supports_foreign_keys
707
753
  and old_field.remote_field
708
- and old_field.db_constraint
754
+ and old_field.db_constraint # type: ignore[attr-defined]
709
755
  and self._field_should_be_altered(
710
756
  old_field,
711
757
  new_field,
@@ -773,10 +819,10 @@ class BaseDatabaseSchemaEditor:
773
819
  # True | False | False | True
774
820
  # True | False | True | True
775
821
  if (
776
- (old_field.remote_field and old_field.db_index)
822
+ (old_field.remote_field and old_field.db_index) # type: ignore[attr-defined]
777
823
  and not old_field.primary_key
778
824
  and (
779
- not (new_field.remote_field and new_field.db_index)
825
+ not (new_field.remote_field and new_field.db_index) # type: ignore[attr-defined]
780
826
  or new_field.primary_key
781
827
  )
782
828
  ):
@@ -938,10 +984,10 @@ class BaseDatabaseSchemaEditor:
938
984
  # True | True | True | False
939
985
  if (
940
986
  (
941
- not (old_field.remote_field and old_field.db_index)
987
+ not (old_field.remote_field and old_field.db_index) # type: ignore[attr-defined]
942
988
  or old_field.primary_key
943
989
  )
944
- and (new_field.remote_field and new_field.db_index)
990
+ and (new_field.remote_field and new_field.db_index) # type: ignore[attr-defined]
945
991
  and not new_field.primary_key
946
992
  ):
947
993
  self.execute(self._create_index_sql(model, fields=[new_field]))
@@ -986,9 +1032,9 @@ class BaseDatabaseSchemaEditor:
986
1032
  self.connection.features.supports_foreign_keys
987
1033
  and new_field.remote_field
988
1034
  and (
989
- fks_dropped or not old_field.remote_field or not old_field.db_constraint
1035
+ fks_dropped or not old_field.remote_field or not old_field.db_constraint # type: ignore[attr-defined]
990
1036
  )
991
- and new_field.db_constraint
1037
+ and new_field.db_constraint # type: ignore[attr-defined]
992
1038
  ):
993
1039
  self.execute(
994
1040
  self._create_fk_sql(model, new_field, "_fk_%(to_table)s_%(to_column)s")
@@ -1023,7 +1069,9 @@ class BaseDatabaseSchemaEditor:
1023
1069
  if self.connection.features.connection_persists_old_columns:
1024
1070
  self.connection.close()
1025
1071
 
1026
- def _alter_column_null_sql(self, model, old_field, new_field):
1072
+ def _alter_column_null_sql(
1073
+ self, model: type[Model], old_field: Field, new_field: Field
1074
+ ) -> tuple[str, list[Any]]:
1027
1075
  """
1028
1076
  Hook to specialize column null alteration.
1029
1077
 
@@ -1045,7 +1093,13 @@ class BaseDatabaseSchemaEditor:
1045
1093
  [],
1046
1094
  )
1047
1095
 
1048
- def _alter_column_default_sql(self, model, old_field, new_field, drop=False):
1096
+ def _alter_column_default_sql(
1097
+ self,
1098
+ model: type[Model],
1099
+ old_field: Field | None,
1100
+ new_field: Field,
1101
+ drop: bool = False,
1102
+ ) -> tuple[str, list[Any]]:
1049
1103
  """
1050
1104
  Hook to specialize column default alteration.
1051
1105
 
@@ -1084,8 +1138,14 @@ class BaseDatabaseSchemaEditor:
1084
1138
  )
1085
1139
 
1086
1140
  def _alter_column_type_sql(
1087
- self, model, old_field, new_field, new_type, old_collation, new_collation
1088
- ):
1141
+ self,
1142
+ model: type[Model],
1143
+ old_field: Field,
1144
+ new_field: Field,
1145
+ new_type: str,
1146
+ old_collation: str | None,
1147
+ new_collation: str | None,
1148
+ ) -> tuple[tuple[str, list[Any]], list[tuple[str, list[Any]]]]:
1089
1149
  """
1090
1150
  Hook to specialize column type alteration for different backends,
1091
1151
  for cases when a creation type is different to an alteration type
@@ -1129,7 +1189,13 @@ class BaseDatabaseSchemaEditor:
1129
1189
  other_actions,
1130
1190
  )
1131
1191
 
1132
- def _alter_column_comment_sql(self, model, new_field, new_type, new_db_comment):
1192
+ def _alter_column_comment_sql(
1193
+ self,
1194
+ model: type[Model],
1195
+ new_field: Field,
1196
+ new_type: str,
1197
+ new_db_comment: str | None,
1198
+ ) -> tuple[str, list[Any]]:
1133
1199
  return (
1134
1200
  self.sql_alter_column_comment
1135
1201
  % {
@@ -1140,42 +1206,46 @@ class BaseDatabaseSchemaEditor:
1140
1206
  [],
1141
1207
  )
1142
1208
 
1143
- def _comment_sql(self, comment):
1209
+ def _comment_sql(self, comment: str | None) -> str:
1144
1210
  return self.quote_value(comment or "")
1145
1211
 
1146
- def _alter_many_to_many(self, model, old_field, new_field, strict):
1212
+ def _alter_many_to_many(
1213
+ self, model: type[Model], old_field: Field, new_field: Field, strict: bool
1214
+ ) -> None:
1147
1215
  """Alter M2Ms to repoint their to= endpoints."""
1148
1216
  # Rename the through table
1149
1217
  if (
1150
- old_field.remote_field.through._meta.db_table
1151
- != new_field.remote_field.through._meta.db_table
1218
+ old_field.remote_field.through._meta.db_table # type: ignore[attr-defined]
1219
+ != new_field.remote_field.through._meta.db_table # type: ignore[attr-defined]
1152
1220
  ):
1153
1221
  self.alter_db_table(
1154
- old_field.remote_field.through,
1155
- old_field.remote_field.through._meta.db_table,
1156
- new_field.remote_field.through._meta.db_table,
1222
+ old_field.remote_field.through, # type: ignore[attr-defined]
1223
+ old_field.remote_field.through._meta.db_table, # type: ignore[attr-defined]
1224
+ new_field.remote_field.through._meta.db_table, # type: ignore[attr-defined]
1157
1225
  )
1158
1226
  # Repoint the FK to the other side
1159
1227
  self.alter_field(
1160
- new_field.remote_field.through,
1228
+ new_field.remote_field.through, # type: ignore[attr-defined]
1161
1229
  # The field that points to the target model is needed, so we can
1162
1230
  # tell alter_field to change it - this is m2m_reverse_field_name()
1163
1231
  # (as opposed to m2m_field_name(), which points to our model).
1164
- old_field.remote_field.through._meta.get_field(
1165
- old_field.m2m_reverse_field_name()
1232
+ old_field.remote_field.through._meta.get_field( # type: ignore[attr-defined]
1233
+ old_field.m2m_reverse_field_name() # type: ignore[attr-defined]
1166
1234
  ),
1167
- new_field.remote_field.through._meta.get_field(
1168
- new_field.m2m_reverse_field_name()
1235
+ new_field.remote_field.through._meta.get_field( # type: ignore[attr-defined]
1236
+ new_field.m2m_reverse_field_name() # type: ignore[attr-defined]
1169
1237
  ),
1170
1238
  )
1171
1239
  self.alter_field(
1172
- new_field.remote_field.through,
1240
+ new_field.remote_field.through, # type: ignore[attr-defined]
1173
1241
  # for self-referential models we need to alter field from the other end too
1174
- old_field.remote_field.through._meta.get_field(old_field.m2m_field_name()),
1175
- new_field.remote_field.through._meta.get_field(new_field.m2m_field_name()),
1242
+ old_field.remote_field.through._meta.get_field(old_field.m2m_field_name()), # type: ignore[attr-defined]
1243
+ new_field.remote_field.through._meta.get_field(new_field.m2m_field_name()), # type: ignore[attr-defined]
1176
1244
  )
1177
1245
 
1178
- def _create_index_name(self, table_name, column_names, suffix=""):
1246
+ def _create_index_name(
1247
+ self, table_name: str, column_names: list[str], suffix: str = ""
1248
+ ) -> str:
1179
1249
  """
1180
1250
  Generate a unique name for an index/unique constraint.
1181
1251
 
@@ -1208,12 +1278,14 @@ class BaseDatabaseSchemaEditor:
1208
1278
  index_name = f"D{index_name[:-1]}"
1209
1279
  return index_name
1210
1280
 
1211
- def _index_condition_sql(self, condition):
1281
+ def _index_condition_sql(self, condition: str | None) -> str:
1212
1282
  if condition:
1213
1283
  return " WHERE " + condition
1214
1284
  return ""
1215
1285
 
1216
- def _index_include_sql(self, model, columns):
1286
+ def _index_include_sql(
1287
+ self, model: type[Model], columns: list[str] | None
1288
+ ) -> str | Statement:
1217
1289
  if not columns or not self.connection.features.supports_covering_indexes:
1218
1290
  return ""
1219
1291
  return Statement(
@@ -1223,19 +1295,19 @@ class BaseDatabaseSchemaEditor:
1223
1295
 
1224
1296
  def _create_index_sql(
1225
1297
  self,
1226
- model,
1298
+ model: type[Model],
1227
1299
  *,
1228
- fields=None,
1229
- name=None,
1230
- suffix="",
1231
- using="",
1232
- col_suffixes=(),
1233
- sql=None,
1234
- opclasses=(),
1235
- condition=None,
1236
- include=None,
1237
- expressions=None,
1238
- ):
1300
+ fields: list[Field] | None = None,
1301
+ name: str | None = None,
1302
+ suffix: str = "",
1303
+ using: str = "",
1304
+ col_suffixes: tuple[str, ...] = (),
1305
+ sql: str | None = None,
1306
+ opclasses: tuple[str, ...] = (),
1307
+ condition: str | None = None,
1308
+ include: list[str] | None = None,
1309
+ expressions: Any = None,
1310
+ ) -> Statement:
1239
1311
  """
1240
1312
  Return the SQL statement to create the index for one or several fields
1241
1313
  or expressions. `sql` can be specified if the syntax differs from the
@@ -1248,7 +1320,7 @@ class BaseDatabaseSchemaEditor:
1248
1320
  sql_create_index = sql or self.sql_create_index
1249
1321
  table = model._meta.db_table
1250
1322
 
1251
- def create_index_name(*args, **kwargs):
1323
+ def create_index_name(*args: Any, **kwargs: Any) -> str:
1252
1324
  nonlocal name
1253
1325
  if name is None:
1254
1326
  name = self._create_index_name(*args, **kwargs)
@@ -1269,14 +1341,18 @@ class BaseDatabaseSchemaEditor:
1269
1341
  include=self._index_include_sql(model, include),
1270
1342
  )
1271
1343
 
1272
- def _delete_index_sql(self, model, name, sql=None):
1344
+ def _delete_index_sql(
1345
+ self, model: type[Model], name: str, sql: str | None = None
1346
+ ) -> Statement:
1273
1347
  return Statement(
1274
1348
  sql or self.sql_delete_index,
1275
1349
  table=Table(model._meta.db_table, self.quote_name),
1276
1350
  name=self.quote_name(name),
1277
1351
  )
1278
1352
 
1279
- def _rename_index_sql(self, model, old_name, new_name):
1353
+ def _rename_index_sql(
1354
+ self, model: type[Model], old_name: str, new_name: str
1355
+ ) -> Statement:
1280
1356
  return Statement(
1281
1357
  self.sql_rename_index,
1282
1358
  table=Table(model._meta.db_table, self.quote_name),
@@ -1284,14 +1360,20 @@ class BaseDatabaseSchemaEditor:
1284
1360
  new_name=self.quote_name(new_name),
1285
1361
  )
1286
1362
 
1287
- def _index_columns(self, table, columns, col_suffixes, opclasses):
1363
+ def _index_columns(
1364
+ self,
1365
+ table: str,
1366
+ columns: list[str],
1367
+ col_suffixes: tuple[str, ...],
1368
+ opclasses: tuple[str, ...],
1369
+ ) -> Columns:
1288
1370
  return Columns(table, columns, self.quote_name, col_suffixes=col_suffixes)
1289
1371
 
1290
- def _model_indexes_sql(self, model):
1372
+ def _model_indexes_sql(self, model: type[Model]) -> list[Statement | None]:
1291
1373
  """
1292
1374
  Return a list of all index SQL statements (field indexes, Meta.indexes) for the specified model.
1293
1375
  """
1294
- output = []
1376
+ output: list[Statement | None] = []
1295
1377
  for field in model._meta.local_fields:
1296
1378
  output.extend(self._field_indexes_sql(model, field))
1297
1379
 
@@ -1303,16 +1385,18 @@ class BaseDatabaseSchemaEditor:
1303
1385
  output.append(index.create_sql(model, self))
1304
1386
  return output
1305
1387
 
1306
- def _field_indexes_sql(self, model, field):
1388
+ def _field_indexes_sql(self, model: type[Model], field: Field) -> list[Statement]:
1307
1389
  """
1308
1390
  Return a list of all index SQL statements for the specified field.
1309
1391
  """
1310
- output = []
1392
+ output: list[Statement] = []
1311
1393
  if self._field_should_be_indexed(model, field):
1312
1394
  output.append(self._create_index_sql(model, fields=[field]))
1313
1395
  return output
1314
1396
 
1315
- def _field_should_be_altered(self, old_field, new_field, ignore=None):
1397
+ def _field_should_be_altered(
1398
+ self, old_field: Field, new_field: Field, ignore: set[str] | None = None
1399
+ ) -> bool:
1316
1400
  ignore = ignore or set()
1317
1401
  _, old_path, old_args, old_kwargs = old_field.deconstruct()
1318
1402
  _, new_path, new_args, new_kwargs = new_field.deconstruct()
@@ -1321,21 +1405,23 @@ class BaseDatabaseSchemaEditor:
1321
1405
  # - changing an attribute that doesn't affect the schema
1322
1406
  # - changing an attribute in the provided set of ignored attributes
1323
1407
  # - adding only a db_column and the column name is not changed
1324
- for attr in ignore.union(old_field.non_db_attrs):
1408
+ for attr in ignore.union(old_field.non_db_attrs): # type: ignore[attr-defined]
1325
1409
  old_kwargs.pop(attr, None)
1326
- for attr in ignore.union(new_field.non_db_attrs):
1410
+ for attr in ignore.union(new_field.non_db_attrs): # type: ignore[attr-defined]
1327
1411
  new_kwargs.pop(attr, None)
1328
1412
  return self.quote_name(old_field.column) != self.quote_name(
1329
1413
  new_field.column
1330
1414
  ) or (old_path, old_args, old_kwargs) != (new_path, new_args, new_kwargs)
1331
1415
 
1332
- def _field_should_be_indexed(self, model, field):
1333
- return (field.remote_field and field.db_index) and not field.primary_key
1416
+ def _field_should_be_indexed(self, model: type[Model], field: Field) -> bool:
1417
+ return (field.remote_field and field.db_index) and not field.primary_key # type: ignore[attr-defined]
1334
1418
 
1335
- def _field_became_primary_key(self, old_field, new_field):
1419
+ def _field_became_primary_key(self, old_field: Field, new_field: Field) -> bool:
1336
1420
  return not old_field.primary_key and new_field.primary_key
1337
1421
 
1338
- def _rename_field_sql(self, table, old_field, new_field, new_type):
1422
+ def _rename_field_sql(
1423
+ self, table: str, old_field: Field, new_field: Field, new_type: str
1424
+ ) -> str:
1339
1425
  return self.sql_rename_column % {
1340
1426
  "table": self.quote_name(table),
1341
1427
  "old_column": self.quote_name(old_field.column),
@@ -1343,14 +1429,16 @@ class BaseDatabaseSchemaEditor:
1343
1429
  "type": new_type,
1344
1430
  }
1345
1431
 
1346
- def _create_fk_sql(self, model, field, suffix):
1432
+ def _create_fk_sql(
1433
+ self, model: type[Model], field: Field, suffix: str
1434
+ ) -> Statement:
1347
1435
  table = Table(model._meta.db_table, self.quote_name)
1348
1436
  name = self._fk_constraint_name(model, field, suffix)
1349
1437
  column = Columns(model._meta.db_table, [field.column], self.quote_name)
1350
- to_table = Table(field.target_field.model._meta.db_table, self.quote_name)
1438
+ to_table = Table(field.target_field.model._meta.db_table, self.quote_name) # type: ignore[attr-defined]
1351
1439
  to_column = Columns(
1352
- field.target_field.model._meta.db_table,
1353
- [field.target_field.column],
1440
+ field.target_field.model._meta.db_table, # type: ignore[attr-defined]
1441
+ [field.target_field.column], # type: ignore[attr-defined]
1354
1442
  self.quote_name,
1355
1443
  )
1356
1444
  deferrable = self.connection.ops.deferrable_sql()
@@ -1364,41 +1452,44 @@ class BaseDatabaseSchemaEditor:
1364
1452
  deferrable=deferrable,
1365
1453
  )
1366
1454
 
1367
- def _fk_constraint_name(self, model, field, suffix):
1368
- def create_fk_name(*args, **kwargs):
1455
+ def _fk_constraint_name(
1456
+ self, model: type[Model], field: Field, suffix: str
1457
+ ) -> ForeignKeyName:
1458
+ def create_fk_name(*args: Any, **kwargs: Any) -> str:
1369
1459
  return self.quote_name(self._create_index_name(*args, **kwargs))
1370
1460
 
1371
1461
  return ForeignKeyName(
1372
1462
  model._meta.db_table,
1373
1463
  [field.column],
1374
- split_identifier(field.target_field.model._meta.db_table)[1],
1375
- [field.target_field.column],
1464
+ split_identifier(field.target_field.model._meta.db_table)[1], # type: ignore[attr-defined]
1465
+ [field.target_field.column], # type: ignore[attr-defined]
1376
1466
  suffix,
1377
1467
  create_fk_name,
1378
1468
  )
1379
1469
 
1380
- def _delete_fk_sql(self, model, name):
1470
+ def _delete_fk_sql(self, model: type[Model], name: str) -> Statement:
1381
1471
  return self._delete_constraint_sql(self.sql_delete_fk, model, name)
1382
1472
 
1383
- def _deferrable_constraint_sql(self, deferrable):
1473
+ def _deferrable_constraint_sql(self, deferrable: Deferrable | None) -> str:
1384
1474
  if deferrable is None:
1385
1475
  return ""
1386
1476
  if deferrable == Deferrable.DEFERRED:
1387
1477
  return " DEFERRABLE INITIALLY DEFERRED"
1388
1478
  if deferrable == Deferrable.IMMEDIATE:
1389
1479
  return " DEFERRABLE INITIALLY IMMEDIATE"
1480
+ return ""
1390
1481
 
1391
1482
  def _unique_sql(
1392
1483
  self,
1393
- model,
1394
- fields,
1395
- name,
1396
- condition=None,
1397
- deferrable=None,
1398
- include=None,
1399
- opclasses=None,
1400
- expressions=None,
1401
- ):
1484
+ model: type[Model],
1485
+ fields: Iterable[Field],
1486
+ name: str,
1487
+ condition: str | None = None,
1488
+ deferrable: Deferrable | None = None,
1489
+ include: list[str] | None = None,
1490
+ opclasses: tuple[str, ...] | None = None,
1491
+ expressions: Any = None,
1492
+ ) -> str | None:
1402
1493
  if (
1403
1494
  deferrable
1404
1495
  and not self.connection.features.supports_deferrable_unique_constraints
@@ -1430,15 +1521,15 @@ class BaseDatabaseSchemaEditor:
1430
1521
 
1431
1522
  def _create_unique_sql(
1432
1523
  self,
1433
- model,
1434
- fields,
1435
- name=None,
1436
- condition=None,
1437
- deferrable=None,
1438
- include=None,
1439
- opclasses=None,
1440
- expressions=None,
1441
- ):
1524
+ model: type[Model],
1525
+ fields: Iterable[Field],
1526
+ name: str | None = None,
1527
+ condition: str | None = None,
1528
+ deferrable: Deferrable | None = None,
1529
+ include: list[str] | None = None,
1530
+ opclasses: tuple[str, ...] | None = None,
1531
+ expressions: Any = None,
1532
+ ) -> Statement | None:
1442
1533
  if (
1443
1534
  (
1444
1535
  deferrable
@@ -1464,25 +1555,27 @@ class BaseDatabaseSchemaEditor:
1464
1555
  else:
1465
1556
  sql = self.sql_create_unique
1466
1557
  if columns:
1467
- columns = self._index_columns(
1558
+ columns_obj: Columns | Expressions = self._index_columns(
1468
1559
  table, columns, col_suffixes=(), opclasses=opclasses
1469
1560
  )
1470
1561
  else:
1471
- columns = Expressions(table, expressions, compiler, self.quote_value)
1562
+ columns_obj = Expressions(table, expressions, compiler, self.quote_value)
1472
1563
  return Statement(
1473
1564
  sql,
1474
1565
  table=Table(table, self.quote_name),
1475
1566
  name=name,
1476
- columns=columns,
1567
+ columns=columns_obj,
1477
1568
  condition=self._index_condition_sql(condition),
1478
1569
  deferrable=self._deferrable_constraint_sql(deferrable),
1479
1570
  include=self._index_include_sql(model, include),
1480
1571
  )
1481
1572
 
1482
- def _unique_constraint_name(self, table, columns, quote=True):
1573
+ def _unique_constraint_name(
1574
+ self, table: str, columns: list[str], quote: bool = True
1575
+ ) -> IndexName | str:
1483
1576
  if quote:
1484
1577
 
1485
- def create_unique_name(*args, **kwargs):
1578
+ def create_unique_name(*args: Any, **kwargs: Any) -> str:
1486
1579
  return self.quote_name(self._create_index_name(*args, **kwargs))
1487
1580
 
1488
1581
  else:
@@ -1492,14 +1585,14 @@ class BaseDatabaseSchemaEditor:
1492
1585
 
1493
1586
  def _delete_unique_sql(
1494
1587
  self,
1495
- model,
1496
- name,
1497
- condition=None,
1498
- deferrable=None,
1499
- include=None,
1500
- opclasses=None,
1501
- expressions=None,
1502
- ):
1588
+ model: type[Model],
1589
+ name: str,
1590
+ condition: str | None = None,
1591
+ deferrable: Deferrable | None = None,
1592
+ include: list[str] | None = None,
1593
+ opclasses: tuple[str, ...] | None = None,
1594
+ expressions: Any = None,
1595
+ ) -> Statement | None:
1503
1596
  if (
1504
1597
  (
1505
1598
  deferrable
@@ -1518,13 +1611,15 @@ class BaseDatabaseSchemaEditor:
1518
1611
  sql = self.sql_delete_unique
1519
1612
  return self._delete_constraint_sql(sql, model, name)
1520
1613
 
1521
- def _check_sql(self, name, check):
1614
+ def _check_sql(self, name: str, check: str) -> str:
1522
1615
  return self.sql_constraint % {
1523
1616
  "name": self.quote_name(name),
1524
1617
  "constraint": self.sql_check_constraint % {"check": check},
1525
1618
  }
1526
1619
 
1527
- def _create_check_sql(self, model, name, check):
1620
+ def _create_check_sql(
1621
+ self, model: type[Model], name: str, check: str
1622
+ ) -> Statement | None:
1528
1623
  if not self.connection.features.supports_table_check_constraints:
1529
1624
  return None
1530
1625
  return Statement(
@@ -1534,12 +1629,14 @@ class BaseDatabaseSchemaEditor:
1534
1629
  check=check,
1535
1630
  )
1536
1631
 
1537
- def _delete_check_sql(self, model, name):
1632
+ def _delete_check_sql(self, model: type[Model], name: str) -> Statement | None:
1538
1633
  if not self.connection.features.supports_table_check_constraints:
1539
1634
  return None
1540
1635
  return self._delete_constraint_sql(self.sql_delete_check, model, name)
1541
1636
 
1542
- def _delete_constraint_sql(self, template, model, name):
1637
+ def _delete_constraint_sql(
1638
+ self, template: str, model: type[Model], name: str
1639
+ ) -> Statement:
1543
1640
  return Statement(
1544
1641
  template,
1545
1642
  table=Table(model._meta.db_table, self.quote_name),
@@ -1548,16 +1645,16 @@ class BaseDatabaseSchemaEditor:
1548
1645
 
1549
1646
  def _constraint_names(
1550
1647
  self,
1551
- model,
1552
- column_names=None,
1553
- unique=None,
1554
- primary_key=None,
1555
- index=None,
1556
- foreign_key=None,
1557
- check=None,
1558
- type_=None,
1559
- exclude=None,
1560
- ):
1648
+ model: type[Model],
1649
+ column_names: list[str] | None = None,
1650
+ unique: bool | None = None,
1651
+ primary_key: bool | None = None,
1652
+ index: bool | None = None,
1653
+ foreign_key: bool | None = None,
1654
+ check: bool | None = None,
1655
+ type_: str | None = None,
1656
+ exclude: set[str] | None = None,
1657
+ ) -> list[str]:
1561
1658
  """Return all constraint names matching the columns and conditions."""
1562
1659
  if column_names is not None:
1563
1660
  column_names = [
@@ -1572,7 +1669,7 @@ class BaseDatabaseSchemaEditor:
1572
1669
  constraints = self.connection.introspection.get_constraints(
1573
1670
  cursor, model._meta.db_table
1574
1671
  )
1575
- result = []
1672
+ result: list[str] = []
1576
1673
  for name, infodict in constraints.items():
1577
1674
  if column_names is None or column_names == infodict["columns"]:
1578
1675
  if unique is not None and infodict["unique"] != unique:
@@ -1591,7 +1688,7 @@ class BaseDatabaseSchemaEditor:
1591
1688
  result.append(name)
1592
1689
  return result
1593
1690
 
1594
- def _delete_primary_key(self, model, strict=False):
1691
+ def _delete_primary_key(self, model: type[Model], strict: bool = False) -> None:
1595
1692
  constraint_names = self._constraint_names(model, primary_key=True)
1596
1693
  if strict and len(constraint_names) != 1:
1597
1694
  raise ValueError(
@@ -1600,7 +1697,7 @@ class BaseDatabaseSchemaEditor:
1600
1697
  for constraint_name in constraint_names:
1601
1698
  self.execute(self._delete_primary_key_sql(model, constraint_name))
1602
1699
 
1603
- def _create_primary_key_sql(self, model, field):
1700
+ def _create_primary_key_sql(self, model: type[Model], field: Field) -> Statement:
1604
1701
  return Statement(
1605
1702
  self.sql_create_pk,
1606
1703
  table=Table(model._meta.db_table, self.quote_name),
@@ -1612,8 +1709,13 @@ class BaseDatabaseSchemaEditor:
1612
1709
  columns=Columns(model._meta.db_table, [field.column], self.quote_name),
1613
1710
  )
1614
1711
 
1615
- def _delete_primary_key_sql(self, model, name):
1712
+ def _delete_primary_key_sql(self, model: type[Model], name: str) -> Statement:
1616
1713
  return self._delete_constraint_sql(self.sql_delete_pk, model, name)
1617
1714
 
1618
- def _collate_sql(self, collation, old_collation=None, table_name=None):
1715
+ def _collate_sql(
1716
+ self,
1717
+ collation: str | None,
1718
+ old_collation: str | None = None,
1719
+ table_name: str | None = None,
1720
+ ) -> str:
1619
1721
  return "COLLATE " + self.quote_name(collation) if collation else ""