plain.models 0.49.1__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 +23 -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 +34 -25
  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.1.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.1.dist-info/RECORD +0 -122
  103. {plain_models-0.49.1.dist-info → plain_models-0.50.0.dist-info}/WHEEL +0 -0
  104. {plain_models-0.49.1.dist-info → plain_models-0.50.0.dist-info}/entry_points.txt +0 -0
  105. {plain_models-0.49.1.dist-info → plain_models-0.50.0.dist-info}/licenses/LICENSE +0 -0
@@ -46,7 +46,10 @@ reverse many-to-one relation.
46
46
  ``ReverseManyToManyDescriptor``, use ``ManyToManyDescriptor`` instead.
47
47
  """
48
48
 
49
+ from __future__ import annotations
50
+
49
51
  from functools import cached_property
52
+ from typing import Any
50
53
 
51
54
  from plain.models.query import QuerySet
52
55
  from plain.models.query_utils import DeferredAttribute
@@ -60,7 +63,7 @@ from .related_managers import (
60
63
 
61
64
 
62
65
  class ForeignKeyDeferredAttribute(DeferredAttribute):
63
- def __set__(self, instance, value):
66
+ def __set__(self, instance: Any, value: Any) -> None:
64
67
  if instance.__dict__.get(self.field.attname) != value and self.field.is_cached(
65
68
  instance
66
69
  ):
@@ -80,11 +83,11 @@ class ForwardManyToOneDescriptor:
80
83
  ``Child.parent`` is a ``ForwardManyToOneDescriptor`` instance.
81
84
  """
82
85
 
83
- def __init__(self, field_with_rel):
86
+ def __init__(self, field_with_rel: Any) -> None:
84
87
  self.field = field_with_rel
85
88
 
86
89
  @cached_property
87
- def RelatedObjectDoesNotExist(self):
90
+ def RelatedObjectDoesNotExist(self) -> type:
88
91
  # The exception can't be created at initialization time since the
89
92
  # related model might not be resolved yet; `self.field.model` might
90
93
  # still be a string model reference.
@@ -97,14 +100,16 @@ class ForwardManyToOneDescriptor:
97
100
  },
98
101
  )
99
102
 
100
- def is_cached(self, instance):
103
+ def is_cached(self, instance: Any) -> bool:
101
104
  return self.field.is_cached(instance)
102
105
 
103
106
  def get_queryset(self) -> QuerySet:
104
107
  qs = self.field.remote_field.model._meta.base_queryset
105
108
  return qs.all()
106
109
 
107
- def get_prefetch_queryset(self, instances, queryset=None):
110
+ def get_prefetch_queryset(
111
+ self, instances: list[Any], queryset: QuerySet | None = None
112
+ ) -> tuple[QuerySet, Any, Any, bool, str, bool]:
108
113
  if queryset is None:
109
114
  queryset = self.get_queryset()
110
115
 
@@ -145,12 +150,14 @@ class ForwardManyToOneDescriptor:
145
150
  False,
146
151
  )
147
152
 
148
- def get_object(self, instance):
153
+ def get_object(self, instance: Any) -> Any:
149
154
  qs = self.get_queryset()
150
155
  # Assuming the database enforces foreign keys, this won't fail.
151
156
  return qs.get(self.field.get_reverse_related_filter(instance))
152
157
 
153
- def __get__(self, instance, cls=None):
158
+ def __get__(
159
+ self, instance: Any | None, cls: type | None = None
160
+ ) -> ForwardManyToOneDescriptor | Any | None:
154
161
  """
155
162
  Get the related instance through the forward relation.
156
163
 
@@ -189,7 +196,7 @@ class ForwardManyToOneDescriptor:
189
196
  else:
190
197
  return rel_obj
191
198
 
192
- def __set__(self, instance, value):
199
+ def __set__(self, instance: Any, value: Any) -> None:
193
200
  """
194
201
  Set the related instance through the forward relation.
195
202
 
@@ -199,9 +206,9 @@ class ForwardManyToOneDescriptor:
199
206
  - ``instance`` is the ``child`` instance
200
207
  - ``value`` is the ``parent`` instance on the right of the equal sign
201
208
  """
202
- # If value is a LazyObject (like SimpleLazyObject used for request.user),
203
- # force its evaluation. For ForeignKey fields, the value should only be
204
- # None or a model instance, never a boolean or other type.
209
+ # If value is a LazyObject, force its evaluation. For ForeignKey fields,
210
+ # the value should only be None or a model instance, never a boolean or
211
+ # other type.
205
212
  if isinstance(value, LazyObject):
206
213
  # This forces evaluation: if it's None, value becomes None;
207
214
  # if it's a User instance, value becomes that instance.
@@ -250,7 +257,7 @@ class ForwardManyToOneDescriptor:
250
257
  if value is not None and not remote_field.multiple:
251
258
  remote_field.set_cached_value(value, instance)
252
259
 
253
- def __reduce__(self):
260
+ def __reduce__(self) -> tuple[Any, tuple[Any, str]]:
254
261
  """
255
262
  Pickling should return the instance attached by self.field on the
256
263
  model, not a new copy of that descriptor. Use getattr() to retrieve
@@ -268,11 +275,13 @@ class RelationDescriptorBase:
268
275
  this because they allow direct assignment.
269
276
  """
270
277
 
271
- def __init__(self, rel):
278
+ def __init__(self, rel: Any) -> None:
272
279
  self.rel = rel
273
280
  self.field = rel.field
274
281
 
275
- def __get__(self, instance, cls=None):
282
+ def __get__(
283
+ self, instance: Any | None, cls: type | None = None
284
+ ) -> RelationDescriptorBase | Any:
276
285
  """
277
286
  Get the related manager when the descriptor is accessed.
278
287
 
@@ -282,19 +291,19 @@ class RelationDescriptorBase:
282
291
  return self
283
292
  return self.get_related_manager(instance)
284
293
 
285
- def get_related_manager(self, instance):
294
+ def get_related_manager(self, instance: Any) -> Any:
286
295
  """Return the appropriate manager for this relation."""
287
296
  raise NotImplementedError(
288
297
  f"{self.__class__.__name__} must implement get_related_manager()"
289
298
  )
290
299
 
291
- def _get_set_deprecation_msg_params(self):
300
+ def _get_set_deprecation_msg_params(self) -> tuple[str, str]:
292
301
  """Return parameters for the error message when direct assignment is attempted."""
293
302
  raise NotImplementedError(
294
303
  f"{self.__class__.__name__} must implement _get_set_deprecation_msg_params()"
295
304
  )
296
305
 
297
- def __set__(self, instance, value):
306
+ def __set__(self, instance: Any, value: Any) -> None:
298
307
  """Prevent direct assignment to the relation."""
299
308
  raise TypeError(
300
309
  "Direct assignment to the {} is prohibited. Use {}.set() instead.".format(
@@ -318,11 +327,11 @@ class ReverseManyToOneDescriptor(RelationDescriptorBase):
318
327
  Most of the implementation is delegated to the ReverseManyToOneManager class.
319
328
  """
320
329
 
321
- def get_related_manager(self, instance):
330
+ def get_related_manager(self, instance: Any) -> ReverseManyToOneManager:
322
331
  """Return the ReverseManyToOneManager for this relation."""
323
332
  return ReverseManyToOneManager(instance, self.rel)
324
333
 
325
- def _get_set_deprecation_msg_params(self):
334
+ def _get_set_deprecation_msg_params(self) -> tuple[str, str]:
326
335
  return (
327
336
  "reverse side of a related set",
328
337
  self.rel.get_accessor_name(),
@@ -343,17 +352,17 @@ class ForwardManyToManyDescriptor(RelationDescriptorBase):
343
352
  """
344
353
 
345
354
  @property
346
- def through(self):
355
+ def through(self) -> Any:
347
356
  # through is provided so that you have easy access to the through
348
357
  # model (Book.authors.through) for inlines, etc. This is done as
349
358
  # a property to ensure that the fully resolved value is returned.
350
359
  return self.rel.through
351
360
 
352
- def get_related_manager(self, instance):
361
+ def get_related_manager(self, instance: Any) -> ForwardManyToManyManager:
353
362
  """Return the ForwardManyToManyManager for this relation."""
354
363
  return ForwardManyToManyManager(instance, self.rel)
355
364
 
356
- def _get_set_deprecation_msg_params(self):
365
+ def _get_set_deprecation_msg_params(self) -> tuple[str, str]:
357
366
  return (
358
367
  "forward side of a many-to-many set",
359
368
  self.field.name,
@@ -374,17 +383,17 @@ class ReverseManyToManyDescriptor(RelationDescriptorBase):
374
383
  """
375
384
 
376
385
  @property
377
- def through(self):
386
+ def through(self) -> Any:
378
387
  # through is provided so that you have easy access to the through
379
388
  # model (Book.authors.through) for inlines, etc. This is done as
380
389
  # a property to ensure that the fully resolved value is returned.
381
390
  return self.rel.through
382
391
 
383
- def get_related_manager(self, instance):
392
+ def get_related_manager(self, instance: Any) -> ReverseManyToManyManager:
384
393
  """Return the ReverseManyToManyManager for this relation."""
385
394
  return ReverseManyToManyManager(instance, self.rel)
386
395
 
387
- def _get_set_deprecation_msg_params(self):
396
+ def _get_set_deprecation_msg_params(self) -> tuple[str, str]:
388
397
  return (
389
398
  "reverse side of a many-to-many set",
390
399
  self.rel.get_accessor_name(),
@@ -1,3 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
1
5
  from plain.models.lookups import (
2
6
  Exact,
3
7
  GreaterThan,
@@ -8,12 +12,16 @@ from plain.models.lookups import (
8
12
  LessThanOrEqual,
9
13
  )
10
14
 
15
+ if TYPE_CHECKING:
16
+ from plain.models.backends.base.base import BaseDatabaseWrapper
17
+ from plain.models.sql.compiler import SQLCompiler
18
+
11
19
 
12
20
  class MultiColSource:
13
21
  contains_aggregate = False
14
22
  contains_over_clause = False
15
23
 
16
- def __init__(self, alias, targets, sources, field):
24
+ def __init__(self, alias: str, targets: Any, sources: Any, field: Any) -> None:
17
25
  self.targets, self.sources, self.field, self.alias = (
18
26
  targets,
19
27
  sources,
@@ -22,22 +30,22 @@ class MultiColSource:
22
30
  )
23
31
  self.output_field = self.field
24
32
 
25
- def __repr__(self):
33
+ def __repr__(self) -> str:
26
34
  return f"{self.__class__.__name__}({self.alias}, {self.field})"
27
35
 
28
- def relabeled_clone(self, relabels):
36
+ def relabeled_clone(self, relabels: dict[str, str]) -> MultiColSource:
29
37
  return self.__class__(
30
38
  relabels.get(self.alias, self.alias), self.targets, self.sources, self.field
31
39
  )
32
40
 
33
- def get_lookup(self, lookup):
41
+ def get_lookup(self, lookup: str) -> Any:
34
42
  return self.output_field.get_lookup(lookup)
35
43
 
36
- def resolve_expression(self, *args, **kwargs):
44
+ def resolve_expression(self, *args: Any, **kwargs: Any) -> MultiColSource:
37
45
  return self
38
46
 
39
47
 
40
- def get_normalized_value(value, lhs):
48
+ def get_normalized_value(value: Any, lhs: Any) -> tuple[Any, ...]:
41
49
  from plain.models import Model
42
50
 
43
51
  if isinstance(value, Model):
@@ -63,7 +71,7 @@ def get_normalized_value(value, lhs):
63
71
 
64
72
 
65
73
  class RelatedIn(In):
66
- def get_prep_lookup(self):
74
+ def get_prep_lookup(self) -> list[Any]: # type: ignore[misc]
67
75
  if not isinstance(self.lhs, MultiColSource):
68
76
  if self.rhs_is_direct_value():
69
77
  # If we get here, we are dealing with single-column relations.
@@ -97,7 +105,9 @@ class RelatedIn(In):
97
105
  self.rhs.set_values([target_field])
98
106
  return super().get_prep_lookup()
99
107
 
100
- def as_sql(self, compiler, connection):
108
+ def as_sql(
109
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
110
+ ) -> tuple[str, list[Any]]: # type: ignore[misc]
101
111
  if isinstance(self.lhs, MultiColSource):
102
112
  # For multicolumn lookups we need to build a multicolumn where clause.
103
113
  # This clause is either a SubqueryConstraint (for values that need
@@ -139,7 +149,7 @@ class RelatedIn(In):
139
149
 
140
150
 
141
151
  class RelatedLookupMixin:
142
- def get_prep_lookup(self):
152
+ def get_prep_lookup(self) -> Any: # type: ignore[misc]
143
153
  if not isinstance(self.lhs, MultiColSource) and not hasattr(
144
154
  self.rhs, "resolve_expression"
145
155
  ):
@@ -155,9 +165,11 @@ class RelatedLookupMixin:
155
165
  target_field = self.lhs.output_field.path_infos[-1].target_fields[-1]
156
166
  self.rhs = target_field.get_prep_value(self.rhs)
157
167
 
158
- return super().get_prep_lookup()
168
+ return super().get_prep_lookup() # type: ignore[misc]
159
169
 
160
- def as_sql(self, compiler, connection):
170
+ def as_sql(
171
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
172
+ ) -> tuple[str, list[Any]]: # type: ignore[misc]
161
173
  if isinstance(self.lhs, MultiColSource):
162
174
  assert self.rhs_is_direct_value()
163
175
  self.rhs = get_normalized_value(self.rhs, self.lhs)
@@ -5,6 +5,10 @@ These managers provide the API for working with collections of related objects
5
5
  through foreign key and many-to-many relationships.
6
6
  """
7
7
 
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
8
12
  from plain.models import transaction
9
13
  from plain.models.db import NotSupportedError, db_connection
10
14
  from plain.models.expressions import Window
@@ -15,7 +19,9 @@ from plain.models.query_utils import Q
15
19
  from plain.models.utils import resolve_callables
16
20
 
17
21
 
18
- def _filter_prefetch_queryset(queryset, field_name, instances):
22
+ def _filter_prefetch_queryset(
23
+ queryset: QuerySet, field_name: str, instances: Any
24
+ ) -> QuerySet:
19
25
  predicate = Q(**{f"{field_name}__in": instances})
20
26
  if queryset.sql_query.is_sliced:
21
27
  if not db_connection.features.supports_over_clause:
@@ -28,9 +34,9 @@ def _filter_prefetch_queryset(queryset, field_name, instances):
28
34
  expr for expr, _ in queryset.sql_query.get_compiler().get_order_by()
29
35
  ]
30
36
  window = Window(RowNumber(), partition_by=field_name, order_by=order_by)
31
- predicate &= GreaterThan(window, low_mark)
37
+ predicate &= GreaterThan(window, low_mark) # type: ignore[unsupported-operator]
32
38
  if high_mark is not None:
33
- predicate &= LessThanOrEqual(window, high_mark)
39
+ predicate &= LessThanOrEqual(window, high_mark) # type: ignore[unsupported-operator]
34
40
  queryset.sql_query.clear_limits()
35
41
  return queryset.filter(predicate)
36
42
 
@@ -59,7 +65,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
59
65
  This manager adds behaviors specific to many-to-one relations.
60
66
  """
61
67
 
62
- def __init__(self, instance, rel):
68
+ def __init__(self, instance: Any, rel: Any):
63
69
  self.model = rel.related_model
64
70
  self.instance = instance
65
71
  self.field = rel.field
@@ -68,7 +74,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
68
74
  self.base_queryset_class = rel.related_model._meta.queryset.__class__
69
75
  self.allow_null = rel.field.allow_null
70
76
 
71
- def _check_fk_val(self):
77
+ def _check_fk_val(self) -> None:
72
78
  for field in self.field.foreign_related_fields:
73
79
  if getattr(self.instance, field.attname) is None:
74
80
  raise ValueError(
@@ -76,7 +82,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
76
82
  f'"{field.attname}" before this relationship can be used.'
77
83
  )
78
84
 
79
- def _apply_rel_filters(self, queryset):
85
+ def _apply_rel_filters(self, queryset: QuerySet) -> QuerySet:
80
86
  """
81
87
  Filter the queryset for the instance this manager is bound to.
82
88
  """
@@ -109,7 +115,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
109
115
  queryset._known_related_objects = {self.field: {rel_obj_id: self.instance}}
110
116
  return queryset
111
117
 
112
- def _remove_prefetched_objects(self):
118
+ def _remove_prefetched_objects(self) -> None:
113
119
  try:
114
120
  self.instance._prefetched_objects_cache.pop(
115
121
  self.field.remote_field.get_cache_name()
@@ -117,7 +123,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
117
123
  except (AttributeError, KeyError):
118
124
  pass # nothing to clear from cache
119
125
 
120
- def get_queryset(self):
126
+ def get_queryset(self) -> QuerySet:
121
127
  # Even if this relation is not to primary key, we require still primary key value.
122
128
  # The wish is that the instance has been already saved to DB,
123
129
  # although having a primary key value isn't a guarantee of that.
@@ -135,7 +141,9 @@ class ReverseManyToOneManager(BaseRelatedManager):
135
141
  queryset = self.base_queryset_class(model=self.model)
136
142
  return self._apply_rel_filters(queryset)
137
143
 
138
- def get_prefetch_queryset(self, instances, queryset=None):
144
+ def get_prefetch_queryset(
145
+ self, instances: Any, queryset: QuerySet | None = None
146
+ ) -> tuple[QuerySet, Any, Any, bool, str, bool]:
139
147
  if queryset is None:
140
148
  queryset = self.base_queryset_class(model=self.model)
141
149
 
@@ -153,11 +161,11 @@ class ReverseManyToOneManager(BaseRelatedManager):
153
161
  cache_name = self.field.remote_field.get_cache_name()
154
162
  return queryset, rel_obj_attr, instance_attr, False, cache_name, False
155
163
 
156
- def add(self, *objs, bulk=True):
164
+ def add(self, *objs: Any, bulk: bool = True) -> None:
157
165
  self._check_fk_val()
158
166
  self._remove_prefetched_objects()
159
167
 
160
- def check_and_update_obj(obj):
168
+ def check_and_update_obj(obj: Any) -> None:
161
169
  if not isinstance(obj, self.model):
162
170
  raise TypeError(
163
171
  f"'{self.model._meta.object_name}' instance expected, got {obj!r}"
@@ -185,22 +193,22 @@ class ReverseManyToOneManager(BaseRelatedManager):
185
193
  check_and_update_obj(obj)
186
194
  obj.save()
187
195
 
188
- def create(self, **kwargs):
196
+ def create(self, **kwargs: Any) -> Any:
189
197
  self._check_fk_val()
190
198
  kwargs[self.field.name] = self.instance
191
199
  return self.base_queryset_class(model=self.model).create(**kwargs)
192
200
 
193
- def get_or_create(self, **kwargs):
201
+ def get_or_create(self, **kwargs: Any) -> tuple[Any, bool]:
194
202
  self._check_fk_val()
195
203
  kwargs[self.field.name] = self.instance
196
204
  return self.base_queryset_class(model=self.model).get_or_create(**kwargs)
197
205
 
198
- def update_or_create(self, **kwargs):
206
+ def update_or_create(self, **kwargs: Any) -> tuple[Any, bool]:
199
207
  self._check_fk_val()
200
208
  kwargs[self.field.name] = self.instance
201
209
  return self.base_queryset_class(model=self.model).update_or_create(**kwargs)
202
210
 
203
- def remove(self, *objs, bulk=True):
211
+ def remove(self, *objs: Any, bulk: bool = True) -> None:
204
212
  # remove() is only provided if the ForeignKey can have a value of null
205
213
  if not self.allow_null:
206
214
  raise AttributeError(
@@ -226,7 +234,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
226
234
  )
227
235
  self._clear(self.query.filter(id__in=old_ids), bulk)
228
236
 
229
- def clear(self, *, bulk=True):
237
+ def clear(self, *, bulk: bool = True) -> None:
230
238
  # clear() is only provided if the ForeignKey can have a value of null
231
239
  if not self.allow_null:
232
240
  raise AttributeError(
@@ -236,7 +244,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
236
244
  self._check_fk_val()
237
245
  self._clear(self.query, bulk)
238
246
 
239
- def _clear(self, queryset, bulk):
247
+ def _clear(self, queryset: QuerySet, bulk: bool) -> None:
240
248
  self._remove_prefetched_objects()
241
249
  if bulk:
242
250
  # `QuerySet.update()` is intrinsically atomic.
@@ -247,7 +255,7 @@ class ReverseManyToOneManager(BaseRelatedManager):
247
255
  setattr(obj, self.field.name, None)
248
256
  obj.save(update_fields=[self.field.name])
249
257
 
250
- def set(self, objs, *, bulk=True, clear=False):
258
+ def set(self, objs: Any, *, bulk: bool = True, clear: bool = False) -> None:
251
259
  self._check_fk_val()
252
260
  # Force evaluation of `objs` in case it's a queryset whose value
253
261
  # could be affected by `manager.clear()`. Refs #19816.
@@ -286,7 +294,7 @@ class BaseManyToManyManager(BaseRelatedManager):
286
294
  - symmetrical (for forward relations)
287
295
  """
288
296
 
289
- def __init__(self, instance, rel):
297
+ def __init__(self, instance: Any, rel: Any):
290
298
  self.instance = instance
291
299
  self.through = rel.through
292
300
  # Subclasses must set model before calling super().__init__
@@ -315,12 +323,12 @@ class BaseManyToManyManager(BaseRelatedManager):
315
323
  "a many-to-many relationship can be used."
316
324
  )
317
325
 
318
- def _apply_rel_filters(self, queryset):
326
+ def _apply_rel_filters(self, queryset: QuerySet) -> QuerySet:
319
327
  """Filter the queryset for the instance this manager is bound to."""
320
328
  queryset._defer_next_filter = True
321
329
  return queryset._next_is_sticky().filter(**self.core_filters)
322
330
 
323
- def _remove_prefetched_objects(self):
331
+ def _remove_prefetched_objects(self) -> None:
324
332
  try:
325
333
  self.instance._prefetched_objects_cache.pop(self.prefetch_cache_name)
326
334
  except (AttributeError, KeyError):
@@ -333,7 +341,9 @@ class BaseManyToManyManager(BaseRelatedManager):
333
341
  queryset = self.base_queryset_class(model=self.model)
334
342
  return self._apply_rel_filters(queryset)
335
343
 
336
- def get_prefetch_queryset(self, instances, queryset=None):
344
+ def get_prefetch_queryset(
345
+ self, instances: Any, queryset: QuerySet | None = None
346
+ ) -> tuple[QuerySet, Any, Any, bool, str, bool]:
337
347
  if queryset is None:
338
348
  queryset = self.base_queryset_class(model=self.model)
339
349
 
@@ -367,7 +377,7 @@ class BaseManyToManyManager(BaseRelatedManager):
367
377
  False,
368
378
  )
369
379
 
370
- def clear(self):
380
+ def clear(self) -> None:
371
381
  with transaction.atomic(savepoint=False):
372
382
  self._remove_prefetched_objects()
373
383
  filters = self._build_remove_filters(
@@ -375,7 +385,13 @@ class BaseManyToManyManager(BaseRelatedManager):
375
385
  )
376
386
  self.through.query.filter(filters).delete()
377
387
 
378
- def set(self, objs, *, clear=False, through_defaults=None):
388
+ def set(
389
+ self,
390
+ objs: Any,
391
+ *,
392
+ clear: bool = False,
393
+ through_defaults: dict[str, Any] | None = None,
394
+ ) -> None:
379
395
  # Force evaluation of `objs` in case it's a queryset whose value
380
396
  # could be affected by `manager.clear()`. Refs #19816.
381
397
  objs = tuple(objs)
@@ -406,12 +422,16 @@ class BaseManyToManyManager(BaseRelatedManager):
406
422
  self.remove(*old_ids)
407
423
  self.add(*new_objs, through_defaults=through_defaults)
408
424
 
409
- def create(self, *, through_defaults=None, **kwargs):
425
+ def create(
426
+ self, *, through_defaults: dict[str, Any] | None = None, **kwargs: Any
427
+ ) -> Any:
410
428
  new_obj = self.base_queryset_class(model=self.model).create(**kwargs)
411
429
  self.add(new_obj, through_defaults=through_defaults)
412
430
  return new_obj
413
431
 
414
- def get_or_create(self, *, through_defaults=None, **kwargs):
432
+ def get_or_create(
433
+ self, *, through_defaults: dict[str, Any] | None = None, **kwargs: Any
434
+ ) -> tuple[Any, bool]:
415
435
  obj, created = self.base_queryset_class(model=self.model).get_or_create(
416
436
  **kwargs
417
437
  )
@@ -421,7 +441,9 @@ class BaseManyToManyManager(BaseRelatedManager):
421
441
  self.add(obj, through_defaults=through_defaults)
422
442
  return obj, created
423
443
 
424
- def update_or_create(self, *, through_defaults=None, **kwargs):
444
+ def update_or_create(
445
+ self, *, through_defaults: dict[str, Any] | None = None, **kwargs: Any
446
+ ) -> tuple[Any, bool]:
425
447
  obj, created = self.base_queryset_class(model=self.model).update_or_create(
426
448
  **kwargs
427
449
  )
@@ -431,7 +453,7 @@ class BaseManyToManyManager(BaseRelatedManager):
431
453
  self.add(obj, through_defaults=through_defaults)
432
454
  return obj, created
433
455
 
434
- def _get_target_ids(self, target_field_name, objs):
456
+ def _get_target_ids(self, target_field_name: str, objs: Any) -> set[Any]:
435
457
  """Return the set of ids of `objs` that the target field references."""
436
458
  from plain.models import Model
437
459
 
@@ -453,7 +475,9 @@ class BaseManyToManyManager(BaseRelatedManager):
453
475
  target_ids.add(target_field.get_prep_value(obj))
454
476
  return target_ids
455
477
 
456
- def _get_missing_target_ids(self, source_field_name, target_field_name, target_ids):
478
+ def _get_missing_target_ids(
479
+ self, source_field_name: str, target_field_name: str, target_ids: set[Any]
480
+ ) -> set[Any]:
457
481
  """Return the subset of ids of `objs` that aren't already assigned to this relationship."""
458
482
  vals = self.through.query.values_list(target_field_name, flat=True).filter(
459
483
  **{
@@ -464,8 +488,12 @@ class BaseManyToManyManager(BaseRelatedManager):
464
488
  return target_ids.difference(vals)
465
489
 
466
490
  def _add_items(
467
- self, source_field_name, target_field_name, *objs, through_defaults=None
468
- ):
491
+ self,
492
+ source_field_name: str,
493
+ target_field_name: str,
494
+ *objs: Any,
495
+ through_defaults: dict[str, Any] | None = None,
496
+ ) -> None:
469
497
  if not objs:
470
498
  return
471
499
 
@@ -490,7 +518,9 @@ class BaseManyToManyManager(BaseRelatedManager):
490
518
  ],
491
519
  )
492
520
 
493
- def _remove_items(self, source_field_name, target_field_name, *objs):
521
+ def _remove_items(
522
+ self, source_field_name: str, target_field_name: str, *objs: Any
523
+ ) -> None:
494
524
  if not objs:
495
525
  return
496
526
 
@@ -515,13 +545,13 @@ class BaseManyToManyManager(BaseRelatedManager):
515
545
  self.through.query.filter(filters).delete()
516
546
 
517
547
  # Subclasses must implement these methods:
518
- def _build_remove_filters(self, removed_vals):
548
+ def _build_remove_filters(self, removed_vals: Any) -> Any:
519
549
  raise NotImplementedError
520
550
 
521
- def add(self, *objs, through_defaults=None):
551
+ def add(self, *objs: Any, through_defaults: dict[str, Any] | None = None) -> None:
522
552
  raise NotImplementedError
523
553
 
524
- def remove(self, *objs):
554
+ def remove(self, *objs: Any) -> None:
525
555
  raise NotImplementedError
526
556
 
527
557
 
@@ -532,7 +562,7 @@ class ForwardManyToManyManager(BaseManyToManyManager):
532
562
  This manager adds behaviors specific to many-to-many relations.
533
563
  """
534
564
 
535
- def __init__(self, instance, rel):
565
+ def __init__(self, instance: Any, rel: Any):
536
566
  # Set required attributes before calling super().__init__
537
567
  self.model = rel.model
538
568
  self.query_field_name = rel.field.related_query_name()
@@ -543,7 +573,7 @@ class ForwardManyToManyManager(BaseManyToManyManager):
543
573
 
544
574
  super().__init__(instance, rel)
545
575
 
546
- def _build_remove_filters(self, removed_vals):
576
+ def _build_remove_filters(self, removed_vals: Any) -> Any:
547
577
  filters = Q.create([(self.source_field_name, self.related_val)])
548
578
  # No need to add a subquery condition if removed_vals is a QuerySet without
549
579
  # filters.
@@ -551,17 +581,19 @@ class ForwardManyToManyManager(BaseManyToManyManager):
551
581
  not isinstance(removed_vals, QuerySet) or removed_vals._has_filters()
552
582
  )
553
583
  if removed_vals_filters:
554
- filters &= Q.create([(f"{self.target_field_name}__in", removed_vals)])
584
+ filters = filters & Q.create( # type: ignore[unsupported-operator]
585
+ [(f"{self.target_field_name}__in", removed_vals)]
586
+ )
555
587
  if self.symmetrical:
556
588
  symmetrical_filters = Q.create([(self.target_field_name, self.related_val)])
557
589
  if removed_vals_filters:
558
- symmetrical_filters &= Q.create(
590
+ symmetrical_filters = symmetrical_filters & Q.create( # type: ignore[unsupported-operator]
559
591
  [(f"{self.source_field_name}__in", removed_vals)]
560
592
  )
561
- filters |= symmetrical_filters
593
+ filters = filters | symmetrical_filters # type: ignore[unsupported-operator]
562
594
  return filters
563
595
 
564
- def add(self, *objs, through_defaults=None):
596
+ def add(self, *objs: Any, through_defaults: dict[str, Any] | None = None) -> None:
565
597
  self._remove_prefetched_objects()
566
598
  with transaction.atomic(savepoint=False):
567
599
  self._add_items(
@@ -580,7 +612,7 @@ class ForwardManyToManyManager(BaseManyToManyManager):
580
612
  through_defaults=through_defaults,
581
613
  )
582
614
 
583
- def remove(self, *objs):
615
+ def remove(self, *objs: Any) -> None:
584
616
  self._remove_prefetched_objects()
585
617
  self._remove_items(self.source_field_name, self.target_field_name, *objs)
586
618
 
@@ -592,7 +624,7 @@ class ReverseManyToManyManager(BaseManyToManyManager):
592
624
  This manager adds behaviors specific to many-to-many relations.
593
625
  """
594
626
 
595
- def __init__(self, instance, rel):
627
+ def __init__(self, instance: Any, rel: Any):
596
628
  # Set required attributes before calling super().__init__
597
629
  self.model = rel.related_model
598
630
  self.query_field_name = rel.field.name
@@ -603,7 +635,7 @@ class ReverseManyToManyManager(BaseManyToManyManager):
603
635
 
604
636
  super().__init__(instance, rel)
605
637
 
606
- def _build_remove_filters(self, removed_vals):
638
+ def _build_remove_filters(self, removed_vals: Any) -> Any:
607
639
  filters = Q.create([(self.source_field_name, self.related_val)])
608
640
  # No need to add a subquery condition if removed_vals is a QuerySet without
609
641
  # filters.
@@ -611,11 +643,13 @@ class ReverseManyToManyManager(BaseManyToManyManager):
611
643
  not isinstance(removed_vals, QuerySet) or removed_vals._has_filters()
612
644
  )
613
645
  if removed_vals_filters:
614
- filters &= Q.create([(f"{self.target_field_name}__in", removed_vals)])
646
+ filters = filters & Q.create( # type: ignore[unsupported-operator]
647
+ [(f"{self.target_field_name}__in", removed_vals)]
648
+ )
615
649
  # Note: reverse relations are never symmetrical, so no symmetrical logic here
616
650
  return filters
617
651
 
618
- def add(self, *objs, through_defaults=None):
652
+ def add(self, *objs: Any, through_defaults: dict[str, Any] | None = None) -> None:
619
653
  self._remove_prefetched_objects()
620
654
  with transaction.atomic(savepoint=False):
621
655
  self._add_items(
@@ -626,6 +660,6 @@ class ReverseManyToManyManager(BaseManyToManyManager):
626
660
  )
627
661
  # Reverse relations are never symmetrical, so no mirror entry logic
628
662
 
629
- def remove(self, *objs):
663
+ def remove(self, *objs: Any) -> None:
630
664
  self._remove_prefetched_objects()
631
665
  self._remove_items(self.source_field_name, self.target_field_name, *objs)