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
plain/models/forms.py CHANGED
@@ -3,7 +3,10 @@ Helper functions for creating Form classes from Plain models
3
3
  and database field objects.
4
4
  """
5
5
 
6
+ from __future__ import annotations
7
+
6
8
  from itertools import chain
9
+ from typing import TYPE_CHECKING, Any
7
10
 
8
11
  from plain.exceptions import (
9
12
  NON_FIELD_ERRORS,
@@ -15,6 +18,9 @@ from plain.forms.fields import ChoiceField, Field
15
18
  from plain.forms.forms import BaseForm, DeclarativeFieldsMetaclass
16
19
  from plain.models.exceptions import FieldError
17
20
 
21
+ if TYPE_CHECKING:
22
+ from plain.models.fields import Field as ModelField
23
+
18
24
  __all__ = (
19
25
  "ModelForm",
20
26
  "BaseModelForm",
@@ -25,18 +31,22 @@ __all__ = (
25
31
  )
26
32
 
27
33
 
28
- def construct_instance(form, instance, fields=None):
34
+ def construct_instance(
35
+ form: BaseModelForm,
36
+ instance: Any,
37
+ fields: list[str] | tuple[str, ...] | None = None,
38
+ ) -> Any:
29
39
  """
30
40
  Construct and return a model instance from the bound ``form``'s
31
41
  ``cleaned_data``, but do not save the returned instance to the database.
32
42
  """
33
43
  from plain import models
34
44
 
35
- opts = instance._meta
45
+ meta = instance._model_meta
36
46
 
37
47
  cleaned_data = form.cleaned_data
38
48
  file_field_list = []
39
- for f in opts.fields:
49
+ for f in meta.fields:
40
50
  if isinstance(f, models.PrimaryKeyField) or f.name not in cleaned_data:
41
51
  continue
42
52
  if fields is not None and f.name not in fields:
@@ -65,7 +75,9 @@ def construct_instance(form, instance, fields=None):
65
75
  # ModelForms #################################################################
66
76
 
67
77
 
68
- def model_to_dict(instance, fields=None):
78
+ def model_to_dict(
79
+ instance: Any, fields: list[str] | tuple[str, ...] | None = None
80
+ ) -> dict[str, Any]:
69
81
  """
70
82
  Return a dict containing the data in ``instance`` suitable for passing as
71
83
  a Form's ``initial`` keyword argument.
@@ -73,9 +85,9 @@ def model_to_dict(instance, fields=None):
73
85
  ``fields`` is an optional list of field names. If provided, return only the
74
86
  named.
75
87
  """
76
- opts = instance._meta
88
+ meta = instance._model_meta
77
89
  data = {}
78
- for f in chain(opts.concrete_fields, opts.many_to_many):
90
+ for f in chain(meta.concrete_fields, meta.many_to_many):
79
91
  if fields is not None and f.name not in fields:
80
92
  continue
81
93
  data[f.name] = f.value_from_object(instance)
@@ -83,12 +95,12 @@ def model_to_dict(instance, fields=None):
83
95
 
84
96
 
85
97
  def fields_for_model(
86
- model,
87
- fields=None,
88
- formfield_callback=None,
89
- error_messages=None,
90
- field_classes=None,
91
- ):
98
+ model: type[Any],
99
+ fields: list[str] | tuple[str, ...] | None = None,
100
+ formfield_callback: Any = None,
101
+ error_messages: dict[str, Any] | None = None,
102
+ field_classes: dict[str, type[Field]] | None = None,
103
+ ) -> dict[str, Field | None]:
92
104
  """
93
105
  Return a dictionary containing form fields for the given model.
94
106
 
@@ -106,9 +118,9 @@ def fields_for_model(
106
118
  """
107
119
  field_dict = {}
108
120
  ignored = []
109
- opts = model._meta
121
+ meta = model._model_meta
110
122
 
111
- for f in sorted(chain(opts.concrete_fields, opts.many_to_many)):
123
+ for f in sorted(chain(meta.concrete_fields, meta.many_to_many)):
112
124
  if fields is not None and f.name not in fields:
113
125
  continue
114
126
 
@@ -135,17 +147,28 @@ def fields_for_model(
135
147
 
136
148
 
137
149
  class ModelFormOptions:
138
- def __init__(self, options=None):
139
- self.model = getattr(options, "model", None)
140
- self.fields = getattr(options, "fields", None)
141
- self.error_messages = getattr(options, "error_messages", None)
142
- self.field_classes = getattr(options, "field_classes", None)
143
- self.formfield_callback = getattr(options, "formfield_callback", None)
150
+ def __init__(self, options: Any = None) -> None:
151
+ self.model: type[Any] | None = getattr(options, "model", None)
152
+ self.fields: list[str] | tuple[str, ...] | None = getattr(
153
+ options, "fields", None
154
+ )
155
+ self.error_messages: dict[str, Any] | None = getattr(
156
+ options, "error_messages", None
157
+ )
158
+ self.field_classes: dict[str, type[Field]] | None = getattr(
159
+ options, "field_classes", None
160
+ )
161
+ self.formfield_callback: Any = getattr(options, "formfield_callback", None)
144
162
 
145
163
 
146
164
  class ModelFormMetaclass(DeclarativeFieldsMetaclass):
147
- def __new__(mcs, name, bases, attrs):
148
- new_class = super().__new__(mcs, name, bases, attrs)
165
+ def __new__(
166
+ mcs: type[ModelFormMetaclass],
167
+ name: str,
168
+ bases: tuple[type, ...],
169
+ attrs: dict[str, Any],
170
+ ) -> type[BaseModelForm]:
171
+ new_class = super().__new__(mcs, name, bases, attrs) # type: ignore[invalid-super-argument]
149
172
 
150
173
  if bases == (BaseModelForm,):
151
174
  return new_class
@@ -203,12 +226,12 @@ class BaseModelForm(BaseForm):
203
226
  def __init__(
204
227
  self,
205
228
  *,
206
- request,
207
- auto_id="id_%s",
208
- prefix=None,
209
- initial=None,
210
- instance=None,
211
- ):
229
+ request: Any,
230
+ auto_id: str = "id_%s",
231
+ prefix: str | None = None,
232
+ initial: dict[str, Any] | None = None,
233
+ instance: Any = None,
234
+ ) -> None:
212
235
  opts = self._meta
213
236
  if opts.model is None:
214
237
  raise ValueError("ModelForm has no model class specified.")
@@ -233,7 +256,7 @@ class BaseModelForm(BaseForm):
233
256
  initial=object_data,
234
257
  )
235
258
 
236
- def _get_validation_exclusions(self):
259
+ def _get_validation_exclusions(self) -> set[str]:
237
260
  """
238
261
  For backwards-compatibility, exclude several types of fields from model
239
262
  validation. See tickets #12507, #12521, #12553.
@@ -241,7 +264,7 @@ class BaseModelForm(BaseForm):
241
264
  exclude = set()
242
265
  # Build up a list of fields that should be excluded from model field
243
266
  # validation and unique checks.
244
- for f in self.instance._meta.fields:
267
+ for f in self.instance._model_meta.fields:
245
268
  field = f.name
246
269
  # Exclude fields that aren't on the form. The developer may be
247
270
  # adding these values to the model after form validation.
@@ -276,11 +299,11 @@ class BaseModelForm(BaseForm):
276
299
  exclude.add(f.name)
277
300
  return exclude
278
301
 
279
- def clean(self):
302
+ def clean(self) -> dict[str, Any]:
280
303
  self._validate_unique = True
281
304
  return self.cleaned_data
282
305
 
283
- def _update_errors(self, errors):
306
+ def _update_errors(self, errors: ValidationError) -> None:
284
307
  # Override any validation error messages defined at the model level
285
308
  # with those defined at the form level.
286
309
  opts = self._meta
@@ -313,7 +336,7 @@ class BaseModelForm(BaseForm):
313
336
 
314
337
  self.add_error(None, errors)
315
338
 
316
- def _post_clean(self):
339
+ def _post_clean(self) -> None:
317
340
  opts = self._meta
318
341
 
319
342
  exclude = self._get_validation_exclusions()
@@ -332,7 +355,7 @@ class BaseModelForm(BaseForm):
332
355
  if self._validate_unique:
333
356
  self.validate_unique()
334
357
 
335
- def validate_unique(self):
358
+ def validate_unique(self) -> None:
336
359
  """
337
360
  Call the instance's validate_unique() method and update the form's
338
361
  validation errors if any were raised.
@@ -343,15 +366,15 @@ class BaseModelForm(BaseForm):
343
366
  except ValidationError as e:
344
367
  self._update_errors(e)
345
368
 
346
- def _save_m2m(self):
369
+ def _save_m2m(self) -> None:
347
370
  """
348
371
  Save the many-to-many fields and generic relations for this form.
349
372
  """
350
373
  cleaned_data = self.cleaned_data
351
374
  fields = self._meta.fields
352
- opts = self.instance._meta
375
+ meta = self.instance._model_meta
353
376
 
354
- for f in opts.many_to_many:
377
+ for f in meta.many_to_many:
355
378
  if not hasattr(f, "save_form_data"):
356
379
  continue
357
380
  if fields and f.name not in fields:
@@ -359,7 +382,7 @@ class BaseModelForm(BaseForm):
359
382
  if f.name in cleaned_data:
360
383
  f.save_form_data(self.instance, cleaned_data[f.name])
361
384
 
362
- def save(self, commit=True):
385
+ def save(self, commit: bool = True) -> Any:
363
386
  """
364
387
  Save this form's self.instance object if commit=True. Otherwise, add
365
388
  a save_m2m() method to the form which can be called after the instance
@@ -368,7 +391,7 @@ class BaseModelForm(BaseForm):
368
391
  if self.errors:
369
392
  raise ValueError(
370
393
  "The {} could not be {} because the data didn't validate.".format(
371
- self.instance._meta.object_name,
394
+ self.instance.model_options.object_name,
372
395
  "created" if self.instance._state.adding else "changed",
373
396
  )
374
397
  )
@@ -391,28 +414,28 @@ class ModelForm(BaseModelForm, metaclass=ModelFormMetaclass):
391
414
 
392
415
 
393
416
  class ModelChoiceIteratorValue:
394
- def __init__(self, value, instance):
417
+ def __init__(self, value: Any, instance: Any) -> None:
395
418
  self.value = value
396
419
  self.instance = instance
397
420
 
398
- def __str__(self):
421
+ def __str__(self) -> str:
399
422
  return str(self.value)
400
423
 
401
- def __hash__(self):
424
+ def __hash__(self) -> int:
402
425
  return hash(self.value)
403
426
 
404
- def __eq__(self, other):
427
+ def __eq__(self, other: object) -> bool:
405
428
  if isinstance(other, ModelChoiceIteratorValue):
406
429
  other = other.value
407
430
  return self.value == other
408
431
 
409
432
 
410
433
  class ModelChoiceIterator:
411
- def __init__(self, field):
434
+ def __init__(self, field: ModelChoiceField) -> None:
412
435
  self.field = field
413
436
  self.queryset = field.queryset
414
437
 
415
- def __iter__(self):
438
+ def __iter__(self) -> Any:
416
439
  if self.field.empty_label is not None:
417
440
  yield ("", self.field.empty_label)
418
441
  queryset = self.queryset
@@ -422,16 +445,16 @@ class ModelChoiceIterator:
422
445
  for obj in queryset:
423
446
  yield self.choice(obj)
424
447
 
425
- def __len__(self):
448
+ def __len__(self) -> int:
426
449
  # count() adds a query but uses less memory since the QuerySet results
427
450
  # won't be cached. In most cases, the choices will only be iterated on,
428
451
  # and __len__() won't be called.
429
452
  return self.queryset.count() + (1 if self.field.empty_label is not None else 0)
430
453
 
431
- def __bool__(self):
454
+ def __bool__(self) -> bool:
432
455
  return self.field.empty_label is not None or self.queryset.exists()
433
456
 
434
- def choice(self, obj):
457
+ def choice(self, obj: Any) -> tuple[ModelChoiceIteratorValue, str]:
435
458
  return (
436
459
  ModelChoiceIteratorValue(self.field.prepare_value(obj), obj),
437
460
  str(obj),
@@ -450,13 +473,13 @@ class ModelChoiceField(ChoiceField):
450
473
 
451
474
  def __init__(
452
475
  self,
453
- queryset,
476
+ queryset: Any,
454
477
  *,
455
- empty_label="---------",
456
- required=True,
457
- initial=None,
458
- **kwargs,
459
- ):
478
+ empty_label: str | None = "---------",
479
+ required: bool = True,
480
+ initial: Any = None,
481
+ **kwargs: Any,
482
+ ) -> None:
460
483
  # Call Field instead of ChoiceField __init__() because we don't need
461
484
  # ChoiceField.__init__().
462
485
  Field.__init__(
@@ -471,22 +494,22 @@ class ModelChoiceField(ChoiceField):
471
494
  self.empty_label = empty_label
472
495
  self.queryset = queryset
473
496
 
474
- def __deepcopy__(self, memo):
497
+ def __deepcopy__(self, memo: dict[int, Any]) -> ModelChoiceField:
475
498
  result = super(ChoiceField, self).__deepcopy__(memo)
476
499
  # Need to force a new ModelChoiceIterator to be created, bug #11183
477
500
  if self.queryset is not None:
478
501
  result.queryset = self.queryset.all()
479
502
  return result
480
503
 
481
- def _get_queryset(self):
504
+ def _get_queryset(self) -> Any:
482
505
  return self._queryset
483
506
 
484
- def _set_queryset(self, queryset):
507
+ def _set_queryset(self, queryset: Any) -> None:
485
508
  self._queryset = None if queryset is None else queryset.all()
486
509
 
487
510
  queryset = property(_get_queryset, _set_queryset)
488
511
 
489
- def _get_choices(self):
512
+ def _get_choices(self) -> ModelChoiceIterator:
490
513
  # If self._choices is set, then somebody must have manually set
491
514
  # the property self.choices. In this case, just return self._choices.
492
515
  if hasattr(self, "_choices"):
@@ -503,12 +526,12 @@ class ModelChoiceField(ChoiceField):
503
526
 
504
527
  choices = property(_get_choices, ChoiceField._set_choices)
505
528
 
506
- def prepare_value(self, value):
507
- if hasattr(value, "_meta"):
529
+ def prepare_value(self, value: Any) -> Any:
530
+ if hasattr(value, "_model_meta"):
508
531
  return value.id
509
532
  return super().prepare_value(value)
510
533
 
511
- def to_python(self, value):
534
+ def to_python(self, value: Any) -> Any:
512
535
  if value in self.empty_values:
513
536
  return None
514
537
  try:
@@ -524,10 +547,10 @@ class ModelChoiceField(ChoiceField):
524
547
  )
525
548
  return value
526
549
 
527
- def validate(self, value):
550
+ def validate(self, value: Any) -> None:
528
551
  return Field.validate(self, value)
529
552
 
530
- def has_changed(self, initial, data):
553
+ def has_changed(self, initial: Any, data: Any) -> bool:
531
554
  initial_value = initial if initial is not None else ""
532
555
  data_value = data if data is not None else ""
533
556
  return str(self.prepare_value(initial_value)) != str(data_value)
@@ -542,15 +565,15 @@ class ModelMultipleChoiceField(ModelChoiceField):
542
565
  "invalid_id_value": "'%(id)s' is not a valid value.",
543
566
  }
544
567
 
545
- def __init__(self, queryset, **kwargs):
568
+ def __init__(self, queryset: Any, **kwargs: Any) -> None:
546
569
  super().__init__(queryset, empty_label=None, **kwargs)
547
570
 
548
- def to_python(self, value):
571
+ def to_python(self, value: Any) -> list[Any]:
549
572
  if not value:
550
573
  return []
551
574
  return list(self._check_values(value))
552
575
 
553
- def clean(self, value):
576
+ def clean(self, value: Any) -> Any:
554
577
  value = self.prepare_value(value)
555
578
  if self.required and not value:
556
579
  raise ValidationError(self.error_messages["required"], code="required")
@@ -567,7 +590,7 @@ class ModelMultipleChoiceField(ModelChoiceField):
567
590
  self.run_validators(value)
568
591
  return qs
569
592
 
570
- def _check_values(self, value):
593
+ def _check_values(self, value: Any) -> Any:
571
594
  """
572
595
  Given a list of possible PK values, return a QuerySet of the
573
596
  corresponding objects. Raise a ValidationError if a given value is
@@ -603,17 +626,17 @@ class ModelMultipleChoiceField(ModelChoiceField):
603
626
  )
604
627
  return qs
605
628
 
606
- def prepare_value(self, value):
629
+ def prepare_value(self, value: Any) -> Any:
607
630
  if (
608
631
  hasattr(value, "__iter__")
609
632
  and not isinstance(value, str)
610
- and not hasattr(value, "_meta")
633
+ and not hasattr(value, "_model_meta")
611
634
  ):
612
635
  prepare_value = super().prepare_value
613
636
  return [prepare_value(v) for v in value]
614
637
  return super().prepare_value(value)
615
638
 
616
- def has_changed(self, initial, data):
639
+ def has_changed(self, initial: Any, data: Any) -> bool:
617
640
  if initial is None:
618
641
  initial = []
619
642
  if data is None:
@@ -624,13 +647,16 @@ class ModelMultipleChoiceField(ModelChoiceField):
624
647
  data_set = {str(value) for value in data}
625
648
  return data_set != initial_set
626
649
 
627
- def value_from_form_data(self, data, files, html_name):
650
+ def value_from_form_data(self, data: Any, files: Any, html_name: str) -> Any:
628
651
  return data.getlist(html_name)
629
652
 
630
653
 
631
654
  def modelfield_to_formfield(
632
- modelfield, form_class=None, choices_form_class=None, **kwargs
633
- ):
655
+ modelfield: ModelField,
656
+ form_class: type[Field] | None = None,
657
+ choices_form_class: type[Field] | None = None,
658
+ **kwargs: Any,
659
+ ) -> Field | None:
634
660
  defaults = {
635
661
  "required": modelfield.required,
636
662
  }
@@ -694,7 +720,7 @@ def modelfield_to_formfield(
694
720
  **defaults,
695
721
  )
696
722
 
697
- if issubclass(modelfield.__class__, models.fields.PositiveIntegerRelDbTypeMixin):
723
+ if issubclass(modelfield.__class__, models.fields.PositiveIntegerRelDbTypeMixin): # type: ignore[attr-defined]
698
724
  return fields.IntegerField(min_value=0, **defaults)
699
725
 
700
726
  if isinstance(modelfield, models.TextField):
@@ -721,7 +747,7 @@ def modelfield_to_formfield(
721
747
 
722
748
  if isinstance(modelfield, models.ForeignKey):
723
749
  return ModelChoiceField(
724
- queryset=modelfield.remote_field.model.query,
750
+ queryset=modelfield.remote_field.model.query, # type: ignore[attr-defined]
725
751
  **defaults,
726
752
  )
727
753
 
@@ -1,11 +1,19 @@
1
1
  """Database functions that do comparisons or type conversions."""
2
2
 
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
3
7
  from plain.models.db import NotSupportedError
4
8
  from plain.models.expressions import Func, Value
5
- from plain.models.fields import TextField
9
+ from plain.models.fields import Field, TextField
6
10
  from plain.models.fields.json import JSONField
7
11
  from plain.utils.regex_helper import _lazy_re_compile
8
12
 
13
+ if TYPE_CHECKING:
14
+ from plain.models.backends.base.base import BaseDatabaseWrapper
15
+ from plain.models.sql.compiler import SQLCompiler
16
+
9
17
 
10
18
  class Cast(Func):
11
19
  """Coerce an expression to a new field type."""
@@ -13,14 +21,24 @@ class Cast(Func):
13
21
  function = "CAST"
14
22
  template = "%(function)s(%(expressions)s AS %(db_type)s)"
15
23
 
16
- def __init__(self, expression, output_field):
24
+ def __init__(self, expression: Any, output_field: Field) -> None:
17
25
  super().__init__(expression, output_field=output_field)
18
26
 
19
- def as_sql(self, compiler, connection, **extra_context):
27
+ def as_sql(
28
+ self,
29
+ compiler: SQLCompiler,
30
+ connection: BaseDatabaseWrapper,
31
+ **extra_context: Any,
32
+ ) -> tuple[str, tuple[Any, ...]]:
20
33
  extra_context["db_type"] = self.output_field.cast_db_type(connection)
21
34
  return super().as_sql(compiler, connection, **extra_context)
22
35
 
23
- def as_sqlite(self, compiler, connection, **extra_context):
36
+ def as_sqlite(
37
+ self,
38
+ compiler: SQLCompiler,
39
+ connection: BaseDatabaseWrapper,
40
+ **extra_context: Any,
41
+ ) -> tuple[str, tuple[Any, ...]]:
24
42
  db_type = self.output_field.db_type(connection)
25
43
  if db_type in {"datetime", "time"}:
26
44
  # Use strftime as datetime/time don't keep fractional seconds.
@@ -38,18 +56,28 @@ class Cast(Func):
38
56
  )
39
57
  return self.as_sql(compiler, connection, **extra_context)
40
58
 
41
- def as_mysql(self, compiler, connection, **extra_context):
59
+ def as_mysql(
60
+ self,
61
+ compiler: SQLCompiler,
62
+ connection: BaseDatabaseWrapper,
63
+ **extra_context: Any,
64
+ ) -> tuple[str, tuple[Any, ...]]:
42
65
  template = None
43
66
  output_type = self.output_field.get_internal_type()
44
67
  # MySQL doesn't support explicit cast to float.
45
68
  if output_type == "FloatField":
46
69
  template = "(%(expressions)s + 0.0)"
47
70
  # MariaDB doesn't support explicit cast to JSON.
48
- elif output_type == "JSONField" and connection.mysql_is_mariadb:
71
+ elif output_type == "JSONField" and connection.mysql_is_mariadb: # type: ignore[attr-defined]
49
72
  template = "JSON_EXTRACT(%(expressions)s, '$')"
50
73
  return self.as_sql(compiler, connection, template=template, **extra_context)
51
74
 
52
- def as_postgresql(self, compiler, connection, **extra_context):
75
+ def as_postgresql(
76
+ self,
77
+ compiler: SQLCompiler,
78
+ connection: BaseDatabaseWrapper,
79
+ **extra_context: Any,
80
+ ) -> tuple[str, tuple[Any, ...]]:
53
81
  # CAST would be valid too, but the :: shortcut syntax is more readable.
54
82
  # 'expressions' is wrapped in parentheses in case it's a complex
55
83
  # expression.
@@ -66,13 +94,13 @@ class Coalesce(Func):
66
94
 
67
95
  function = "COALESCE"
68
96
 
69
- def __init__(self, *expressions, **extra):
97
+ def __init__(self, *expressions: Any, **extra: Any) -> None:
70
98
  if len(expressions) < 2:
71
99
  raise ValueError("Coalesce must take at least two expressions")
72
100
  super().__init__(*expressions, **extra)
73
101
 
74
102
  @property
75
- def empty_result_set_value(self):
103
+ def empty_result_set_value(self) -> Any:
76
104
  for expression in self.get_source_expressions():
77
105
  result = expression.empty_result_set_value
78
106
  if result is NotImplemented or result is not None:
@@ -87,13 +115,18 @@ class Collate(Func):
87
115
  # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
88
116
  collation_re = _lazy_re_compile(r"^[\w\-]+$")
89
117
 
90
- def __init__(self, expression, collation):
118
+ def __init__(self, expression: Any, collation: str) -> None:
91
119
  if not (collation and self.collation_re.match(collation)):
92
120
  raise ValueError(f"Invalid collation name: {collation!r}.")
93
121
  self.collation = collation
94
122
  super().__init__(expression)
95
123
 
96
- def as_sql(self, compiler, connection, **extra_context):
124
+ def as_sql(
125
+ self,
126
+ compiler: SQLCompiler,
127
+ connection: BaseDatabaseWrapper,
128
+ **extra_context: Any,
129
+ ) -> tuple[str, tuple[Any, ...]]:
97
130
  extra_context.setdefault("collation", connection.ops.quote_name(self.collation))
98
131
  return super().as_sql(compiler, connection, **extra_context)
99
132
 
@@ -109,12 +142,17 @@ class Greatest(Func):
109
142
 
110
143
  function = "GREATEST"
111
144
 
112
- def __init__(self, *expressions, **extra):
145
+ def __init__(self, *expressions: Any, **extra: Any) -> None:
113
146
  if len(expressions) < 2:
114
147
  raise ValueError("Greatest must take at least two expressions")
115
148
  super().__init__(*expressions, **extra)
116
149
 
117
- def as_sqlite(self, compiler, connection, **extra_context):
150
+ def as_sqlite(
151
+ self,
152
+ compiler: SQLCompiler,
153
+ connection: BaseDatabaseWrapper,
154
+ **extra_context: Any,
155
+ ) -> tuple[str, tuple[Any, ...]]:
118
156
  """Use the MAX function on SQLite."""
119
157
  return super().as_sqlite(compiler, connection, function="MAX", **extra_context)
120
158
 
@@ -123,20 +161,30 @@ class JSONObject(Func):
123
161
  function = "JSON_OBJECT"
124
162
  output_field = JSONField()
125
163
 
126
- def __init__(self, **fields):
164
+ def __init__(self, **fields: Any) -> None:
127
165
  expressions = []
128
166
  for key, value in fields.items():
129
167
  expressions.extend((Value(key), value))
130
168
  super().__init__(*expressions)
131
169
 
132
- def as_sql(self, compiler, connection, **extra_context):
170
+ def as_sql(
171
+ self,
172
+ compiler: SQLCompiler,
173
+ connection: BaseDatabaseWrapper,
174
+ **extra_context: Any,
175
+ ) -> tuple[str, tuple[Any, ...]]:
133
176
  if not connection.features.has_json_object_function:
134
177
  raise NotSupportedError(
135
178
  "JSONObject() is not supported on this database backend."
136
179
  )
137
180
  return super().as_sql(compiler, connection, **extra_context)
138
181
 
139
- def as_postgresql(self, compiler, connection, **extra_context):
182
+ def as_postgresql(
183
+ self,
184
+ compiler: SQLCompiler,
185
+ connection: BaseDatabaseWrapper,
186
+ **extra_context: Any,
187
+ ) -> tuple[str, tuple[Any, ...]]:
140
188
  copy = self.copy()
141
189
  copy.set_source_expressions(
142
190
  [
@@ -163,12 +211,17 @@ class Least(Func):
163
211
 
164
212
  function = "LEAST"
165
213
 
166
- def __init__(self, *expressions, **extra):
214
+ def __init__(self, *expressions: Any, **extra: Any) -> None:
167
215
  if len(expressions) < 2:
168
216
  raise ValueError("Least must take at least two expressions")
169
217
  super().__init__(*expressions, **extra)
170
218
 
171
- def as_sqlite(self, compiler, connection, **extra_context):
219
+ def as_sqlite(
220
+ self,
221
+ compiler: SQLCompiler,
222
+ connection: BaseDatabaseWrapper,
223
+ **extra_context: Any,
224
+ ) -> tuple[str, tuple[Any, ...]]:
172
225
  """Use the MIN function on SQLite."""
173
226
  return super().as_sqlite(compiler, connection, function="MIN", **extra_context)
174
227