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/functions/window.py
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Any
|
4
|
+
|
1
5
|
from plain.models.expressions import Func
|
2
|
-
from plain.models.fields import FloatField, IntegerField
|
6
|
+
from plain.models.fields import Field, FloatField, IntegerField
|
3
7
|
|
4
8
|
__all__ = [
|
5
9
|
"CumeDist",
|
@@ -37,7 +41,9 @@ class FirstValue(Func):
|
|
37
41
|
class LagLeadFunction(Func):
|
38
42
|
window_compatible = True
|
39
43
|
|
40
|
-
def __init__(
|
44
|
+
def __init__(
|
45
|
+
self, expression: Any, offset: int = 1, default: Any = None, **extra: Any
|
46
|
+
) -> None:
|
41
47
|
if expression is None:
|
42
48
|
raise ValueError(
|
43
49
|
f"{self.__class__.__name__} requires a non-null source expression."
|
@@ -51,7 +57,7 @@ class LagLeadFunction(Func):
|
|
51
57
|
args += (default,)
|
52
58
|
super().__init__(*args, **extra)
|
53
59
|
|
54
|
-
def _resolve_output_field(self):
|
60
|
+
def _resolve_output_field(self) -> Field:
|
55
61
|
sources = self.get_source_expressions()
|
56
62
|
return sources[0].output_field
|
57
63
|
|
@@ -74,7 +80,7 @@ class NthValue(Func):
|
|
74
80
|
function = "NTH_VALUE"
|
75
81
|
window_compatible = True
|
76
82
|
|
77
|
-
def __init__(self, expression, nth=1, **extra):
|
83
|
+
def __init__(self, expression: Any, nth: int = 1, **extra: Any) -> None:
|
78
84
|
if expression is None:
|
79
85
|
raise ValueError(
|
80
86
|
f"{self.__class__.__name__} requires a non-null source expression."
|
@@ -85,7 +91,7 @@ class NthValue(Func):
|
|
85
91
|
)
|
86
92
|
super().__init__(expression, nth, **extra)
|
87
93
|
|
88
|
-
def _resolve_output_field(self):
|
94
|
+
def _resolve_output_field(self) -> Field:
|
89
95
|
sources = self.get_source_expressions()
|
90
96
|
return sources[0].output_field
|
91
97
|
|
@@ -95,7 +101,7 @@ class Ntile(Func):
|
|
95
101
|
output_field = IntegerField()
|
96
102
|
window_compatible = True
|
97
103
|
|
98
|
-
def __init__(self, num_buckets=1, **extra):
|
104
|
+
def __init__(self, num_buckets: int = 1, **extra: Any) -> None:
|
99
105
|
if num_buckets <= 0:
|
100
106
|
raise ValueError("num_buckets must be greater than 0.")
|
101
107
|
super().__init__(num_buckets, **extra)
|
plain/models/indexes.py
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
from types import NoneType
|
4
|
+
from typing import TYPE_CHECKING, Any
|
2
5
|
|
3
6
|
from plain.models.backends.utils import names_digest, split_identifier
|
4
7
|
from plain.models.expressions import Col, ExpressionList, F, Func, OrderBy
|
@@ -7,6 +10,14 @@ from plain.models.query_utils import Q
|
|
7
10
|
from plain.models.sql import Query
|
8
11
|
from plain.utils.functional import partition
|
9
12
|
|
13
|
+
if TYPE_CHECKING:
|
14
|
+
from plain.models.backends.base.base import BaseDatabaseWrapper
|
15
|
+
from plain.models.backends.base.schema import BaseDatabaseSchemaEditor
|
16
|
+
from plain.models.backends.ddl_references import Statement
|
17
|
+
from plain.models.base import Model
|
18
|
+
from plain.models.expressions import Expression
|
19
|
+
from plain.models.sql.compiler import SQLCompiler
|
20
|
+
|
10
21
|
__all__ = ["Index"]
|
11
22
|
|
12
23
|
|
@@ -18,13 +29,13 @@ class Index:
|
|
18
29
|
|
19
30
|
def __init__(
|
20
31
|
self,
|
21
|
-
*expressions,
|
22
|
-
fields=(),
|
23
|
-
name=None,
|
24
|
-
opclasses=(),
|
25
|
-
condition=None,
|
26
|
-
include=None,
|
27
|
-
):
|
32
|
+
*expressions: Any,
|
33
|
+
fields: tuple[str, ...] | list[str] = (),
|
34
|
+
name: str | None = None,
|
35
|
+
opclasses: tuple[str, ...] | list[str] = (),
|
36
|
+
condition: Q | None = None,
|
37
|
+
include: tuple[str, ...] | list[str] | None = None,
|
38
|
+
) -> None:
|
28
39
|
if opclasses and not name:
|
29
40
|
raise ValueError("An index must be named to use opclasses.")
|
30
41
|
if not isinstance(condition, NoneType | Q):
|
@@ -77,10 +88,12 @@ class Index:
|
|
77
88
|
)
|
78
89
|
|
79
90
|
@property
|
80
|
-
def contains_expressions(self):
|
91
|
+
def contains_expressions(self) -> bool:
|
81
92
|
return bool(self.expressions)
|
82
93
|
|
83
|
-
def _get_condition_sql(
|
94
|
+
def _get_condition_sql(
|
95
|
+
self, model: type[Model], schema_editor: BaseDatabaseSchemaEditor
|
96
|
+
) -> str | None:
|
84
97
|
if self.condition is None:
|
85
98
|
return None
|
86
99
|
query = Query(model=model, alias_cols=False)
|
@@ -89,9 +102,12 @@ class Index:
|
|
89
102
|
sql, params = where.as_sql(compiler, schema_editor.connection)
|
90
103
|
return sql % tuple(schema_editor.quote_value(p) for p in params)
|
91
104
|
|
92
|
-
def create_sql(
|
105
|
+
def create_sql(
|
106
|
+
self, model: type[Model], schema_editor: BaseDatabaseSchemaEditor, **kwargs: Any
|
107
|
+
) -> Statement:
|
93
108
|
include = [
|
94
|
-
model.
|
109
|
+
model._model_meta.get_field(field_name).column
|
110
|
+
for field_name in self.include
|
95
111
|
]
|
96
112
|
condition = self._get_condition_sql(model, schema_editor)
|
97
113
|
if self.expressions:
|
@@ -107,19 +123,19 @@ class Index:
|
|
107
123
|
col_suffixes = None
|
108
124
|
else:
|
109
125
|
fields = [
|
110
|
-
model.
|
126
|
+
model._model_meta.get_field(field_name)
|
111
127
|
for field_name, _ in self.fields_orders
|
112
128
|
]
|
113
129
|
if schema_editor.connection.features.supports_index_column_ordering:
|
114
|
-
col_suffixes =
|
130
|
+
col_suffixes = tuple(order[1] for order in self.fields_orders)
|
115
131
|
else:
|
116
|
-
col_suffixes =
|
132
|
+
col_suffixes = ("",) * len(self.fields_orders)
|
117
133
|
expressions = None
|
118
134
|
return schema_editor._create_index_sql(
|
119
135
|
model,
|
120
136
|
fields=fields,
|
121
137
|
name=self.name,
|
122
|
-
col_suffixes=col_suffixes,
|
138
|
+
col_suffixes=col_suffixes, # type: ignore[arg-type]
|
123
139
|
opclasses=self.opclasses,
|
124
140
|
condition=condition,
|
125
141
|
include=include,
|
@@ -127,10 +143,12 @@ class Index:
|
|
127
143
|
**kwargs,
|
128
144
|
)
|
129
145
|
|
130
|
-
def remove_sql(
|
146
|
+
def remove_sql(
|
147
|
+
self, model: type[Model], schema_editor: BaseDatabaseSchemaEditor, **kwargs: Any
|
148
|
+
) -> Statement:
|
131
149
|
return schema_editor._delete_index_sql(model, self.name, **kwargs)
|
132
150
|
|
133
|
-
def deconstruct(self):
|
151
|
+
def deconstruct(self) -> tuple[str, tuple[Expression, ...], dict[str, Any]]:
|
134
152
|
path = f"{self.__class__.__module__}.{self.__class__.__name__}"
|
135
153
|
path = path.replace("plain.models.indexes", "plain.models")
|
136
154
|
kwargs = {"name": self.name}
|
@@ -144,12 +162,12 @@ class Index:
|
|
144
162
|
kwargs["include"] = self.include
|
145
163
|
return (path, self.expressions, kwargs)
|
146
164
|
|
147
|
-
def clone(self):
|
165
|
+
def clone(self) -> Index:
|
148
166
|
"""Create a copy of this Index."""
|
149
167
|
_, args, kwargs = self.deconstruct()
|
150
168
|
return self.__class__(*args, **kwargs)
|
151
169
|
|
152
|
-
def set_name_with_model(self, model):
|
170
|
+
def set_name_with_model(self, model: type[Model]) -> None:
|
153
171
|
"""
|
154
172
|
Generate a unique name for the index.
|
155
173
|
|
@@ -157,9 +175,9 @@ class Index:
|
|
157
175
|
(8 chars) and unique hash + suffix (10 chars). Each part is made to
|
158
176
|
fit its size by truncating the excess length.
|
159
177
|
"""
|
160
|
-
_, table_name = split_identifier(model.
|
178
|
+
_, table_name = split_identifier(model.model_options.db_table)
|
161
179
|
column_names = [
|
162
|
-
model.
|
180
|
+
model._model_meta.get_field(field_name).column
|
163
181
|
for field_name, order in self.fields_orders
|
164
182
|
]
|
165
183
|
column_names_with_order = [
|
@@ -184,7 +202,7 @@ class Index:
|
|
184
202
|
if self.name[0] == "_" or self.name[0].isdigit():
|
185
203
|
self.name = f"D{self.name[1:]}"
|
186
204
|
|
187
|
-
def __repr__(self):
|
205
|
+
def __repr__(self) -> str:
|
188
206
|
return "<{}:{}{}{}{}{}{}>".format(
|
189
207
|
self.__class__.__qualname__,
|
190
208
|
"" if not self.fields else f" fields={repr(self.fields)}",
|
@@ -195,9 +213,9 @@ class Index:
|
|
195
213
|
"" if not self.opclasses else f" opclasses={repr(self.opclasses)}",
|
196
214
|
)
|
197
215
|
|
198
|
-
def __eq__(self, other):
|
216
|
+
def __eq__(self, other: object) -> bool:
|
199
217
|
if self.__class__ == other.__class__:
|
200
|
-
return self.deconstruct() == other.deconstruct()
|
218
|
+
return self.deconstruct() == other.deconstruct() # type: ignore[attr-defined]
|
201
219
|
return NotImplemented
|
202
220
|
|
203
221
|
|
@@ -207,7 +225,9 @@ class IndexExpression(Func):
|
|
207
225
|
template = "%(expressions)s"
|
208
226
|
wrapper_classes = (OrderBy, Collate)
|
209
227
|
|
210
|
-
def set_wrapper_classes(
|
228
|
+
def set_wrapper_classes(
|
229
|
+
self, connection: BaseDatabaseWrapper | None = None
|
230
|
+
) -> None:
|
211
231
|
# Some databases (e.g. MySQL) treats COLLATE as an indexed expression.
|
212
232
|
if connection and connection.features.collate_as_index_expression:
|
213
233
|
self.wrapper_classes = tuple(
|
@@ -220,12 +240,12 @@ class IndexExpression(Func):
|
|
220
240
|
|
221
241
|
def resolve_expression(
|
222
242
|
self,
|
223
|
-
query=None,
|
224
|
-
allow_joins=True,
|
225
|
-
reuse=None,
|
226
|
-
summarize=False,
|
227
|
-
for_save=False,
|
228
|
-
):
|
243
|
+
query: Any = None,
|
244
|
+
allow_joins: bool = True,
|
245
|
+
reuse: Any = None,
|
246
|
+
summarize: bool = False,
|
247
|
+
for_save: bool = False,
|
248
|
+
) -> Expression:
|
229
249
|
expressions = list(self.flatten())
|
230
250
|
# Split expressions and wrappers.
|
231
251
|
index_expressions, wrappers = partition(
|
@@ -287,6 +307,11 @@ class IndexExpression(Func):
|
|
287
307
|
query, allow_joins, reuse, summarize, for_save
|
288
308
|
)
|
289
309
|
|
290
|
-
def as_sqlite(
|
310
|
+
def as_sqlite(
|
311
|
+
self,
|
312
|
+
compiler: SQLCompiler,
|
313
|
+
connection: BaseDatabaseWrapper,
|
314
|
+
**extra_context: Any,
|
315
|
+
) -> tuple[str, tuple[Any, ...]]:
|
291
316
|
# Casting to numeric is unnecessary.
|
292
317
|
return self.as_sql(compiler, connection, **extra_context)
|