plain.models 0.50.0__py3-none-any.whl → 0.51.1__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 (49) hide show
  1. plain/models/CHANGELOG.md +24 -0
  2. plain/models/README.md +26 -42
  3. plain/models/__init__.py +2 -0
  4. plain/models/backends/base/creation.py +2 -2
  5. plain/models/backends/base/introspection.py +8 -4
  6. plain/models/backends/base/schema.py +89 -71
  7. plain/models/backends/base/validation.py +1 -1
  8. plain/models/backends/mysql/compiler.py +1 -1
  9. plain/models/backends/mysql/operations.py +1 -1
  10. plain/models/backends/mysql/schema.py +4 -4
  11. plain/models/backends/postgresql/operations.py +1 -1
  12. plain/models/backends/postgresql/schema.py +3 -3
  13. plain/models/backends/sqlite3/operations.py +1 -1
  14. plain/models/backends/sqlite3/schema.py +61 -50
  15. plain/models/base.py +116 -163
  16. plain/models/cli.py +4 -4
  17. plain/models/constraints.py +14 -9
  18. plain/models/deletion.py +15 -14
  19. plain/models/expressions.py +21 -5
  20. plain/models/fields/__init__.py +20 -16
  21. plain/models/fields/json.py +3 -3
  22. plain/models/fields/related.py +73 -71
  23. plain/models/fields/related_descriptors.py +2 -2
  24. plain/models/fields/related_lookups.py +1 -1
  25. plain/models/fields/related_managers.py +21 -32
  26. plain/models/fields/reverse_related.py +8 -8
  27. plain/models/forms.py +12 -12
  28. plain/models/indexes.py +5 -4
  29. plain/models/meta.py +505 -0
  30. plain/models/migrations/operations/base.py +1 -1
  31. plain/models/migrations/operations/fields.py +6 -6
  32. plain/models/migrations/operations/models.py +18 -16
  33. plain/models/migrations/recorder.py +9 -5
  34. plain/models/migrations/state.py +35 -46
  35. plain/models/migrations/utils.py +1 -1
  36. plain/models/options.py +182 -518
  37. plain/models/preflight.py +7 -5
  38. plain/models/query.py +119 -65
  39. plain/models/query_utils.py +18 -13
  40. plain/models/registry.py +6 -5
  41. plain/models/sql/compiler.py +51 -37
  42. plain/models/sql/query.py +77 -68
  43. plain/models/sql/subqueries.py +4 -4
  44. plain/models/utils.py +4 -1
  45. {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/METADATA +27 -43
  46. {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/RECORD +49 -48
  47. {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/WHEEL +0 -0
  48. {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/entry_points.txt +0 -0
  49. {plain_models-0.50.0.dist-info → plain_models-0.51.1.dist-info}/licenses/LICENSE +0 -0
@@ -117,7 +117,7 @@ class AddField(FieldOperation):
117
117
  from_model = from_state.models_registry.get_model(
118
118
  package_label, self.model_name
119
119
  )
120
- field = to_model._meta.get_field(self.name)
120
+ field = to_model._model_meta.get_field(self.name)
121
121
  if not self.preserve_default:
122
122
  field.default = self.field.default
123
123
  schema_editor.add_field(
@@ -187,7 +187,7 @@ class RemoveField(FieldOperation):
187
187
  )
188
188
  if self.allow_migrate_model(schema_editor.connection, from_model):
189
189
  schema_editor.remove_field(
190
- from_model, from_model._meta.get_field(self.name)
190
+ from_model, from_model._model_meta.get_field(self.name)
191
191
  )
192
192
 
193
193
  def describe(self) -> str:
@@ -253,8 +253,8 @@ class AlterField(FieldOperation):
253
253
  from_model = from_state.models_registry.get_model(
254
254
  package_label, self.model_name
255
255
  )
256
- from_field = from_model._meta.get_field(self.name)
257
- to_field = to_model._meta.get_field(self.name)
256
+ from_field = from_model._model_meta.get_field(self.name)
257
+ to_field = to_model._model_meta.get_field(self.name)
258
258
  if not self.preserve_default:
259
259
  to_field.default = self.field.default
260
260
  schema_editor.alter_field(from_model, from_field, to_field)
@@ -334,8 +334,8 @@ class RenameField(FieldOperation):
334
334
  )
335
335
  schema_editor.alter_field(
336
336
  from_model,
337
- from_model._meta.get_field(self.old_name),
338
- to_model._meta.get_field(self.new_name),
337
+ from_model._model_meta.get_field(self.old_name),
338
+ to_model._model_meta.get_field(self.new_name),
339
339
  )
340
340
 
341
341
  def describe(self) -> str:
@@ -70,8 +70,10 @@ class CreateModel(ModelOperation):
70
70
  _check_for_duplicates(
71
71
  "bases",
72
72
  (
73
- base._meta.label_lower # type: ignore[attr-defined]
74
- if hasattr(base, "_meta")
73
+ base.model_options.label_lower
74
+ if not isinstance(base, str)
75
+ and base is not models.Model
76
+ and hasattr(base, "_model_meta")
75
77
  else base.lower()
76
78
  if isinstance(base, str)
77
79
  else base
@@ -312,26 +314,26 @@ class RenameModel(ModelOperation):
312
314
  # Move the main table
313
315
  schema_editor.alter_db_table(
314
316
  new_model,
315
- old_model._meta.db_table, # type: ignore[attr-defined]
316
- new_model._meta.db_table, # type: ignore[attr-defined]
317
+ old_model.model_options.db_table,
318
+ new_model.model_options.db_table,
317
319
  )
318
320
  # Alter the fields pointing to us
319
- for related_object in old_model._meta.related_objects: # type: ignore[attr-defined]
321
+ for related_object in old_model._model_meta.related_objects:
320
322
  if related_object.related_model == old_model: # type: ignore[attr-defined]
321
323
  model = new_model
322
324
  related_key = (package_label, self.new_name_lower)
323
325
  else:
324
326
  model = related_object.related_model # type: ignore[attr-defined]
325
327
  related_key = (
326
- related_object.related_model._meta.package_label, # type: ignore[attr-defined]
327
- related_object.related_model._meta.model_name, # type: ignore[attr-defined]
328
+ related_object.related_model.model_options.package_label,
329
+ related_object.related_model.model_options.model_name,
328
330
  )
329
- to_field = to_state.models_registry.get_model( # type: ignore[attr-defined]
331
+ to_field = to_state.models_registry.get_model(
330
332
  *related_key
331
- )._meta.get_field(related_object.field.name) # type: ignore[attr-defined]
333
+ )._model_meta.get_field(related_object.field.name)
332
334
  schema_editor.alter_field(
333
335
  model,
334
- related_object.field, # type: ignore[attr-defined]
336
+ related_object.field,
335
337
  to_field,
336
338
  )
337
339
 
@@ -410,8 +412,8 @@ class AlterModelTable(ModelOptionOperation):
410
412
  old_model = from_state.models_registry.get_model(package_label, self.name) # type: ignore[attr-defined]
411
413
  schema_editor.alter_db_table(
412
414
  new_model,
413
- old_model._meta.db_table, # type: ignore[attr-defined]
414
- new_model._meta.db_table, # type: ignore[attr-defined]
415
+ old_model.model_options.db_table,
416
+ new_model.model_options.db_table,
415
417
  )
416
418
 
417
419
  def describe(self) -> str:
@@ -454,8 +456,8 @@ class AlterModelTableComment(ModelOptionOperation):
454
456
  old_model = from_state.models_registry.get_model(package_label, self.name) # type: ignore[attr-defined]
455
457
  schema_editor.alter_db_table_comment(
456
458
  new_model,
457
- old_model._meta.db_table_comment, # type: ignore[attr-defined]
458
- new_model._meta.db_table_comment, # type: ignore[attr-defined]
459
+ old_model.model_options.db_table_comment,
460
+ new_model.model_options.db_table_comment,
459
461
  )
460
462
 
461
463
  def describe(self) -> str:
@@ -693,7 +695,7 @@ class RenameIndex(IndexOperation):
693
695
  package_label, self.model_name
694
696
  )
695
697
  columns = [
696
- from_model._meta.get_field(field).column # type: ignore[attr-defined]
698
+ from_model._model_meta.get_field(field).column
697
699
  for field in self.old_fields
698
700
  ]
699
701
  matching_index_name = schema_editor._constraint_names(
@@ -703,7 +705,7 @@ class RenameIndex(IndexOperation):
703
705
  raise ValueError(
704
706
  "Found wrong number ({}) of indexes for {}({}).".format(
705
707
  len(matching_index_name),
706
- from_model._meta.db_table, # type: ignore[attr-defined]
708
+ from_model.model_options.db_table,
707
709
  ", ".join(columns),
708
710
  )
709
711
  )
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any
4
4
 
5
5
  from plain import models
6
6
  from plain.models.db import DatabaseError
7
+ from plain.models.meta import Meta
7
8
  from plain.models.registry import ModelsRegistry
8
9
  from plain.utils.functional import classproperty
9
10
  from plain.utils.timezone import now
@@ -44,10 +45,13 @@ class MigrationRecorder:
44
45
  name = models.CharField(max_length=255)
45
46
  applied = models.DateTimeField(default=now)
46
47
 
47
- class Meta:
48
- models_registry = _models_registry
49
- package_label = "migrations"
50
- db_table = "plainmigrations"
48
+ # Use isolated models registry for migrations
49
+ _model_meta = Meta(models_registry=_models_registry)
50
+
51
+ model_options = models.Options(
52
+ package_label="migrations",
53
+ db_table="plainmigrations",
54
+ )
51
55
 
52
56
  def __str__(self) -> str:
53
57
  return f"Migration {self.name} for {self.app}"
@@ -66,7 +70,7 @@ class MigrationRecorder:
66
70
  """Return True if the plainmigrations table exists."""
67
71
  with self.connection.cursor() as cursor:
68
72
  tables = self.connection.introspection.table_names(cursor)
69
- return self.Migration._meta.db_table in tables
73
+ return self.Migration.model_options.db_table in tables
70
74
 
71
75
  def ensure_schema(self) -> None:
72
76
  """Ensure the table exists and has the correct schema."""
@@ -10,8 +10,8 @@ from plain import models
10
10
  from plain.models.exceptions import FieldDoesNotExist
11
11
  from plain.models.fields import NOT_PROVIDED
12
12
  from plain.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT
13
+ from plain.models.meta import Meta
13
14
  from plain.models.migrations.utils import field_is_referenced, get_references
14
- from plain.models.options import DEFAULT_NAMES
15
15
  from plain.models.registry import ModelsRegistry
16
16
  from plain.models.registry import models_registry as global_models
17
17
  from plain.packages import packages_registry
@@ -32,7 +32,7 @@ def _get_package_label_and_model_name(
32
32
  split = model.split(".", 1)
33
33
  return tuple(split) if len(split) == 2 else (package_label, split[0]) # type: ignore[return-value]
34
34
  else:
35
- return model._meta.package_label, model._meta.model_name # type: ignore[attr-defined]
35
+ return model.model_options.package_label, model.model_options.model_name
36
36
 
37
37
 
38
38
  def _get_related_models(m: type[models.Model]) -> list[type[models.Model]]:
@@ -43,7 +43,7 @@ def _get_related_models(m: type[models.Model]) -> list[type[models.Model]]:
43
43
  if issubclass(subclass, models.Model)
44
44
  ]
45
45
  related_fields_models = set()
46
- for f in m._meta.get_fields(include_hidden=True): # type: ignore[attr-defined]
46
+ for f in m._model_meta.get_fields(include_hidden=True):
47
47
  if (
48
48
  f.is_relation # type: ignore[attr-defined]
49
49
  and f.related_model is not None # type: ignore[attr-defined]
@@ -60,7 +60,7 @@ def get_related_models_tuples(model: type[models.Model]) -> set[tuple[str, str]]
60
60
  models for the given model.
61
61
  """
62
62
  return {
63
- (rel_mod._meta.package_label, rel_mod._meta.model_name) # type: ignore[attr-defined]
63
+ (rel_mod.model_options.package_label, rel_mod.model_options.model_name)
64
64
  for rel_mod in _get_related_models(model)
65
65
  }
66
66
 
@@ -78,14 +78,14 @@ def get_related_models_recursive(model: type[models.Model]) -> set[tuple[str, st
78
78
  queue = _get_related_models(model)
79
79
  for rel_mod in queue:
80
80
  rel_package_label, rel_model_name = (
81
- rel_mod._meta.package_label, # type: ignore[attr-defined]
82
- rel_mod._meta.model_name, # type: ignore[attr-defined]
81
+ rel_mod.model_options.package_label,
82
+ rel_mod.model_options.model_name,
83
83
  )
84
84
  if (rel_package_label, rel_model_name) in seen:
85
85
  continue
86
86
  seen.add((rel_package_label, rel_model_name))
87
87
  queue.extend(_get_related_models(rel_mod))
88
- return seen - {(model._meta.package_label, model._meta.model_name)} # type: ignore[attr-defined]
88
+ return seen - {(model.model_options.package_label, model.model_options.model_name)}
89
89
 
90
90
 
91
91
  class ProjectState:
@@ -362,7 +362,7 @@ class ProjectState:
362
362
  pass
363
363
  else:
364
364
  # Get all relations to and from the old model before reloading,
365
- # as _meta.models_registry may change
365
+ # as _model_meta.models_registry may change
366
366
  if delay:
367
367
  related_models = get_related_models_tuples(old_model)
368
368
  else:
@@ -645,7 +645,7 @@ class StateModelsRegistry(ModelsRegistry):
645
645
  return clone
646
646
 
647
647
  def register_model(self, package_label: str, model: type[models.Model]) -> None:
648
- self.all_models[package_label][model._meta.model_name] = model # type: ignore[attr-defined]
648
+ self.all_models[package_label][model.model_options.model_name] = model
649
649
  self.do_pending_operations(model)
650
650
  self.clear_cache()
651
651
 
@@ -689,12 +689,14 @@ class ModelState:
689
689
  f'ModelState.fields cannot be bound to a model - "{name}" is.'
690
690
  )
691
691
  # Sanity-check that relation fields are NOT referring to a model class.
692
- if field.is_relation and hasattr(field.related_model, "_meta"): # type: ignore[attr-defined]
692
+ if field.is_relation and hasattr(field.related_model, "_model_meta"):
693
693
  raise ValueError(
694
694
  f'ModelState.fields cannot refer to a model class - "{name}.to" does. '
695
695
  "Use a string reference instead."
696
696
  )
697
- if field.many_to_many and hasattr(field.remote_field.through, "_meta"): # type: ignore[attr-defined]
697
+ if field.many_to_many and hasattr(
698
+ field.remote_field.through, "_model_meta"
699
+ ):
698
700
  raise ValueError(
699
701
  f'ModelState.fields cannot refer to a model class - "{name}.through" '
700
702
  "does. Use a string reference instead."
@@ -721,7 +723,7 @@ class ModelState:
721
723
  """Given a model, return a ModelState representing it."""
722
724
  # Deconstruct the fields
723
725
  fields = []
724
- for field in model._meta.local_fields: # type: ignore[attr-defined]
726
+ for field in model._model_meta.local_fields:
725
727
  if getattr(field, "remote_field", None) and exclude_rels:
726
728
  continue
727
729
  name = field.name # type: ignore[attr-defined]
@@ -729,37 +731,17 @@ class ModelState:
729
731
  fields.append((name, field.clone())) # type: ignore[attr-defined]
730
732
  except TypeError as e:
731
733
  raise TypeError(
732
- f"Couldn't reconstruct field {name} on {model._meta.label}: {e}" # type: ignore[attr-defined]
734
+ f"Couldn't reconstruct field {name} on {model.model_options.label}: {e}"
733
735
  )
734
736
  if not exclude_rels:
735
- for field in model._meta.local_many_to_many: # type: ignore[attr-defined]
737
+ for field in model._model_meta.local_many_to_many:
736
738
  name = field.name # type: ignore[attr-defined]
737
739
  try:
738
740
  fields.append((name, field.clone())) # type: ignore[attr-defined]
739
741
  except TypeError as e:
740
742
  raise TypeError(
741
- f"Couldn't reconstruct m2m field {name} on {model._meta.object_name}: {e}" # type: ignore[attr-defined]
743
+ f"Couldn't reconstruct m2m field {name} on {model.model_options.object_name}: {e}"
742
744
  )
743
- # Extract the options
744
- options = {}
745
- for name in DEFAULT_NAMES:
746
- # Ignore some special options
747
- if name in ["models_registry", "package_label"]:
748
- continue
749
- elif name in model._meta.original_attrs: # type: ignore[attr-defined]
750
- if name == "indexes":
751
- indexes = [idx.clone() for idx in model._meta.indexes] # type: ignore[attr-defined]
752
- for index in indexes:
753
- if not index.name: # type: ignore[attr-defined]
754
- index.set_name_with_model(model) # type: ignore[attr-defined]
755
- options["indexes"] = indexes
756
- elif name == "constraints":
757
- options["constraints"] = [
758
- con.clone()
759
- for con in model._meta.constraints # type: ignore[attr-defined]
760
- ]
761
- else:
762
- options[name] = model._meta.original_attrs[name] # type: ignore[attr-defined]
763
745
 
764
746
  def flatten_bases(model: type[models.Model]) -> list[type[models.Model]]:
765
747
  bases = []
@@ -778,7 +760,13 @@ class ModelState:
778
760
 
779
761
  # Make our record
780
762
  bases = tuple(
781
- (base._meta.label_lower if hasattr(base, "_meta") else base) # type: ignore[attr-defined]
763
+ (
764
+ base.model_options.label_lower
765
+ if not isinstance(base, str)
766
+ and base is not models.Model
767
+ and hasattr(base, "_model_meta")
768
+ else base
769
+ )
782
770
  for base in flattened_bases
783
771
  )
784
772
  # Ensure at least one base inherits from models.Model
@@ -789,10 +777,10 @@ class ModelState:
789
777
 
790
778
  # Construct the new ModelState
791
779
  return cls(
792
- model._meta.package_label, # type: ignore[attr-defined]
793
- model._meta.object_name, # type: ignore[attr-defined]
780
+ model.model_options.package_label,
781
+ model.model_options.object_name,
794
782
  fields,
795
- options,
783
+ model.model_options.export_for_migrations(),
796
784
  bases,
797
785
  )
798
786
 
@@ -811,13 +799,11 @@ class ModelState:
811
799
 
812
800
  def render(self, models_registry: ModelsRegistry) -> type[models.Model]:
813
801
  """Create a Model object from our current state into the given packages."""
814
- # First, make a Meta object
815
- meta_contents = {
816
- "package_label": self.package_label,
817
- "models_registry": models_registry,
802
+ # Create Options instance with metadata
803
+ meta_options = models.Options(
804
+ package_label=self.package_label,
818
805
  **self.options,
819
- }
820
- meta = type("Meta", (), meta_contents)
806
+ )
821
807
  # Then, work out our bases
822
808
  try:
823
809
  bases = tuple(
@@ -830,7 +816,10 @@ class ModelState:
830
816
  )
831
817
  # Clone fields for the body, add other bits.
832
818
  body = {name: field.clone() for name, field in self.fields.items()} # type: ignore[attr-defined]
833
- body["Meta"] = meta
819
+ body["model_options"] = meta_options
820
+ body["_model_meta"] = Meta(
821
+ models_registry=models_registry
822
+ ) # Use custom registry
834
823
  body["__module__"] = "__fake__"
835
824
 
836
825
  # Then, make a Model object (models_registry.register_model is called in __new__)
@@ -56,7 +56,7 @@ def resolve_relation(
56
56
  "package_label must be provided to resolve unscoped model relationships."
57
57
  )
58
58
  return package_label, model.lower()
59
- return model._meta.package_label, model._meta.model_name
59
+ return model.model_options.package_label, model.model_options.model_name
60
60
 
61
61
 
62
62
  def field_references(