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.
Files changed (105) hide show
  1. plain/models/CHANGELOG.md +13 -0
  2. plain/models/aggregates.py +42 -19
  3. plain/models/backends/base/base.py +125 -105
  4. plain/models/backends/base/client.py +11 -3
  5. plain/models/backends/base/creation.py +22 -12
  6. plain/models/backends/base/features.py +10 -4
  7. plain/models/backends/base/introspection.py +29 -16
  8. plain/models/backends/base/operations.py +187 -91
  9. plain/models/backends/base/schema.py +267 -165
  10. plain/models/backends/base/validation.py +12 -3
  11. plain/models/backends/ddl_references.py +85 -43
  12. plain/models/backends/mysql/base.py +29 -26
  13. plain/models/backends/mysql/client.py +7 -2
  14. plain/models/backends/mysql/compiler.py +12 -3
  15. plain/models/backends/mysql/creation.py +5 -2
  16. plain/models/backends/mysql/features.py +24 -22
  17. plain/models/backends/mysql/introspection.py +22 -13
  18. plain/models/backends/mysql/operations.py +106 -39
  19. plain/models/backends/mysql/schema.py +48 -24
  20. plain/models/backends/mysql/validation.py +13 -6
  21. plain/models/backends/postgresql/base.py +41 -34
  22. plain/models/backends/postgresql/client.py +7 -2
  23. plain/models/backends/postgresql/creation.py +10 -5
  24. plain/models/backends/postgresql/introspection.py +15 -8
  25. plain/models/backends/postgresql/operations.py +109 -42
  26. plain/models/backends/postgresql/schema.py +85 -46
  27. plain/models/backends/sqlite3/_functions.py +151 -115
  28. plain/models/backends/sqlite3/base.py +37 -23
  29. plain/models/backends/sqlite3/client.py +7 -1
  30. plain/models/backends/sqlite3/creation.py +9 -5
  31. plain/models/backends/sqlite3/features.py +5 -3
  32. plain/models/backends/sqlite3/introspection.py +32 -16
  33. plain/models/backends/sqlite3/operations.py +125 -42
  34. plain/models/backends/sqlite3/schema.py +82 -58
  35. plain/models/backends/utils.py +52 -29
  36. plain/models/backups/cli.py +8 -6
  37. plain/models/backups/clients.py +16 -7
  38. plain/models/backups/core.py +24 -13
  39. plain/models/base.py +113 -74
  40. plain/models/cli.py +94 -63
  41. plain/models/config.py +1 -1
  42. plain/models/connections.py +23 -7
  43. plain/models/constraints.py +65 -47
  44. plain/models/database_url.py +1 -1
  45. plain/models/db.py +6 -2
  46. plain/models/deletion.py +66 -43
  47. plain/models/entrypoints.py +1 -1
  48. plain/models/enums.py +22 -11
  49. plain/models/exceptions.py +23 -8
  50. plain/models/expressions.py +440 -257
  51. plain/models/fields/__init__.py +253 -202
  52. plain/models/fields/json.py +120 -54
  53. plain/models/fields/mixins.py +12 -8
  54. plain/models/fields/related.py +284 -252
  55. plain/models/fields/related_descriptors.py +31 -22
  56. plain/models/fields/related_lookups.py +23 -11
  57. plain/models/fields/related_managers.py +81 -47
  58. plain/models/fields/reverse_related.py +58 -55
  59. plain/models/forms.py +89 -63
  60. plain/models/functions/comparison.py +71 -18
  61. plain/models/functions/datetime.py +79 -29
  62. plain/models/functions/math.py +43 -10
  63. plain/models/functions/mixins.py +24 -7
  64. plain/models/functions/text.py +104 -25
  65. plain/models/functions/window.py +12 -6
  66. plain/models/indexes.py +52 -28
  67. plain/models/lookups.py +228 -153
  68. plain/models/migrations/autodetector.py +86 -43
  69. plain/models/migrations/exceptions.py +7 -3
  70. plain/models/migrations/executor.py +33 -7
  71. plain/models/migrations/graph.py +79 -50
  72. plain/models/migrations/loader.py +45 -22
  73. plain/models/migrations/migration.py +23 -18
  74. plain/models/migrations/operations/base.py +37 -19
  75. plain/models/migrations/operations/fields.py +89 -42
  76. plain/models/migrations/operations/models.py +245 -143
  77. plain/models/migrations/operations/special.py +82 -25
  78. plain/models/migrations/optimizer.py +7 -2
  79. plain/models/migrations/questioner.py +58 -31
  80. plain/models/migrations/recorder.py +18 -11
  81. plain/models/migrations/serializer.py +50 -39
  82. plain/models/migrations/state.py +220 -133
  83. plain/models/migrations/utils.py +29 -13
  84. plain/models/migrations/writer.py +17 -14
  85. plain/models/options.py +63 -56
  86. plain/models/otel.py +16 -6
  87. plain/models/preflight.py +35 -12
  88. plain/models/query.py +323 -228
  89. plain/models/query_utils.py +93 -58
  90. plain/models/registry.py +34 -16
  91. plain/models/sql/compiler.py +146 -97
  92. plain/models/sql/datastructures.py +38 -25
  93. plain/models/sql/query.py +255 -169
  94. plain/models/sql/subqueries.py +32 -21
  95. plain/models/sql/where.py +54 -29
  96. plain/models/test/pytest.py +15 -11
  97. plain/models/test/utils.py +4 -2
  98. plain/models/transaction.py +20 -7
  99. plain/models/utils.py +13 -5
  100. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/METADATA +1 -1
  101. plain_models-0.50.0.dist-info/RECORD +122 -0
  102. plain_models-0.49.2.dist-info/RECORD +0 -122
  103. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/WHEEL +0 -0
  104. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/entry_points.txt +0 -0
  105. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,3 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ if TYPE_CHECKING:
6
+ from plain.models.backends.base.base import BaseDatabaseWrapper
7
+ from plain.models.backends.base.schema import BaseDatabaseSchemaEditor
8
+ from plain.models.migrations.state import ProjectState
9
+
10
+
1
11
  class Operation:
2
12
  """
3
13
  Base class for migration operations.
@@ -24,15 +34,15 @@ class Operation:
24
34
  # Should this operation be considered safe to elide and optimize across?
25
35
  elidable = False
26
36
 
27
- serialization_expand_args = []
37
+ serialization_expand_args: list[str] = []
28
38
 
29
- def __new__(cls, *args, **kwargs):
39
+ def __new__(cls, *args: Any, **kwargs: Any) -> Operation:
30
40
  # We capture the arguments to make returning them trivial
31
41
  self = object.__new__(cls)
32
- self._constructor_args = (args, kwargs)
33
- return self
42
+ self._constructor_args = (args, kwargs) # type: ignore[attr-defined]
43
+ return self # type: ignore[return-value]
34
44
 
35
- def deconstruct(self):
45
+ def deconstruct(self) -> tuple[str, tuple[Any, ...], dict[str, Any]]:
36
46
  """
37
47
  Return a 3-tuple of class import path (or just name if it lives
38
48
  under plain.models.migrations), positional arguments, and keyword
@@ -40,11 +50,11 @@ class Operation:
40
50
  """
41
51
  return (
42
52
  self.__class__.__name__,
43
- self._constructor_args[0],
44
- self._constructor_args[1],
53
+ self._constructor_args[0], # type: ignore[attr-defined]
54
+ self._constructor_args[1], # type: ignore[attr-defined]
45
55
  )
46
56
 
47
- def state_forwards(self, package_label, state):
57
+ def state_forwards(self, package_label: str, state: ProjectState) -> None:
48
58
  """
49
59
  Take the state from the previous migration, and mutate it
50
60
  so that it matches what this migration would perform.
@@ -53,7 +63,13 @@ class Operation:
53
63
  "subclasses of Operation must provide a state_forwards() method"
54
64
  )
55
65
 
56
- def database_forwards(self, package_label, schema_editor, from_state, to_state):
66
+ def database_forwards(
67
+ self,
68
+ package_label: str,
69
+ schema_editor: BaseDatabaseSchemaEditor,
70
+ from_state: ProjectState,
71
+ to_state: ProjectState,
72
+ ) -> None:
57
73
  """
58
74
  Perform the mutation on the database schema in the normal
59
75
  (forwards) direction.
@@ -62,21 +78,21 @@ class Operation:
62
78
  "subclasses of Operation must provide a database_forwards() method"
63
79
  )
64
80
 
65
- def describe(self):
81
+ def describe(self) -> str:
66
82
  """
67
83
  Output a brief summary of what the action does.
68
84
  """
69
- return f"{self.__class__.__name__}: {self._constructor_args}"
85
+ return f"{self.__class__.__name__}: {self._constructor_args}" # type: ignore[attr-defined]
70
86
 
71
87
  @property
72
- def migration_name_fragment(self):
88
+ def migration_name_fragment(self) -> str | None:
73
89
  """
74
90
  A filename part suitable for automatically naming a migration
75
91
  containing this operation, or None if not applicable.
76
92
  """
77
93
  return None
78
94
 
79
- def references_model(self, name, package_label):
95
+ def references_model(self, name: str, package_label: str) -> bool:
80
96
  """
81
97
  Return True if there is a chance this operation references the given
82
98
  model name (as a string), with an app label for accuracy.
@@ -88,7 +104,7 @@ class Operation:
88
104
  """
89
105
  return True
90
106
 
91
- def references_field(self, model_name, name, package_label):
107
+ def references_field(self, model_name: str, name: str, package_label: str) -> bool:
92
108
  """
93
109
  Return True if there is a chance this operation references the given
94
110
  field name, with an app label for accuracy.
@@ -97,14 +113,16 @@ class Operation:
97
113
  """
98
114
  return self.references_model(model_name, package_label)
99
115
 
100
- def allow_migrate_model(self, connection, model):
116
+ def allow_migrate_model(self, connection: BaseDatabaseWrapper, model: Any) -> bool:
101
117
  """Return whether or not a model may be migrated."""
102
118
  if not model._meta.can_migrate(connection):
103
119
  return False
104
120
 
105
121
  return True
106
122
 
107
- def reduce(self, operation, package_label):
123
+ def reduce(
124
+ self, operation: Operation, package_label: str
125
+ ) -> list[Operation] | bool:
108
126
  """
109
127
  Return either a list of operations the actual operation should be
110
128
  replaced with or a boolean that indicates whether or not the specified
@@ -116,9 +134,9 @@ class Operation:
116
134
  return [self]
117
135
  return False
118
136
 
119
- def __repr__(self):
137
+ def __repr__(self) -> str:
120
138
  return "<{} {}{}>".format(
121
139
  self.__class__.__name__,
122
- ", ".join(map(repr, self._constructor_args[0])),
123
- ",".join(" {}={!r}".format(*x) for x in self._constructor_args[1].items()),
140
+ ", ".join(map(repr, self._constructor_args[0])), # type: ignore[attr-defined]
141
+ ",".join(" {}={!r}".format(*x) for x in self._constructor_args[1].items()), # type: ignore[attr-defined]
124
142
  )
@@ -1,35 +1,43 @@
1
+ from __future__ import annotations
2
+
1
3
  from functools import cached_property
4
+ from typing import TYPE_CHECKING, Any
2
5
 
3
6
  from plain.models.fields import NOT_PROVIDED
4
7
  from plain.models.migrations.utils import field_references
5
8
 
6
9
  from .base import Operation
7
10
 
11
+ if TYPE_CHECKING:
12
+ from plain.models.backends.base.schema import BaseDatabaseSchemaEditor
13
+ from plain.models.fields import Field
14
+ from plain.models.migrations.state import ProjectState
15
+
8
16
 
9
17
  class FieldOperation(Operation):
10
- def __init__(self, model_name, name, field=None):
18
+ def __init__(self, model_name: str, name: str, field: Field | None = None) -> None:
11
19
  self.model_name = model_name
12
20
  self.name = name
13
21
  self.field = field
14
22
 
15
23
  @cached_property
16
- def model_name_lower(self):
24
+ def model_name_lower(self) -> str:
17
25
  return self.model_name.lower()
18
26
 
19
27
  @cached_property
20
- def name_lower(self):
28
+ def name_lower(self) -> str:
21
29
  return self.name.lower()
22
30
 
23
- def is_same_model_operation(self, operation):
31
+ def is_same_model_operation(self, operation: FieldOperation) -> bool:
24
32
  return self.model_name_lower == operation.model_name_lower
25
33
 
26
- def is_same_field_operation(self, operation):
34
+ def is_same_field_operation(self, operation: FieldOperation) -> bool:
27
35
  return (
28
36
  self.is_same_model_operation(operation)
29
37
  and self.name_lower == operation.name_lower
30
38
  )
31
39
 
32
- def references_model(self, name, package_label):
40
+ def references_model(self, name: str, package_label: str) -> bool:
33
41
  name_lower = name.lower()
34
42
  if name_lower == self.model_name_lower:
35
43
  return True
@@ -43,7 +51,7 @@ class FieldOperation(Operation):
43
51
  )
44
52
  return False
45
53
 
46
- def references_field(self, model_name, name, package_label):
54
+ def references_field(self, model_name: str, name: str, package_label: str) -> bool:
47
55
  model_name_lower = model_name.lower()
48
56
  # Check if this operation locally references the field.
49
57
  if model_name_lower == self.model_name_lower:
@@ -61,7 +69,9 @@ class FieldOperation(Operation):
61
69
  )
62
70
  )
63
71
 
64
- def reduce(self, operation, package_label):
72
+ def reduce(
73
+ self, operation: Operation, package_label: str
74
+ ) -> list[Operation] | bool:
65
75
  return super().reduce(
66
76
  operation, package_label
67
77
  ) or not operation.references_field(self.model_name, self.name, package_label)
@@ -70,12 +80,14 @@ class FieldOperation(Operation):
70
80
  class AddField(FieldOperation):
71
81
  """Add a field to a model."""
72
82
 
73
- def __init__(self, model_name, name, field, preserve_default=True):
83
+ def __init__(
84
+ self, model_name: str, name: str, field: Field, preserve_default: bool = True
85
+ ) -> None:
74
86
  self.preserve_default = preserve_default
75
87
  super().__init__(model_name, name, field)
76
88
 
77
- def deconstruct(self):
78
- kwargs = {
89
+ def deconstruct(self) -> tuple[str, list[Any], dict[str, Any]]:
90
+ kwargs: dict[str, Any] = {
79
91
  "model_name": self.model_name,
80
92
  "name": self.name,
81
93
  "field": self.field,
@@ -84,7 +96,7 @@ class AddField(FieldOperation):
84
96
  kwargs["preserve_default"] = self.preserve_default
85
97
  return (self.__class__.__name__, [], kwargs)
86
98
 
87
- def state_forwards(self, package_label, state):
99
+ def state_forwards(self, package_label: str, state: Any) -> None:
88
100
  state.add_field(
89
101
  package_label,
90
102
  self.model_name_lower,
@@ -93,7 +105,13 @@ class AddField(FieldOperation):
93
105
  self.preserve_default,
94
106
  )
95
107
 
96
- def database_forwards(self, package_label, schema_editor, from_state, to_state):
108
+ def database_forwards(
109
+ self,
110
+ package_label: str,
111
+ schema_editor: BaseDatabaseSchemaEditor,
112
+ from_state: ProjectState,
113
+ to_state: ProjectState,
114
+ ) -> None:
97
115
  to_model = to_state.models_registry.get_model(package_label, self.model_name)
98
116
  if self.allow_migrate_model(schema_editor.connection, to_model):
99
117
  from_model = from_state.models_registry.get_model(
@@ -109,18 +127,21 @@ class AddField(FieldOperation):
109
127
  if not self.preserve_default:
110
128
  field.default = NOT_PROVIDED
111
129
 
112
- def describe(self):
130
+ def describe(self) -> str:
113
131
  return f"Add field {self.name} to {self.model_name}"
114
132
 
115
133
  @property
116
- def migration_name_fragment(self):
134
+ def migration_name_fragment(self) -> str:
117
135
  return f"{self.model_name_lower}_{self.name_lower}"
118
136
 
119
- def reduce(self, operation, package_label):
137
+ def reduce(
138
+ self, operation: Operation, package_label: str
139
+ ) -> list[Operation] | bool:
120
140
  if isinstance(operation, FieldOperation) and self.is_same_field_operation(
121
141
  operation
122
142
  ):
123
143
  if isinstance(operation, AlterField):
144
+ assert operation.field is not None
124
145
  return [
125
146
  AddField(
126
147
  model_name=self.model_name,
@@ -144,17 +165,23 @@ class AddField(FieldOperation):
144
165
  class RemoveField(FieldOperation):
145
166
  """Remove a field from a model."""
146
167
 
147
- def deconstruct(self):
148
- kwargs = {
168
+ def deconstruct(self) -> tuple[str, list[Any], dict[str, Any]]:
169
+ kwargs: dict[str, Any] = {
149
170
  "model_name": self.model_name,
150
171
  "name": self.name,
151
172
  }
152
173
  return (self.__class__.__name__, [], kwargs)
153
174
 
154
- def state_forwards(self, package_label, state):
175
+ def state_forwards(self, package_label: str, state: Any) -> None:
155
176
  state.remove_field(package_label, self.model_name_lower, self.name)
156
177
 
157
- def database_forwards(self, package_label, schema_editor, from_state, to_state):
178
+ def database_forwards(
179
+ self,
180
+ package_label: str,
181
+ schema_editor: BaseDatabaseSchemaEditor,
182
+ from_state: ProjectState,
183
+ to_state: ProjectState,
184
+ ) -> None:
158
185
  from_model = from_state.models_registry.get_model(
159
186
  package_label, self.model_name
160
187
  )
@@ -163,14 +190,16 @@ class RemoveField(FieldOperation):
163
190
  from_model, from_model._meta.get_field(self.name)
164
191
  )
165
192
 
166
- def describe(self):
193
+ def describe(self) -> str:
167
194
  return f"Remove field {self.name} from {self.model_name}"
168
195
 
169
196
  @property
170
- def migration_name_fragment(self):
197
+ def migration_name_fragment(self) -> str:
171
198
  return f"remove_{self.model_name_lower}_{self.name_lower}"
172
199
 
173
- def reduce(self, operation, package_label):
200
+ def reduce(
201
+ self, operation: Operation, package_label: str
202
+ ) -> list[Operation] | bool:
174
203
  from .models import DeleteModel
175
204
 
176
205
  if (
@@ -187,12 +216,14 @@ class AlterField(FieldOperation):
187
216
  new field.
188
217
  """
189
218
 
190
- def __init__(self, model_name, name, field, preserve_default=True):
219
+ def __init__(
220
+ self, model_name: str, name: str, field: Field, preserve_default: bool = True
221
+ ) -> None:
191
222
  self.preserve_default = preserve_default
192
223
  super().__init__(model_name, name, field)
193
224
 
194
- def deconstruct(self):
195
- kwargs = {
225
+ def deconstruct(self) -> tuple[str, list[Any], dict[str, Any]]:
226
+ kwargs: dict[str, Any] = {
196
227
  "model_name": self.model_name,
197
228
  "name": self.name,
198
229
  "field": self.field,
@@ -201,7 +232,7 @@ class AlterField(FieldOperation):
201
232
  kwargs["preserve_default"] = self.preserve_default
202
233
  return (self.__class__.__name__, [], kwargs)
203
234
 
204
- def state_forwards(self, package_label, state):
235
+ def state_forwards(self, package_label: str, state: Any) -> None:
205
236
  state.alter_field(
206
237
  package_label,
207
238
  self.model_name_lower,
@@ -210,7 +241,13 @@ class AlterField(FieldOperation):
210
241
  self.preserve_default,
211
242
  )
212
243
 
213
- def database_forwards(self, package_label, schema_editor, from_state, to_state):
244
+ def database_forwards(
245
+ self,
246
+ package_label: str,
247
+ schema_editor: BaseDatabaseSchemaEditor,
248
+ from_state: ProjectState,
249
+ to_state: ProjectState,
250
+ ) -> None:
214
251
  to_model = to_state.models_registry.get_model(package_label, self.model_name)
215
252
  if self.allow_migrate_model(schema_editor.connection, to_model):
216
253
  from_model = from_state.models_registry.get_model(
@@ -224,14 +261,16 @@ class AlterField(FieldOperation):
224
261
  if not self.preserve_default:
225
262
  to_field.default = NOT_PROVIDED
226
263
 
227
- def describe(self):
264
+ def describe(self) -> str:
228
265
  return f"Alter field {self.name} on {self.model_name}"
229
266
 
230
267
  @property
231
- def migration_name_fragment(self):
268
+ def migration_name_fragment(self) -> str:
232
269
  return f"alter_{self.model_name_lower}_{self.name_lower}"
233
270
 
234
- def reduce(self, operation, package_label):
271
+ def reduce(
272
+ self, operation: Operation, package_label: str
273
+ ) -> list[Operation] | bool:
235
274
  if isinstance(
236
275
  operation, AlterField | RemoveField
237
276
  ) and self.is_same_field_operation(operation):
@@ -255,33 +294,39 @@ class AlterField(FieldOperation):
255
294
  class RenameField(FieldOperation):
256
295
  """Rename a field on the model. Might affect db_column too."""
257
296
 
258
- def __init__(self, model_name, old_name, new_name):
297
+ def __init__(self, model_name: str, old_name: str, new_name: str) -> None:
259
298
  self.old_name = old_name
260
299
  self.new_name = new_name
261
300
  super().__init__(model_name, old_name)
262
301
 
263
302
  @cached_property
264
- def old_name_lower(self):
303
+ def old_name_lower(self) -> str:
265
304
  return self.old_name.lower()
266
305
 
267
306
  @cached_property
268
- def new_name_lower(self):
307
+ def new_name_lower(self) -> str:
269
308
  return self.new_name.lower()
270
309
 
271
- def deconstruct(self):
272
- kwargs = {
310
+ def deconstruct(self) -> tuple[str, list[Any], dict[str, Any]]:
311
+ kwargs: dict[str, Any] = {
273
312
  "model_name": self.model_name,
274
313
  "old_name": self.old_name,
275
314
  "new_name": self.new_name,
276
315
  }
277
316
  return (self.__class__.__name__, [], kwargs)
278
317
 
279
- def state_forwards(self, package_label, state):
318
+ def state_forwards(self, package_label: str, state: Any) -> None:
280
319
  state.rename_field(
281
320
  package_label, self.model_name_lower, self.old_name, self.new_name
282
321
  )
283
322
 
284
- def database_forwards(self, package_label, schema_editor, from_state, to_state):
323
+ def database_forwards(
324
+ self,
325
+ package_label: str,
326
+ schema_editor: BaseDatabaseSchemaEditor,
327
+ from_state: ProjectState,
328
+ to_state: ProjectState,
329
+ ) -> None:
285
330
  to_model = to_state.models_registry.get_model(package_label, self.model_name)
286
331
  if self.allow_migrate_model(schema_editor.connection, to_model):
287
332
  from_model = from_state.models_registry.get_model(
@@ -293,19 +338,21 @@ class RenameField(FieldOperation):
293
338
  to_model._meta.get_field(self.new_name),
294
339
  )
295
340
 
296
- def describe(self):
341
+ def describe(self) -> str:
297
342
  return f"Rename field {self.old_name} on {self.model_name} to {self.new_name}"
298
343
 
299
344
  @property
300
- def migration_name_fragment(self):
345
+ def migration_name_fragment(self) -> str:
301
346
  return f"rename_{self.old_name_lower}_{self.model_name_lower}_{self.new_name_lower}"
302
347
 
303
- def references_field(self, model_name, name, package_label):
348
+ def references_field(self, model_name: str, name: str, package_label: str) -> bool:
304
349
  return self.references_model(model_name, package_label) and (
305
350
  name.lower() == self.old_name_lower or name.lower() == self.new_name_lower
306
351
  )
307
352
 
308
- def reduce(self, operation, package_label):
353
+ def reduce(
354
+ self, operation: Operation, package_label: str
355
+ ) -> list[Operation] | bool:
309
356
  if (
310
357
  isinstance(operation, RenameField)
311
358
  and self.is_same_model_operation(operation)