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/deletion.py
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
from collections import Counter, defaultdict
|
4
|
+
from collections.abc import Callable, Generator, Iterable
|
2
5
|
from functools import partial, reduce
|
3
6
|
from itertools import chain
|
4
7
|
from operator import attrgetter, or_
|
8
|
+
from typing import TYPE_CHECKING, Any
|
5
9
|
|
6
10
|
from plain.models import (
|
7
11
|
query_utils,
|
@@ -9,25 +13,29 @@ from plain.models import (
|
|
9
13
|
transaction,
|
10
14
|
)
|
11
15
|
from plain.models.db import IntegrityError, db_connection
|
16
|
+
from plain.models.meta import Meta
|
12
17
|
from plain.models.query import QuerySet
|
13
18
|
|
19
|
+
if TYPE_CHECKING:
|
20
|
+
from plain.models.fields import Field
|
21
|
+
|
14
22
|
|
15
23
|
class ProtectedError(IntegrityError):
|
16
|
-
def __init__(self, msg, protected_objects):
|
24
|
+
def __init__(self, msg: str, protected_objects: Iterable[Any]) -> None:
|
17
25
|
self.protected_objects = protected_objects
|
18
26
|
super().__init__(msg, protected_objects)
|
19
27
|
|
20
28
|
|
21
29
|
class RestrictedError(IntegrityError):
|
22
|
-
def __init__(self, msg, restricted_objects):
|
30
|
+
def __init__(self, msg: str, restricted_objects: Iterable[Any]) -> None:
|
23
31
|
self.restricted_objects = restricted_objects
|
24
32
|
super().__init__(msg, restricted_objects)
|
25
33
|
|
26
34
|
|
27
|
-
def CASCADE(collector, field, sub_objs):
|
35
|
+
def CASCADE(collector: Collector, field: Field, sub_objs: Any) -> None:
|
28
36
|
collector.collect(
|
29
37
|
sub_objs,
|
30
|
-
source=field.remote_field.model,
|
38
|
+
source=field.remote_field.model, # type: ignore[attr-defined]
|
31
39
|
nullable=field.allow_null,
|
32
40
|
fail_on_restricted=False,
|
33
41
|
)
|
@@ -35,84 +43,96 @@ def CASCADE(collector, field, sub_objs):
|
|
35
43
|
collector.add_field_update(field, None, sub_objs)
|
36
44
|
|
37
45
|
|
38
|
-
def PROTECT(collector, field, sub_objs):
|
46
|
+
def PROTECT(collector: Collector, field: Field, sub_objs: Any) -> None:
|
39
47
|
raise ProtectedError(
|
40
|
-
f"Cannot delete some instances of model '{field.remote_field.model.__name__}' because they are "
|
48
|
+
f"Cannot delete some instances of model '{field.remote_field.model.__name__}' because they are " # type: ignore[attr-defined]
|
41
49
|
f"referenced through a protected foreign key: '{sub_objs[0].__class__.__name__}.{field.name}'",
|
42
50
|
sub_objs,
|
43
51
|
)
|
44
52
|
|
45
53
|
|
46
|
-
def RESTRICT(collector, field, sub_objs):
|
54
|
+
def RESTRICT(collector: Collector, field: Field, sub_objs: Any) -> None:
|
47
55
|
collector.add_restricted_objects(field, sub_objs)
|
48
|
-
collector.add_dependency(field.remote_field.model, field.model)
|
56
|
+
collector.add_dependency(field.remote_field.model, field.model) # type: ignore[attr-defined]
|
49
57
|
|
50
58
|
|
51
|
-
def SET(value):
|
59
|
+
def SET(value: Any) -> Callable[[Collector, Field, Any], None]:
|
52
60
|
if callable(value):
|
53
61
|
|
54
|
-
def set_on_delete(collector, field, sub_objs):
|
62
|
+
def set_on_delete(collector: Collector, field: Field, sub_objs: Any) -> None:
|
55
63
|
collector.add_field_update(field, value(), sub_objs)
|
56
64
|
|
57
65
|
else:
|
58
66
|
|
59
|
-
def set_on_delete(collector, field, sub_objs):
|
67
|
+
def set_on_delete(collector: Collector, field: Field, sub_objs: Any) -> None:
|
60
68
|
collector.add_field_update(field, value, sub_objs)
|
61
69
|
|
62
|
-
set_on_delete.deconstruct = lambda: ("plain.models.SET", (value,), {})
|
63
|
-
set_on_delete.lazy_sub_objs = True
|
70
|
+
set_on_delete.deconstruct = lambda: ("plain.models.SET", (value,), {}) # type: ignore[attr-defined]
|
71
|
+
set_on_delete.lazy_sub_objs = True # type: ignore[attr-defined]
|
64
72
|
return set_on_delete
|
65
73
|
|
66
74
|
|
67
|
-
def SET_NULL(collector, field, sub_objs):
|
75
|
+
def SET_NULL(collector: Collector, field: Field, sub_objs: Any) -> None:
|
68
76
|
collector.add_field_update(field, None, sub_objs)
|
69
77
|
|
70
78
|
|
71
|
-
SET_NULL.lazy_sub_objs = True
|
79
|
+
SET_NULL.lazy_sub_objs = True # type: ignore[attr-defined]
|
72
80
|
|
73
81
|
|
74
|
-
def SET_DEFAULT(collector, field, sub_objs):
|
82
|
+
def SET_DEFAULT(collector: Collector, field: Field, sub_objs: Any) -> None:
|
75
83
|
collector.add_field_update(field, field.get_default(), sub_objs)
|
76
84
|
|
77
85
|
|
78
|
-
SET_DEFAULT.lazy_sub_objs = True
|
86
|
+
SET_DEFAULT.lazy_sub_objs = True # type: ignore[attr-defined]
|
79
87
|
|
80
88
|
|
81
|
-
def DO_NOTHING(collector, field, sub_objs):
|
89
|
+
def DO_NOTHING(collector: Collector, field: Field, sub_objs: Any) -> None:
|
82
90
|
pass
|
83
91
|
|
84
92
|
|
85
|
-
def get_candidate_relations_to_delete(
|
93
|
+
def get_candidate_relations_to_delete(meta: Meta) -> Generator[Any, None, None]:
|
86
94
|
# The candidate relations are the ones that come from N-1 and 1-1 relations.
|
87
95
|
# N-N (i.e., many-to-many) relations aren't candidates for deletion.
|
88
96
|
return (
|
89
97
|
f
|
90
|
-
for f in
|
98
|
+
for f in meta.get_fields(include_hidden=True)
|
91
99
|
if f.auto_created and not f.concrete and f.one_to_many
|
92
100
|
)
|
93
101
|
|
94
102
|
|
95
103
|
class Collector:
|
96
|
-
def __init__(self, origin=None):
|
104
|
+
def __init__(self, origin: Any = None) -> None:
|
97
105
|
# A Model or QuerySet object.
|
98
106
|
self.origin = origin
|
99
107
|
# Initially, {model: {instances}}, later values become lists.
|
100
|
-
self.data = defaultdict(set)
|
108
|
+
self.data: defaultdict[Any, Any] = defaultdict(set)
|
101
109
|
# {(field, value): [instances, …]}
|
102
|
-
self.field_updates = defaultdict(
|
110
|
+
self.field_updates: defaultdict[tuple[Field, Any], list[Any]] = defaultdict(
|
111
|
+
list
|
112
|
+
)
|
103
113
|
# {model: {field: {instances}}}
|
104
|
-
self.restricted_objects = defaultdict(
|
114
|
+
self.restricted_objects: defaultdict[Any, Any] = defaultdict(
|
115
|
+
partial(defaultdict, set)
|
116
|
+
)
|
105
117
|
# fast_deletes is a list of queryset-likes that can be deleted without
|
106
118
|
# fetching the objects into memory.
|
107
|
-
self.fast_deletes = []
|
119
|
+
self.fast_deletes: list[Any] = []
|
108
120
|
|
109
121
|
# Tracks deletion-order dependency for databases without transactions
|
110
122
|
# or ability to defer constraint checks. Only concrete model classes
|
111
123
|
# should be included, as the dependencies exist only between actual
|
112
124
|
# database tables.
|
113
|
-
self.dependencies = defaultdict(
|
125
|
+
self.dependencies: defaultdict[Any, set[Any]] = defaultdict(
|
126
|
+
set
|
127
|
+
) # {model: {models}}
|
114
128
|
|
115
|
-
def add(
|
129
|
+
def add(
|
130
|
+
self,
|
131
|
+
objs: Iterable[Any],
|
132
|
+
source: Any = None,
|
133
|
+
nullable: bool = False,
|
134
|
+
reverse_dependency: bool = False,
|
135
|
+
) -> list[Any]:
|
116
136
|
"""
|
117
137
|
Add 'objs' to the collection of objects to be deleted. If the call is
|
118
138
|
the result of a cascade, 'source' should be the model that caused it,
|
@@ -136,32 +156,34 @@ class Collector:
|
|
136
156
|
self.add_dependency(source, model, reverse_dependency=reverse_dependency)
|
137
157
|
return new_objs
|
138
158
|
|
139
|
-
def add_dependency(
|
159
|
+
def add_dependency(
|
160
|
+
self, model: Any, dependency: Any, reverse_dependency: bool = False
|
161
|
+
) -> None:
|
140
162
|
if reverse_dependency:
|
141
163
|
model, dependency = dependency, model
|
142
164
|
self.dependencies[model].add(dependency)
|
143
165
|
self.data.setdefault(dependency, self.data.default_factory())
|
144
166
|
|
145
|
-
def add_field_update(self, field, value, objs):
|
167
|
+
def add_field_update(self, field: Field, value: Any, objs: Iterable[Any]) -> None:
|
146
168
|
"""
|
147
169
|
Schedule a field update. 'objs' must be a homogeneous iterable
|
148
170
|
collection of model instances (e.g. a QuerySet).
|
149
171
|
"""
|
150
172
|
self.field_updates[field, value].append(objs)
|
151
173
|
|
152
|
-
def add_restricted_objects(self, field, objs):
|
174
|
+
def add_restricted_objects(self, field: Field, objs: Iterable[Any]) -> None:
|
153
175
|
if objs:
|
154
176
|
model = objs[0].__class__
|
155
177
|
self.restricted_objects[model][field].update(objs)
|
156
178
|
|
157
|
-
def clear_restricted_objects_from_set(self, model, objs):
|
179
|
+
def clear_restricted_objects_from_set(self, model: Any, objs: set[Any]) -> None:
|
158
180
|
if model in self.restricted_objects:
|
159
181
|
self.restricted_objects[model] = {
|
160
182
|
field: items - objs
|
161
183
|
for field, items in self.restricted_objects[model].items()
|
162
184
|
}
|
163
185
|
|
164
|
-
def clear_restricted_objects_from_queryset(self, model, qs):
|
186
|
+
def clear_restricted_objects_from_queryset(self, model: Any, qs: QuerySet) -> None:
|
165
187
|
if model in self.restricted_objects:
|
166
188
|
objs = set(
|
167
189
|
qs.filter(
|
@@ -174,7 +196,7 @@ class Collector:
|
|
174
196
|
)
|
175
197
|
self.clear_restricted_objects_from_set(model, objs)
|
176
198
|
|
177
|
-
def can_fast_delete(self, objs, from_field=None):
|
199
|
+
def can_fast_delete(self, objs: Any, from_field: Any = None) -> bool:
|
178
200
|
"""
|
179
201
|
Determine if the objects in the given queryset-like or single object
|
180
202
|
can be fast-deleted. This can be done if there are no cascades, no
|
@@ -187,8 +209,8 @@ class Collector:
|
|
187
209
|
"""
|
188
210
|
if from_field and from_field.remote_field.on_delete is not CASCADE:
|
189
211
|
return False
|
190
|
-
if hasattr(objs, "
|
191
|
-
model = objs.
|
212
|
+
if hasattr(objs, "_model_meta"):
|
213
|
+
model = objs._model_meta.model
|
192
214
|
elif hasattr(objs, "model") and hasattr(objs, "_raw_delete"):
|
193
215
|
model = objs.model
|
194
216
|
else:
|
@@ -196,16 +218,16 @@ class Collector:
|
|
196
218
|
|
197
219
|
# The use of from_field comes from the need to avoid cascade back to
|
198
220
|
# parent when parent delete is cascading to child.
|
199
|
-
|
221
|
+
meta = model._model_meta
|
200
222
|
return (
|
201
223
|
# Foreign keys pointing to this model.
|
202
224
|
all(
|
203
225
|
related.field.remote_field.on_delete is DO_NOTHING
|
204
|
-
for related in get_candidate_relations_to_delete(
|
226
|
+
for related in get_candidate_relations_to_delete(meta)
|
205
227
|
)
|
206
228
|
)
|
207
229
|
|
208
|
-
def get_del_batches(self, objs, fields):
|
230
|
+
def get_del_batches(self, objs: list[Any], fields: list[Field]) -> list[list[Any]]:
|
209
231
|
"""
|
210
232
|
Return the objs in suitably sized batches for the used db_connection.
|
211
233
|
"""
|
@@ -224,13 +246,13 @@ class Collector:
|
|
224
246
|
|
225
247
|
def collect(
|
226
248
|
self,
|
227
|
-
objs,
|
228
|
-
source=None,
|
229
|
-
nullable=False,
|
230
|
-
collect_related=True,
|
231
|
-
reverse_dependency=False,
|
232
|
-
fail_on_restricted=True,
|
233
|
-
):
|
249
|
+
objs: Iterable[Any],
|
250
|
+
source: Any = None,
|
251
|
+
nullable: bool = False,
|
252
|
+
collect_related: bool = True,
|
253
|
+
reverse_dependency: bool = False,
|
254
|
+
fail_on_restricted: bool = True,
|
255
|
+
) -> None:
|
234
256
|
"""
|
235
257
|
Add 'objs' to the collection of objects to be deleted as well as all
|
236
258
|
parent instances. 'objs' must be a homogeneous iterable collection of
|
@@ -268,7 +290,7 @@ class Collector:
|
|
268
290
|
|
269
291
|
model_fast_deletes = defaultdict(list)
|
270
292
|
protected_objects = defaultdict(list)
|
271
|
-
for related in get_candidate_relations_to_delete(model.
|
293
|
+
for related in get_candidate_relations_to_delete(model._model_meta):
|
272
294
|
field = related.field
|
273
295
|
on_delete = field.remote_field.on_delete
|
274
296
|
if on_delete == DO_NOTHING:
|
@@ -291,7 +313,7 @@ class Collector:
|
|
291
313
|
chain.from_iterable(
|
292
314
|
(rf.attname for rf in rel.field.foreign_related_fields)
|
293
315
|
for rel in get_candidate_relations_to_delete(
|
294
|
-
related_model.
|
316
|
+
related_model._model_meta
|
295
317
|
)
|
296
318
|
)
|
297
319
|
)
|
@@ -342,7 +364,9 @@ class Collector:
|
|
342
364
|
set(chain.from_iterable(restricted_objects.values())),
|
343
365
|
)
|
344
366
|
|
345
|
-
def related_objects(
|
367
|
+
def related_objects(
|
368
|
+
self, related_model: Any, related_fields: list[Field], objs: Iterable[Any]
|
369
|
+
) -> QuerySet:
|
346
370
|
"""
|
347
371
|
Get a QuerySet of the related model to objs via related fields.
|
348
372
|
"""
|
@@ -350,9 +374,9 @@ class Collector:
|
|
350
374
|
[(f"{related_field.name}__in", objs) for related_field in related_fields],
|
351
375
|
connector=query_utils.Q.OR,
|
352
376
|
)
|
353
|
-
return related_model.
|
377
|
+
return related_model._model_meta.base_queryset.filter(predicate)
|
354
378
|
|
355
|
-
def sort(self):
|
379
|
+
def sort(self) -> None:
|
356
380
|
sorted_models = []
|
357
381
|
concrete_models = set()
|
358
382
|
models = list(self.data)
|
@@ -370,7 +394,7 @@ class Collector:
|
|
370
394
|
return
|
371
395
|
self.data = {model: self.data[model] for model in sorted_models}
|
372
396
|
|
373
|
-
def delete(self):
|
397
|
+
def delete(self) -> tuple[int, dict[str, int]]:
|
374
398
|
# sort instance collections
|
375
399
|
for model, instances in self.data.items():
|
376
400
|
self.data[model] = sorted(instances, key=attrgetter("id"))
|
@@ -388,15 +412,15 @@ class Collector:
|
|
388
412
|
if self.can_fast_delete(instance):
|
389
413
|
with transaction.mark_for_rollback_on_error():
|
390
414
|
count = sql.DeleteQuery(model).delete_batch([instance.id])
|
391
|
-
setattr(instance, model.
|
392
|
-
return count, {model.
|
415
|
+
setattr(instance, model._model_meta.get_field("id").attname, None)
|
416
|
+
return count, {model.model_options.label: count}
|
393
417
|
|
394
418
|
with transaction.atomic(savepoint=False):
|
395
419
|
# fast deletes
|
396
420
|
for qs in self.fast_deletes:
|
397
421
|
count = qs._raw_delete()
|
398
422
|
if count:
|
399
|
-
deleted_counter[qs.model.
|
423
|
+
deleted_counter[qs.model.model_options.label] += count
|
400
424
|
|
401
425
|
# update fields
|
402
426
|
for (field, value), instances_list in self.field_updates.items():
|
@@ -430,9 +454,9 @@ class Collector:
|
|
430
454
|
id_list = [obj.id for obj in instances]
|
431
455
|
count = query.delete_batch(id_list)
|
432
456
|
if count:
|
433
|
-
deleted_counter[model.
|
457
|
+
deleted_counter[model.model_options.label] += count
|
434
458
|
|
435
459
|
for model, instances in self.data.items():
|
436
460
|
for instance in instances:
|
437
|
-
setattr(instance, model.
|
461
|
+
setattr(instance, model._model_meta.get_field("id").attname, None)
|
438
462
|
return sum(deleted_counter.values()), dict(deleted_counter)
|
plain/models/entrypoints.py
CHANGED
plain/models/enums.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import enum
|
2
4
|
from types import DynamicClassAttribute
|
5
|
+
from typing import Any
|
3
6
|
|
4
7
|
from plain.utils.functional import Promise
|
5
8
|
|
@@ -9,7 +12,13 @@ __all__ = ["Choices", "IntegerChoices", "TextChoices"]
|
|
9
12
|
class ChoicesMeta(enum.EnumMeta):
|
10
13
|
"""A metaclass for creating a enum choices."""
|
11
14
|
|
12
|
-
def __new__(
|
15
|
+
def __new__(
|
16
|
+
metacls: type,
|
17
|
+
classname: str,
|
18
|
+
bases: tuple[type, ...],
|
19
|
+
classdict: Any,
|
20
|
+
**kwds: Any,
|
21
|
+
) -> type:
|
13
22
|
labels = []
|
14
23
|
for key in classdict._member_names:
|
15
24
|
value = classdict[key]
|
@@ -26,33 +35,33 @@ class ChoicesMeta(enum.EnumMeta):
|
|
26
35
|
# Use dict.__setitem__() to suppress defenses against double
|
27
36
|
# assignment in enum's classdict.
|
28
37
|
dict.__setitem__(classdict, key, value)
|
29
|
-
cls = super().__new__(metacls, classname, bases, classdict, **kwds)
|
38
|
+
cls = super().__new__(metacls, classname, bases, classdict, **kwds) # type: ignore[misc]
|
30
39
|
for member, label in zip(cls.__members__.values(), labels):
|
31
40
|
member._label_ = label
|
32
41
|
return enum.unique(cls)
|
33
42
|
|
34
|
-
def __contains__(cls, member):
|
43
|
+
def __contains__(cls, member: object) -> bool:
|
35
44
|
if not isinstance(member, enum.Enum):
|
36
45
|
# Allow non-enums to match against member values.
|
37
46
|
return any(x.value == member for x in cls)
|
38
47
|
return super().__contains__(member)
|
39
48
|
|
40
49
|
@property
|
41
|
-
def names(cls):
|
50
|
+
def names(cls) -> list[str]:
|
42
51
|
empty = ["__empty__"] if hasattr(cls, "__empty__") else []
|
43
52
|
return empty + [member.name for member in cls]
|
44
53
|
|
45
54
|
@property
|
46
|
-
def choices(cls):
|
55
|
+
def choices(cls) -> list[tuple[Any, str]]:
|
47
56
|
empty = [(None, cls.__empty__)] if hasattr(cls, "__empty__") else []
|
48
57
|
return empty + [(member.value, member.label) for member in cls]
|
49
58
|
|
50
59
|
@property
|
51
|
-
def labels(cls):
|
60
|
+
def labels(cls) -> list[str]:
|
52
61
|
return [label for _, label in cls.choices]
|
53
62
|
|
54
63
|
@property
|
55
|
-
def values(cls):
|
64
|
+
def values(cls) -> list[Any]:
|
56
65
|
return [value for value, _ in cls.choices]
|
57
66
|
|
58
67
|
|
@@ -60,10 +69,10 @@ class Choices(enum.Enum, metaclass=ChoicesMeta):
|
|
60
69
|
"""Class for creating enumerated choices."""
|
61
70
|
|
62
71
|
@DynamicClassAttribute
|
63
|
-
def label(self):
|
72
|
+
def label(self) -> str:
|
64
73
|
return self._label_
|
65
74
|
|
66
|
-
def __str__(self):
|
75
|
+
def __str__(self) -> str:
|
67
76
|
"""
|
68
77
|
Use value when cast to str, so that Choices set as model instance
|
69
78
|
attributes are rendered as expected in templates and similar contexts.
|
@@ -71,7 +80,7 @@ class Choices(enum.Enum, metaclass=ChoicesMeta):
|
|
71
80
|
return str(self.value)
|
72
81
|
|
73
82
|
# A similar format was proposed for Python 3.10.
|
74
|
-
def __repr__(self):
|
83
|
+
def __repr__(self) -> str:
|
75
84
|
return f"{self.__class__.__qualname__}.{self._name_}"
|
76
85
|
|
77
86
|
|
@@ -84,5 +93,7 @@ class IntegerChoices(int, Choices):
|
|
84
93
|
class TextChoices(str, Choices):
|
85
94
|
"""Class for creating enumerated string choices."""
|
86
95
|
|
87
|
-
def _generate_next_value_(
|
96
|
+
def _generate_next_value_(
|
97
|
+
name: str, start: int, count: int, last_values: list[str]
|
98
|
+
) -> str:
|
88
99
|
return name
|
plain/models/exceptions.py
CHANGED
@@ -1,4 +1,12 @@
|
|
1
|
-
from
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from collections.abc import Callable
|
4
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from plain.models.backends.base.base import BaseDatabaseWrapper
|
8
|
+
|
9
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
2
10
|
|
3
11
|
# MARK: Database Query Exceptions
|
4
12
|
|
@@ -152,7 +160,7 @@ class DatabaseErrorWrapper:
|
|
152
160
|
exceptions using Plain's common wrappers.
|
153
161
|
"""
|
154
162
|
|
155
|
-
def __init__(self, wrapper):
|
163
|
+
def __init__(self, wrapper: BaseDatabaseWrapper) -> None:
|
156
164
|
"""
|
157
165
|
wrapper is a database wrapper.
|
158
166
|
|
@@ -160,10 +168,15 @@ class DatabaseErrorWrapper:
|
|
160
168
|
"""
|
161
169
|
self.wrapper = wrapper
|
162
170
|
|
163
|
-
def __enter__(self):
|
171
|
+
def __enter__(self) -> None:
|
164
172
|
pass
|
165
173
|
|
166
|
-
def __exit__(
|
174
|
+
def __exit__(
|
175
|
+
self,
|
176
|
+
exc_type: type[BaseException] | None,
|
177
|
+
exc_value: BaseException | None,
|
178
|
+
traceback: Any,
|
179
|
+
) -> None:
|
167
180
|
if exc_type is None:
|
168
181
|
return
|
169
182
|
for plain_exc_type in (
|
@@ -179,18 +192,20 @@ class DatabaseErrorWrapper:
|
|
179
192
|
):
|
180
193
|
db_exc_type = getattr(self.wrapper.Database, plain_exc_type.__name__)
|
181
194
|
if issubclass(exc_type, db_exc_type):
|
182
|
-
plain_exc_value =
|
195
|
+
plain_exc_value = (
|
196
|
+
plain_exc_type(*exc_value.args) if exc_value else plain_exc_type()
|
197
|
+
)
|
183
198
|
# Only set the 'errors_occurred' flag for errors that may make
|
184
199
|
# the connection unusable.
|
185
200
|
if plain_exc_type not in (DataError, IntegrityError):
|
186
201
|
self.wrapper.errors_occurred = True
|
187
202
|
raise plain_exc_value.with_traceback(traceback) from exc_value
|
188
203
|
|
189
|
-
def __call__(self, func):
|
204
|
+
def __call__(self, func: F) -> F:
|
190
205
|
# Note that we are intentionally not using @wraps here for performance
|
191
206
|
# reasons. Refs #21109.
|
192
|
-
def inner(*args, **kwargs):
|
207
|
+
def inner(*args: Any, **kwargs: Any) -> Any:
|
193
208
|
with self:
|
194
209
|
return func(*args, **kwargs)
|
195
210
|
|
196
|
-
return inner
|
211
|
+
return inner # type: ignore[return-value]
|