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.
- plain/models/CHANGELOG.md +13 -0
- plain/models/aggregates.py +42 -19
- plain/models/backends/base/base.py +125 -105
- plain/models/backends/base/client.py +11 -3
- plain/models/backends/base/creation.py +22 -12
- plain/models/backends/base/features.py +10 -4
- plain/models/backends/base/introspection.py +29 -16
- plain/models/backends/base/operations.py +187 -91
- plain/models/backends/base/schema.py +267 -165
- plain/models/backends/base/validation.py +12 -3
- plain/models/backends/ddl_references.py +85 -43
- plain/models/backends/mysql/base.py +29 -26
- plain/models/backends/mysql/client.py +7 -2
- plain/models/backends/mysql/compiler.py +12 -3
- plain/models/backends/mysql/creation.py +5 -2
- plain/models/backends/mysql/features.py +24 -22
- plain/models/backends/mysql/introspection.py +22 -13
- plain/models/backends/mysql/operations.py +106 -39
- plain/models/backends/mysql/schema.py +48 -24
- plain/models/backends/mysql/validation.py +13 -6
- plain/models/backends/postgresql/base.py +41 -34
- plain/models/backends/postgresql/client.py +7 -2
- plain/models/backends/postgresql/creation.py +10 -5
- plain/models/backends/postgresql/introspection.py +15 -8
- plain/models/backends/postgresql/operations.py +109 -42
- plain/models/backends/postgresql/schema.py +85 -46
- plain/models/backends/sqlite3/_functions.py +151 -115
- plain/models/backends/sqlite3/base.py +37 -23
- plain/models/backends/sqlite3/client.py +7 -1
- plain/models/backends/sqlite3/creation.py +9 -5
- plain/models/backends/sqlite3/features.py +5 -3
- plain/models/backends/sqlite3/introspection.py +32 -16
- plain/models/backends/sqlite3/operations.py +125 -42
- plain/models/backends/sqlite3/schema.py +82 -58
- plain/models/backends/utils.py +52 -29
- plain/models/backups/cli.py +8 -6
- plain/models/backups/clients.py +16 -7
- plain/models/backups/core.py +24 -13
- plain/models/base.py +113 -74
- plain/models/cli.py +94 -63
- plain/models/config.py +1 -1
- plain/models/connections.py +23 -7
- plain/models/constraints.py +65 -47
- plain/models/database_url.py +1 -1
- plain/models/db.py +6 -2
- plain/models/deletion.py +66 -43
- plain/models/entrypoints.py +1 -1
- plain/models/enums.py +22 -11
- plain/models/exceptions.py +23 -8
- plain/models/expressions.py +440 -257
- plain/models/fields/__init__.py +253 -202
- plain/models/fields/json.py +120 -54
- plain/models/fields/mixins.py +12 -8
- plain/models/fields/related.py +284 -252
- plain/models/fields/related_descriptors.py +31 -22
- plain/models/fields/related_lookups.py +23 -11
- plain/models/fields/related_managers.py +81 -47
- plain/models/fields/reverse_related.py +58 -55
- plain/models/forms.py +89 -63
- plain/models/functions/comparison.py +71 -18
- plain/models/functions/datetime.py +79 -29
- plain/models/functions/math.py +43 -10
- plain/models/functions/mixins.py +24 -7
- plain/models/functions/text.py +104 -25
- plain/models/functions/window.py +12 -6
- plain/models/indexes.py +52 -28
- plain/models/lookups.py +228 -153
- plain/models/migrations/autodetector.py +86 -43
- plain/models/migrations/exceptions.py +7 -3
- plain/models/migrations/executor.py +33 -7
- plain/models/migrations/graph.py +79 -50
- plain/models/migrations/loader.py +45 -22
- plain/models/migrations/migration.py +23 -18
- plain/models/migrations/operations/base.py +37 -19
- plain/models/migrations/operations/fields.py +89 -42
- plain/models/migrations/operations/models.py +245 -143
- plain/models/migrations/operations/special.py +82 -25
- plain/models/migrations/optimizer.py +7 -2
- plain/models/migrations/questioner.py +58 -31
- plain/models/migrations/recorder.py +18 -11
- plain/models/migrations/serializer.py +50 -39
- plain/models/migrations/state.py +220 -133
- plain/models/migrations/utils.py +29 -13
- plain/models/migrations/writer.py +17 -14
- plain/models/options.py +63 -56
- plain/models/otel.py +16 -6
- plain/models/preflight.py +35 -12
- plain/models/query.py +323 -228
- plain/models/query_utils.py +93 -58
- plain/models/registry.py +34 -16
- plain/models/sql/compiler.py +146 -97
- plain/models/sql/datastructures.py +38 -25
- plain/models/sql/query.py +255 -169
- plain/models/sql/subqueries.py +32 -21
- plain/models/sql/where.py +54 -29
- plain/models/test/pytest.py +15 -11
- plain/models/test/utils.py +4 -2
- plain/models/transaction.py +20 -7
- plain/models/utils.py +13 -5
- {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/METADATA +1 -1
- plain_models-0.50.0.dist-info/RECORD +122 -0
- plain_models-0.49.2.dist-info/RECORD +0 -122
- {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/WHEEL +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/entry_points.txt +0 -0
- {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
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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('"')
|