plain.models 0.49.2__py3-none-any.whl → 0.51.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. plain/models/CHANGELOG.md +27 -0
  2. plain/models/README.md +26 -42
  3. plain/models/__init__.py +2 -0
  4. plain/models/aggregates.py +42 -19
  5. plain/models/backends/base/base.py +125 -105
  6. plain/models/backends/base/client.py +11 -3
  7. plain/models/backends/base/creation.py +24 -14
  8. plain/models/backends/base/features.py +10 -4
  9. plain/models/backends/base/introspection.py +37 -20
  10. plain/models/backends/base/operations.py +187 -91
  11. plain/models/backends/base/schema.py +338 -218
  12. plain/models/backends/base/validation.py +13 -4
  13. plain/models/backends/ddl_references.py +85 -43
  14. plain/models/backends/mysql/base.py +29 -26
  15. plain/models/backends/mysql/client.py +7 -2
  16. plain/models/backends/mysql/compiler.py +13 -4
  17. plain/models/backends/mysql/creation.py +5 -2
  18. plain/models/backends/mysql/features.py +24 -22
  19. plain/models/backends/mysql/introspection.py +22 -13
  20. plain/models/backends/mysql/operations.py +107 -40
  21. plain/models/backends/mysql/schema.py +52 -28
  22. plain/models/backends/mysql/validation.py +13 -6
  23. plain/models/backends/postgresql/base.py +41 -34
  24. plain/models/backends/postgresql/client.py +7 -2
  25. plain/models/backends/postgresql/creation.py +10 -5
  26. plain/models/backends/postgresql/introspection.py +15 -8
  27. plain/models/backends/postgresql/operations.py +110 -43
  28. plain/models/backends/postgresql/schema.py +88 -49
  29. plain/models/backends/sqlite3/_functions.py +151 -115
  30. plain/models/backends/sqlite3/base.py +37 -23
  31. plain/models/backends/sqlite3/client.py +7 -1
  32. plain/models/backends/sqlite3/creation.py +9 -5
  33. plain/models/backends/sqlite3/features.py +5 -3
  34. plain/models/backends/sqlite3/introspection.py +32 -16
  35. plain/models/backends/sqlite3/operations.py +126 -43
  36. plain/models/backends/sqlite3/schema.py +127 -92
  37. plain/models/backends/utils.py +52 -29
  38. plain/models/backups/cli.py +8 -6
  39. plain/models/backups/clients.py +16 -7
  40. plain/models/backups/core.py +24 -13
  41. plain/models/base.py +221 -229
  42. plain/models/cli.py +98 -67
  43. plain/models/config.py +1 -1
  44. plain/models/connections.py +23 -7
  45. plain/models/constraints.py +79 -56
  46. plain/models/database_url.py +1 -1
  47. plain/models/db.py +6 -2
  48. plain/models/deletion.py +80 -56
  49. plain/models/entrypoints.py +1 -1
  50. plain/models/enums.py +22 -11
  51. plain/models/exceptions.py +23 -8
  52. plain/models/expressions.py +441 -258
  53. plain/models/fields/__init__.py +272 -217
  54. plain/models/fields/json.py +123 -57
  55. plain/models/fields/mixins.py +12 -8
  56. plain/models/fields/related.py +324 -290
  57. plain/models/fields/related_descriptors.py +33 -24
  58. plain/models/fields/related_lookups.py +24 -12
  59. plain/models/fields/related_managers.py +102 -79
  60. plain/models/fields/reverse_related.py +66 -63
  61. plain/models/forms.py +101 -75
  62. plain/models/functions/comparison.py +71 -18
  63. plain/models/functions/datetime.py +79 -29
  64. plain/models/functions/math.py +43 -10
  65. plain/models/functions/mixins.py +24 -7
  66. plain/models/functions/text.py +104 -25
  67. plain/models/functions/window.py +12 -6
  68. plain/models/indexes.py +57 -32
  69. plain/models/lookups.py +228 -153
  70. plain/models/meta.py +505 -0
  71. plain/models/migrations/autodetector.py +86 -43
  72. plain/models/migrations/exceptions.py +7 -3
  73. plain/models/migrations/executor.py +33 -7
  74. plain/models/migrations/graph.py +79 -50
  75. plain/models/migrations/loader.py +45 -22
  76. plain/models/migrations/migration.py +23 -18
  77. plain/models/migrations/operations/base.py +38 -20
  78. plain/models/migrations/operations/fields.py +95 -48
  79. plain/models/migrations/operations/models.py +246 -142
  80. plain/models/migrations/operations/special.py +82 -25
  81. plain/models/migrations/optimizer.py +7 -2
  82. plain/models/migrations/questioner.py +58 -31
  83. plain/models/migrations/recorder.py +27 -16
  84. plain/models/migrations/serializer.py +50 -39
  85. plain/models/migrations/state.py +232 -156
  86. plain/models/migrations/utils.py +30 -14
  87. plain/models/migrations/writer.py +17 -14
  88. plain/models/options.py +189 -518
  89. plain/models/otel.py +16 -6
  90. plain/models/preflight.py +42 -17
  91. plain/models/query.py +400 -251
  92. plain/models/query_utils.py +109 -69
  93. plain/models/registry.py +40 -21
  94. plain/models/sql/compiler.py +190 -127
  95. plain/models/sql/datastructures.py +38 -25
  96. plain/models/sql/query.py +320 -225
  97. plain/models/sql/subqueries.py +36 -25
  98. plain/models/sql/where.py +54 -29
  99. plain/models/test/pytest.py +15 -11
  100. plain/models/test/utils.py +4 -2
  101. plain/models/transaction.py +20 -7
  102. plain/models/utils.py +17 -6
  103. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/METADATA +27 -43
  104. plain_models-0.51.0.dist-info/RECORD +123 -0
  105. plain_models-0.49.2.dist-info/RECORD +0 -122
  106. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/WHEEL +0 -0
  107. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/entry_points.txt +0 -0
  108. {plain_models-0.49.2.dist-info → plain_models-0.51.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,14 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  import sys
5
+ from typing import TYPE_CHECKING, Any
3
6
 
4
7
  from plain.runtime import settings
5
8
 
9
+ if TYPE_CHECKING:
10
+ from plain.models.backends.base.base import BaseDatabaseWrapper
11
+
6
12
  # The prefix to put on the default database name when creating
7
13
  # the test database.
8
14
  TEST_DATABASE_PREFIX = "test_"
@@ -14,16 +20,16 @@ class BaseDatabaseCreation:
14
20
  destruction of the test database.
15
21
  """
16
22
 
17
- def __init__(self, connection):
23
+ def __init__(self, connection: BaseDatabaseWrapper):
18
24
  self.connection = connection
19
25
 
20
- def _nodb_cursor(self):
26
+ def _nodb_cursor(self) -> Any:
21
27
  return self.connection._nodb_cursor()
22
28
 
23
- def log(self, msg):
29
+ def log(self, msg: str) -> None:
24
30
  sys.stderr.write(msg + os.linesep)
25
31
 
26
- def create_test_db(self, verbosity=1, prefix=""):
32
+ def create_test_db(self, verbosity: int = 1, prefix: str = "") -> str:
27
33
  """
28
34
  Create a test database, prompting the user for confirmation if the
29
35
  database already exists. Return the name of the test database created.
@@ -67,7 +73,7 @@ class BaseDatabaseCreation:
67
73
 
68
74
  return test_database_name
69
75
 
70
- def set_as_test_mirror(self, primary_settings_dict):
76
+ def set_as_test_mirror(self, primary_settings_dict: dict[str, Any]) -> None:
71
77
  """
72
78
  Set this database up to be used in testing as a mirror of a primary
73
79
  database whose settings are given.
@@ -92,7 +98,7 @@ class BaseDatabaseCreation:
92
98
  # and package_config.package_label in loader.migrated_packages
93
99
  # ):
94
100
  # for model in package_config.get_models():
95
- # if model._meta.can_migrate(
101
+ # if model.model_options.can_migrate(
96
102
  # self.connection
97
103
  # ) and router.allow_migrate_model(self.connection.alias, model):
98
104
  # queryset = model._base_manager.using(
@@ -121,12 +127,12 @@ class BaseDatabaseCreation:
121
127
  # "json", data, using=self.connection.alias
122
128
  # ):
123
129
  # obj.save()
124
- # table_names.add(obj.object.__class__._meta.db_table)
130
+ # table_names.add(obj.object.model_options.db_table)
125
131
  # # Manually check for any invalid keys that might have been added,
126
132
  # # because constraint checks were disabled.
127
133
  # self.connection.check_constraints(table_names=table_names)
128
134
 
129
- def _get_test_db_name(self, prefix=""):
135
+ def _get_test_db_name(self, prefix: str = "") -> str:
130
136
  """
131
137
  Internal implementation - return the name of the test DB that will be
132
138
  created. Only useful when called from create_test_db() and
@@ -146,10 +152,12 @@ class BaseDatabaseCreation:
146
152
  return self.connection.settings_dict["TEST"]["NAME"]
147
153
  return TEST_DATABASE_PREFIX + self.connection.settings_dict["NAME"]
148
154
 
149
- def _execute_create_test_db(self, cursor, parameters):
155
+ def _execute_create_test_db(self, cursor: Any, parameters: dict[str, str]) -> None:
150
156
  cursor.execute("CREATE DATABASE {dbname} {suffix}".format(**parameters))
151
157
 
152
- def _create_test_db(self, *, test_database_name, verbosity, autoclobber):
158
+ def _create_test_db(
159
+ self, *, test_database_name: str, verbosity: int, autoclobber: bool
160
+ ) -> str:
153
161
  """
154
162
  Internal implementation - create the test db tables.
155
163
  """
@@ -187,7 +195,9 @@ class BaseDatabaseCreation:
187
195
 
188
196
  return test_database_name
189
197
 
190
- def destroy_test_db(self, old_database_name=None, verbosity=1):
198
+ def destroy_test_db(
199
+ self, old_database_name: str | None = None, verbosity: int = 1
200
+ ) -> None:
191
201
  """
192
202
  Destroy a test database, prompting the user for confirmation if the
193
203
  database already exists.
@@ -205,7 +215,7 @@ class BaseDatabaseCreation:
205
215
  settings.DATABASE["NAME"] = old_database_name
206
216
  self.connection.settings_dict["NAME"] = old_database_name
207
217
 
208
- def _destroy_test_db(self, test_database_name, verbosity):
218
+ def _destroy_test_db(self, test_database_name: str, verbosity: int) -> None:
209
219
  """
210
220
  Internal implementation - remove the test db tables.
211
221
  """
@@ -218,13 +228,13 @@ class BaseDatabaseCreation:
218
228
  f"DROP DATABASE {self.connection.ops.quote_name(test_database_name)}"
219
229
  )
220
230
 
221
- def sql_table_creation_suffix(self):
231
+ def sql_table_creation_suffix(self) -> str:
222
232
  """
223
233
  SQL to append to the end of the test table creation statements.
224
234
  """
225
235
  return ""
226
236
 
227
- def test_db_signature(self, prefix=""):
237
+ def test_db_signature(self, prefix: str = "") -> tuple[str, str, str, str]:
228
238
  """
229
239
  Return a tuple with elements of self.connection.settings_dict (a
230
240
  DATABASE setting value) that uniquely identify a database
@@ -1,4 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  from functools import cached_property
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ if TYPE_CHECKING:
7
+ from plain.models.backends.base.base import BaseDatabaseWrapper
2
8
 
3
9
 
4
10
  class BaseDatabaseFeatures:
@@ -185,16 +191,16 @@ class BaseDatabaseFeatures:
185
191
  # Does the backend support unlimited character columns?
186
192
  supports_unlimited_charfield = False
187
193
 
188
- def __init__(self, connection):
194
+ def __init__(self, connection: BaseDatabaseWrapper):
189
195
  self.connection = connection
190
196
 
191
197
  @cached_property
192
- def supports_explaining_query_execution(self):
198
+ def supports_explaining_query_execution(self) -> bool:
193
199
  """Does this backend support explaining query execution?"""
194
200
  return self.connection.ops.explain_prefix is not None
195
201
 
196
202
  @cached_property
197
- def supports_transactions(self):
203
+ def supports_transactions(self) -> bool:
198
204
  """Confirm support for transactions."""
199
205
  with self.connection.cursor() as cursor:
200
206
  cursor.execute("CREATE TABLE ROLLBACK_TEST (X INT)")
@@ -207,7 +213,7 @@ class BaseDatabaseFeatures:
207
213
  cursor.execute("DROP TABLE ROLLBACK_TEST")
208
214
  return count == 0
209
215
 
210
- def allows_group_by_selected_pks_on_model(self, model):
216
+ def allows_group_by_selected_pks_on_model(self, model: Any) -> bool:
211
217
  if not self.allows_group_by_selected_pks:
212
218
  return False
213
219
  return True
@@ -1,4 +1,11 @@
1
+ from __future__ import annotations
2
+
1
3
  from collections import namedtuple
4
+ from collections.abc import Generator
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ if TYPE_CHECKING:
8
+ from plain.models.backends.base.base import BaseDatabaseWrapper
2
9
 
3
10
  # Structure returned by DatabaseIntrospection.get_table_list()
4
11
  TableInfo = namedtuple("TableInfo", ["name", "type"])
@@ -14,12 +21,12 @@ FieldInfo = namedtuple(
14
21
  class BaseDatabaseIntrospection:
15
22
  """Encapsulate backend-specific introspection utilities."""
16
23
 
17
- data_types_reverse = {}
24
+ data_types_reverse: dict[Any, str] = {}
18
25
 
19
- def __init__(self, connection):
26
+ def __init__(self, connection: BaseDatabaseWrapper) -> None:
20
27
  self.connection = connection
21
28
 
22
- def get_field_type(self, data_type, description):
29
+ def get_field_type(self, data_type: Any, description: Any) -> str:
23
30
  """
24
31
  Hook for a database backend to use the cursor description to
25
32
  match a Plain field type to a database column.
@@ -29,7 +36,7 @@ class BaseDatabaseIntrospection:
29
36
  """
30
37
  return self.data_types_reverse[data_type]
31
38
 
32
- def identifier_converter(self, name):
39
+ def identifier_converter(self, name: str) -> str:
33
40
  """
34
41
  Apply a conversion to the identifier for the purposes of comparison.
35
42
 
@@ -37,7 +44,7 @@ class BaseDatabaseIntrospection:
37
44
  """
38
45
  return name
39
46
 
40
- def table_names(self, cursor=None, include_views=False):
47
+ def table_names(self, cursor: Any = None, include_views: bool = False) -> list[str]:
41
48
  """
42
49
  Return a list of names of all tables that exist in the database.
43
50
  Sort the returned table list by Python's default sorting. Do NOT use
@@ -45,7 +52,7 @@ class BaseDatabaseIntrospection:
45
52
  order between databases.
46
53
  """
47
54
 
48
- def get_names(cursor):
55
+ def get_names(cursor: Any) -> list[str]:
49
56
  return sorted(
50
57
  ti.name
51
58
  for ti in self.get_table_list(cursor)
@@ -57,7 +64,7 @@ class BaseDatabaseIntrospection:
57
64
  return get_names(cursor)
58
65
  return get_names(cursor)
59
66
 
60
- def get_table_list(self, cursor):
67
+ def get_table_list(self, cursor: Any) -> list[TableInfo]:
61
68
  """
62
69
  Return an unsorted list of TableInfo named tuples of all tables and
63
70
  views that exist in the database.
@@ -67,7 +74,7 @@ class BaseDatabaseIntrospection:
67
74
  "method"
68
75
  )
69
76
 
70
- def get_table_description(self, cursor, table_name):
77
+ def get_table_description(self, cursor: Any, table_name: str) -> list[FieldInfo]:
71
78
  """
72
79
  Return a description of the table with the DB-API cursor.description
73
80
  interface.
@@ -77,7 +84,7 @@ class BaseDatabaseIntrospection:
77
84
  "get_table_description() method."
78
85
  )
79
86
 
80
- def get_migratable_models(self):
87
+ def get_migratable_models(self) -> Generator[Any, None, None]:
81
88
  from plain.models import models_registry
82
89
  from plain.packages import packages_registry
83
90
 
@@ -87,10 +94,12 @@ class BaseDatabaseIntrospection:
87
94
  for model in models_registry.get_models(
88
95
  package_label=package_config.package_label
89
96
  )
90
- if model._meta.can_migrate(self.connection)
97
+ if model.model_options.can_migrate(self.connection)
91
98
  )
92
99
 
93
- def plain_table_names(self, only_existing=False, include_views=True):
100
+ def plain_table_names(
101
+ self, only_existing: bool = False, include_views: bool = True
102
+ ) -> list[str]:
94
103
  """
95
104
  Return a list of all table names that have associated Plain models and
96
105
  are in INSTALLED_PACKAGES.
@@ -99,8 +108,10 @@ class BaseDatabaseIntrospection:
99
108
  """
100
109
  tables = set()
101
110
  for model in self.get_migratable_models():
102
- tables.add(model._meta.db_table)
103
- tables.update(f.m2m_db_table() for f in model._meta.local_many_to_many)
111
+ tables.add(model.model_options.db_table)
112
+ tables.update(
113
+ f.m2m_db_table() for f in model._model_meta.local_many_to_many
114
+ )
104
115
  tables = list(tables)
105
116
  if only_existing:
106
117
  existing_tables = set(self.table_names(include_views=include_views))
@@ -109,7 +120,7 @@ class BaseDatabaseIntrospection:
109
120
  ]
110
121
  return tables
111
122
 
112
- def sequence_list(self):
123
+ def sequence_list(self) -> list[dict[str, Any]]:
113
124
  """
114
125
  Return a list of information about all DB sequences for all models in
115
126
  all packages.
@@ -119,12 +130,16 @@ class BaseDatabaseIntrospection:
119
130
  for model in self.get_migratable_models():
120
131
  sequence_list.extend(
121
132
  self.get_sequences(
122
- cursor, model._meta.db_table, model._meta.local_fields
133
+ cursor,
134
+ model.model_options.db_table,
135
+ model._model_meta.local_fields,
123
136
  )
124
137
  )
125
138
  return sequence_list
126
139
 
127
- def get_sequences(self, cursor, table_name, table_fields=()):
140
+ def get_sequences(
141
+ self, cursor: Any, table_name: str, table_fields: tuple[Any, ...] = ()
142
+ ) -> list[dict[str, Any]]:
128
143
  """
129
144
  Return a list of introspected sequences for table_name. Each sequence
130
145
  is a dict: {'table': <table_name>, 'column': <column_name>}. An optional
@@ -135,7 +150,7 @@ class BaseDatabaseIntrospection:
135
150
  "method"
136
151
  )
137
152
 
138
- def get_relations(self, cursor, table_name):
153
+ def get_relations(self, cursor: Any, table_name: str) -> dict[str, tuple[str, str]]:
139
154
  """
140
155
  Return a dictionary of {field_name: (field_name_other_table, other_table)}
141
156
  representing all foreign keys in the given table.
@@ -145,21 +160,23 @@ class BaseDatabaseIntrospection:
145
160
  "get_relations() method."
146
161
  )
147
162
 
148
- def get_primary_key_column(self, cursor, table_name):
163
+ def get_primary_key_column(self, cursor: Any, table_name: str) -> str | None:
149
164
  """
150
165
  Return the name of the primary key column for the given table.
151
166
  """
152
167
  columns = self.get_primary_key_columns(cursor, table_name)
153
168
  return columns[0] if columns else None
154
169
 
155
- def get_primary_key_columns(self, cursor, table_name):
170
+ def get_primary_key_columns(self, cursor: Any, table_name: str) -> list[str] | None:
156
171
  """Return a list of primary key columns for the given table."""
157
172
  for constraint in self.get_constraints(cursor, table_name).values():
158
173
  if constraint["primary_key"]:
159
174
  return constraint["columns"]
160
175
  return None
161
176
 
162
- def get_constraints(self, cursor, table_name):
177
+ def get_constraints(
178
+ self, cursor: Any, table_name: str
179
+ ) -> dict[str, dict[str, Any]]:
163
180
  """
164
181
  Retrieve any constraints or keys (unique, pk, fk, check, index)
165
182
  across one or more columns.