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.
Files changed (108) hide show
  1. plain/models/CHANGELOG.md +27 -0
  2. plain/models/README.md +26 -42
  3. plain/models/__init__.py +2 -0
  4. plain/models/aggregates.py +42 -19
  5. plain/models/backends/base/base.py +125 -105
  6. plain/models/backends/base/client.py +11 -3
  7. plain/models/backends/base/creation.py +24 -14
  8. plain/models/backends/base/features.py +10 -4
  9. plain/models/backends/base/introspection.py +37 -20
  10. plain/models/backends/base/operations.py +187 -91
  11. plain/models/backends/base/schema.py +338 -218
  12. plain/models/backends/base/validation.py +13 -4
  13. plain/models/backends/ddl_references.py +85 -43
  14. plain/models/backends/mysql/base.py +29 -26
  15. plain/models/backends/mysql/client.py +7 -2
  16. plain/models/backends/mysql/compiler.py +13 -4
  17. plain/models/backends/mysql/creation.py +5 -2
  18. plain/models/backends/mysql/features.py +24 -22
  19. plain/models/backends/mysql/introspection.py +22 -13
  20. plain/models/backends/mysql/operations.py +107 -40
  21. plain/models/backends/mysql/schema.py +52 -28
  22. plain/models/backends/mysql/validation.py +13 -6
  23. plain/models/backends/postgresql/base.py +41 -34
  24. plain/models/backends/postgresql/client.py +7 -2
  25. plain/models/backends/postgresql/creation.py +10 -5
  26. plain/models/backends/postgresql/introspection.py +15 -8
  27. plain/models/backends/postgresql/operations.py +110 -43
  28. plain/models/backends/postgresql/schema.py +88 -49
  29. plain/models/backends/sqlite3/_functions.py +151 -115
  30. plain/models/backends/sqlite3/base.py +37 -23
  31. plain/models/backends/sqlite3/client.py +7 -1
  32. plain/models/backends/sqlite3/creation.py +9 -5
  33. plain/models/backends/sqlite3/features.py +5 -3
  34. plain/models/backends/sqlite3/introspection.py +32 -16
  35. plain/models/backends/sqlite3/operations.py +126 -43
  36. plain/models/backends/sqlite3/schema.py +127 -92
  37. plain/models/backends/utils.py +52 -29
  38. plain/models/backups/cli.py +8 -6
  39. plain/models/backups/clients.py +16 -7
  40. plain/models/backups/core.py +24 -13
  41. plain/models/base.py +221 -229
  42. plain/models/cli.py +98 -67
  43. plain/models/config.py +1 -1
  44. plain/models/connections.py +23 -7
  45. plain/models/constraints.py +79 -56
  46. plain/models/database_url.py +1 -1
  47. plain/models/db.py +6 -2
  48. plain/models/deletion.py +80 -56
  49. plain/models/entrypoints.py +1 -1
  50. plain/models/enums.py +22 -11
  51. plain/models/exceptions.py +23 -8
  52. plain/models/expressions.py +441 -258
  53. plain/models/fields/__init__.py +272 -217
  54. plain/models/fields/json.py +123 -57
  55. plain/models/fields/mixins.py +12 -8
  56. plain/models/fields/related.py +324 -290
  57. plain/models/fields/related_descriptors.py +33 -24
  58. plain/models/fields/related_lookups.py +24 -12
  59. plain/models/fields/related_managers.py +102 -79
  60. plain/models/fields/reverse_related.py +66 -63
  61. plain/models/forms.py +101 -75
  62. plain/models/functions/comparison.py +71 -18
  63. plain/models/functions/datetime.py +79 -29
  64. plain/models/functions/math.py +43 -10
  65. plain/models/functions/mixins.py +24 -7
  66. plain/models/functions/text.py +104 -25
  67. plain/models/functions/window.py +12 -6
  68. plain/models/indexes.py +57 -32
  69. plain/models/lookups.py +228 -153
  70. plain/models/meta.py +505 -0
  71. plain/models/migrations/autodetector.py +86 -43
  72. plain/models/migrations/exceptions.py +7 -3
  73. plain/models/migrations/executor.py +33 -7
  74. plain/models/migrations/graph.py +79 -50
  75. plain/models/migrations/loader.py +45 -22
  76. plain/models/migrations/migration.py +23 -18
  77. plain/models/migrations/operations/base.py +38 -20
  78. plain/models/migrations/operations/fields.py +95 -48
  79. plain/models/migrations/operations/models.py +246 -142
  80. plain/models/migrations/operations/special.py +82 -25
  81. plain/models/migrations/optimizer.py +7 -2
  82. plain/models/migrations/questioner.py +58 -31
  83. plain/models/migrations/recorder.py +27 -16
  84. plain/models/migrations/serializer.py +50 -39
  85. plain/models/migrations/state.py +232 -156
  86. plain/models/migrations/utils.py +30 -14
  87. plain/models/migrations/writer.py +17 -14
  88. plain/models/options.py +189 -518
  89. plain/models/otel.py +16 -6
  90. plain/models/preflight.py +42 -17
  91. plain/models/query.py +400 -251
  92. plain/models/query_utils.py +109 -69
  93. plain/models/registry.py +40 -21
  94. plain/models/sql/compiler.py +190 -127
  95. plain/models/sql/datastructures.py +38 -25
  96. plain/models/sql/query.py +320 -225
  97. plain/models/sql/subqueries.py +36 -25
  98. plain/models/sql/where.py +54 -29
  99. plain/models/test/pytest.py +15 -11
  100. plain/models/test/utils.py +4 -2
  101. plain/models/transaction.py +20 -7
  102. plain/models/utils.py +17 -6
  103. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/METADATA +27 -43
  104. plain_models-0.51.0.dist-info/RECORD +123 -0
  105. plain_models-0.49.2.dist-info/RECORD +0 -122
  106. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/WHEEL +0 -0
  107. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/entry_points.txt +0 -0
  108. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/licenses/LICENSE +0 -0
@@ -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__(self, expression, offset=1, default=None, **extra):
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(self, model, schema_editor):
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(self, model, schema_editor, **kwargs):
105
+ def create_sql(
106
+ self, model: type[Model], schema_editor: BaseDatabaseSchemaEditor, **kwargs: Any
107
+ ) -> Statement:
93
108
  include = [
94
- model._meta.get_field(field_name).column for field_name in self.include
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._meta.get_field(field_name)
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 = [order[1] for order in self.fields_orders]
130
+ col_suffixes = tuple(order[1] for order in self.fields_orders)
115
131
  else:
116
- col_suffixes = [""] * len(self.fields_orders)
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(self, model, schema_editor, **kwargs):
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._meta.db_table)
178
+ _, table_name = split_identifier(model.model_options.db_table)
161
179
  column_names = [
162
- model._meta.get_field(field_name).column
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(self, connection=None):
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(self, compiler, connection, **extra_context):
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)