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
plain/models/migrations/utils.py
CHANGED
@@ -1,30 +1,39 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import datetime
|
2
4
|
import re
|
3
5
|
from collections import namedtuple
|
6
|
+
from collections.abc import Generator
|
7
|
+
from typing import TYPE_CHECKING, Any
|
4
8
|
|
5
9
|
from plain.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT
|
6
10
|
|
11
|
+
if TYPE_CHECKING:
|
12
|
+
from plain.models.fields import Field
|
13
|
+
|
7
14
|
FieldReference = namedtuple("FieldReference", "to through")
|
8
15
|
|
9
16
|
COMPILED_REGEX_TYPE = type(re.compile(""))
|
10
17
|
|
11
18
|
|
12
19
|
class RegexObject:
|
13
|
-
def __init__(self, obj):
|
20
|
+
def __init__(self, obj: Any) -> None:
|
14
21
|
self.pattern = obj.pattern
|
15
22
|
self.flags = obj.flags
|
16
23
|
|
17
|
-
def __eq__(self, other):
|
24
|
+
def __eq__(self, other: Any) -> bool:
|
18
25
|
if not isinstance(other, RegexObject):
|
19
26
|
return NotImplemented
|
20
27
|
return self.pattern == other.pattern and self.flags == other.flags
|
21
28
|
|
22
29
|
|
23
|
-
def get_migration_name_timestamp():
|
30
|
+
def get_migration_name_timestamp() -> str:
|
24
31
|
return datetime.datetime.now().strftime("%Y%m%d_%H%M")
|
25
32
|
|
26
33
|
|
27
|
-
def resolve_relation(
|
34
|
+
def resolve_relation(
|
35
|
+
model: str | Any, package_label: str | None = None, model_name: str | None = None
|
36
|
+
) -> tuple[str, str]:
|
28
37
|
"""
|
29
38
|
Turn a model class or model reference string and return a model tuple.
|
30
39
|
|
@@ -51,12 +60,12 @@ def resolve_relation(model, package_label=None, model_name=None):
|
|
51
60
|
|
52
61
|
|
53
62
|
def field_references(
|
54
|
-
model_tuple,
|
55
|
-
field,
|
56
|
-
reference_model_tuple,
|
57
|
-
reference_field_name=None,
|
58
|
-
reference_field=None,
|
59
|
-
):
|
63
|
+
model_tuple: tuple[str, str],
|
64
|
+
field: Field,
|
65
|
+
reference_model_tuple: tuple[str, str],
|
66
|
+
reference_field_name: str | None = None,
|
67
|
+
reference_field: Field | None = None,
|
68
|
+
) -> FieldReference | bool:
|
60
69
|
"""
|
61
70
|
Return either False or a FieldReference if `field` references provided
|
62
71
|
context.
|
@@ -97,7 +106,9 @@ def field_references(
|
|
97
106
|
return FieldReference(references_to, references_through)
|
98
107
|
|
99
108
|
|
100
|
-
def get_references(
|
109
|
+
def get_references(
|
110
|
+
state: Any, model_tuple: tuple[str, str], field_tuple: tuple[Any, ...] = ()
|
111
|
+
) -> Generator[tuple[Any, str, Field, FieldReference], None, None]:
|
101
112
|
"""
|
102
113
|
Generator of (model_state, name, field, reference) referencing
|
103
114
|
provided context.
|
@@ -108,12 +119,17 @@ def get_references(state, model_tuple, field_tuple=()):
|
|
108
119
|
for state_model_tuple, model_state in state.models.items():
|
109
120
|
for name, field in model_state.fields.items():
|
110
121
|
reference = field_references(
|
111
|
-
state_model_tuple,
|
122
|
+
state_model_tuple,
|
123
|
+
field,
|
124
|
+
model_tuple,
|
125
|
+
*field_tuple, # type: ignore[arg-type]
|
112
126
|
)
|
113
127
|
if reference:
|
114
128
|
yield model_state, name, field, reference
|
115
129
|
|
116
130
|
|
117
|
-
def field_is_referenced(
|
131
|
+
def field_is_referenced(
|
132
|
+
state: Any, model_tuple: tuple[str, str], field_tuple: tuple[Any, ...]
|
133
|
+
) -> bool:
|
118
134
|
"""Return whether `field_tuple` is referenced by any state models."""
|
119
135
|
return next(get_references(state, model_tuple, field_tuple), None) is not None
|
@@ -1,6 +1,9 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import os
|
2
4
|
import re
|
3
5
|
from importlib import import_module
|
6
|
+
from typing import Any
|
4
7
|
|
5
8
|
from plain.models import migrations
|
6
9
|
from plain.models.migrations.loader import MigrationLoader
|
@@ -14,13 +17,13 @@ from plain.utils.timezone import now
|
|
14
17
|
|
15
18
|
|
16
19
|
class OperationWriter:
|
17
|
-
def __init__(self, operation, indentation=2):
|
20
|
+
def __init__(self, operation: Any, indentation: int = 2) -> None:
|
18
21
|
self.operation = operation
|
19
|
-
self.buff = []
|
22
|
+
self.buff: list[str] = []
|
20
23
|
self.indentation = indentation
|
21
24
|
|
22
|
-
def serialize(self):
|
23
|
-
def _write(_arg_name, _arg_value):
|
25
|
+
def serialize(self) -> tuple[str, set[str]]:
|
26
|
+
def _write(_arg_name: str, _arg_value: Any) -> None:
|
24
27
|
if _arg_name in self.operation.serialization_expand_args and isinstance(
|
25
28
|
_arg_value, list | tuple | dict
|
26
29
|
):
|
@@ -100,16 +103,16 @@ class OperationWriter:
|
|
100
103
|
self.feed("),")
|
101
104
|
return self.render(), imports
|
102
105
|
|
103
|
-
def indent(self):
|
106
|
+
def indent(self) -> None:
|
104
107
|
self.indentation += 1
|
105
108
|
|
106
|
-
def unindent(self):
|
109
|
+
def unindent(self) -> None:
|
107
110
|
self.indentation -= 1
|
108
111
|
|
109
|
-
def feed(self, line):
|
112
|
+
def feed(self, line: str) -> None:
|
110
113
|
self.buff.append(" " * (self.indentation * 4) + line)
|
111
114
|
|
112
|
-
def render(self):
|
115
|
+
def render(self) -> str:
|
113
116
|
return "\n".join(self.buff)
|
114
117
|
|
115
118
|
|
@@ -119,12 +122,12 @@ class MigrationWriter:
|
|
119
122
|
of the migration file from it.
|
120
123
|
"""
|
121
124
|
|
122
|
-
def __init__(self, migration, include_header=True):
|
125
|
+
def __init__(self, migration: Any, include_header: bool = True) -> None:
|
123
126
|
self.migration = migration
|
124
127
|
self.include_header = include_header
|
125
128
|
self.needs_manual_porting = False
|
126
129
|
|
127
|
-
def as_string(self):
|
130
|
+
def as_string(self) -> str:
|
128
131
|
"""Return a string of the file contents."""
|
129
132
|
items = {
|
130
133
|
"replaces_str": "",
|
@@ -198,7 +201,7 @@ class MigrationWriter:
|
|
198
201
|
return MIGRATION_TEMPLATE % items
|
199
202
|
|
200
203
|
@property
|
201
|
-
def basedir(self):
|
204
|
+
def basedir(self) -> str:
|
202
205
|
migrations_package_name, _ = MigrationLoader.migrations_module(
|
203
206
|
self.migration.package_label
|
204
207
|
)
|
@@ -266,15 +269,15 @@ class MigrationWriter:
|
|
266
269
|
return final_dir
|
267
270
|
|
268
271
|
@property
|
269
|
-
def filename(self):
|
272
|
+
def filename(self) -> str:
|
270
273
|
return f"{self.migration.name}.py"
|
271
274
|
|
272
275
|
@property
|
273
|
-
def path(self):
|
276
|
+
def path(self) -> str:
|
274
277
|
return os.path.join(self.basedir, self.filename)
|
275
278
|
|
276
279
|
@classmethod
|
277
|
-
def serialize(cls, value):
|
280
|
+
def serialize(cls, value: Any) -> tuple[str, set[str]]:
|
278
281
|
return serializer_factory(value).serialize()
|
279
282
|
|
280
283
|
|
plain/models/options.py
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import bisect
|
2
4
|
import inspect
|
3
5
|
from collections import defaultdict
|
4
6
|
from functools import cached_property
|
7
|
+
from typing import TYPE_CHECKING, Any
|
5
8
|
|
6
9
|
from plain.models import models_registry
|
7
10
|
from plain.models.constraints import UniqueConstraint
|
@@ -10,6 +13,10 @@ from plain.models.exceptions import FieldDoesNotExist
|
|
10
13
|
from plain.models.query import QuerySet
|
11
14
|
from plain.utils.datastructures import ImmutableList
|
12
15
|
|
16
|
+
if TYPE_CHECKING:
|
17
|
+
from plain.models.backends.base.base import BaseDatabaseWrapper
|
18
|
+
from plain.models.fields import Field
|
19
|
+
|
13
20
|
PROXY_PARENTS = object()
|
14
21
|
|
15
22
|
EMPTY_RELATION_TREE = ()
|
@@ -33,7 +40,7 @@ DEFAULT_NAMES = (
|
|
33
40
|
)
|
34
41
|
|
35
42
|
|
36
|
-
def make_immutable_fields_list(name, data):
|
43
|
+
def make_immutable_fields_list(name: str, data: Any) -> ImmutableList:
|
37
44
|
return ImmutableList(data, warning=IMMUTABLE_WARNING % name)
|
38
45
|
|
39
46
|
|
@@ -52,43 +59,43 @@ class Options:
|
|
52
59
|
|
53
60
|
default_models_registry = models_registry
|
54
61
|
|
55
|
-
def __init__(self, meta, package_label=None):
|
56
|
-
self._get_fields_cache = {}
|
57
|
-
self.local_fields = []
|
58
|
-
self.local_many_to_many = []
|
59
|
-
self.queryset_class = None
|
60
|
-
self.model_name = None
|
61
|
-
self.db_table = ""
|
62
|
-
self.db_table_comment = ""
|
63
|
-
self.ordering = []
|
64
|
-
self.indexes = []
|
65
|
-
self.constraints = []
|
66
|
-
self.object_name = None
|
67
|
-
self.package_label = package_label
|
68
|
-
self.required_db_features = []
|
69
|
-
self.required_db_vendor = None
|
70
|
-
self.meta = meta
|
62
|
+
def __init__(self, meta: Any, package_label: str | None = None):
|
63
|
+
self._get_fields_cache: dict[tuple[bool, bool, bool, bool], Any] = {}
|
64
|
+
self.local_fields: list[Field] = []
|
65
|
+
self.local_many_to_many: list[Field] = []
|
66
|
+
self.queryset_class: type[QuerySet] | None = None
|
67
|
+
self.model_name: str | None = None
|
68
|
+
self.db_table: str = ""
|
69
|
+
self.db_table_comment: str = ""
|
70
|
+
self.ordering: list[Any] = []
|
71
|
+
self.indexes: list[Any] = []
|
72
|
+
self.constraints: list[Any] = []
|
73
|
+
self.object_name: str | None = None
|
74
|
+
self.package_label: str | None = package_label
|
75
|
+
self.required_db_features: list[str] = []
|
76
|
+
self.required_db_vendor: str | None = None
|
77
|
+
self.meta: Any = meta
|
71
78
|
|
72
79
|
# List of all lookups defined in ForeignKey 'limit_choices_to' options
|
73
80
|
# from *other* models. Needed for some admin checks. Internal use only.
|
74
|
-
self.related_fkey_lookups = []
|
81
|
+
self.related_fkey_lookups: list[Any] = []
|
75
82
|
|
76
83
|
# A custom app registry to use, if you're making a separate model set.
|
77
|
-
self.models_registry = self.default_models_registry
|
84
|
+
self.models_registry: Any = self.default_models_registry
|
78
85
|
|
79
86
|
@property
|
80
|
-
def label(self):
|
87
|
+
def label(self) -> str:
|
81
88
|
return f"{self.package_label}.{self.object_name}"
|
82
89
|
|
83
90
|
@property
|
84
|
-
def label_lower(self):
|
91
|
+
def label_lower(self) -> str:
|
85
92
|
return f"{self.package_label}.{self.model_name}"
|
86
93
|
|
87
|
-
def contribute_to_class(self, cls, name):
|
94
|
+
def contribute_to_class(self, cls: type[Any], name: str) -> None:
|
88
95
|
from plain.models.backends.utils import truncate_name
|
89
96
|
|
90
97
|
cls._meta = self
|
91
|
-
self.model = cls
|
98
|
+
self.model: type[Any] = cls
|
92
99
|
# First, construct the default values for these options.
|
93
100
|
self.object_name = cls.__name__
|
94
101
|
self.model_name = self.object_name.lower()
|
@@ -138,7 +145,7 @@ class Options:
|
|
138
145
|
db_connection.ops.max_name_length(),
|
139
146
|
)
|
140
147
|
|
141
|
-
def _format_names_with_class(self, cls, objs):
|
148
|
+
def _format_names_with_class(self, cls: type[Any], objs: list[Any]) -> list[Any]:
|
142
149
|
"""Package label/class name interpolation for object names."""
|
143
150
|
new_objs = []
|
144
151
|
for obj in objs:
|
@@ -150,7 +157,7 @@ class Options:
|
|
150
157
|
new_objs.append(obj)
|
151
158
|
return new_objs
|
152
159
|
|
153
|
-
def add_field(self, field):
|
160
|
+
def add_field(self, field: Field) -> None:
|
154
161
|
# Insert the given field in the order in which it was created, using
|
155
162
|
# the "creation_counter" attribute of the field.
|
156
163
|
# Move many-to-many related fields from self.fields into
|
@@ -181,13 +188,13 @@ class Options:
|
|
181
188
|
else:
|
182
189
|
self._expire_cache(reverse=False)
|
183
190
|
|
184
|
-
def __repr__(self):
|
191
|
+
def __repr__(self) -> str:
|
185
192
|
return f"<Options for {self.object_name}>"
|
186
193
|
|
187
|
-
def __str__(self):
|
194
|
+
def __str__(self) -> str:
|
188
195
|
return self.label_lower
|
189
196
|
|
190
|
-
def can_migrate(self, connection):
|
197
|
+
def can_migrate(self, connection: BaseDatabaseWrapper) -> bool:
|
191
198
|
"""
|
192
199
|
Return True if the model can/should be migrated on the given
|
193
200
|
`connection` object.
|
@@ -202,7 +209,7 @@ class Options:
|
|
202
209
|
return True
|
203
210
|
|
204
211
|
@property
|
205
|
-
def base_queryset(self):
|
212
|
+
def base_queryset(self) -> QuerySet:
|
206
213
|
"""
|
207
214
|
The base queryset is used by Plain's internal operations like cascading
|
208
215
|
deletes, migrations, and related object lookups. It provides access to
|
@@ -216,13 +223,13 @@ class Options:
|
|
216
223
|
return QuerySet(model=self.model)
|
217
224
|
|
218
225
|
@property
|
219
|
-
def queryset(self):
|
226
|
+
def queryset(self) -> QuerySet:
|
220
227
|
if self.queryset_class:
|
221
228
|
return self.queryset_class(model=self.model)
|
222
229
|
return QuerySet(model=self.model)
|
223
230
|
|
224
231
|
@cached_property
|
225
|
-
def fields(self):
|
232
|
+
def fields(self) -> ImmutableList:
|
226
233
|
"""
|
227
234
|
Return a list of all forward fields on the model and its parents,
|
228
235
|
excluding ManyToManyFields.
|
@@ -239,13 +246,13 @@ class Options:
|
|
239
246
|
# use that property directly because related_model is a cached property,
|
240
247
|
# and all the models may not have been loaded yet; we don't want to cache
|
241
248
|
# the string reference to the related_model.
|
242
|
-
def is_not_an_m2m_field(f):
|
249
|
+
def is_not_an_m2m_field(f: Any) -> bool:
|
243
250
|
return not (f.is_relation and f.many_to_many)
|
244
251
|
|
245
|
-
def is_not_a_generic_relation(f):
|
252
|
+
def is_not_a_generic_relation(f: Any) -> bool:
|
246
253
|
return not (f.is_relation and f.one_to_many)
|
247
254
|
|
248
|
-
def is_not_a_generic_foreign_key(f):
|
255
|
+
def is_not_a_generic_foreign_key(f: Any) -> bool:
|
249
256
|
return not (
|
250
257
|
f.is_relation
|
251
258
|
and f.many_to_one
|
@@ -264,7 +271,7 @@ class Options:
|
|
264
271
|
)
|
265
272
|
|
266
273
|
@cached_property
|
267
|
-
def concrete_fields(self):
|
274
|
+
def concrete_fields(self) -> ImmutableList:
|
268
275
|
"""
|
269
276
|
Return a list of all concrete fields on the model and its parents.
|
270
277
|
|
@@ -277,7 +284,7 @@ class Options:
|
|
277
284
|
)
|
278
285
|
|
279
286
|
@cached_property
|
280
|
-
def local_concrete_fields(self):
|
287
|
+
def local_concrete_fields(self) -> ImmutableList:
|
281
288
|
"""
|
282
289
|
Return a list of all concrete fields on the model.
|
283
290
|
|
@@ -290,7 +297,7 @@ class Options:
|
|
290
297
|
)
|
291
298
|
|
292
299
|
@cached_property
|
293
|
-
def many_to_many(self):
|
300
|
+
def many_to_many(self) -> ImmutableList:
|
294
301
|
"""
|
295
302
|
Return a list of all many to many fields on the model and its parents.
|
296
303
|
|
@@ -308,7 +315,7 @@ class Options:
|
|
308
315
|
)
|
309
316
|
|
310
317
|
@cached_property
|
311
|
-
def related_objects(self):
|
318
|
+
def related_objects(self) -> ImmutableList:
|
312
319
|
"""
|
313
320
|
Return all related objects pointing to the current model. The related
|
314
321
|
objects can come from a one-to-one, one-to-many, or many-to-many field
|
@@ -331,7 +338,7 @@ class Options:
|
|
331
338
|
)
|
332
339
|
|
333
340
|
@cached_property
|
334
|
-
def _forward_fields_map(self):
|
341
|
+
def _forward_fields_map(self) -> dict[str, Any]:
|
335
342
|
res = {}
|
336
343
|
fields = self._get_fields(reverse=False)
|
337
344
|
for field in fields:
|
@@ -346,7 +353,7 @@ class Options:
|
|
346
353
|
return res
|
347
354
|
|
348
355
|
@cached_property
|
349
|
-
def fields_map(self):
|
356
|
+
def fields_map(self) -> dict[str, Any]:
|
350
357
|
res = {}
|
351
358
|
fields = self._get_fields(forward=False, include_hidden=True)
|
352
359
|
for field in fields:
|
@@ -360,7 +367,7 @@ class Options:
|
|
360
367
|
pass
|
361
368
|
return res
|
362
369
|
|
363
|
-
def get_field(self, field_name):
|
370
|
+
def get_field(self, field_name: str) -> Any:
|
364
371
|
"""
|
365
372
|
Return a field instance given the name of a forward or reverse field.
|
366
373
|
"""
|
@@ -387,14 +394,14 @@ class Options:
|
|
387
394
|
f"{self.object_name} has no field named '{field_name}'"
|
388
395
|
)
|
389
396
|
|
390
|
-
def _populate_directed_relation_graph(self):
|
397
|
+
def _populate_directed_relation_graph(self) -> Any:
|
391
398
|
"""
|
392
399
|
This method is used by each model to find its reverse objects. As this
|
393
400
|
method is very expensive and is accessed frequently (it looks up every
|
394
401
|
field in a model, in every app), it is computed on first access and then
|
395
402
|
is set as a property on every model.
|
396
403
|
"""
|
397
|
-
related_objects_graph = defaultdict(list)
|
404
|
+
related_objects_graph: defaultdict[str, list[Any]] = defaultdict(list)
|
398
405
|
|
399
406
|
all_models = self.models_registry.get_models()
|
400
407
|
for model in all_models:
|
@@ -423,10 +430,10 @@ class Options:
|
|
423
430
|
return self.__dict__.get("_relation_tree", EMPTY_RELATION_TREE)
|
424
431
|
|
425
432
|
@cached_property
|
426
|
-
def _relation_tree(self):
|
433
|
+
def _relation_tree(self) -> Any:
|
427
434
|
return self._populate_directed_relation_graph()
|
428
435
|
|
429
|
-
def _expire_cache(self, forward=True, reverse=True):
|
436
|
+
def _expire_cache(self, forward: bool = True, reverse: bool = True) -> None:
|
430
437
|
# This method is usually called by packages.cache_clear(), when the
|
431
438
|
# registry is finalized, or when a new field is added.
|
432
439
|
if forward:
|
@@ -439,7 +446,7 @@ class Options:
|
|
439
446
|
delattr(self, cache_key)
|
440
447
|
self._get_fields_cache = {}
|
441
448
|
|
442
|
-
def get_fields(self, include_hidden=False):
|
449
|
+
def get_fields(self, include_hidden: bool = False) -> ImmutableList:
|
443
450
|
"""
|
444
451
|
Return a list of fields associated to the model. By default, include
|
445
452
|
forward and reverse fields, fields derived from inheritance, but not
|
@@ -452,11 +459,11 @@ class Options:
|
|
452
459
|
|
453
460
|
def _get_fields(
|
454
461
|
self,
|
455
|
-
forward=True,
|
456
|
-
reverse=True,
|
457
|
-
include_hidden=False,
|
458
|
-
seen_models=None,
|
459
|
-
):
|
462
|
+
forward: bool = True,
|
463
|
+
reverse: bool = True,
|
464
|
+
include_hidden: bool = False,
|
465
|
+
seen_models: set[type[Any]] | None = None,
|
466
|
+
) -> ImmutableList:
|
460
467
|
"""
|
461
468
|
Internal helper function to return fields of the model.
|
462
469
|
* If forward=True, then fields defined on this model are returned.
|
@@ -471,7 +478,7 @@ class Options:
|
|
471
478
|
# We must keep track of which models we have already seen. Otherwise we
|
472
479
|
# could include the same field multiple times from different models.
|
473
480
|
topmost_call = seen_models is None
|
474
|
-
if
|
481
|
+
if seen_models is None:
|
475
482
|
seen_models = set()
|
476
483
|
seen_models.add(self.model)
|
477
484
|
|
@@ -511,7 +518,7 @@ class Options:
|
|
511
518
|
return fields
|
512
519
|
|
513
520
|
@cached_property
|
514
|
-
def total_unique_constraints(self):
|
521
|
+
def total_unique_constraints(self) -> list[UniqueConstraint]:
|
515
522
|
"""
|
516
523
|
Return a list of total unique constraints. Useful for determining set
|
517
524
|
of fields guaranteed to be unique for all rows.
|
@@ -527,7 +534,7 @@ class Options:
|
|
527
534
|
]
|
528
535
|
|
529
536
|
@cached_property
|
530
|
-
def _property_names(self):
|
537
|
+
def _property_names(self) -> frozenset[str]:
|
531
538
|
"""Return a set of the names of the properties defined on the model."""
|
532
539
|
names = []
|
533
540
|
for name in dir(self.model):
|
@@ -537,7 +544,7 @@ class Options:
|
|
537
544
|
return frozenset(names)
|
538
545
|
|
539
546
|
@cached_property
|
540
|
-
def _non_pk_concrete_field_names(self):
|
547
|
+
def _non_pk_concrete_field_names(self) -> frozenset[str]:
|
541
548
|
"""
|
542
549
|
Return a set of the non-primary key concrete field names defined on the model.
|
543
550
|
"""
|
@@ -550,7 +557,7 @@ class Options:
|
|
550
557
|
return frozenset(names)
|
551
558
|
|
552
559
|
@cached_property
|
553
|
-
def db_returning_fields(self):
|
560
|
+
def db_returning_fields(self) -> list[Field]:
|
554
561
|
"""
|
555
562
|
Private API intended only to be used by Plain itself.
|
556
563
|
Fields to be returned after a database insert.
|
plain/models/otel.py
CHANGED
@@ -1,10 +1,18 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import re
|
2
4
|
import traceback
|
5
|
+
from collections.abc import Generator
|
3
6
|
from contextlib import contextmanager
|
4
|
-
from typing import Any
|
7
|
+
from typing import TYPE_CHECKING, Any
|
5
8
|
|
6
9
|
from opentelemetry import context as otel_context
|
7
10
|
from opentelemetry import trace
|
11
|
+
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from opentelemetry.trace import Span
|
14
|
+
|
15
|
+
from plain.models.backends.base.base import BaseDatabaseWrapper
|
8
16
|
from opentelemetry.semconv._incubating.attributes.db_attributes import (
|
9
17
|
DB_QUERY_PARAMETER_TEMPLATE,
|
10
18
|
DB_USER,
|
@@ -99,7 +107,9 @@ def _clean_identifier(identifier: str) -> str:
|
|
99
107
|
|
100
108
|
|
101
109
|
@contextmanager
|
102
|
-
def db_span(
|
110
|
+
def db_span(
|
111
|
+
db: BaseDatabaseWrapper, sql: Any, *, many: bool = False, params: Any = None
|
112
|
+
) -> Generator[Span | None, None, None]:
|
103
113
|
"""Open an OpenTelemetry CLIENT span for a database query.
|
104
114
|
|
105
115
|
All common attributes (`db.*`, `network.*`, etc.) are set automatically.
|
@@ -107,7 +117,7 @@ def db_span(db, sql: Any, *, many: bool = False, params=None):
|
|
107
117
|
"""
|
108
118
|
|
109
119
|
# Fast-exit if instrumentation suppression flag set in context.
|
110
|
-
if otel_context.get_value(_SUPPRESS_KEY):
|
120
|
+
if otel_context.get_value(_SUPPRESS_KEY): # type: ignore[arg-type]
|
111
121
|
yield None
|
112
122
|
return
|
113
123
|
|
@@ -177,15 +187,15 @@ def db_span(db, sql: Any, *, many: bool = False, params=None):
|
|
177
187
|
|
178
188
|
|
179
189
|
@contextmanager
|
180
|
-
def suppress_db_tracing():
|
181
|
-
token = otel_context.attach(otel_context.set_value(_SUPPRESS_KEY, True))
|
190
|
+
def suppress_db_tracing() -> Generator[None, None, None]:
|
191
|
+
token = otel_context.attach(otel_context.set_value(_SUPPRESS_KEY, True)) # type: ignore[arg-type]
|
182
192
|
try:
|
183
193
|
yield
|
184
194
|
finally:
|
185
195
|
otel_context.detach(token)
|
186
196
|
|
187
197
|
|
188
|
-
def _get_code_attributes():
|
198
|
+
def _get_code_attributes() -> dict[str, Any]:
|
189
199
|
"""Extract code context attributes for the current database query.
|
190
200
|
|
191
201
|
Returns a dict of OpenTelemetry code attributes.
|