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
plain/models/constraints.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
from enum import Enum
|
2
4
|
from types import NoneType
|
5
|
+
from typing import Any
|
3
6
|
|
4
7
|
from plain.exceptions import ValidationError
|
5
8
|
from plain.models.exceptions import FieldError
|
@@ -13,13 +16,17 @@ __all__ = ["BaseConstraint", "CheckConstraint", "Deferrable", "UniqueConstraint"
|
|
13
16
|
|
14
17
|
|
15
18
|
class BaseConstraint:
|
16
|
-
default_violation_error_message =
|
17
|
-
violation_error_code = None
|
18
|
-
violation_error_message = None
|
19
|
+
default_violation_error_message = 'Constraint "%(name)s" is violated.'
|
20
|
+
violation_error_code: str | None = None
|
21
|
+
violation_error_message: str | None = None
|
19
22
|
|
20
23
|
def __init__(
|
21
|
-
self,
|
22
|
-
|
24
|
+
self,
|
25
|
+
*,
|
26
|
+
name: str,
|
27
|
+
violation_error_code: str | None = None,
|
28
|
+
violation_error_message: str | None = None,
|
29
|
+
) -> None:
|
23
30
|
self.name = name
|
24
31
|
if violation_error_code is not None:
|
25
32
|
self.violation_error_code = violation_error_code
|
@@ -29,28 +36,30 @@ class BaseConstraint:
|
|
29
36
|
self.violation_error_message = self.default_violation_error_message
|
30
37
|
|
31
38
|
@property
|
32
|
-
def contains_expressions(self):
|
39
|
+
def contains_expressions(self) -> bool:
|
33
40
|
return False
|
34
41
|
|
35
|
-
def constraint_sql(self, model, schema_editor):
|
42
|
+
def constraint_sql(self, model: Any, schema_editor: Any) -> str:
|
36
43
|
raise NotImplementedError("This method must be implemented by a subclass.")
|
37
44
|
|
38
|
-
def create_sql(self, model, schema_editor):
|
45
|
+
def create_sql(self, model: Any, schema_editor: Any) -> str:
|
39
46
|
raise NotImplementedError("This method must be implemented by a subclass.")
|
40
47
|
|
41
|
-
def remove_sql(self, model, schema_editor):
|
48
|
+
def remove_sql(self, model: Any, schema_editor: Any) -> str:
|
42
49
|
raise NotImplementedError("This method must be implemented by a subclass.")
|
43
50
|
|
44
|
-
def validate(
|
51
|
+
def validate(
|
52
|
+
self, model: Any, instance: Any, exclude: set[str] | None = None
|
53
|
+
) -> None:
|
45
54
|
raise NotImplementedError("This method must be implemented by a subclass.")
|
46
55
|
|
47
|
-
def get_violation_error_message(self):
|
48
|
-
return self.violation_error_message % {"name": self.name}
|
56
|
+
def get_violation_error_message(self) -> str:
|
57
|
+
return self.violation_error_message % {"name": self.name} # type: ignore[operator]
|
49
58
|
|
50
|
-
def deconstruct(self):
|
59
|
+
def deconstruct(self) -> tuple[str, tuple[Any, ...], dict[str, Any]]:
|
51
60
|
path = f"{self.__class__.__module__}.{self.__class__.__name__}"
|
52
61
|
path = path.replace("plain.models.constraints", "plain.models")
|
53
|
-
kwargs = {"name": self.name}
|
62
|
+
kwargs: dict[str, Any] = {"name": self.name}
|
54
63
|
if (
|
55
64
|
self.violation_error_message is not None
|
56
65
|
and self.violation_error_message != self.default_violation_error_message
|
@@ -60,15 +69,20 @@ class BaseConstraint:
|
|
60
69
|
kwargs["violation_error_code"] = self.violation_error_code
|
61
70
|
return (path, (), kwargs)
|
62
71
|
|
63
|
-
def clone(self):
|
72
|
+
def clone(self) -> BaseConstraint:
|
64
73
|
_, args, kwargs = self.deconstruct()
|
65
74
|
return self.__class__(*args, **kwargs)
|
66
75
|
|
67
76
|
|
68
77
|
class CheckConstraint(BaseConstraint):
|
69
78
|
def __init__(
|
70
|
-
self,
|
71
|
-
|
79
|
+
self,
|
80
|
+
*,
|
81
|
+
check: Q,
|
82
|
+
name: str,
|
83
|
+
violation_error_code: str | None = None,
|
84
|
+
violation_error_message: str | None = None,
|
85
|
+
) -> None:
|
72
86
|
self.check = check
|
73
87
|
if not getattr(check, "conditional", False):
|
74
88
|
raise TypeError(
|
@@ -80,26 +94,28 @@ class CheckConstraint(BaseConstraint):
|
|
80
94
|
violation_error_message=violation_error_message,
|
81
95
|
)
|
82
96
|
|
83
|
-
def _get_check_sql(self, model, schema_editor):
|
97
|
+
def _get_check_sql(self, model: Any, schema_editor: Any) -> str:
|
84
98
|
query = Query(model=model, alias_cols=False)
|
85
99
|
where = query.build_where(self.check)
|
86
100
|
compiler = query.get_compiler()
|
87
101
|
sql, params = where.as_sql(compiler, schema_editor.connection)
|
88
102
|
return sql % tuple(schema_editor.quote_value(p) for p in params)
|
89
103
|
|
90
|
-
def constraint_sql(self, model, schema_editor):
|
104
|
+
def constraint_sql(self, model: Any, schema_editor: Any) -> str:
|
91
105
|
check = self._get_check_sql(model, schema_editor)
|
92
106
|
return schema_editor._check_sql(self.name, check)
|
93
107
|
|
94
|
-
def create_sql(self, model, schema_editor):
|
108
|
+
def create_sql(self, model: Any, schema_editor: Any) -> str:
|
95
109
|
check = self._get_check_sql(model, schema_editor)
|
96
110
|
return schema_editor._create_check_sql(model, self.name, check)
|
97
111
|
|
98
|
-
def remove_sql(self, model, schema_editor):
|
112
|
+
def remove_sql(self, model: Any, schema_editor: Any) -> str:
|
99
113
|
return schema_editor._delete_check_sql(model, self.name)
|
100
114
|
|
101
|
-
def validate(
|
102
|
-
|
115
|
+
def validate(
|
116
|
+
self, model: Any, instance: Any, exclude: set[str] | None = None
|
117
|
+
) -> None:
|
118
|
+
against = instance._get_field_value_map(meta=model._model_meta, exclude=exclude)
|
103
119
|
try:
|
104
120
|
if not Q(self.check).check(against):
|
105
121
|
raise ValidationError(
|
@@ -108,7 +124,7 @@ class CheckConstraint(BaseConstraint):
|
|
108
124
|
except FieldError:
|
109
125
|
pass
|
110
126
|
|
111
|
-
def __repr__(self):
|
127
|
+
def __repr__(self) -> str:
|
112
128
|
return "<{}: check={} name={}{}{}>".format(
|
113
129
|
self.__class__.__qualname__,
|
114
130
|
self.check,
|
@@ -126,7 +142,7 @@ class CheckConstraint(BaseConstraint):
|
|
126
142
|
),
|
127
143
|
)
|
128
144
|
|
129
|
-
def __eq__(self, other):
|
145
|
+
def __eq__(self, other: object) -> bool:
|
130
146
|
if isinstance(other, CheckConstraint):
|
131
147
|
return (
|
132
148
|
self.name == other.name
|
@@ -136,7 +152,7 @@ class CheckConstraint(BaseConstraint):
|
|
136
152
|
)
|
137
153
|
return super().__eq__(other)
|
138
154
|
|
139
|
-
def deconstruct(self):
|
155
|
+
def deconstruct(self) -> tuple[str, tuple[Any, ...], dict[str, Any]]:
|
140
156
|
path, args, kwargs = super().deconstruct()
|
141
157
|
kwargs["check"] = self.check
|
142
158
|
return path, args, kwargs
|
@@ -147,23 +163,23 @@ class Deferrable(Enum):
|
|
147
163
|
IMMEDIATE = "immediate"
|
148
164
|
|
149
165
|
# A similar format was proposed for Python 3.10.
|
150
|
-
def __repr__(self):
|
166
|
+
def __repr__(self) -> str:
|
151
167
|
return f"{self.__class__.__qualname__}.{self._name_}"
|
152
168
|
|
153
169
|
|
154
170
|
class UniqueConstraint(BaseConstraint):
|
155
171
|
def __init__(
|
156
172
|
self,
|
157
|
-
*expressions,
|
158
|
-
fields=(),
|
159
|
-
name=None,
|
160
|
-
condition=None,
|
161
|
-
deferrable=None,
|
162
|
-
include=None,
|
163
|
-
opclasses=(),
|
164
|
-
violation_error_code=None,
|
165
|
-
violation_error_message=None,
|
166
|
-
):
|
173
|
+
*expressions: Any,
|
174
|
+
fields: tuple[str, ...] | list[str] = (),
|
175
|
+
name: str | None = None,
|
176
|
+
condition: Q | None = None,
|
177
|
+
deferrable: Deferrable | None = None,
|
178
|
+
include: tuple[str, ...] | list[str] | None = None,
|
179
|
+
opclasses: tuple[str, ...] | list[str] = (),
|
180
|
+
violation_error_code: str | None = None,
|
181
|
+
violation_error_message: str | None = None,
|
182
|
+
) -> None:
|
167
183
|
if not name:
|
168
184
|
raise ValueError("A unique constraint must be named.")
|
169
185
|
if not expressions and not fields:
|
@@ -213,16 +229,16 @@ class UniqueConstraint(BaseConstraint):
|
|
213
229
|
for expression in expressions
|
214
230
|
)
|
215
231
|
super().__init__(
|
216
|
-
name=name,
|
232
|
+
name=name, # type: ignore[arg-type]
|
217
233
|
violation_error_code=violation_error_code,
|
218
234
|
violation_error_message=violation_error_message,
|
219
235
|
)
|
220
236
|
|
221
237
|
@property
|
222
|
-
def contains_expressions(self):
|
238
|
+
def contains_expressions(self) -> bool:
|
223
239
|
return bool(self.expressions)
|
224
240
|
|
225
|
-
def _get_condition_sql(self, model, schema_editor):
|
241
|
+
def _get_condition_sql(self, model: Any, schema_editor: Any) -> str | None:
|
226
242
|
if self.condition is None:
|
227
243
|
return None
|
228
244
|
query = Query(model=model, alias_cols=False)
|
@@ -231,7 +247,7 @@ class UniqueConstraint(BaseConstraint):
|
|
231
247
|
sql, params = where.as_sql(compiler, schema_editor.connection)
|
232
248
|
return sql % tuple(schema_editor.quote_value(p) for p in params)
|
233
249
|
|
234
|
-
def _get_index_expressions(self, model, schema_editor):
|
250
|
+
def _get_index_expressions(self, model: Any, schema_editor: Any) -> Any:
|
235
251
|
if not self.expressions:
|
236
252
|
return None
|
237
253
|
index_expressions = []
|
@@ -243,10 +259,11 @@ class UniqueConstraint(BaseConstraint):
|
|
243
259
|
Query(model, alias_cols=False),
|
244
260
|
)
|
245
261
|
|
246
|
-
def constraint_sql(self, model, schema_editor):
|
247
|
-
fields = [model.
|
262
|
+
def constraint_sql(self, model: Any, schema_editor: Any) -> str:
|
263
|
+
fields = [model._model_meta.get_field(field_name) for field_name in self.fields]
|
248
264
|
include = [
|
249
|
-
model.
|
265
|
+
model._model_meta.get_field(field_name).column
|
266
|
+
for field_name in self.include
|
250
267
|
]
|
251
268
|
condition = self._get_condition_sql(model, schema_editor)
|
252
269
|
expressions = self._get_index_expressions(model, schema_editor)
|
@@ -261,10 +278,11 @@ class UniqueConstraint(BaseConstraint):
|
|
261
278
|
expressions=expressions,
|
262
279
|
)
|
263
280
|
|
264
|
-
def create_sql(self, model, schema_editor):
|
265
|
-
fields = [model.
|
281
|
+
def create_sql(self, model: Any, schema_editor: Any) -> str:
|
282
|
+
fields = [model._model_meta.get_field(field_name) for field_name in self.fields]
|
266
283
|
include = [
|
267
|
-
model.
|
284
|
+
model._model_meta.get_field(field_name).column
|
285
|
+
for field_name in self.include
|
268
286
|
]
|
269
287
|
condition = self._get_condition_sql(model, schema_editor)
|
270
288
|
expressions = self._get_index_expressions(model, schema_editor)
|
@@ -279,10 +297,11 @@ class UniqueConstraint(BaseConstraint):
|
|
279
297
|
expressions=expressions,
|
280
298
|
)
|
281
299
|
|
282
|
-
def remove_sql(self, model, schema_editor):
|
300
|
+
def remove_sql(self, model: Any, schema_editor: Any) -> str:
|
283
301
|
condition = self._get_condition_sql(model, schema_editor)
|
284
302
|
include = [
|
285
|
-
model.
|
303
|
+
model._model_meta.get_field(field_name).column
|
304
|
+
for field_name in self.include
|
286
305
|
]
|
287
306
|
expressions = self._get_index_expressions(model, schema_editor)
|
288
307
|
return schema_editor._delete_unique_sql(
|
@@ -295,7 +314,7 @@ class UniqueConstraint(BaseConstraint):
|
|
295
314
|
expressions=expressions,
|
296
315
|
)
|
297
316
|
|
298
|
-
def __repr__(self):
|
317
|
+
def __repr__(self) -> str:
|
299
318
|
return "<{}:{}{}{}{}{}{}{}{}{}>".format(
|
300
319
|
self.__class__.__qualname__,
|
301
320
|
"" if not self.fields else f" fields={repr(self.fields)}",
|
@@ -318,7 +337,7 @@ class UniqueConstraint(BaseConstraint):
|
|
318
337
|
),
|
319
338
|
)
|
320
339
|
|
321
|
-
def __eq__(self, other):
|
340
|
+
def __eq__(self, other: object) -> bool:
|
322
341
|
if isinstance(other, UniqueConstraint):
|
323
342
|
return (
|
324
343
|
self.name == other.name
|
@@ -333,7 +352,7 @@ class UniqueConstraint(BaseConstraint):
|
|
333
352
|
)
|
334
353
|
return super().__eq__(other)
|
335
354
|
|
336
|
-
def deconstruct(self):
|
355
|
+
def deconstruct(self) -> tuple[str, tuple[Any, ...], dict[str, Any]]:
|
337
356
|
path, args, kwargs = super().deconstruct()
|
338
357
|
if self.fields:
|
339
358
|
kwargs["fields"] = self.fields
|
@@ -347,14 +366,16 @@ class UniqueConstraint(BaseConstraint):
|
|
347
366
|
kwargs["opclasses"] = self.opclasses
|
348
367
|
return path, self.expressions, kwargs
|
349
368
|
|
350
|
-
def validate(
|
369
|
+
def validate(
|
370
|
+
self, model: Any, instance: Any, exclude: set[str] | None = None
|
371
|
+
) -> None:
|
351
372
|
queryset = model.query
|
352
373
|
if self.fields:
|
353
374
|
lookup_kwargs = {}
|
354
375
|
for field_name in self.fields:
|
355
376
|
if exclude and field_name in exclude:
|
356
377
|
return
|
357
|
-
field = model.
|
378
|
+
field = model._model_meta.get_field(field_name)
|
358
379
|
lookup_value = getattr(instance, field.attname)
|
359
380
|
if lookup_value is None:
|
360
381
|
# A composite constraint containing NULL value cannot cause
|
@@ -375,7 +396,7 @@ class UniqueConstraint(BaseConstraint):
|
|
375
396
|
replacements = {
|
376
397
|
F(field): value
|
377
398
|
for field, value in instance._get_field_value_map(
|
378
|
-
meta=model.
|
399
|
+
meta=model._model_meta, exclude=exclude
|
379
400
|
).items()
|
380
401
|
}
|
381
402
|
expressions = []
|
@@ -404,7 +425,9 @@ class UniqueConstraint(BaseConstraint):
|
|
404
425
|
instance.unique_error_message(model, self.fields),
|
405
426
|
)
|
406
427
|
else:
|
407
|
-
against = instance._get_field_value_map(
|
428
|
+
against = instance._get_field_value_map(
|
429
|
+
meta=model._model_meta, exclude=exclude
|
430
|
+
)
|
408
431
|
try:
|
409
432
|
if (self.condition & Exists(queryset.filter(self.condition))).check(
|
410
433
|
against
|
plain/models/database_url.py
CHANGED
plain/models/db.py
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Any
|
4
|
+
|
1
5
|
from plain import signals
|
2
6
|
|
3
7
|
from .connections import DatabaseConnection
|
@@ -22,7 +26,7 @@ db_connection = DatabaseConnection()
|
|
22
26
|
|
23
27
|
|
24
28
|
# Register an event to reset saved queries when a Plain request is started.
|
25
|
-
def reset_queries(**kwargs):
|
29
|
+
def reset_queries(**kwargs: Any) -> None:
|
26
30
|
if db_connection.has_connection():
|
27
31
|
db_connection.queries_log.clear()
|
28
32
|
|
@@ -32,7 +36,7 @@ signals.request_started.connect(reset_queries)
|
|
32
36
|
|
33
37
|
# Register an event to reset transaction state and close connections past
|
34
38
|
# their lifetime.
|
35
|
-
def close_old_connections(**kwargs):
|
39
|
+
def close_old_connections(**kwargs: Any) -> None:
|
36
40
|
if db_connection.has_connection():
|
37
41
|
db_connection.close_if_unusable_or_obsolete()
|
38
42
|
|