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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. plain/models/CHANGELOG.md +13 -0
  2. plain/models/aggregates.py +42 -19
  3. plain/models/backends/base/base.py +125 -105
  4. plain/models/backends/base/client.py +11 -3
  5. plain/models/backends/base/creation.py +22 -12
  6. plain/models/backends/base/features.py +10 -4
  7. plain/models/backends/base/introspection.py +29 -16
  8. plain/models/backends/base/operations.py +187 -91
  9. plain/models/backends/base/schema.py +267 -165
  10. plain/models/backends/base/validation.py +12 -3
  11. plain/models/backends/ddl_references.py +85 -43
  12. plain/models/backends/mysql/base.py +29 -26
  13. plain/models/backends/mysql/client.py +7 -2
  14. plain/models/backends/mysql/compiler.py +12 -3
  15. plain/models/backends/mysql/creation.py +5 -2
  16. plain/models/backends/mysql/features.py +24 -22
  17. plain/models/backends/mysql/introspection.py +22 -13
  18. plain/models/backends/mysql/operations.py +106 -39
  19. plain/models/backends/mysql/schema.py +48 -24
  20. plain/models/backends/mysql/validation.py +13 -6
  21. plain/models/backends/postgresql/base.py +41 -34
  22. plain/models/backends/postgresql/client.py +7 -2
  23. plain/models/backends/postgresql/creation.py +10 -5
  24. plain/models/backends/postgresql/introspection.py +15 -8
  25. plain/models/backends/postgresql/operations.py +109 -42
  26. plain/models/backends/postgresql/schema.py +85 -46
  27. plain/models/backends/sqlite3/_functions.py +151 -115
  28. plain/models/backends/sqlite3/base.py +37 -23
  29. plain/models/backends/sqlite3/client.py +7 -1
  30. plain/models/backends/sqlite3/creation.py +9 -5
  31. plain/models/backends/sqlite3/features.py +5 -3
  32. plain/models/backends/sqlite3/introspection.py +32 -16
  33. plain/models/backends/sqlite3/operations.py +125 -42
  34. plain/models/backends/sqlite3/schema.py +82 -58
  35. plain/models/backends/utils.py +52 -29
  36. plain/models/backups/cli.py +8 -6
  37. plain/models/backups/clients.py +16 -7
  38. plain/models/backups/core.py +24 -13
  39. plain/models/base.py +113 -74
  40. plain/models/cli.py +94 -63
  41. plain/models/config.py +1 -1
  42. plain/models/connections.py +23 -7
  43. plain/models/constraints.py +65 -47
  44. plain/models/database_url.py +1 -1
  45. plain/models/db.py +6 -2
  46. plain/models/deletion.py +66 -43
  47. plain/models/entrypoints.py +1 -1
  48. plain/models/enums.py +22 -11
  49. plain/models/exceptions.py +23 -8
  50. plain/models/expressions.py +440 -257
  51. plain/models/fields/__init__.py +253 -202
  52. plain/models/fields/json.py +120 -54
  53. plain/models/fields/mixins.py +12 -8
  54. plain/models/fields/related.py +284 -252
  55. plain/models/fields/related_descriptors.py +31 -22
  56. plain/models/fields/related_lookups.py +23 -11
  57. plain/models/fields/related_managers.py +81 -47
  58. plain/models/fields/reverse_related.py +58 -55
  59. plain/models/forms.py +89 -63
  60. plain/models/functions/comparison.py +71 -18
  61. plain/models/functions/datetime.py +79 -29
  62. plain/models/functions/math.py +43 -10
  63. plain/models/functions/mixins.py +24 -7
  64. plain/models/functions/text.py +104 -25
  65. plain/models/functions/window.py +12 -6
  66. plain/models/indexes.py +52 -28
  67. plain/models/lookups.py +228 -153
  68. plain/models/migrations/autodetector.py +86 -43
  69. plain/models/migrations/exceptions.py +7 -3
  70. plain/models/migrations/executor.py +33 -7
  71. plain/models/migrations/graph.py +79 -50
  72. plain/models/migrations/loader.py +45 -22
  73. plain/models/migrations/migration.py +23 -18
  74. plain/models/migrations/operations/base.py +37 -19
  75. plain/models/migrations/operations/fields.py +89 -42
  76. plain/models/migrations/operations/models.py +245 -143
  77. plain/models/migrations/operations/special.py +82 -25
  78. plain/models/migrations/optimizer.py +7 -2
  79. plain/models/migrations/questioner.py +58 -31
  80. plain/models/migrations/recorder.py +18 -11
  81. plain/models/migrations/serializer.py +50 -39
  82. plain/models/migrations/state.py +220 -133
  83. plain/models/migrations/utils.py +29 -13
  84. plain/models/migrations/writer.py +17 -14
  85. plain/models/options.py +63 -56
  86. plain/models/otel.py +16 -6
  87. plain/models/preflight.py +35 -12
  88. plain/models/query.py +323 -228
  89. plain/models/query_utils.py +93 -58
  90. plain/models/registry.py +34 -16
  91. plain/models/sql/compiler.py +146 -97
  92. plain/models/sql/datastructures.py +38 -25
  93. plain/models/sql/query.py +255 -169
  94. plain/models/sql/subqueries.py +32 -21
  95. plain/models/sql/where.py +54 -29
  96. plain/models/test/pytest.py +15 -11
  97. plain/models/test/utils.py +4 -2
  98. plain/models/transaction.py +20 -7
  99. plain/models/utils.py +13 -5
  100. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/METADATA +1 -1
  101. plain_models-0.50.0.dist-info/RECORD +122 -0
  102. plain_models-0.49.2.dist-info/RECORD +0 -122
  103. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/WHEEL +0 -0
  104. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/entry_points.txt +0 -0
  105. {plain_models-0.49.2.dist-info → plain_models-0.50.0.dist-info}/licenses/LICENSE +0 -0
plain/models/lookups.py CHANGED
@@ -1,6 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  import itertools
2
4
  import math
3
5
  from functools import cached_property
6
+ from typing import TYPE_CHECKING, Any
4
7
 
5
8
  from plain.models.exceptions import EmptyResultSet, FullResultSet
6
9
  from plain.models.expressions import Expression, Func, Value
@@ -16,18 +19,22 @@ from plain.models.query_utils import RegisterLookupMixin
16
19
  from plain.utils.datastructures import OrderedSet
17
20
  from plain.utils.hashable import make_hashable
18
21
 
22
+ if TYPE_CHECKING:
23
+ from plain.models.backends.base.base import BaseDatabaseWrapper
24
+ from plain.models.sql.compiler import SQLCompiler
25
+
19
26
 
20
27
  class Lookup(Expression):
21
- lookup_name = None
22
- prepare_rhs = True
23
- can_use_none_as_rhs = False
28
+ lookup_name: str | None = None
29
+ prepare_rhs: bool = True
30
+ can_use_none_as_rhs: bool = False
24
31
 
25
- def __init__(self, lhs, rhs):
32
+ def __init__(self, lhs: Any, rhs: Any):
26
33
  self.lhs, self.rhs = lhs, rhs
27
34
  self.rhs = self.get_prep_lookup()
28
35
  self.lhs = self.get_prep_lhs()
29
36
  if hasattr(self.lhs, "get_bilateral_transforms"):
30
- bilateral_transforms = self.lhs.get_bilateral_transforms()
37
+ bilateral_transforms = self.lhs.get_bilateral_transforms() # type: ignore[attr-defined]
31
38
  else:
32
39
  bilateral_transforms = []
33
40
  if bilateral_transforms:
@@ -41,23 +48,25 @@ class Lookup(Expression):
41
48
  )
42
49
  self.bilateral_transforms = bilateral_transforms
43
50
 
44
- def apply_bilateral_transforms(self, value):
51
+ def apply_bilateral_transforms(self, value: Any) -> Any:
45
52
  for transform in self.bilateral_transforms:
46
53
  value = transform(value)
47
54
  return value
48
55
 
49
- def __repr__(self):
56
+ def __repr__(self) -> str:
50
57
  return f"{self.__class__.__name__}({self.lhs!r}, {self.rhs!r})"
51
58
 
52
- def batch_process_rhs(self, compiler, connection, rhs=None):
59
+ def batch_process_rhs(
60
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper, rhs: Any = None
61
+ ) -> tuple[list[str], list[Any]]:
53
62
  if rhs is None:
54
63
  rhs = self.rhs
55
64
  if self.bilateral_transforms:
56
65
  sqls, sqls_params = [], []
57
66
  for p in rhs:
58
- value = Value(p, output_field=self.lhs.output_field)
67
+ value = Value(p, output_field=self.lhs.output_field) # type: ignore[attr-defined]
59
68
  value = self.apply_bilateral_transforms(value)
60
- value = value.resolve_expression(compiler.query)
69
+ value = value.resolve_expression(compiler.query) # type: ignore[attr-defined]
61
70
  sql, sql_params = compiler.compile(value)
62
71
  sqls.append(sql)
63
72
  sqls_params.extend(sql_params)
@@ -66,54 +75,60 @@ class Lookup(Expression):
66
75
  sqls, sqls_params = ["%s"] * len(params), params
67
76
  return sqls, sqls_params
68
77
 
69
- def get_source_expressions(self):
78
+ def get_source_expressions(self) -> list[Any]:
70
79
  if self.rhs_is_direct_value():
71
80
  return [self.lhs]
72
81
  return [self.lhs, self.rhs]
73
82
 
74
- def set_source_expressions(self, new_exprs):
83
+ def set_source_expressions(self, new_exprs: list[Any]) -> None:
75
84
  if len(new_exprs) == 1:
76
85
  self.lhs = new_exprs[0]
77
86
  else:
78
87
  self.lhs, self.rhs = new_exprs
79
88
 
80
- def get_prep_lookup(self):
89
+ def get_prep_lookup(self) -> Any:
81
90
  if not self.prepare_rhs or hasattr(self.rhs, "resolve_expression"):
82
91
  return self.rhs
83
92
  if hasattr(self.lhs, "output_field"):
84
- if hasattr(self.lhs.output_field, "get_prep_value"):
85
- return self.lhs.output_field.get_prep_value(self.rhs)
93
+ if hasattr(self.lhs.output_field, "get_prep_value"): # type: ignore[attr-defined]
94
+ return self.lhs.output_field.get_prep_value(self.rhs) # type: ignore[attr-defined]
86
95
  elif self.rhs_is_direct_value():
87
96
  return Value(self.rhs)
88
97
  return self.rhs
89
98
 
90
- def get_prep_lhs(self):
99
+ def get_prep_lhs(self) -> Any:
91
100
  if hasattr(self.lhs, "resolve_expression"):
92
101
  return self.lhs
93
102
  return Value(self.lhs)
94
103
 
95
- def get_db_prep_lookup(self, value, connection):
104
+ def get_db_prep_lookup(
105
+ self, value: Any, connection: BaseDatabaseWrapper
106
+ ) -> tuple[str, list[Any]]:
96
107
  return ("%s", [value])
97
108
 
98
- def process_lhs(self, compiler, connection, lhs=None):
109
+ def process_lhs(
110
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper, lhs: Any = None
111
+ ) -> tuple[str, list[Any]]:
99
112
  lhs = lhs or self.lhs
100
113
  if hasattr(lhs, "resolve_expression"):
101
- lhs = lhs.resolve_expression(compiler.query)
114
+ lhs = lhs.resolve_expression(compiler.query) # type: ignore[attr-defined]
102
115
  sql, params = compiler.compile(lhs)
103
116
  if isinstance(lhs, Lookup):
104
117
  # Wrapped in parentheses to respect operator precedence.
105
118
  sql = f"({sql})"
106
- return sql, params
119
+ return sql, list(params)
107
120
 
108
- def process_rhs(self, compiler, connection):
121
+ def process_rhs(
122
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
123
+ ) -> tuple[str, list[Any]]:
109
124
  value = self.rhs
110
125
  if self.bilateral_transforms:
111
126
  if self.rhs_is_direct_value():
112
127
  # Do not call get_db_prep_lookup here as the value will be
113
128
  # transformed before being used for lookup
114
- value = Value(value, output_field=self.lhs.output_field)
129
+ value = Value(value, output_field=self.lhs.output_field) # type: ignore[attr-defined]
115
130
  value = self.apply_bilateral_transforms(value)
116
- value = value.resolve_expression(compiler.query)
131
+ value = value.resolve_expression(compiler.query) # type: ignore[attr-defined]
117
132
  if hasattr(value, "as_sql"):
118
133
  sql, params = compiler.compile(value)
119
134
  # Ensure expression is wrapped in parentheses to respect operator
@@ -121,50 +136,57 @@ class Lookup(Expression):
121
136
  # on some backends (e.g. subqueries on SQLite).
122
137
  if sql and sql[0] != "(":
123
138
  sql = f"({sql})"
124
- return sql, params
139
+ return sql, list(params)
125
140
  else:
126
141
  return self.get_db_prep_lookup(value, connection)
127
142
 
128
- def rhs_is_direct_value(self):
143
+ def rhs_is_direct_value(self) -> bool:
129
144
  return not hasattr(self.rhs, "as_sql")
130
145
 
131
- def get_group_by_cols(self):
146
+ def get_group_by_cols(self) -> list[Any]:
132
147
  cols = []
133
148
  for source in self.get_source_expressions():
134
- cols.extend(source.get_group_by_cols())
149
+ cols.extend(source.get_group_by_cols()) # type: ignore[attr-defined]
135
150
  return cols
136
151
 
137
152
  @cached_property
138
- def output_field(self):
153
+ def output_field(self) -> BooleanField:
139
154
  return BooleanField()
140
155
 
141
156
  @property
142
- def identity(self):
157
+ def identity(self) -> tuple[type[Lookup], Any, Any]:
143
158
  return self.__class__, self.lhs, self.rhs
144
159
 
145
- def __eq__(self, other):
160
+ def __eq__(self, other: object) -> bool:
146
161
  if not isinstance(other, Lookup):
147
162
  return NotImplemented
148
163
  return self.identity == other.identity
149
164
 
150
- def __hash__(self):
165
+ def __hash__(self) -> int:
151
166
  return hash(make_hashable(self.identity))
152
167
 
153
168
  def resolve_expression(
154
- self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
155
- ):
156
- c = self.copy()
169
+ self,
170
+ query: Any = None,
171
+ allow_joins: bool = True,
172
+ reuse: Any = None,
173
+ summarize: bool = False,
174
+ for_save: bool = False,
175
+ ) -> Lookup:
176
+ c = self.copy() # type: ignore[attr-defined]
157
177
  c.is_summary = summarize
158
- c.lhs = self.lhs.resolve_expression(
178
+ c.lhs = self.lhs.resolve_expression( # type: ignore[attr-defined]
159
179
  query, allow_joins, reuse, summarize, for_save
160
180
  )
161
181
  if hasattr(self.rhs, "resolve_expression"):
162
- c.rhs = self.rhs.resolve_expression(
182
+ c.rhs = self.rhs.resolve_expression( # type: ignore[attr-defined]
163
183
  query, allow_joins, reuse, summarize, for_save
164
184
  )
165
185
  return c
166
186
 
167
- def select_format(self, compiler, sql, params):
187
+ def select_format(
188
+ self, compiler: SQLCompiler, sql: str, params: list[Any]
189
+ ) -> tuple[str, list[Any]]:
168
190
  # Wrap filters with a CASE WHEN expression if a database backend
169
191
  # (e.g. Oracle) doesn't support boolean expression in SELECT or GROUP
170
192
  # BY list.
@@ -179,16 +201,16 @@ class Transform(RegisterLookupMixin, Func):
179
201
  first examine self and then check output_field.
180
202
  """
181
203
 
182
- bilateral = False
183
- arity = 1
204
+ bilateral: bool = False
205
+ arity: int = 1
184
206
 
185
207
  @property
186
- def lhs(self):
208
+ def lhs(self) -> Any:
187
209
  return self.get_source_expressions()[0]
188
210
 
189
- def get_bilateral_transforms(self):
211
+ def get_bilateral_transforms(self) -> list[type[Transform]]:
190
212
  if hasattr(self.lhs, "get_bilateral_transforms"):
191
- bilateral_transforms = self.lhs.get_bilateral_transforms()
213
+ bilateral_transforms = self.lhs.get_bilateral_transforms() # type: ignore[attr-defined]
192
214
  else:
193
215
  bilateral_transforms = []
194
216
  if self.bilateral:
@@ -197,25 +219,29 @@ class Transform(RegisterLookupMixin, Func):
197
219
 
198
220
 
199
221
  class BuiltinLookup(Lookup):
200
- def process_lhs(self, compiler, connection, lhs=None):
201
- lhs_sql, params = super().process_lhs(compiler, connection, lhs)
202
- field_internal_type = self.lhs.output_field.get_internal_type()
203
- db_type = self.lhs.output_field.db_type(connection=connection)
222
+ def process_lhs(
223
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper, lhs: Any = None
224
+ ) -> tuple[str, list[Any]]:
225
+ lhs_sql, params = super().process_lhs(compiler, connection, lhs) # type: ignore[misc]
226
+ field_internal_type = self.lhs.output_field.get_internal_type() # type: ignore[attr-defined]
227
+ db_type = self.lhs.output_field.db_type(connection=connection) # type: ignore[attr-defined]
204
228
  lhs_sql = connection.ops.field_cast_sql(db_type, field_internal_type) % lhs_sql
205
229
  lhs_sql = (
206
- connection.ops.lookup_cast(self.lookup_name, field_internal_type) % lhs_sql
230
+ connection.ops.lookup_cast(self.lookup_name, field_internal_type) % lhs_sql # type: ignore[arg-type]
207
231
  )
208
232
  return lhs_sql, list(params)
209
233
 
210
- def as_sql(self, compiler, connection):
234
+ def as_sql(
235
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
236
+ ) -> tuple[str, list[Any]]:
211
237
  lhs_sql, params = self.process_lhs(compiler, connection)
212
238
  rhs_sql, rhs_params = self.process_rhs(compiler, connection)
213
239
  params.extend(rhs_params)
214
240
  rhs_sql = self.get_rhs_op(connection, rhs_sql)
215
241
  return f"{lhs_sql} {rhs_sql}", params
216
242
 
217
- def get_rhs_op(self, connection, rhs):
218
- return connection.operators[self.lookup_name] % rhs
243
+ def get_rhs_op(self, connection: BaseDatabaseWrapper, rhs: str) -> str:
244
+ return connection.operators[self.lookup_name] % rhs # type: ignore[index]
219
245
 
220
246
 
221
247
  class FieldGetDbPrepValueMixin:
@@ -224,15 +250,19 @@ class FieldGetDbPrepValueMixin:
224
250
  inputs.
225
251
  """
226
252
 
227
- get_db_prep_lookup_value_is_iterable = False
253
+ get_db_prep_lookup_value_is_iterable: bool = False
254
+ lhs: Any
255
+ rhs: Any
228
256
 
229
- def get_db_prep_lookup(self, value, connection):
257
+ def get_db_prep_lookup(
258
+ self, value: Any, connection: BaseDatabaseWrapper
259
+ ) -> tuple[str, list[Any]]:
230
260
  # For relational fields, use the 'target_field' attribute of the
231
261
  # output_field.
232
- field = getattr(self.lhs.output_field, "target_field", None)
262
+ field = getattr(self.lhs.output_field, "target_field", None) # type: ignore[attr-defined]
233
263
  get_db_prep_value = (
234
264
  getattr(field, "get_db_prep_value", None)
235
- or self.lhs.output_field.get_db_prep_value
265
+ or self.lhs.output_field.get_db_prep_value # type: ignore[attr-defined]
236
266
  )
237
267
  return (
238
268
  "%s",
@@ -248,9 +278,10 @@ class FieldGetDbPrepValueIterableMixin(FieldGetDbPrepValueMixin):
248
278
  in an iterable.
249
279
  """
250
280
 
251
- get_db_prep_lookup_value_is_iterable = True
281
+ get_db_prep_lookup_value_is_iterable: bool = True
282
+ prepare_rhs: bool
252
283
 
253
- def get_prep_lookup(self):
284
+ def get_prep_lookup(self) -> Any:
254
285
  if hasattr(self.rhs, "resolve_expression"):
255
286
  return self.rhs
256
287
  prepared_values = []
@@ -259,29 +290,40 @@ class FieldGetDbPrepValueIterableMixin(FieldGetDbPrepValueMixin):
259
290
  # An expression will be handled by the database but can coexist
260
291
  # alongside real values.
261
292
  pass
262
- elif self.prepare_rhs and hasattr(self.lhs.output_field, "get_prep_value"):
263
- rhs_value = self.lhs.output_field.get_prep_value(rhs_value)
293
+ elif self.prepare_rhs and hasattr(self.lhs.output_field, "get_prep_value"): # type: ignore[attr-defined]
294
+ rhs_value = self.lhs.output_field.get_prep_value(rhs_value) # type: ignore[attr-defined]
264
295
  prepared_values.append(rhs_value)
265
296
  return prepared_values
266
297
 
267
- def process_rhs(self, compiler, connection):
268
- if self.rhs_is_direct_value():
298
+ def process_rhs(
299
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
300
+ ) -> tuple[str, list[Any]]:
301
+ if self.rhs_is_direct_value(): # type: ignore[attr-defined]
269
302
  # rhs should be an iterable of values. Use batch_process_rhs()
270
303
  # to prepare/transform those values.
271
- return self.batch_process_rhs(compiler, connection)
304
+ return self.batch_process_rhs(compiler, connection) # type: ignore[attr-defined]
272
305
  else:
273
- return super().process_rhs(compiler, connection)
274
-
275
- def resolve_expression_parameter(self, compiler, connection, sql, param):
276
- params = [param]
306
+ return super().process_rhs(compiler, connection) # type: ignore[misc]
307
+
308
+ def resolve_expression_parameter(
309
+ self,
310
+ compiler: SQLCompiler,
311
+ connection: BaseDatabaseWrapper,
312
+ sql: str,
313
+ param: Any,
314
+ ) -> tuple[str, list[Any]]:
315
+ params: list[Any] = [param]
277
316
  if hasattr(param, "resolve_expression"):
278
- param = param.resolve_expression(compiler.query)
317
+ param = param.resolve_expression(compiler.query) # type: ignore[attr-defined]
279
318
  if hasattr(param, "as_sql"):
280
- sql, params = compiler.compile(param)
319
+ sql, compiled_params = compiler.compile(param)
320
+ params = list(compiled_params)
281
321
  return sql, params
282
322
 
283
- def batch_process_rhs(self, compiler, connection, rhs=None):
284
- pre_processed = super().batch_process_rhs(compiler, connection, rhs)
323
+ def batch_process_rhs(
324
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper, rhs: Any = None
325
+ ) -> tuple[tuple[str, ...], tuple[Any, ...]]:
326
+ pre_processed = super().batch_process_rhs(compiler, connection, rhs) # type: ignore[misc]
285
327
  # The params list may contain expressions which compile to a
286
328
  # sql/param pair. Zip them to get sql and param pairs that refer to the
287
329
  # same argument and attempt to replace them with the result of
@@ -299,9 +341,11 @@ class FieldGetDbPrepValueIterableMixin(FieldGetDbPrepValueMixin):
299
341
  class PostgresOperatorLookup(Lookup):
300
342
  """Lookup defined by operators on PostgreSQL."""
301
343
 
302
- postgres_operator = None
344
+ postgres_operator: str | None = None
303
345
 
304
- def as_postgresql(self, compiler, connection):
346
+ def as_postgresql(
347
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
348
+ ) -> tuple[str, tuple[Any, ...]]:
305
349
  lhs, lhs_params = self.process_lhs(compiler, connection)
306
350
  rhs, rhs_params = self.process_rhs(compiler, connection)
307
351
  params = tuple(lhs_params) + tuple(rhs_params)
@@ -310,9 +354,9 @@ class PostgresOperatorLookup(Lookup):
310
354
 
311
355
  @Field.register_lookup
312
356
  class Exact(FieldGetDbPrepValueMixin, BuiltinLookup):
313
- lookup_name = "exact"
357
+ lookup_name: str = "exact"
314
358
 
315
- def get_prep_lookup(self):
359
+ def get_prep_lookup(self) -> Any:
316
360
  from plain.models.sql.query import Query # avoid circular import
317
361
 
318
362
  if isinstance(self.rhs, Query):
@@ -325,9 +369,11 @@ class Exact(FieldGetDbPrepValueMixin, BuiltinLookup):
325
369
  "The QuerySet value for an exact lookup must be limited to "
326
370
  "one result using slicing."
327
371
  )
328
- return super().get_prep_lookup()
372
+ return super().get_prep_lookup() # type: ignore[misc]
329
373
 
330
- def as_sql(self, compiler, connection):
374
+ def as_sql(
375
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
376
+ ) -> tuple[str, list[Any]]:
331
377
  # Avoid comparison against direct rhs if lhs is a boolean value. That
332
378
  # turns "boolfield__exact=True" into "WHERE boolean_field" instead of
333
379
  # "WHERE boolean_field = True" when allowed.
@@ -341,16 +387,18 @@ class Exact(FieldGetDbPrepValueMixin, BuiltinLookup):
341
387
  lhs_sql, params = self.process_lhs(compiler, connection)
342
388
  template = "%s" if self.rhs else "NOT %s"
343
389
  return template % lhs_sql, params
344
- return super().as_sql(compiler, connection)
390
+ return super().as_sql(compiler, connection) # type: ignore[misc]
345
391
 
346
392
 
347
393
  @Field.register_lookup
348
394
  class IExact(BuiltinLookup):
349
- lookup_name = "iexact"
350
- prepare_rhs = False
395
+ lookup_name: str = "iexact"
396
+ prepare_rhs: bool = False
351
397
 
352
- def process_rhs(self, qn, connection):
353
- rhs, params = super().process_rhs(qn, connection)
398
+ def process_rhs(
399
+ self, qn: SQLCompiler, connection: BaseDatabaseWrapper
400
+ ) -> tuple[str, list[Any]]:
401
+ rhs, params = super().process_rhs(qn, connection) # type: ignore[misc]
354
402
  if params:
355
403
  params[0] = connection.ops.prep_for_iexact_query(params[0])
356
404
  return rhs, params
@@ -358,32 +406,36 @@ class IExact(BuiltinLookup):
358
406
 
359
407
  @Field.register_lookup
360
408
  class GreaterThan(FieldGetDbPrepValueMixin, BuiltinLookup):
361
- lookup_name = "gt"
409
+ lookup_name: str = "gt"
362
410
 
363
411
 
364
412
  @Field.register_lookup
365
413
  class GreaterThanOrEqual(FieldGetDbPrepValueMixin, BuiltinLookup):
366
- lookup_name = "gte"
414
+ lookup_name: str = "gte"
367
415
 
368
416
 
369
417
  @Field.register_lookup
370
418
  class LessThan(FieldGetDbPrepValueMixin, BuiltinLookup):
371
- lookup_name = "lt"
419
+ lookup_name: str = "lt"
372
420
 
373
421
 
374
422
  @Field.register_lookup
375
423
  class LessThanOrEqual(FieldGetDbPrepValueMixin, BuiltinLookup):
376
- lookup_name = "lte"
424
+ lookup_name: str = "lte"
377
425
 
378
426
 
379
427
  class IntegerFieldOverflow:
380
- underflow_exception = EmptyResultSet
381
- overflow_exception = EmptyResultSet
382
-
383
- def process_rhs(self, compiler, connection):
428
+ underflow_exception: type[Exception] = EmptyResultSet
429
+ overflow_exception: type[Exception] = EmptyResultSet
430
+ lhs: Any
431
+ rhs: Any
432
+
433
+ def process_rhs(
434
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
435
+ ) -> tuple[str, list[Any]]:
384
436
  rhs = self.rhs
385
437
  if isinstance(rhs, int):
386
- field_internal_type = self.lhs.output_field.get_internal_type()
438
+ field_internal_type = self.lhs.output_field.get_internal_type() # type: ignore[attr-defined]
387
439
  min_value, max_value = connection.ops.integer_field_range(
388
440
  field_internal_type
389
441
  )
@@ -391,7 +443,7 @@ class IntegerFieldOverflow:
391
443
  raise self.underflow_exception
392
444
  if max_value is not None and rhs > max_value:
393
445
  raise self.overflow_exception
394
- return super().process_rhs(compiler, connection)
446
+ return super().process_rhs(compiler, connection) # type: ignore[misc]
395
447
 
396
448
 
397
449
  class IntegerFieldFloatRounding:
@@ -400,10 +452,12 @@ class IntegerFieldFloatRounding:
400
452
  decimal portion of the float would always be discarded.
401
453
  """
402
454
 
403
- def get_prep_lookup(self):
455
+ rhs: Any
456
+
457
+ def get_prep_lookup(self) -> Any:
404
458
  if isinstance(self.rhs, float):
405
459
  self.rhs = math.ceil(self.rhs)
406
- return super().get_prep_lookup()
460
+ return super().get_prep_lookup() # type: ignore[misc]
407
461
 
408
462
 
409
463
  @IntegerField.register_lookup
@@ -435,9 +489,9 @@ class IntegerLessThanOrEqual(IntegerFieldOverflow, LessThanOrEqual):
435
489
 
436
490
  @Field.register_lookup
437
491
  class In(FieldGetDbPrepValueIterableMixin, BuiltinLookup):
438
- lookup_name = "in"
492
+ lookup_name: str = "in"
439
493
 
440
- def get_prep_lookup(self):
494
+ def get_prep_lookup(self) -> Any:
441
495
  from plain.models.sql.query import Query # avoid circular import
442
496
 
443
497
  if isinstance(self.rhs, Query):
@@ -445,10 +499,12 @@ class In(FieldGetDbPrepValueIterableMixin, BuiltinLookup):
445
499
  if not self.rhs.has_select_fields:
446
500
  self.rhs.clear_select_clause()
447
501
  self.rhs.add_fields(["id"])
448
- return super().get_prep_lookup()
502
+ return super().get_prep_lookup() # type: ignore[misc]
449
503
 
450
- def process_rhs(self, compiler, connection):
451
- if self.rhs_is_direct_value():
504
+ def process_rhs(
505
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
506
+ ) -> tuple[str, list[Any]] | tuple[str, tuple[Any, ...]]:
507
+ if self.rhs_is_direct_value(): # type: ignore[attr-defined]
452
508
  # Remove None from the list as NULL is never equal to anything.
453
509
  try:
454
510
  rhs = OrderedSet(self.rhs)
@@ -461,39 +517,43 @@ class In(FieldGetDbPrepValueIterableMixin, BuiltinLookup):
461
517
 
462
518
  # rhs should be an iterable; use batch_process_rhs() to
463
519
  # prepare/transform those values.
464
- sqls, sqls_params = self.batch_process_rhs(compiler, connection, rhs)
520
+ sqls, sqls_params = self.batch_process_rhs(compiler, connection, rhs) # type: ignore[attr-defined]
465
521
  placeholder = "(" + ", ".join(sqls) + ")"
466
522
  return (placeholder, sqls_params)
467
- return super().process_rhs(compiler, connection)
523
+ return super().process_rhs(compiler, connection) # type: ignore[misc]
468
524
 
469
- def get_rhs_op(self, connection, rhs):
525
+ def get_rhs_op(self, connection: BaseDatabaseWrapper, rhs: str) -> str:
470
526
  return f"IN {rhs}"
471
527
 
472
- def as_sql(self, compiler, connection):
528
+ def as_sql(
529
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
530
+ ) -> tuple[str, list[Any]]:
473
531
  max_in_list_size = connection.ops.max_in_list_size()
474
532
  if (
475
- self.rhs_is_direct_value()
533
+ self.rhs_is_direct_value() # type: ignore[attr-defined]
476
534
  and max_in_list_size
477
535
  and len(self.rhs) > max_in_list_size
478
536
  ):
479
537
  return self.split_parameter_list_as_sql(compiler, connection)
480
- return super().as_sql(compiler, connection)
538
+ return super().as_sql(compiler, connection) # type: ignore[misc]
481
539
 
482
- def split_parameter_list_as_sql(self, compiler, connection):
540
+ def split_parameter_list_as_sql(
541
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
542
+ ) -> tuple[str, list[Any]]:
483
543
  # This is a special case for databases which limit the number of
484
544
  # elements which can appear in an 'IN' clause.
485
545
  max_in_list_size = connection.ops.max_in_list_size()
486
546
  lhs, lhs_params = self.process_lhs(compiler, connection)
487
- rhs, rhs_params = self.batch_process_rhs(compiler, connection)
547
+ rhs, rhs_params = self.batch_process_rhs(compiler, connection) # type: ignore[attr-defined]
488
548
  in_clause_elements = ["("]
489
549
  params = []
490
- for offset in range(0, len(rhs_params), max_in_list_size):
550
+ for offset in range(0, len(rhs_params), max_in_list_size): # type: ignore[arg-type]
491
551
  if offset > 0:
492
552
  in_clause_elements.append(" OR ")
493
553
  in_clause_elements.append(f"{lhs} IN (")
494
554
  params.extend(lhs_params)
495
- sqls = rhs[offset : offset + max_in_list_size]
496
- sqls_params = rhs_params[offset : offset + max_in_list_size]
555
+ sqls = rhs[offset : offset + max_in_list_size] # type: ignore[operator]
556
+ sqls_params = rhs_params[offset : offset + max_in_list_size] # type: ignore[index,operator]
497
557
  param_group = ", ".join(sqls)
498
558
  in_clause_elements.append(param_group)
499
559
  in_clause_elements.append(")")
@@ -503,10 +563,11 @@ class In(FieldGetDbPrepValueIterableMixin, BuiltinLookup):
503
563
 
504
564
 
505
565
  class PatternLookup(BuiltinLookup):
506
- param_pattern = "%%%s%%"
507
- prepare_rhs = False
566
+ param_pattern: str = "%%%s%%"
567
+ prepare_rhs: bool = False
568
+ bilateral_transforms: list[Any]
508
569
 
509
- def get_rhs_op(self, connection, rhs):
570
+ def get_rhs_op(self, connection: BaseDatabaseWrapper, rhs: str) -> str:
510
571
  # Assume we are in startswith. We need to produce SQL like:
511
572
  # col LIKE %s, ['thevalue%']
512
573
  # For python values we can (and should) do that directly in Python,
@@ -517,16 +578,18 @@ class PatternLookup(BuiltinLookup):
517
578
  # SQL reference values or SQL transformations we need the correct
518
579
  # pattern added.
519
580
  if hasattr(self.rhs, "as_sql") or self.bilateral_transforms:
520
- pattern = connection.pattern_ops[self.lookup_name].format(
521
- connection.pattern_esc
581
+ pattern = connection.pattern_ops[self.lookup_name].format( # type: ignore[index]
582
+ connection.pattern_esc # type: ignore[attr-defined]
522
583
  )
523
584
  return pattern.format(rhs)
524
585
  else:
525
586
  return super().get_rhs_op(connection, rhs)
526
587
 
527
- def process_rhs(self, qn, connection):
528
- rhs, params = super().process_rhs(qn, connection)
529
- if self.rhs_is_direct_value() and params and not self.bilateral_transforms:
588
+ def process_rhs(
589
+ self, qn: SQLCompiler, connection: BaseDatabaseWrapper
590
+ ) -> tuple[str, list[Any]]:
591
+ rhs, params = super().process_rhs(qn, connection) # type: ignore[misc]
592
+ if self.rhs_is_direct_value() and params and not self.bilateral_transforms: # type: ignore[attr-defined]
530
593
  params[0] = self.param_pattern % connection.ops.prep_for_like_query(
531
594
  params[0]
532
595
  )
@@ -535,50 +598,52 @@ class PatternLookup(BuiltinLookup):
535
598
 
536
599
  @Field.register_lookup
537
600
  class Contains(PatternLookup):
538
- lookup_name = "contains"
601
+ lookup_name: str = "contains"
539
602
 
540
603
 
541
604
  @Field.register_lookup
542
605
  class IContains(Contains):
543
- lookup_name = "icontains"
606
+ lookup_name: str = "icontains"
544
607
 
545
608
 
546
609
  @Field.register_lookup
547
610
  class StartsWith(PatternLookup):
548
- lookup_name = "startswith"
549
- param_pattern = "%s%%"
611
+ lookup_name: str = "startswith"
612
+ param_pattern: str = "%s%%"
550
613
 
551
614
 
552
615
  @Field.register_lookup
553
616
  class IStartsWith(StartsWith):
554
- lookup_name = "istartswith"
617
+ lookup_name: str = "istartswith"
555
618
 
556
619
 
557
620
  @Field.register_lookup
558
621
  class EndsWith(PatternLookup):
559
- lookup_name = "endswith"
560
- param_pattern = "%%%s"
622
+ lookup_name: str = "endswith"
623
+ param_pattern: str = "%%%s"
561
624
 
562
625
 
563
626
  @Field.register_lookup
564
627
  class IEndsWith(EndsWith):
565
- lookup_name = "iendswith"
628
+ lookup_name: str = "iendswith"
566
629
 
567
630
 
568
631
  @Field.register_lookup
569
632
  class Range(FieldGetDbPrepValueIterableMixin, BuiltinLookup):
570
- lookup_name = "range"
633
+ lookup_name: str = "range"
571
634
 
572
- def get_rhs_op(self, connection, rhs):
635
+ def get_rhs_op(self, connection: BaseDatabaseWrapper, rhs: str) -> str:
573
636
  return f"BETWEEN {rhs[0]} AND {rhs[1]}"
574
637
 
575
638
 
576
639
  @Field.register_lookup
577
640
  class IsNull(BuiltinLookup):
578
- lookup_name = "isnull"
579
- prepare_rhs = False
641
+ lookup_name: str = "isnull"
642
+ prepare_rhs: bool = False
580
643
 
581
- def as_sql(self, compiler, connection):
644
+ def as_sql(
645
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
646
+ ) -> tuple[str, list[Any]]:
582
647
  if not isinstance(self.rhs, bool):
583
648
  raise ValueError(
584
649
  "The QuerySet value for an isnull lookup must be True or False."
@@ -592,12 +657,14 @@ class IsNull(BuiltinLookup):
592
657
 
593
658
  @Field.register_lookup
594
659
  class Regex(BuiltinLookup):
595
- lookup_name = "regex"
596
- prepare_rhs = False
597
-
598
- def as_sql(self, compiler, connection):
599
- if self.lookup_name in connection.operators:
600
- return super().as_sql(compiler, connection)
660
+ lookup_name: str = "regex"
661
+ prepare_rhs: bool = False
662
+
663
+ def as_sql(
664
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
665
+ ) -> tuple[str, list[Any]]:
666
+ if self.lookup_name in connection.operators: # type: ignore[operator]
667
+ return super().as_sql(compiler, connection) # type: ignore[misc]
601
668
  else:
602
669
  lhs, lhs_params = self.process_lhs(compiler, connection)
603
670
  rhs, rhs_params = self.process_rhs(compiler, connection)
@@ -607,15 +674,17 @@ class Regex(BuiltinLookup):
607
674
 
608
675
  @Field.register_lookup
609
676
  class IRegex(Regex):
610
- lookup_name = "iregex"
677
+ lookup_name: str = "iregex"
611
678
 
612
679
 
613
680
  class YearLookup(Lookup):
614
- def year_lookup_bounds(self, connection, year):
681
+ def year_lookup_bounds(
682
+ self, connection: BaseDatabaseWrapper, year: int
683
+ ) -> list[str | Any | None]:
615
684
  from plain.models.functions import ExtractIsoYear
616
685
 
617
686
  iso_year = isinstance(self.lhs, ExtractIsoYear)
618
- output_field = self.lhs.lhs.output_field
687
+ output_field = self.lhs.lhs.output_field # type: ignore[attr-defined]
619
688
  if isinstance(output_field, DateTimeField):
620
689
  bounds = connection.ops.year_lookup_bounds_for_datetime_field(
621
690
  year,
@@ -628,54 +697,56 @@ class YearLookup(Lookup):
628
697
  )
629
698
  return bounds
630
699
 
631
- def as_sql(self, compiler, connection):
700
+ def as_sql(
701
+ self, compiler: SQLCompiler, connection: BaseDatabaseWrapper
702
+ ) -> tuple[str, list[Any]]:
632
703
  # Avoid the extract operation if the rhs is a direct value to allow
633
704
  # indexes to be used.
634
705
  if self.rhs_is_direct_value():
635
706
  # Skip the extract part by directly using the originating field,
636
707
  # that is self.lhs.lhs.
637
- lhs_sql, params = self.process_lhs(compiler, connection, self.lhs.lhs)
708
+ lhs_sql, params = self.process_lhs(compiler, connection, self.lhs.lhs) # type: ignore[attr-defined]
638
709
  rhs_sql, _ = self.process_rhs(compiler, connection)
639
710
  rhs_sql = self.get_direct_rhs_sql(connection, rhs_sql)
640
711
  start, finish = self.year_lookup_bounds(connection, self.rhs)
641
712
  params.extend(self.get_bound_params(start, finish))
642
713
  return f"{lhs_sql} {rhs_sql}", params
643
- return super().as_sql(compiler, connection)
714
+ return super().as_sql(compiler, connection) # type: ignore[misc]
644
715
 
645
- def get_direct_rhs_sql(self, connection, rhs):
646
- return connection.operators[self.lookup_name] % rhs
716
+ def get_direct_rhs_sql(self, connection: BaseDatabaseWrapper, rhs: str) -> str:
717
+ return connection.operators[self.lookup_name] % rhs # type: ignore[index]
647
718
 
648
- def get_bound_params(self, start, finish):
719
+ def get_bound_params(self, start: Any, finish: Any) -> tuple[Any, ...]:
649
720
  raise NotImplementedError(
650
721
  "subclasses of YearLookup must provide a get_bound_params() method"
651
722
  )
652
723
 
653
724
 
654
725
  class YearExact(YearLookup, Exact):
655
- def get_direct_rhs_sql(self, connection, rhs):
726
+ def get_direct_rhs_sql(self, connection: BaseDatabaseWrapper, rhs: str) -> str:
656
727
  return "BETWEEN %s AND %s"
657
728
 
658
- def get_bound_params(self, start, finish):
729
+ def get_bound_params(self, start: Any, finish: Any) -> tuple[Any, Any]:
659
730
  return (start, finish)
660
731
 
661
732
 
662
733
  class YearGt(YearLookup, GreaterThan):
663
- def get_bound_params(self, start, finish):
734
+ def get_bound_params(self, start: Any, finish: Any) -> tuple[Any]:
664
735
  return (finish,)
665
736
 
666
737
 
667
738
  class YearGte(YearLookup, GreaterThanOrEqual):
668
- def get_bound_params(self, start, finish):
739
+ def get_bound_params(self, start: Any, finish: Any) -> tuple[Any]:
669
740
  return (start,)
670
741
 
671
742
 
672
743
  class YearLt(YearLookup, LessThan):
673
- def get_bound_params(self, start, finish):
744
+ def get_bound_params(self, start: Any, finish: Any) -> tuple[Any]:
674
745
  return (start,)
675
746
 
676
747
 
677
748
  class YearLte(YearLookup, LessThanOrEqual):
678
- def get_bound_params(self, start, finish):
749
+ def get_bound_params(self, start: Any, finish: Any) -> tuple[Any]:
679
750
  return (finish,)
680
751
 
681
752
 
@@ -685,16 +756,20 @@ class UUIDTextMixin:
685
756
  a native datatype for UUID.
686
757
  """
687
758
 
688
- def process_rhs(self, qn, connection):
759
+ rhs: Any
760
+
761
+ def process_rhs(
762
+ self, qn: SQLCompiler, connection: BaseDatabaseWrapper
763
+ ) -> tuple[str, list[Any]]:
689
764
  if not connection.features.has_native_uuid_field:
690
765
  from plain.models.functions import Replace
691
766
 
692
- if self.rhs_is_direct_value():
767
+ if self.rhs_is_direct_value(): # type: ignore[attr-defined]
693
768
  self.rhs = Value(self.rhs)
694
769
  self.rhs = Replace(
695
770
  self.rhs, Value("-"), Value(""), output_field=CharField()
696
771
  )
697
- rhs, params = super().process_rhs(qn, connection)
772
+ rhs, params = super().process_rhs(qn, connection) # type: ignore[misc]
698
773
  return rhs, params
699
774
 
700
775