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,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 = "Constraint “%(name)s is violated."
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, *, name, violation_error_code=None, violation_error_message=None
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(self, model, instance, exclude=None):
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, *, check, name, violation_error_code=None, violation_error_message=None
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(self, model, instance, exclude=None):
102
- against = instance._get_field_value_map(meta=model._meta, exclude=exclude)
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._meta.get_field(field_name) for field_name in self.fields]
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._meta.get_field(field_name).column for field_name in self.include
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._meta.get_field(field_name) for field_name in self.fields]
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._meta.get_field(field_name).column for field_name in self.include
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._meta.get_field(field_name).column for field_name in self.include
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(self, model, instance, exclude=None):
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._meta.get_field(field_name)
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._meta, exclude=exclude
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(meta=model._meta, exclude=exclude)
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
@@ -182,4 +182,4 @@ def build_database_url(config: dict) -> str:
182
182
 
183
183
  path = f"/{name}"
184
184
  url = urlparse.urlunsplit((scheme, netloc, path, query, ""))
185
- return url
185
+ return str(url)
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