plain.models 0.49.2__py3-none-any.whl → 0.51.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plain/models/CHANGELOG.md +27 -0
- plain/models/README.md +26 -42
- plain/models/__init__.py +2 -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 +24 -14
- plain/models/backends/base/features.py +10 -4
- plain/models/backends/base/introspection.py +37 -20
- plain/models/backends/base/operations.py +187 -91
- plain/models/backends/base/schema.py +338 -218
- plain/models/backends/base/validation.py +13 -4
- 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 +13 -4
- 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 +107 -40
- plain/models/backends/mysql/schema.py +52 -28
- 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 +110 -43
- plain/models/backends/postgresql/schema.py +88 -49
- 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 +126 -43
- plain/models/backends/sqlite3/schema.py +127 -92
- 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 +221 -229
- plain/models/cli.py +98 -67
- plain/models/config.py +1 -1
- plain/models/connections.py +23 -7
- plain/models/constraints.py +79 -56
- plain/models/database_url.py +1 -1
- plain/models/db.py +6 -2
- plain/models/deletion.py +80 -56
- plain/models/entrypoints.py +1 -1
- plain/models/enums.py +22 -11
- plain/models/exceptions.py +23 -8
- plain/models/expressions.py +441 -258
- plain/models/fields/__init__.py +272 -217
- plain/models/fields/json.py +123 -57
- plain/models/fields/mixins.py +12 -8
- plain/models/fields/related.py +324 -290
- plain/models/fields/related_descriptors.py +33 -24
- plain/models/fields/related_lookups.py +24 -12
- plain/models/fields/related_managers.py +102 -79
- plain/models/fields/reverse_related.py +66 -63
- plain/models/forms.py +101 -75
- 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 +57 -32
- plain/models/lookups.py +228 -153
- plain/models/meta.py +505 -0
- 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 +38 -20
- plain/models/migrations/operations/fields.py +95 -48
- plain/models/migrations/operations/models.py +246 -142
- 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 +27 -16
- plain/models/migrations/serializer.py +50 -39
- plain/models/migrations/state.py +232 -156
- plain/models/migrations/utils.py +30 -14
- plain/models/migrations/writer.py +17 -14
- plain/models/options.py +189 -518
- plain/models/otel.py +16 -6
- plain/models/preflight.py +42 -17
- plain/models/query.py +400 -251
- plain/models/query_utils.py +109 -69
- plain/models/registry.py +40 -21
- plain/models/sql/compiler.py +190 -127
- plain/models/sql/datastructures.py +38 -25
- plain/models/sql/query.py +320 -225
- plain/models/sql/subqueries.py +36 -25
- 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 +17 -6
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/METADATA +27 -43
- plain_models-0.51.0.dist-info/RECORD +123 -0
- plain_models-0.49.2.dist-info/RECORD +0 -122
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/WHEEL +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,14 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import os
|
2
4
|
import sys
|
5
|
+
from typing import TYPE_CHECKING, Any
|
3
6
|
|
4
7
|
from plain.runtime import settings
|
5
8
|
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from plain.models.backends.base.base import BaseDatabaseWrapper
|
11
|
+
|
6
12
|
# The prefix to put on the default database name when creating
|
7
13
|
# the test database.
|
8
14
|
TEST_DATABASE_PREFIX = "test_"
|
@@ -14,16 +20,16 @@ class BaseDatabaseCreation:
|
|
14
20
|
destruction of the test database.
|
15
21
|
"""
|
16
22
|
|
17
|
-
def __init__(self, connection):
|
23
|
+
def __init__(self, connection: BaseDatabaseWrapper):
|
18
24
|
self.connection = connection
|
19
25
|
|
20
|
-
def _nodb_cursor(self):
|
26
|
+
def _nodb_cursor(self) -> Any:
|
21
27
|
return self.connection._nodb_cursor()
|
22
28
|
|
23
|
-
def log(self, msg):
|
29
|
+
def log(self, msg: str) -> None:
|
24
30
|
sys.stderr.write(msg + os.linesep)
|
25
31
|
|
26
|
-
def create_test_db(self, verbosity=1, prefix=""):
|
32
|
+
def create_test_db(self, verbosity: int = 1, prefix: str = "") -> str:
|
27
33
|
"""
|
28
34
|
Create a test database, prompting the user for confirmation if the
|
29
35
|
database already exists. Return the name of the test database created.
|
@@ -67,7 +73,7 @@ class BaseDatabaseCreation:
|
|
67
73
|
|
68
74
|
return test_database_name
|
69
75
|
|
70
|
-
def set_as_test_mirror(self, primary_settings_dict):
|
76
|
+
def set_as_test_mirror(self, primary_settings_dict: dict[str, Any]) -> None:
|
71
77
|
"""
|
72
78
|
Set this database up to be used in testing as a mirror of a primary
|
73
79
|
database whose settings are given.
|
@@ -92,7 +98,7 @@ class BaseDatabaseCreation:
|
|
92
98
|
# and package_config.package_label in loader.migrated_packages
|
93
99
|
# ):
|
94
100
|
# for model in package_config.get_models():
|
95
|
-
# if model.
|
101
|
+
# if model.model_options.can_migrate(
|
96
102
|
# self.connection
|
97
103
|
# ) and router.allow_migrate_model(self.connection.alias, model):
|
98
104
|
# queryset = model._base_manager.using(
|
@@ -121,12 +127,12 @@ class BaseDatabaseCreation:
|
|
121
127
|
# "json", data, using=self.connection.alias
|
122
128
|
# ):
|
123
129
|
# obj.save()
|
124
|
-
# table_names.add(obj.object.
|
130
|
+
# table_names.add(obj.object.model_options.db_table)
|
125
131
|
# # Manually check for any invalid keys that might have been added,
|
126
132
|
# # because constraint checks were disabled.
|
127
133
|
# self.connection.check_constraints(table_names=table_names)
|
128
134
|
|
129
|
-
def _get_test_db_name(self, prefix=""):
|
135
|
+
def _get_test_db_name(self, prefix: str = "") -> str:
|
130
136
|
"""
|
131
137
|
Internal implementation - return the name of the test DB that will be
|
132
138
|
created. Only useful when called from create_test_db() and
|
@@ -146,10 +152,12 @@ class BaseDatabaseCreation:
|
|
146
152
|
return self.connection.settings_dict["TEST"]["NAME"]
|
147
153
|
return TEST_DATABASE_PREFIX + self.connection.settings_dict["NAME"]
|
148
154
|
|
149
|
-
def _execute_create_test_db(self, cursor, parameters):
|
155
|
+
def _execute_create_test_db(self, cursor: Any, parameters: dict[str, str]) -> None:
|
150
156
|
cursor.execute("CREATE DATABASE {dbname} {suffix}".format(**parameters))
|
151
157
|
|
152
|
-
def _create_test_db(
|
158
|
+
def _create_test_db(
|
159
|
+
self, *, test_database_name: str, verbosity: int, autoclobber: bool
|
160
|
+
) -> str:
|
153
161
|
"""
|
154
162
|
Internal implementation - create the test db tables.
|
155
163
|
"""
|
@@ -187,7 +195,9 @@ class BaseDatabaseCreation:
|
|
187
195
|
|
188
196
|
return test_database_name
|
189
197
|
|
190
|
-
def destroy_test_db(
|
198
|
+
def destroy_test_db(
|
199
|
+
self, old_database_name: str | None = None, verbosity: int = 1
|
200
|
+
) -> None:
|
191
201
|
"""
|
192
202
|
Destroy a test database, prompting the user for confirmation if the
|
193
203
|
database already exists.
|
@@ -205,7 +215,7 @@ class BaseDatabaseCreation:
|
|
205
215
|
settings.DATABASE["NAME"] = old_database_name
|
206
216
|
self.connection.settings_dict["NAME"] = old_database_name
|
207
217
|
|
208
|
-
def _destroy_test_db(self, test_database_name, verbosity):
|
218
|
+
def _destroy_test_db(self, test_database_name: str, verbosity: int) -> None:
|
209
219
|
"""
|
210
220
|
Internal implementation - remove the test db tables.
|
211
221
|
"""
|
@@ -218,13 +228,13 @@ class BaseDatabaseCreation:
|
|
218
228
|
f"DROP DATABASE {self.connection.ops.quote_name(test_database_name)}"
|
219
229
|
)
|
220
230
|
|
221
|
-
def sql_table_creation_suffix(self):
|
231
|
+
def sql_table_creation_suffix(self) -> str:
|
222
232
|
"""
|
223
233
|
SQL to append to the end of the test table creation statements.
|
224
234
|
"""
|
225
235
|
return ""
|
226
236
|
|
227
|
-
def test_db_signature(self, prefix=""):
|
237
|
+
def test_db_signature(self, prefix: str = "") -> tuple[str, str, str, str]:
|
228
238
|
"""
|
229
239
|
Return a tuple with elements of self.connection.settings_dict (a
|
230
240
|
DATABASE setting value) that uniquely identify a database
|
@@ -1,4 +1,10 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
from functools import cached_property
|
4
|
+
from typing import TYPE_CHECKING, Any
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from plain.models.backends.base.base import BaseDatabaseWrapper
|
2
8
|
|
3
9
|
|
4
10
|
class BaseDatabaseFeatures:
|
@@ -185,16 +191,16 @@ class BaseDatabaseFeatures:
|
|
185
191
|
# Does the backend support unlimited character columns?
|
186
192
|
supports_unlimited_charfield = False
|
187
193
|
|
188
|
-
def __init__(self, connection):
|
194
|
+
def __init__(self, connection: BaseDatabaseWrapper):
|
189
195
|
self.connection = connection
|
190
196
|
|
191
197
|
@cached_property
|
192
|
-
def supports_explaining_query_execution(self):
|
198
|
+
def supports_explaining_query_execution(self) -> bool:
|
193
199
|
"""Does this backend support explaining query execution?"""
|
194
200
|
return self.connection.ops.explain_prefix is not None
|
195
201
|
|
196
202
|
@cached_property
|
197
|
-
def supports_transactions(self):
|
203
|
+
def supports_transactions(self) -> bool:
|
198
204
|
"""Confirm support for transactions."""
|
199
205
|
with self.connection.cursor() as cursor:
|
200
206
|
cursor.execute("CREATE TABLE ROLLBACK_TEST (X INT)")
|
@@ -207,7 +213,7 @@ class BaseDatabaseFeatures:
|
|
207
213
|
cursor.execute("DROP TABLE ROLLBACK_TEST")
|
208
214
|
return count == 0
|
209
215
|
|
210
|
-
def allows_group_by_selected_pks_on_model(self, model):
|
216
|
+
def allows_group_by_selected_pks_on_model(self, model: Any) -> bool:
|
211
217
|
if not self.allows_group_by_selected_pks:
|
212
218
|
return False
|
213
219
|
return True
|
@@ -1,4 +1,11 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
from collections import namedtuple
|
4
|
+
from collections.abc import Generator
|
5
|
+
from typing import TYPE_CHECKING, Any
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from plain.models.backends.base.base import BaseDatabaseWrapper
|
2
9
|
|
3
10
|
# Structure returned by DatabaseIntrospection.get_table_list()
|
4
11
|
TableInfo = namedtuple("TableInfo", ["name", "type"])
|
@@ -14,12 +21,12 @@ FieldInfo = namedtuple(
|
|
14
21
|
class BaseDatabaseIntrospection:
|
15
22
|
"""Encapsulate backend-specific introspection utilities."""
|
16
23
|
|
17
|
-
data_types_reverse = {}
|
24
|
+
data_types_reverse: dict[Any, str] = {}
|
18
25
|
|
19
|
-
def __init__(self, connection):
|
26
|
+
def __init__(self, connection: BaseDatabaseWrapper) -> None:
|
20
27
|
self.connection = connection
|
21
28
|
|
22
|
-
def get_field_type(self, data_type, description):
|
29
|
+
def get_field_type(self, data_type: Any, description: Any) -> str:
|
23
30
|
"""
|
24
31
|
Hook for a database backend to use the cursor description to
|
25
32
|
match a Plain field type to a database column.
|
@@ -29,7 +36,7 @@ class BaseDatabaseIntrospection:
|
|
29
36
|
"""
|
30
37
|
return self.data_types_reverse[data_type]
|
31
38
|
|
32
|
-
def identifier_converter(self, name):
|
39
|
+
def identifier_converter(self, name: str) -> str:
|
33
40
|
"""
|
34
41
|
Apply a conversion to the identifier for the purposes of comparison.
|
35
42
|
|
@@ -37,7 +44,7 @@ class BaseDatabaseIntrospection:
|
|
37
44
|
"""
|
38
45
|
return name
|
39
46
|
|
40
|
-
def table_names(self, cursor=None, include_views=False):
|
47
|
+
def table_names(self, cursor: Any = None, include_views: bool = False) -> list[str]:
|
41
48
|
"""
|
42
49
|
Return a list of names of all tables that exist in the database.
|
43
50
|
Sort the returned table list by Python's default sorting. Do NOT use
|
@@ -45,7 +52,7 @@ class BaseDatabaseIntrospection:
|
|
45
52
|
order between databases.
|
46
53
|
"""
|
47
54
|
|
48
|
-
def get_names(cursor):
|
55
|
+
def get_names(cursor: Any) -> list[str]:
|
49
56
|
return sorted(
|
50
57
|
ti.name
|
51
58
|
for ti in self.get_table_list(cursor)
|
@@ -57,7 +64,7 @@ class BaseDatabaseIntrospection:
|
|
57
64
|
return get_names(cursor)
|
58
65
|
return get_names(cursor)
|
59
66
|
|
60
|
-
def get_table_list(self, cursor):
|
67
|
+
def get_table_list(self, cursor: Any) -> list[TableInfo]:
|
61
68
|
"""
|
62
69
|
Return an unsorted list of TableInfo named tuples of all tables and
|
63
70
|
views that exist in the database.
|
@@ -67,7 +74,7 @@ class BaseDatabaseIntrospection:
|
|
67
74
|
"method"
|
68
75
|
)
|
69
76
|
|
70
|
-
def get_table_description(self, cursor, table_name):
|
77
|
+
def get_table_description(self, cursor: Any, table_name: str) -> list[FieldInfo]:
|
71
78
|
"""
|
72
79
|
Return a description of the table with the DB-API cursor.description
|
73
80
|
interface.
|
@@ -77,7 +84,7 @@ class BaseDatabaseIntrospection:
|
|
77
84
|
"get_table_description() method."
|
78
85
|
)
|
79
86
|
|
80
|
-
def get_migratable_models(self):
|
87
|
+
def get_migratable_models(self) -> Generator[Any, None, None]:
|
81
88
|
from plain.models import models_registry
|
82
89
|
from plain.packages import packages_registry
|
83
90
|
|
@@ -87,10 +94,12 @@ class BaseDatabaseIntrospection:
|
|
87
94
|
for model in models_registry.get_models(
|
88
95
|
package_label=package_config.package_label
|
89
96
|
)
|
90
|
-
if model.
|
97
|
+
if model.model_options.can_migrate(self.connection)
|
91
98
|
)
|
92
99
|
|
93
|
-
def plain_table_names(
|
100
|
+
def plain_table_names(
|
101
|
+
self, only_existing: bool = False, include_views: bool = True
|
102
|
+
) -> list[str]:
|
94
103
|
"""
|
95
104
|
Return a list of all table names that have associated Plain models and
|
96
105
|
are in INSTALLED_PACKAGES.
|
@@ -99,8 +108,10 @@ class BaseDatabaseIntrospection:
|
|
99
108
|
"""
|
100
109
|
tables = set()
|
101
110
|
for model in self.get_migratable_models():
|
102
|
-
tables.add(model.
|
103
|
-
tables.update(
|
111
|
+
tables.add(model.model_options.db_table)
|
112
|
+
tables.update(
|
113
|
+
f.m2m_db_table() for f in model._model_meta.local_many_to_many
|
114
|
+
)
|
104
115
|
tables = list(tables)
|
105
116
|
if only_existing:
|
106
117
|
existing_tables = set(self.table_names(include_views=include_views))
|
@@ -109,7 +120,7 @@ class BaseDatabaseIntrospection:
|
|
109
120
|
]
|
110
121
|
return tables
|
111
122
|
|
112
|
-
def sequence_list(self):
|
123
|
+
def sequence_list(self) -> list[dict[str, Any]]:
|
113
124
|
"""
|
114
125
|
Return a list of information about all DB sequences for all models in
|
115
126
|
all packages.
|
@@ -119,12 +130,16 @@ class BaseDatabaseIntrospection:
|
|
119
130
|
for model in self.get_migratable_models():
|
120
131
|
sequence_list.extend(
|
121
132
|
self.get_sequences(
|
122
|
-
cursor,
|
133
|
+
cursor,
|
134
|
+
model.model_options.db_table,
|
135
|
+
model._model_meta.local_fields,
|
123
136
|
)
|
124
137
|
)
|
125
138
|
return sequence_list
|
126
139
|
|
127
|
-
def get_sequences(
|
140
|
+
def get_sequences(
|
141
|
+
self, cursor: Any, table_name: str, table_fields: tuple[Any, ...] = ()
|
142
|
+
) -> list[dict[str, Any]]:
|
128
143
|
"""
|
129
144
|
Return a list of introspected sequences for table_name. Each sequence
|
130
145
|
is a dict: {'table': <table_name>, 'column': <column_name>}. An optional
|
@@ -135,7 +150,7 @@ class BaseDatabaseIntrospection:
|
|
135
150
|
"method"
|
136
151
|
)
|
137
152
|
|
138
|
-
def get_relations(self, cursor, table_name):
|
153
|
+
def get_relations(self, cursor: Any, table_name: str) -> dict[str, tuple[str, str]]:
|
139
154
|
"""
|
140
155
|
Return a dictionary of {field_name: (field_name_other_table, other_table)}
|
141
156
|
representing all foreign keys in the given table.
|
@@ -145,21 +160,23 @@ class BaseDatabaseIntrospection:
|
|
145
160
|
"get_relations() method."
|
146
161
|
)
|
147
162
|
|
148
|
-
def get_primary_key_column(self, cursor, table_name):
|
163
|
+
def get_primary_key_column(self, cursor: Any, table_name: str) -> str | None:
|
149
164
|
"""
|
150
165
|
Return the name of the primary key column for the given table.
|
151
166
|
"""
|
152
167
|
columns = self.get_primary_key_columns(cursor, table_name)
|
153
168
|
return columns[0] if columns else None
|
154
169
|
|
155
|
-
def get_primary_key_columns(self, cursor, table_name):
|
170
|
+
def get_primary_key_columns(self, cursor: Any, table_name: str) -> list[str] | None:
|
156
171
|
"""Return a list of primary key columns for the given table."""
|
157
172
|
for constraint in self.get_constraints(cursor, table_name).values():
|
158
173
|
if constraint["primary_key"]:
|
159
174
|
return constraint["columns"]
|
160
175
|
return None
|
161
176
|
|
162
|
-
def get_constraints(
|
177
|
+
def get_constraints(
|
178
|
+
self, cursor: Any, table_name: str
|
179
|
+
) -> dict[str, dict[str, Any]]:
|
163
180
|
"""
|
164
181
|
Retrieve any constraints or keys (unique, pk, fk, check, index)
|
165
182
|
across one or more columns.
|