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,19 +1,28 @@
1
+ from __future__ import annotations
2
+
1
3
  import ipaddress
2
4
  import json
5
+ from collections.abc import Callable
3
6
  from functools import lru_cache, partial
7
+ from typing import TYPE_CHECKING, Any
4
8
 
5
- from psycopg import ClientCursor, errors
6
- from psycopg.types import numeric
7
- from psycopg.types.json import Jsonb
9
+ from psycopg import ClientCursor, errors # type: ignore[import-untyped]
10
+ from psycopg.types import numeric # type: ignore[import-untyped]
11
+ from psycopg.types.json import Jsonb # type: ignore[import-untyped]
8
12
 
9
13
  from plain.models.backends.base.operations import BaseDatabaseOperations
10
14
  from plain.models.backends.utils import split_tzname_delta
11
15
  from plain.models.constants import OnConflict
12
16
  from plain.utils.regex_helper import _lazy_re_compile
13
17
 
18
+ if TYPE_CHECKING:
19
+ from plain.models.fields import Field
20
+
14
21
 
15
22
  @lru_cache
16
- def get_json_dumps(encoder):
23
+ def get_json_dumps(
24
+ encoder: type[json.JSONEncoder] | None,
25
+ ) -> Callable[..., str]:
17
26
  if encoder is None:
18
27
  return json.dumps
19
28
  return partial(json.dumps, cls=encoder)
@@ -47,7 +56,7 @@ class DatabaseOperations(BaseDatabaseOperations):
47
56
  "PositiveBigIntegerField": numeric.Int8,
48
57
  }
49
58
 
50
- def unification_cast_sql(self, output_field):
59
+ def unification_cast_sql(self, output_field: Field) -> str:
51
60
  internal_type = output_field.get_internal_type()
52
61
  if internal_type in (
53
62
  "GenericIPAddressField",
@@ -61,15 +70,17 @@ class DatabaseOperations(BaseDatabaseOperations):
61
70
  # PostgreSQL configuration so we need to explicitly cast them.
62
71
  # We must also remove components of the type within brackets:
63
72
  # varchar(255) -> varchar.
64
- return "CAST(%s AS {})".format(
65
- output_field.db_type(self.connection).split("(")[0]
66
- )
73
+ db_type = output_field.db_type(self.connection)
74
+ if db_type:
75
+ return "CAST(%s AS {})".format(db_type.split("(")[0])
67
76
  return "%s"
68
77
 
69
78
  # EXTRACT format cannot be passed in parameters.
70
79
  _extract_format_re = _lazy_re_compile(r"[A-Z_]+")
71
80
 
72
- def date_extract_sql(self, lookup_type, sql, params):
81
+ def date_extract_sql(
82
+ self, lookup_type: str, sql: str, params: list[Any] | tuple[Any, ...]
83
+ ) -> tuple[str, list[Any] | tuple[Any, ...]]:
73
84
  # https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT
74
85
  if lookup_type == "week_day":
75
86
  # For consistency across backends, we return Sunday=1, Saturday=7.
@@ -84,65 +95,97 @@ class DatabaseOperations(BaseDatabaseOperations):
84
95
  raise ValueError(f"Invalid lookup type: {lookup_type!r}")
85
96
  return f"EXTRACT({lookup_type} FROM {sql})", params
86
97
 
87
- def date_trunc_sql(self, lookup_type, sql, params, tzname=None):
98
+ def date_trunc_sql(
99
+ self,
100
+ lookup_type: str,
101
+ sql: str,
102
+ params: list[Any] | tuple[Any, ...],
103
+ tzname: str | None = None,
104
+ ) -> tuple[str, tuple[Any, ...]]:
88
105
  sql, params = self._convert_sql_to_tz(sql, params, tzname)
89
106
  # https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
90
107
  return f"DATE_TRUNC(%s, {sql})", (lookup_type, *params)
91
108
 
92
- def _prepare_tzname_delta(self, tzname):
109
+ def _prepare_tzname_delta(self, tzname: str) -> str:
93
110
  tzname, sign, offset = split_tzname_delta(tzname)
94
111
  if offset:
95
112
  sign = "-" if sign == "+" else "+"
96
113
  return f"{tzname}{sign}{offset}"
97
114
  return tzname
98
115
 
99
- def _convert_sql_to_tz(self, sql, params, tzname):
116
+ def _convert_sql_to_tz(
117
+ self, sql: str, params: list[Any] | tuple[Any, ...], tzname: str | None
118
+ ) -> tuple[str, list[Any] | tuple[Any, ...]]:
100
119
  if tzname:
101
120
  tzname_param = self._prepare_tzname_delta(tzname)
102
121
  return f"{sql} AT TIME ZONE %s", (*params, tzname_param)
103
122
  return sql, params
104
123
 
105
- def datetime_cast_date_sql(self, sql, params, tzname):
124
+ def datetime_cast_date_sql(
125
+ self, sql: str, params: list[Any] | tuple[Any, ...], tzname: str | None
126
+ ) -> tuple[str, list[Any] | tuple[Any, ...]]:
106
127
  sql, params = self._convert_sql_to_tz(sql, params, tzname)
107
128
  return f"({sql})::date", params
108
129
 
109
- def datetime_cast_time_sql(self, sql, params, tzname):
130
+ def datetime_cast_time_sql(
131
+ self, sql: str, params: list[Any] | tuple[Any, ...], tzname: str | None
132
+ ) -> tuple[str, list[Any] | tuple[Any, ...]]:
110
133
  sql, params = self._convert_sql_to_tz(sql, params, tzname)
111
134
  return f"({sql})::time", params
112
135
 
113
- def datetime_extract_sql(self, lookup_type, sql, params, tzname):
136
+ def datetime_extract_sql(
137
+ self,
138
+ lookup_type: str,
139
+ sql: str,
140
+ params: list[Any] | tuple[Any, ...],
141
+ tzname: str | None,
142
+ ) -> tuple[str, list[Any] | tuple[Any, ...]]:
114
143
  sql, params = self._convert_sql_to_tz(sql, params, tzname)
115
144
  if lookup_type == "second":
116
145
  # Truncate fractional seconds.
117
146
  return f"EXTRACT(SECOND FROM DATE_TRUNC(%s, {sql}))", ("second", *params)
118
147
  return self.date_extract_sql(lookup_type, sql, params)
119
148
 
120
- def datetime_trunc_sql(self, lookup_type, sql, params, tzname):
149
+ def datetime_trunc_sql(
150
+ self,
151
+ lookup_type: str,
152
+ sql: str,
153
+ params: list[Any] | tuple[Any, ...],
154
+ tzname: str | None,
155
+ ) -> tuple[str, tuple[Any, ...]]:
121
156
  sql, params = self._convert_sql_to_tz(sql, params, tzname)
122
157
  # https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
123
158
  return f"DATE_TRUNC(%s, {sql})", (lookup_type, *params)
124
159
 
125
- def time_extract_sql(self, lookup_type, sql, params):
160
+ def time_extract_sql(
161
+ self, lookup_type: str, sql: str, params: list[Any] | tuple[Any, ...]
162
+ ) -> tuple[str, list[Any] | tuple[Any, ...]]:
126
163
  if lookup_type == "second":
127
164
  # Truncate fractional seconds.
128
165
  return f"EXTRACT(SECOND FROM DATE_TRUNC(%s, {sql}))", ("second", *params)
129
166
  return self.date_extract_sql(lookup_type, sql, params)
130
167
 
131
- def time_trunc_sql(self, lookup_type, sql, params, tzname=None):
168
+ def time_trunc_sql(
169
+ self,
170
+ lookup_type: str,
171
+ sql: str,
172
+ params: list[Any] | tuple[Any, ...],
173
+ tzname: str | None = None,
174
+ ) -> tuple[str, tuple[Any, ...]]:
132
175
  sql, params = self._convert_sql_to_tz(sql, params, tzname)
133
176
  return f"DATE_TRUNC(%s, {sql})::time", (lookup_type, *params)
134
177
 
135
- def deferrable_sql(self):
178
+ def deferrable_sql(self) -> str:
136
179
  return " DEFERRABLE INITIALLY DEFERRED"
137
180
 
138
- def fetch_returned_insert_rows(self, cursor):
181
+ def fetch_returned_insert_rows(self, cursor: Any) -> list[Any]:
139
182
  """
140
183
  Given a cursor object that has just performed an INSERT...RETURNING
141
184
  statement into a table, return the tuple of returned data.
142
185
  """
143
186
  return cursor.fetchall()
144
187
 
145
- def lookup_cast(self, lookup_type, internal_type=None):
188
+ def lookup_cast(self, lookup_type: str, internal_type: str | None = None) -> str:
146
189
  lookup = "%s"
147
190
 
148
191
  if lookup_type == "isnull" and internal_type in (
@@ -175,27 +218,27 @@ class DatabaseOperations(BaseDatabaseOperations):
175
218
 
176
219
  return lookup
177
220
 
178
- def no_limit_value(self):
221
+ def no_limit_value(self) -> None:
179
222
  return None
180
223
 
181
- def prepare_sql_script(self, sql):
224
+ def prepare_sql_script(self, sql: str) -> list[str]:
182
225
  return [sql]
183
226
 
184
- def quote_name(self, name):
227
+ def quote_name(self, name: str) -> str:
185
228
  if name.startswith('"') and name.endswith('"'):
186
229
  return name # Quoting once is enough.
187
230
  return f'"{name}"'
188
231
 
189
- def compose_sql(self, sql, params):
232
+ def compose_sql(self, sql: str, params: Any) -> bytes:
190
233
  return ClientCursor(self.connection.connection).mogrify(sql, params)
191
234
 
192
- def set_time_zone_sql(self):
235
+ def set_time_zone_sql(self) -> str:
193
236
  return "SELECT set_config('TimeZone', %s, false)"
194
237
 
195
- def prep_for_iexact_query(self, x):
238
+ def prep_for_iexact_query(self, x: str) -> str:
196
239
  return x
197
240
 
198
- def max_name_length(self):
241
+ def max_name_length(self) -> int:
199
242
  """
200
243
  Return the maximum length of an identifier.
201
244
 
@@ -208,20 +251,22 @@ class DatabaseOperations(BaseDatabaseOperations):
208
251
  """
209
252
  return 63
210
253
 
211
- def distinct_sql(self, fields, params):
254
+ def distinct_sql(
255
+ self, fields: list[str], params: list[Any] | tuple[Any, ...]
256
+ ) -> tuple[list[str], list[Any]]:
212
257
  if fields:
213
258
  params = [param for param_list in params for param in param_list]
214
259
  return (["DISTINCT ON ({})".format(", ".join(fields))], params)
215
260
  else:
216
261
  return ["DISTINCT"], []
217
262
 
218
- def last_executed_query(self, cursor, sql, params):
263
+ def last_executed_query(self, cursor: Any, sql: str, params: Any) -> bytes | None:
219
264
  try:
220
265
  return self.compose_sql(sql, params)
221
266
  except errors.DataError:
222
267
  return None
223
268
 
224
- def return_insert_columns(self, fields):
269
+ def return_insert_columns(self, fields: list[Field]) -> tuple[str, tuple[Any, ...]]:
225
270
  if not fields:
226
271
  return "", ()
227
272
  columns = [
@@ -230,37 +275,53 @@ class DatabaseOperations(BaseDatabaseOperations):
230
275
  ]
231
276
  return "RETURNING {}".format(", ".join(columns)), ()
232
277
 
233
- def bulk_insert_sql(self, fields, placeholder_rows):
278
+ def bulk_insert_sql(
279
+ self, fields: list[Field], placeholder_rows: list[list[str]]
280
+ ) -> str:
234
281
  placeholder_rows_sql = (", ".join(row) for row in placeholder_rows)
235
282
  values_sql = ", ".join(f"({sql})" for sql in placeholder_rows_sql)
236
283
  return "VALUES " + values_sql
237
284
 
238
- def adapt_integerfield_value(self, value, internal_type):
285
+ def adapt_integerfield_value(
286
+ self, value: int | Any | None, internal_type: str
287
+ ) -> int | Any | None:
239
288
  if value is None or hasattr(value, "resolve_expression"):
240
289
  return value
241
290
  return self.integerfield_type_map[internal_type](value)
242
291
 
243
- def adapt_datefield_value(self, value):
292
+ def adapt_datefield_value(self, value: Any) -> Any:
244
293
  return value
245
294
 
246
- def adapt_datetimefield_value(self, value):
295
+ def adapt_datetimefield_value(self, value: Any) -> Any:
247
296
  return value
248
297
 
249
- def adapt_timefield_value(self, value):
298
+ def adapt_timefield_value(self, value: Any) -> Any:
250
299
  return value
251
300
 
252
- def adapt_decimalfield_value(self, value, max_digits=None, decimal_places=None):
301
+ def adapt_decimalfield_value(
302
+ self,
303
+ value: Any,
304
+ max_digits: int | None = None,
305
+ decimal_places: int | None = None,
306
+ ) -> Any:
253
307
  return value
254
308
 
255
- def adapt_ipaddressfield_value(self, value):
309
+ def adapt_ipaddressfield_value(
310
+ self, value: str | None
311
+ ) -> ipaddress.IPv4Address | ipaddress.IPv6Address | None:
256
312
  if value:
257
313
  return ipaddress.ip_address(value)
258
314
  return None
259
315
 
260
- def adapt_json_value(self, value, encoder):
316
+ def adapt_json_value(self, value: Any, encoder: type[json.JSONEncoder]) -> Jsonb:
261
317
  return Jsonb(value, dumps=get_json_dumps(encoder))
262
318
 
263
- def subtract_temporals(self, internal_type, lhs, rhs):
319
+ def subtract_temporals(
320
+ self,
321
+ internal_type: str,
322
+ lhs: tuple[str, list[Any] | tuple[Any, ...]],
323
+ rhs: tuple[str, list[Any] | tuple[Any, ...]],
324
+ ) -> tuple[str, tuple[Any, ...]]:
264
325
  if internal_type == "DateField":
265
326
  lhs_sql, lhs_params = lhs
266
327
  rhs_sql, rhs_params = rhs
@@ -268,7 +329,7 @@ class DatabaseOperations(BaseDatabaseOperations):
268
329
  return f"(interval '1 day' * ({lhs_sql} - {rhs_sql}))", params
269
330
  return super().subtract_temporals(internal_type, lhs, rhs)
270
331
 
271
- def explain_query_prefix(self, format=None, **options):
332
+ def explain_query_prefix(self, format: str | None = None, **options: Any) -> str:
272
333
  extra = {}
273
334
  # Normalize options.
274
335
  if options:
@@ -289,7 +350,13 @@ class DatabaseOperations(BaseDatabaseOperations):
289
350
  )
290
351
  return prefix
291
352
 
292
- def on_conflict_suffix_sql(self, fields, on_conflict, update_fields, unique_fields):
353
+ def on_conflict_suffix_sql(
354
+ self,
355
+ fields: list[Field],
356
+ on_conflict: OnConflict | None,
357
+ update_fields: list[Field],
358
+ unique_fields: list[Field],
359
+ ) -> str:
293
360
  if on_conflict == OnConflict.IGNORE:
294
361
  return "ON CONFLICT DO NOTHING"
295
362
  if on_conflict == OnConflict.UPDATE:
@@ -1,9 +1,20 @@
1
- from psycopg import sql
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from psycopg import sql # type: ignore[import-untyped]
2
6
 
3
7
  from plain.models.backends.base.schema import BaseDatabaseSchemaEditor
4
- from plain.models.backends.ddl_references import IndexColumns
8
+ from plain.models.backends.ddl_references import Columns, IndexColumns, Statement
5
9
  from plain.models.backends.utils import strip_quotes
6
10
 
11
+ if TYPE_CHECKING:
12
+ from collections.abc import Generator
13
+
14
+ from plain.models.base import Model
15
+ from plain.models.fields import Field
16
+ from plain.models.indexes import Index
17
+
7
18
 
8
19
  class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
9
20
  # Setting all constraints to IMMEDIATE to allow changing data in the same
@@ -39,7 +50,9 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
39
50
  "ALTER TABLE %(table)s DROP CONSTRAINT %(name)s"
40
51
  )
41
52
 
42
- def execute(self, sql, params=()):
53
+ def execute(
54
+ self, sql: str | Statement, params: tuple[Any, ...] | list[Any] | None = ()
55
+ ) -> None:
43
56
  # Merge the query client-side, as PostgreSQL won't do it server-side.
44
57
  if params is None:
45
58
  return super().execute(sql, params)
@@ -55,19 +68,19 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
55
68
  "ALTER TABLE %(table)s ALTER COLUMN %(column)s DROP IDENTITY IF EXISTS"
56
69
  )
57
70
 
58
- def quote_value(self, value):
71
+ def quote_value(self, value: Any) -> str:
59
72
  if isinstance(value, str):
60
73
  value = value.replace("%", "%%")
61
74
  return sql.quote(value, self.connection.connection)
62
75
 
63
- def _field_indexes_sql(self, model, field):
76
+ def _field_indexes_sql(self, model: type[Model], field: Field) -> list[Statement]:
64
77
  output = super()._field_indexes_sql(model, field)
65
78
  like_index_statement = self._create_like_index_sql(model, field)
66
79
  if like_index_statement is not None:
67
80
  output.append(like_index_statement)
68
81
  return output
69
82
 
70
- def _field_data_type(self, field):
83
+ def _field_data_type(self, field: Field) -> str | None:
71
84
  if field.is_relation:
72
85
  return field.rel_db_type(self.connection)
73
86
  return self.connection.data_types.get(
@@ -75,14 +88,16 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
75
88
  field.db_type(self.connection),
76
89
  )
77
90
 
78
- def _field_base_data_types(self, field):
91
+ def _field_base_data_types(self, field: Field) -> Generator[str | None, None, None]:
79
92
  # Yield base data types for array fields.
80
- if field.base_field.get_internal_type() == "ArrayField":
81
- yield from self._field_base_data_types(field.base_field)
93
+ if field.base_field.get_internal_type() == "ArrayField": # type: ignore[attr-defined]
94
+ yield from self._field_base_data_types(field.base_field) # type: ignore[attr-defined]
82
95
  else:
83
- yield self._field_data_type(field.base_field)
96
+ yield self._field_data_type(field.base_field) # type: ignore[attr-defined]
84
97
 
85
- def _create_like_index_sql(self, model, field):
98
+ def _create_like_index_sql(
99
+ self, model: type[Model], field: Field
100
+ ) -> Statement | None:
86
101
  """
87
102
  Return the statement to create an index with varchar operator pattern
88
103
  when the column type is 'varchar' or 'text', otherwise return None.
@@ -107,18 +122,18 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
107
122
  model,
108
123
  fields=[field],
109
124
  suffix="_like",
110
- opclasses=["varchar_pattern_ops"],
125
+ opclasses=("varchar_pattern_ops",),
111
126
  )
112
127
  elif db_type.startswith("text"):
113
128
  return self._create_index_sql(
114
129
  model,
115
130
  fields=[field],
116
131
  suffix="_like",
117
- opclasses=["text_pattern_ops"],
132
+ opclasses=("text_pattern_ops",),
118
133
  )
119
134
  return None
120
135
 
121
- def _using_sql(self, new_field, old_field):
136
+ def _using_sql(self, new_field: Field, old_field: Field) -> str:
122
137
  using_sql = " USING %(column)s::%(type)s"
123
138
  new_internal_type = new_field.get_internal_type()
124
139
  old_internal_type = old_field.get_internal_type()
@@ -132,7 +147,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
132
147
  return using_sql
133
148
  return ""
134
149
 
135
- def _get_sequence_name(self, table, column):
150
+ def _get_sequence_name(self, table: str, column: str) -> str | None:
136
151
  with self.connection.cursor() as cursor:
137
152
  for sequence in self.connection.introspection.get_sequences(cursor, table):
138
153
  if sequence["column"] == column:
@@ -140,8 +155,14 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
140
155
  return None
141
156
 
142
157
  def _alter_column_type_sql(
143
- self, model, old_field, new_field, new_type, old_collation, new_collation
144
- ):
158
+ self,
159
+ model: type[Model],
160
+ old_field: Field,
161
+ new_field: Field,
162
+ new_type: str,
163
+ old_collation: str | None,
164
+ new_collation: str | None,
165
+ ) -> tuple[tuple[str, list[Any]], list[tuple[str, list[Any]]]]:
145
166
  # Drop indexes on varchar/text/citext columns that are changing to a
146
167
  # different type.
147
168
  old_db_params = old_field.db_parameters(connection=self.connection)
@@ -248,15 +269,15 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
248
269
 
249
270
  def _alter_field(
250
271
  self,
251
- model,
252
- old_field,
253
- new_field,
254
- old_type,
255
- new_type,
256
- old_db_params,
257
- new_db_params,
258
- strict=False,
259
- ):
272
+ model: type[Model],
273
+ old_field: Field,
274
+ new_field: Field,
275
+ old_type: str,
276
+ new_type: str,
277
+ old_db_params: dict[str, Any],
278
+ new_db_params: dict[str, Any],
279
+ strict: bool = False,
280
+ ) -> None:
260
281
  super()._alter_field(
261
282
  model,
262
283
  old_field,
@@ -270,9 +291,10 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
270
291
  # Added an index? Create any PostgreSQL-specific indexes.
271
292
  if (
272
293
  not (
273
- (old_field.remote_field and old_field.db_index) or old_field.primary_key
294
+ (old_field.remote_field and old_field.db_index) # type: ignore[attr-defined]
295
+ or old_field.primary_key
274
296
  )
275
- and (new_field.remote_field and new_field.db_index)
297
+ and (new_field.remote_field and new_field.db_index) # type: ignore[attr-defined]
276
298
  ) or (not old_field.primary_key and new_field.primary_key):
277
299
  like_index_statement = self._create_like_index_sql(model, new_field)
278
300
  if like_index_statement is not None:
@@ -280,14 +302,21 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
280
302
 
281
303
  # Removed an index? Drop any PostgreSQL-specific indexes.
282
304
  if old_field.primary_key and not (
283
- (new_field.remote_field and new_field.db_index) or new_field.primary_key
305
+ (new_field.remote_field and new_field.db_index) # type: ignore[attr-defined]
306
+ or new_field.primary_key
284
307
  ):
285
308
  index_to_remove = self._create_index_name(
286
309
  model._meta.db_table, [old_field.column], suffix="_like"
287
310
  )
288
311
  self.execute(self._delete_index_sql(model, index_to_remove))
289
312
 
290
- def _index_columns(self, table, columns, col_suffixes, opclasses):
313
+ def _index_columns(
314
+ self,
315
+ table: str,
316
+ columns: list[str],
317
+ col_suffixes: tuple[str, ...],
318
+ opclasses: tuple[str, ...],
319
+ ) -> Columns | IndexColumns:
291
320
  if opclasses:
292
321
  return IndexColumns(
293
322
  table,
@@ -298,15 +327,25 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
298
327
  )
299
328
  return super()._index_columns(table, columns, col_suffixes, opclasses)
300
329
 
301
- def add_index(self, model, index, concurrently=False):
330
+ def add_index(
331
+ self, model: type[Model], index: Index, concurrently: bool = False
332
+ ) -> None:
302
333
  self.execute(
303
334
  index.create_sql(model, self, concurrently=concurrently), params=None
304
335
  )
305
336
 
306
- def remove_index(self, model, index, concurrently=False):
337
+ def remove_index(
338
+ self, model: type[Model], index: Index, concurrently: bool = False
339
+ ) -> None:
307
340
  self.execute(index.remove_sql(model, self, concurrently=concurrently))
308
341
 
309
- def _delete_index_sql(self, model, name, sql=None, concurrently=False):
342
+ def _delete_index_sql(
343
+ self,
344
+ model: type[Model],
345
+ name: str,
346
+ sql: str | None = None,
347
+ concurrently: bool = False,
348
+ ) -> Statement:
310
349
  sql = (
311
350
  self.sql_delete_index_concurrently
312
351
  if concurrently
@@ -316,20 +355,20 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
316
355
 
317
356
  def _create_index_sql(
318
357
  self,
319
- model,
358
+ model: type[Model],
320
359
  *,
321
- fields=None,
322
- name=None,
323
- suffix="",
324
- using="",
325
- col_suffixes=(),
326
- sql=None,
327
- opclasses=(),
328
- condition=None,
329
- concurrently=False,
330
- include=None,
331
- expressions=None,
332
- ):
360
+ fields: list[Field] | None = None,
361
+ name: str | None = None,
362
+ suffix: str = "",
363
+ using: str = "",
364
+ col_suffixes: tuple[str, ...] = (),
365
+ sql: str | None = None,
366
+ opclasses: tuple[str, ...] = (),
367
+ condition: str | None = None,
368
+ concurrently: bool = False,
369
+ include: list[str] | None = None,
370
+ expressions: Any = None,
371
+ ) -> Statement:
333
372
  sql = sql or (
334
373
  self.sql_create_index
335
374
  if not concurrently