plain.models 0.49.2__py3-none-any.whl → 0.50.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. plain/models/CHANGELOG.md +13 -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 +31 -22
  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.2.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.2.dist-info/RECORD +0 -122
  103. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/WHEEL +0 -0
  104. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/entry_points.txt +0 -0
  105. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/licenses/LICENSE +0 -0
@@ -2,12 +2,15 @@
2
2
  SQLite backend for the sqlite3 module in the standard library.
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  import datetime
6
8
  import decimal
7
9
  import warnings
8
- from collections.abc import Mapping
10
+ from collections.abc import Callable, Iterable, Mapping
9
11
  from itertools import chain, tee
10
12
  from sqlite3 import dbapi2 as Database
13
+ from typing import Any
11
14
 
12
15
  from plain.exceptions import ImproperlyConfigured
13
16
  from plain.models.backends.base.base import BaseDatabaseWrapper
@@ -24,22 +27,22 @@ from .operations import DatabaseOperations
24
27
  from .schema import DatabaseSchemaEditor
25
28
 
26
29
 
27
- def decoder(conv_func):
30
+ def decoder(conv_func: Callable[[str], Any]) -> Callable[[bytes], Any]:
28
31
  """
29
32
  Convert bytestrings from Python's sqlite3 interface to a regular string.
30
33
  """
31
34
  return lambda s: conv_func(s.decode())
32
35
 
33
36
 
34
- def adapt_date(val):
37
+ def adapt_date(val: datetime.date) -> str:
35
38
  return val.isoformat()
36
39
 
37
40
 
38
- def adapt_datetime(val):
41
+ def adapt_datetime(val: datetime.datetime) -> str:
39
42
  return val.isoformat(" ")
40
43
 
41
44
 
42
- def _get_varchar_column(data):
45
+ def _get_varchar_column(data: dict[str, Any]) -> str:
43
46
  if data["max_length"] is None:
44
47
  return "varchar"
45
48
  return "varchar({max_length})".format(**data)
@@ -56,7 +59,7 @@ Database.register_adapter(datetime.date, adapt_date)
56
59
  Database.register_adapter(datetime.datetime, adapt_datetime)
57
60
 
58
61
 
59
- class DatabaseWrapper(BaseDatabaseWrapper):
62
+ class SQLiteDatabaseWrapper(BaseDatabaseWrapper):
60
63
  vendor = "sqlite"
61
64
  display_name = "SQLite"
62
65
  # SQLite doesn't actually support most of these types, but it "does the right
@@ -140,7 +143,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
140
143
  introspection_class = DatabaseIntrospection
141
144
  ops_class = DatabaseOperations
142
145
 
143
- def get_connection_params(self):
146
+ def get_connection_params(self) -> dict[str, Any]:
144
147
  settings_dict = self.settings_dict
145
148
  if not settings_dict["NAME"]:
146
149
  raise ImproperlyConfigured(
@@ -169,11 +172,11 @@ class DatabaseWrapper(BaseDatabaseWrapper):
169
172
  kwargs.update({"check_same_thread": False, "uri": True})
170
173
  return kwargs
171
174
 
172
- def get_database_version(self):
175
+ def get_database_version(self) -> tuple[int, ...]:
173
176
  return self.Database.sqlite_version_info
174
177
 
175
- def get_new_connection(self, conn_params):
176
- conn = Database.connect(**conn_params)
178
+ def get_new_connection(self, conn_params: dict[str, Any]) -> Any:
179
+ conn = Database.connect(**conn_params) # type: ignore[call-overload]
177
180
  register_functions(conn)
178
181
 
179
182
  conn.execute("PRAGMA foreign_keys = ON")
@@ -182,18 +185,19 @@ class DatabaseWrapper(BaseDatabaseWrapper):
182
185
  conn.execute("PRAGMA legacy_alter_table = OFF")
183
186
  return conn
184
187
 
185
- def create_cursor(self, name=None):
188
+ def create_cursor(self, name: str | None = None) -> Any:
186
189
  return self.connection.cursor(factory=SQLiteCursorWrapper)
187
190
 
188
- def close(self):
191
+ def close(self) -> None:
189
192
  self.validate_thread_sharing()
190
193
  # If database is in memory, closing the connection destroys the
191
194
  # database. To prevent accidental data loss, ignore close requests on
192
195
  # an in-memory db.
193
196
  if not self.is_in_memory_db():
194
197
  BaseDatabaseWrapper.close(self)
198
+ return None
195
199
 
196
- def _savepoint_allowed(self):
200
+ def _savepoint_allowed(self) -> bool:
197
201
  # When 'isolation_level' is not None, sqlite3 commits before each
198
202
  # savepoint; it's a bug. When it is None, savepoints don't make sense
199
203
  # because autocommit is enabled. The only exception is inside 'atomic'
@@ -201,7 +205,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
201
205
  # transaction explicitly rather than simply disable autocommit.
202
206
  return self.in_atomic_block
203
207
 
204
- def _set_autocommit(self, autocommit):
208
+ def _set_autocommit(self, autocommit: bool) -> None:
205
209
  if autocommit:
206
210
  level = None
207
211
  else:
@@ -212,8 +216,9 @@ class DatabaseWrapper(BaseDatabaseWrapper):
212
216
  # SQLite always runs at the SERIALIZABLE isolation level.
213
217
  with self.wrap_database_errors:
214
218
  self.connection.isolation_level = level
219
+ return None
215
220
 
216
- def disable_constraint_checking(self):
221
+ def disable_constraint_checking(self) -> bool:
217
222
  with self.cursor() as cursor:
218
223
  cursor.execute("PRAGMA foreign_keys = OFF")
219
224
  # Foreign key constraints cannot be turned off while in a multi-
@@ -222,11 +227,12 @@ class DatabaseWrapper(BaseDatabaseWrapper):
222
227
  enabled = cursor.execute("PRAGMA foreign_keys").fetchone()[0]
223
228
  return not bool(enabled)
224
229
 
225
- def enable_constraint_checking(self):
230
+ def enable_constraint_checking(self) -> None:
226
231
  with self.cursor() as cursor:
227
232
  cursor.execute("PRAGMA foreign_keys = ON")
233
+ return None
228
234
 
229
- def check_constraints(self, table_names=None):
235
+ def check_constraints(self, table_names: list[str] | None = None) -> None:
230
236
  """
231
237
  Check each table name in `table_names` for rows with invalid foreign
232
238
  key references. This method is intended to be used in conjunction with
@@ -267,11 +273,12 @@ class DatabaseWrapper(BaseDatabaseWrapper):
267
273
  f"invalid foreign key: {table_name}.{column_name} contains a value '{bad_value}' that "
268
274
  f"does not have a corresponding value in {referenced_table_name}.{referenced_column_name}."
269
275
  )
276
+ return None
270
277
 
271
- def is_usable(self):
278
+ def is_usable(self) -> bool:
272
279
  return True
273
280
 
274
- def _start_transaction_under_autocommit(self):
281
+ def _start_transaction_under_autocommit(self) -> None:
275
282
  """
276
283
  Start a transaction explicitly in autocommit mode.
277
284
 
@@ -279,8 +286,9 @@ class DatabaseWrapper(BaseDatabaseWrapper):
279
286
  savepoints when autocommit is disabled.
280
287
  """
281
288
  self.cursor().execute("BEGIN")
289
+ return None
282
290
 
283
- def is_in_memory_db(self):
291
+ def is_in_memory_db(self) -> bool:
284
292
  return self.creation.is_in_memory_db(self.settings_dict["NAME"])
285
293
 
286
294
 
@@ -300,7 +308,9 @@ class SQLiteCursorWrapper(Database.Cursor):
300
308
  In both cases, if you want to use a literal "%s", you'll need to use "%%s".
301
309
  """
302
310
 
303
- def execute(self, query, params=None):
311
+ def execute( # type: ignore[override]
312
+ self, query: str, params: Iterable[Any] | Mapping[str, Any] | None = None
313
+ ) -> Any:
304
314
  if params is None:
305
315
  return super().execute(query)
306
316
  # Extract names if params is a mapping, i.e. "pyformat" style is used.
@@ -308,7 +318,11 @@ class SQLiteCursorWrapper(Database.Cursor):
308
318
  query = self.convert_query(query, param_names=param_names)
309
319
  return super().execute(query, params)
310
320
 
311
- def executemany(self, query, param_list):
321
+ def executemany( # type: ignore[override]
322
+ self,
323
+ query: str,
324
+ param_list: Iterable[Iterable[Any] | Mapping[str, Any]],
325
+ ) -> Any:
312
326
  # Extract names if params is a mapping, i.e. "pyformat" style is used.
313
327
  # Peek carefully as a generator can be passed instead of a list/tuple.
314
328
  peekable, param_list = tee(iter(param_list))
@@ -319,7 +333,7 @@ class SQLiteCursorWrapper(Database.Cursor):
319
333
  query = self.convert_query(query, param_names=param_names)
320
334
  return super().executemany(query, param_list)
321
335
 
322
- def convert_query(self, query, *, param_names=None):
336
+ def convert_query(self, query: str, *, param_names: list[str] | None = None) -> str:
323
337
  if param_names is None:
324
338
  # Convert from "format" style to "qmark" style.
325
339
  return FORMAT_QMARK_REGEX.sub("?", query).replace("%%", "%")
@@ -1,3 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
1
5
  from plain.models.backends.base.client import BaseDatabaseClient
2
6
 
3
7
 
@@ -5,6 +9,8 @@ class DatabaseClient(BaseDatabaseClient):
5
9
  executable_name = "sqlite3"
6
10
 
7
11
  @classmethod
8
- def settings_to_cmd_args_env(cls, settings_dict, parameters):
12
+ def settings_to_cmd_args_env(
13
+ cls, settings_dict: dict[str, Any], parameters: list[str]
14
+ ) -> tuple[list[str], None]:
9
15
  args = [cls.executable_name, settings_dict["NAME"], *parameters]
10
16
  return args, None
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  import sys
3
5
  from pathlib import Path
@@ -7,12 +9,12 @@ from plain.models.backends.base.creation import BaseDatabaseCreation
7
9
 
8
10
  class DatabaseCreation(BaseDatabaseCreation):
9
11
  @staticmethod
10
- def is_in_memory_db(database_name):
12
+ def is_in_memory_db(database_name: str | Path) -> bool:
11
13
  return not isinstance(database_name, Path) and (
12
14
  database_name == ":memory:" or "mode=memory" in database_name
13
15
  )
14
16
 
15
- def _get_test_db_name(self, prefix=""):
17
+ def _get_test_db_name(self, prefix: str = "") -> str:
16
18
  raw_name = self.connection.settings_dict["TEST"]["NAME"] or ":memory:"
17
19
  # Special in-memory case
18
20
  if raw_name == ":memory:":
@@ -25,7 +27,9 @@ class DatabaseCreation(BaseDatabaseCreation):
25
27
 
26
28
  return test_database_name
27
29
 
28
- def _create_test_db(self, *, test_database_name, verbosity, autoclobber):
30
+ def _create_test_db(
31
+ self, *, test_database_name: str, verbosity: int, autoclobber: bool
32
+ ) -> str:
29
33
  """
30
34
  Internal implementation - delete existing SQLite test DB file if needed.
31
35
  """
@@ -50,12 +54,12 @@ class DatabaseCreation(BaseDatabaseCreation):
50
54
  sys.exit(1)
51
55
  return test_database_name
52
56
 
53
- def _destroy_test_db(self, test_database_name, verbosity):
57
+ def _destroy_test_db(self, test_database_name: str, verbosity: int) -> None:
54
58
  if test_database_name and not self.is_in_memory_db(test_database_name):
55
59
  # Remove the SQLite database file
56
60
  os.remove(test_database_name)
57
61
 
58
- def test_db_signature(self, prefix=""):
62
+ def test_db_signature(self, prefix: str = "") -> tuple[str, str]:
59
63
  """
60
64
  Return a tuple that uniquely identifies a test database.
61
65
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import operator
2
4
  from functools import cached_property
3
5
 
@@ -34,11 +36,11 @@ class DatabaseFeatures(BaseDatabaseFeatures):
34
36
  supports_unlimited_charfield = True
35
37
 
36
38
  @cached_property
37
- def supports_atomic_references_rename(self):
39
+ def supports_atomic_references_rename(self) -> bool:
38
40
  return Database.sqlite_version_info >= (3, 26, 0)
39
41
 
40
42
  @cached_property
41
- def supports_json_field(self):
43
+ def supports_json_field(self) -> bool:
42
44
  with self.connection.cursor() as cursor:
43
45
  try:
44
46
  with transaction.atomic():
@@ -51,7 +53,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
51
53
  has_json_object_function = property(operator.attrgetter("supports_json_field"))
52
54
 
53
55
  @cached_property
54
- def can_return_columns_from_insert(self):
56
+ def can_return_columns_from_insert(self) -> bool:
55
57
  return Database.sqlite_version_info >= (3, 35)
56
58
 
57
59
  can_return_rows_from_bulk_insert = property(
@@ -1,6 +1,12 @@
1
+ from __future__ import annotations
2
+
1
3
  from collections import namedtuple
4
+ from collections.abc import Generator
5
+ from typing import Any
2
6
 
3
7
  import sqlparse
8
+ import sqlparse.sql
9
+ import sqlparse.tokens
4
10
 
5
11
  from plain.models import Index
6
12
  from plain.models.backends.base.introspection import (
@@ -18,7 +24,7 @@ FieldInfo = namedtuple(
18
24
  field_size_re = _lazy_re_compile(r"^\s*(?:var)?char\s*\(\s*(\d+)\s*\)\s*$")
19
25
 
20
26
 
21
- def get_field_size(name):
27
+ def get_field_size(name: str) -> int | None:
22
28
  """Extract the size number from a "varchar(11)" type name"""
23
29
  m = field_size_re.search(name)
24
30
  return int(m[1]) if m else None
@@ -53,7 +59,7 @@ class FlexibleFieldLookupDict:
53
59
  "time": "TimeField",
54
60
  }
55
61
 
56
- def __getitem__(self, key):
62
+ def __getitem__(self, key: str) -> str:
57
63
  key = key.lower().split("(", 1)[0].strip()
58
64
  return self.base_data_types_reverse[key]
59
65
 
@@ -61,7 +67,7 @@ class FlexibleFieldLookupDict:
61
67
  class DatabaseIntrospection(BaseDatabaseIntrospection):
62
68
  data_types_reverse = FlexibleFieldLookupDict()
63
69
 
64
- def get_field_type(self, data_type, description):
70
+ def get_field_type(self, data_type: Any, description: Any) -> str:
65
71
  field_type = super().get_field_type(data_type, description)
66
72
  if description.pk and field_type == "BigIntegerField":
67
73
  return "PrimaryKeyField"
@@ -69,7 +75,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
69
75
  return "JSONField"
70
76
  return field_type
71
77
 
72
- def get_table_list(self, cursor):
78
+ def get_table_list(self, cursor: Any) -> list[TableInfo]:
73
79
  """Return a list of table and view names in the current database."""
74
80
  # Skip the sqlite_sequence system table used for autoincrement key
75
81
  # generation.
@@ -81,7 +87,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
81
87
  )
82
88
  return [TableInfo(row[0], row[1][0]) for row in cursor.fetchall()]
83
89
 
84
- def get_table_description(self, cursor, table_name):
90
+ def get_table_description(self, cursor: Any, table_name: str) -> list[FieldInfo]:
85
91
  """
86
92
  Return a description of the table with the DB-API cursor.description
87
93
  interface.
@@ -128,11 +134,13 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
128
134
  for cid, name, data_type, notnull, default, pk in table_info
129
135
  ]
130
136
 
131
- def get_sequences(self, cursor, table_name, table_fields=()):
137
+ def get_sequences(
138
+ self, cursor: Any, table_name: str, table_fields: tuple[Any, ...] = ()
139
+ ) -> list[dict[str, Any]]:
132
140
  pk_col = self.get_primary_key_column(cursor, table_name)
133
141
  return [{"table": table_name, "column": pk_col}]
134
142
 
135
- def get_relations(self, cursor, table_name):
143
+ def get_relations(self, cursor: Any, table_name: str) -> dict[str, tuple[str, str]]:
136
144
  """
137
145
  Return a dictionary of {column_name: (ref_column_name, ref_table_name)}
138
146
  representing all foreign keys in the given table.
@@ -152,13 +160,15 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
152
160
  ) in cursor.fetchall()
153
161
  }
154
162
 
155
- def get_primary_key_columns(self, cursor, table_name):
163
+ def get_primary_key_columns(self, cursor: Any, table_name: str) -> list[str]:
156
164
  cursor.execute(
157
165
  f"PRAGMA table_info({self.connection.ops.quote_name(table_name)})"
158
166
  )
159
167
  return [name for _, name, *_, pk in cursor.fetchall() if pk]
160
168
 
161
- def _parse_column_or_constraint_definition(self, tokens, columns):
169
+ def _parse_column_or_constraint_definition(
170
+ self, tokens: Generator[Any, None, None], columns: set[str]
171
+ ) -> tuple[str | None, dict[str, Any] | None, dict[str, Any] | None, Any]:
162
172
  token = None
163
173
  is_constraint_definition = None
164
174
  field_name = None
@@ -258,11 +268,13 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
258
268
  )
259
269
  return constraint_name, unique_constraint, check_constraint, token
260
270
 
261
- def _parse_table_constraints(self, sql, columns):
271
+ def _parse_table_constraints(
272
+ self, sql: str, columns: set[str]
273
+ ) -> dict[str, dict[str, Any]]:
262
274
  # Check constraint parsing is based of SQLite syntax diagram.
263
275
  # https://www.sqlite.org/syntaxdiagrams.html#table-constraint
264
276
  statement = sqlparse.parse(sql)[0]
265
- constraints = {}
277
+ constraints: dict[str, dict[str, Any]] = {}
266
278
  unnamed_constrains_index = 0
267
279
  tokens = (token for token in statement.flatten() if not token.is_whitespace)
268
280
  # Go to columns and constraint definition
@@ -297,12 +309,14 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
297
309
  break
298
310
  return constraints
299
311
 
300
- def get_constraints(self, cursor, table_name):
312
+ def get_constraints(
313
+ self, cursor: Any, table_name: str
314
+ ) -> dict[str, dict[str, Any]]:
301
315
  """
302
316
  Retrieve any constraints or keys (unique, pk, fk, check, index) across
303
317
  one or more columns.
304
318
  """
305
- constraints = {}
319
+ constraints: dict[str, dict[str, Any]] = {}
306
320
  # Find inline check constraints.
307
321
  try:
308
322
  table_schema = cursor.execute(
@@ -393,7 +407,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
393
407
  )
394
408
  return constraints
395
409
 
396
- def _get_index_columns_orders(self, sql):
410
+ def _get_index_columns_orders(self, sql: str) -> list[str] | None:
397
411
  tokens = sqlparse.parse(sql)[0]
398
412
  for token in tokens:
399
413
  if isinstance(token, sqlparse.sql.Parenthesis):
@@ -401,7 +415,9 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
401
415
  return ["DESC" if info.endswith("DESC") else "ASC" for info in columns]
402
416
  return None
403
417
 
404
- def _get_column_collations(self, cursor, table_name):
418
+ def _get_column_collations(
419
+ self, cursor: Any, table_name: str
420
+ ) -> dict[str, str | None]:
405
421
  row = cursor.execute(
406
422
  """
407
423
  SELECT sql
@@ -415,7 +431,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
415
431
 
416
432
  sql = row[0]
417
433
  columns = str(sqlparse.parse(sql)[0][-1]).strip("()").split(", ")
418
- collations = {}
434
+ collations: dict[str, str | None] = {}
419
435
  for column in columns:
420
436
  tokens = column[1:].split()
421
437
  column_name = tokens[0].strip('"')