TypeDAL 5.1.2__tar.gz → 5.1.4__tar.gz

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 (29) hide show
  1. {typedal-5.1.2 → typedal-5.1.4}/PKG-INFO +2 -1
  2. {typedal-5.1.2 → typedal-5.1.4}/pyproject.toml +6 -2
  3. {typedal-5.1.2 → typedal-5.1.4}/pyproject.toml.orig +4 -2
  4. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/cli.py +1 -1
  5. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/core.py +13 -37
  6. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/define.py +1 -1
  7. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/fields.py +2 -2
  8. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/helpers.py +1 -1
  9. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/query_builder.py +9 -6
  10. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/rows.py +20 -11
  11. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/tables.py +10 -8
  12. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/types.py +12 -72
  13. {typedal-5.1.2 → typedal-5.1.4}/README.md +0 -0
  14. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/__about__.py +0 -0
  15. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/__init__.py +0 -0
  16. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/asynchronous.py +0 -0
  17. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/caching.py +0 -0
  18. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/config.py +0 -0
  19. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/constants.py +0 -0
  20. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/enum_helpers.py +0 -0
  21. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/for_py4web.py +0 -0
  22. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/for_web2py.py +0 -0
  23. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/mixins.py +0 -0
  24. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/py.typed +0 -0
  25. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/relationships.py +0 -0
  26. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/serializers/as_json.py +0 -0
  27. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/serializers/typescript.py +0 -0
  28. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/warnings.py +0 -0
  29. {typedal-5.1.2 → typedal-5.1.4}/src/typedal/web2py_py4web_shared.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: TypeDAL
3
- Version: 5.1.2
3
+ Version: 5.1.4
4
4
  Summary: Typing support for PyDAL
5
5
  Keywords:
6
6
  Author: Robin van der Noord
@@ -13,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.14
13
13
  Classifier: Programming Language :: Python :: Implementation :: CPython
14
14
  Classifier: Programming Language :: Python :: Implementation :: PyPy
15
15
  Requires-Dist: pydal>=20251012.3
16
+ Requires-Dist: pydal-stubs>=0.1.0
16
17
  Requires-Dist: dill<1
17
18
  Requires-Dist: configuraptor>=2.1.0,<3
18
19
  Requires-Dist: configurable-json<2
@@ -6,7 +6,7 @@ build-backend = "uv_build"
6
6
  name = "TypeDAL"
7
7
  description = "Typing support for PyDAL"
8
8
  readme = "README.md"
9
- version = "5.1.2"
9
+ version = "5.1.4"
10
10
  requires-python = ">=3.12"
11
11
  license-expression = "MIT"
12
12
  keywords = []
@@ -21,6 +21,7 @@ classifiers = [
21
21
  ]
22
22
  dependencies = [
23
23
  "pydal >= 20251012.3",
24
+ "pydal-stubs >= 0.1.0",
24
25
  "dill < 1",
25
26
  "configuraptor >= 2.1.0, < 3",
26
27
  "Configurable-JSON < 2",
@@ -226,7 +227,10 @@ extend-ignore = [
226
227
  "D212",
227
228
  "D418",
228
229
  ]
229
- ignore = ["RUF013"]
230
+ ignore = [
231
+ "RUF012",
232
+ "RUF013",
233
+ ]
230
234
 
231
235
  [tool.ruff.lint.pydocstyle]
232
236
  convention = "google"
@@ -6,7 +6,7 @@ build-backend = "uv_build"
6
6
  name = "TypeDAL"
7
7
  description = 'Typing support for PyDAL'
8
8
  readme = "README.md"
9
- version = "5.1.2"
9
+ version = "5.1.4"
10
10
  requires-python = ">=3.12"
11
11
  license-expression = "MIT"
12
12
  keywords = []
@@ -24,6 +24,7 @@ classifiers = [
24
24
  ]
25
25
  dependencies = [
26
26
  "pydal >= 20251012.3", # core
27
+ "pydal-stubs >= 0.1.0", # improved typing for pydal
27
28
  "dill < 1", # caching
28
29
  "configuraptor >= 2.1.0, < 3", # config
29
30
  "Configurable-JSON < 2", # json dumping
@@ -270,7 +271,8 @@ extend-ignore = [
270
271
  ]
271
272
 
272
273
  ignore = [
273
- "RUF013" # implicit optional
274
+ "RUF012", # mutable class attributes
275
+ "RUF013", # implicit optional
274
276
  ]
275
277
 
276
278
 
@@ -227,7 +227,7 @@ def setup(
227
227
  data.pop("connection", None)
228
228
 
229
229
  # ignore any None:
230
- old_contents["tool"]["typedal"] = {k: v for k, v in data.items() if v is not None}
230
+ old_contents["tool"]["typedal"] = {k: v for k, v in data.items() if v is not None} # type: ignore
231
231
 
232
232
  with toml_path.open("w") as f:
233
233
  tomlkit.dump(old_contents, f)
@@ -46,7 +46,7 @@ except ImportError: # pragma: no cover
46
46
  if t.TYPE_CHECKING:
47
47
  from .fields import TypedField
48
48
  from .query_builder import QueryBuilder
49
- from .types import AnyDict, DefineKwargs, Rows, Set, T_Query, Table
49
+ from .types import AnyDict, DefineKwargs, Rows, T_Query, Table
50
50
 
51
51
 
52
52
  def _expression_subclasses() -> t.Iterator[type[Expression]]:
@@ -200,36 +200,8 @@ def resolve_annotation(ftype: str, namespace: t.Mapping[str, type] | None = None
200
200
  return resolve_annotation_314(ftype, namespace=namespace)
201
201
 
202
202
 
203
- if t.TYPE_CHECKING:
204
-
205
- class _TypeDALBase:
206
- # attributes accessed throughout the codebase
207
- _adapter: t.Any
208
- _migrate: t.Any
209
- representers: t.Any
210
-
211
- def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: ...
212
-
213
- def __call__(self, query: t.Any = None) -> "Set": ...
214
-
215
- def commit(self) -> None: ...
216
-
217
- def rollback(self) -> None: ...
218
-
219
- def define_table(self, *args: t.Any, **kwargs: t.Any) -> "Table": ...
220
-
221
- def has_representer(self, field_type: str) -> bool: ...
222
-
223
- def represent(self, name: str, *args: t.Any, **kwargs: t.Any) -> str: ...
224
-
225
- # pydal exposes dynamic table attributes like `db.my_table`.
226
- # this keeps type checkers from flagging these as missing attributes.
227
- def __getattr__(self, item: str) -> "Table": ...
228
-
229
- else:
230
-
231
- class _TypeDALBase(pydal.DAL):
232
- pass
203
+ class _TypeDALBase(pydal.DAL):
204
+ pass
233
205
 
234
206
 
235
207
  class TypeDAL(_TypeDALBase):
@@ -251,7 +223,7 @@ class TypeDAL(_TypeDALBase):
251
223
  _async_workers: ConnectionWorkerPool
252
224
 
253
225
  # pydal runs these around every statement; drop BlockingAccessHandler to disable the guard.
254
- execution_handlers: t.ClassVar[list[type[ExecutionHandler]]] = [
226
+ execution_handlers: list[type[ExecutionHandler]] = [
255
227
  *pydal.DAL.execution_handlers,
256
228
  BlockingAccessHandler,
257
229
  ]
@@ -419,7 +391,7 @@ class TypeDAL(_TypeDALBase):
419
391
  self._async_workers.shutdown()
420
392
  adapter = self._adapter
421
393
  try:
422
- super().close() # ty: ignore[unresolved-attribute]
394
+ super().close()
423
395
  finally:
424
396
  for model in set(self._builder.class_map.values()):
425
397
  model.unbind()
@@ -447,7 +419,9 @@ class TypeDAL(_TypeDALBase):
447
419
  hooks.clear()
448
420
 
449
421
  if hasattr(table, "_db"):
450
- table._db = None
422
+ # outside pydal's contract: pydal only ever nulls a *field's*
423
+ # bindings, but the cycle has to be broken here too.
424
+ table._db = None # ty: ignore[invalid-assignment]
451
425
 
452
426
  for field_name in getattr(table, "fields", ()):
453
427
  field: Field | None = getattr(table, field_name, None)
@@ -561,7 +535,9 @@ class TypeDAL(_TypeDALBase):
561
535
  db(query).select()
562
536
 
563
537
  """
564
- args = list(_args)
538
+ # heterogeneous on purpose: this list is splatted across pydal's
539
+ # (query, ignore_common_filters) parameters.
540
+ args: list[t.Any] = list(_args)
565
541
  if args:
566
542
  cls = args[0]
567
543
  if isinstance(cls, bool):
@@ -584,7 +560,7 @@ class TypeDAL(_TypeDALBase):
584
560
  Example:
585
561
  db['users'] -> user
586
562
  """
587
- return t.cast(Table, super().__getitem__(str(key))) # ty: ignore[unresolved-attribute]
563
+ return super().__getitem__(str(key))
588
564
 
589
565
  def find_model(self, table_name: str) -> t.Type["TypedTable"] | None:
590
566
  """
@@ -664,7 +640,7 @@ class TypeDAL(_TypeDALBase):
664
640
  if SYSTEM_SUPPORTS_TEMPLATES and isinstance(query, Template): # pragma: no cover
665
641
  query = sql_escape_template(self, query)
666
642
 
667
- rows: list[t.Any] = super().executesql( # ty: ignore[unresolved-attribute]
643
+ rows: list[t.Any] = super().executesql(
668
644
  query,
669
645
  placeholders=placeholders,
670
646
  as_dict=as_dict,
@@ -133,7 +133,7 @@ class TableDefinitionBuilder:
133
133
 
134
134
  table._after_insert.append(lambda _row, _id: remove_cache_for_table(tablename))
135
135
  table._before_update.append(lambda s, _: _remove_cache(s, tablename))
136
- table._before_delete.append(lambda s: _remove_cache(s, tablename)) # ty: ignore[invalid-argument-type]
136
+ table._before_delete.append(lambda s: _remove_cache(s, tablename))
137
137
 
138
138
  return cls
139
139
 
@@ -268,7 +268,7 @@ class TypedField[T_Value](Expression): # pragma: no cover
268
268
  if not self._field: # pragma: no cover
269
269
  raise ValueError("Unbound Field can not be inverted!")
270
270
 
271
- return t.cast(Expression, ~self._field)
271
+ return ~self._field
272
272
 
273
273
  def lower(self) -> Expression:
274
274
  """
@@ -277,7 +277,7 @@ class TypedField[T_Value](Expression): # pragma: no cover
277
277
  if not self._field: # pragma: no cover
278
278
  raise ValueError("Unbound Field can not be lowered!")
279
279
 
280
- return t.cast(Expression, self._field.lower())
280
+ return self._field.lower()
281
281
 
282
282
 
283
283
  def rname(field: TypedField[t.Any] | Field) -> str:
@@ -311,7 +311,7 @@ def get_table(table: "TypedTable | Table") -> "Table":
311
311
  """
312
312
  Get the underlying pydal table for a typedal table.
313
313
  """
314
- return t.cast("Table", table._table) # ty: ignore[unresolved-attribute]
314
+ return t.cast("Table", table._table)
315
315
 
316
316
 
317
317
  def get_field(field: "TypedField[t.Any] | Field") -> "Field":
@@ -111,7 +111,7 @@ class QueryBuilder[T_MetaInstance: _TypedTable](Select):
111
111
  """
112
112
  self.model = model
113
113
  table = self._ensure_table_defined()
114
- default_query: Query = t.cast(Query, table.id > 0) # ty: ignore[unresolved-attribute]
114
+ default_query: Query = table.id > 0
115
115
  self.query = add_query or default_query
116
116
  self.select_args = select_args or []
117
117
  self.select_kwargs = select_kwargs or {}
@@ -152,7 +152,7 @@ class QueryBuilder[T_MetaInstance: _TypedTable](Select):
152
152
  Querybuilder is truthy if it has t.Any conditions.
153
153
  """
154
154
  table = self._ensure_table_defined()
155
- default_query: Query = t.cast(Query, table.id > 0) # ty: ignore[unresolved-attribute]
155
+ default_query: Query = table.id > 0
156
156
  return any(
157
157
  [
158
158
  self.query != default_query,
@@ -330,13 +330,14 @@ class QueryBuilder[T_MetaInstance: _TypedTable](Select):
330
330
  subquery |= t.cast(Query, query_part)
331
331
  elif callable(query_part):
332
332
  if result := query_part(self.model):
333
- subquery |= result
333
+ subquery |= t.cast(Query, result)
334
334
  elif isinstance(query_part, dict):
335
335
  subsubquery = DummyQuery()
336
336
  for field, value in query_part.items():
337
337
  subsubquery &= table[field] == value
338
338
  if subsubquery:
339
- subquery |= subsubquery
339
+ # DummyQuery is falsy, so reaching here means it became a Query.
340
+ subquery |= t.cast(Query, subsubquery)
340
341
  else:
341
342
  raise ValueError(f"Unexpected query type ({type(query_part)}).")
342
343
 
@@ -559,7 +560,9 @@ class QueryBuilder[T_MetaInstance: _TypedTable](Select):
559
560
  db = self._get_db()
560
561
  return str(db(self.query)._delete())
561
562
 
562
- def update(self, **fields: t.Any) -> list[int]:
563
+ # QueryBuilder subclasses Select for its query-building surface but yields
564
+ # model instances rather than fields, so these two are not substitutable.
565
+ def update(self, **fields: t.Any) -> list[int]: # ty: ignore[invalid-method-override]
563
566
  """
564
567
  Based on the current query, update `fields` and return a list of updated IDs.
565
568
  """
@@ -1262,7 +1265,7 @@ class QueryBuilder[T_MetaInstance: _TypedTable](Select):
1262
1265
  """
1263
1266
  return self.collect() or throw(exception or ValueError("Nothing found!"))
1264
1267
 
1265
- def __iter__(self) -> t.Generator[T_MetaInstance, None, None]:
1268
+ def __iter__(self) -> t.Generator[T_MetaInstance, None, None]: # ty: ignore[invalid-method-override]
1266
1269
  """
1267
1270
  You can start iterating a Query Builder object before calling collect, for ease of use.
1268
1271
  """
@@ -106,7 +106,10 @@ class TypedRows(t.Collection[T_MetaInstance], Rows):
106
106
  """
107
107
  return ind in self.records
108
108
 
109
- def first(self) -> T_MetaInstance | None:
109
+ # TypedRows deliberately re-types pydal's row container: its elements are
110
+ # _TypedTable instances, which are row-like but not pydal Rows. The overrides
111
+ # below therefore narrow Row to T_MetaInstance and are not Liskov-substitutable.
112
+ def first(self) -> T_MetaInstance | None: # ty: ignore[invalid-method-override]
110
113
  """
111
114
  Get the row with the lowest id.
112
115
  """
@@ -115,7 +118,7 @@ class TypedRows(t.Collection[T_MetaInstance], Rows):
115
118
 
116
119
  return next(iter(self))
117
120
 
118
- def last(self) -> T_MetaInstance | None:
121
+ def last(self) -> T_MetaInstance | None: # ty: ignore[invalid-method-override]
119
122
  """
120
123
  Get the row with the highest id.
121
124
  """
@@ -125,7 +128,7 @@ class TypedRows(t.Collection[T_MetaInstance], Rows):
125
128
  max_id = max(self.records.keys())
126
129
  return self[max_id]
127
130
 
128
- def find(
131
+ def find( # ty: ignore[invalid-method-override]
129
132
  self,
130
133
  f: t.Callable[[T_MetaInstance], Query],
131
134
  limitby: tuple[int, int] | None = None,
@@ -152,7 +155,9 @@ class TypedRows(t.Collection[T_MetaInstance], Rows):
152
155
 
153
156
  return self.__class__(self, self.model, records)
154
157
 
155
- def exclude(self, f: t.Callable[[T_MetaInstance], Query]) -> "TypedRows[T_MetaInstance]":
158
+ def exclude( # ty: ignore[invalid-method-override]
159
+ self, f: t.Callable[[T_MetaInstance], Query]
160
+ ) -> "TypedRows[T_MetaInstance]":
156
161
  """
157
162
  Removes elements from the calling Rows object, filtered by the function `f`, \
158
163
  and returns a new Rows object containing the removed elements.
@@ -175,7 +180,9 @@ class TypedRows(t.Collection[T_MetaInstance], Rows):
175
180
  removed,
176
181
  )
177
182
 
178
- def sort(self, f: t.Callable[[T_MetaInstance], t.Any], reverse: bool = False) -> list[T_MetaInstance]:
183
+ def sort( # ty: ignore[invalid-method-override]
184
+ self, f: t.Callable[[T_MetaInstance], t.Any], reverse: bool = False
185
+ ) -> list[T_MetaInstance]:
179
186
  """
180
187
  Returns a list of sorted elements (not sorted in place).
181
188
  """
@@ -240,7 +247,7 @@ class TypedRows(t.Collection[T_MetaInstance], Rows):
240
247
  """
241
248
  Dump the data to csv.
242
249
  """
243
- return t.cast(str, super().as_csv())
250
+ return super().as_csv()
244
251
 
245
252
  def as_dict(
246
253
  self,
@@ -283,7 +290,7 @@ class TypedRows(t.Collection[T_MetaInstance], Rows):
283
290
 
284
291
  def json(
285
292
  self, default: t.Callable[[t.Any], t.Any] | None = None, indent: int | None = None, **kwargs: t.Any
286
- ) -> str: # ty: ignore[invalid-method-override]
293
+ ) -> str:
287
294
  """
288
295
  Turn the data into a dict and then dump to JSON.
289
296
  """
@@ -360,7 +367,7 @@ class TypedRows(t.Collection[T_MetaInstance], Rows):
360
367
  """
361
368
  return await run_async(self.db, self.delete)
362
369
 
363
- def join(
370
+ def join( # ty: ignore[invalid-method-override]
364
371
  self,
365
372
  field: "Field | TypedField[t.Any]",
366
373
  name: str | None = None,
@@ -373,7 +380,9 @@ class TypedRows(t.Collection[T_MetaInstance], Rows):
373
380
 
374
381
  Using the querybuilder's .join() method is prefered!
375
382
  """
376
- result = super().join(field, name, constraint, fields or [], orderby)
383
+ # TypedField is an Expression, not a pydal Field, but carries the
384
+ # .name/.tablename attributes join() actually reads.
385
+ result = super().join(field, name, constraint, fields or [], orderby) # ty: ignore[invalid-argument-type]
377
386
  return t.cast(T_MetaInstance, result)
378
387
 
379
388
  def export_to_csv_file(
@@ -465,7 +474,7 @@ class TypedRows(t.Collection[T_MetaInstance], Rows):
465
474
  ) -> T_MetaInstance:
466
475
  """With an index, return one rendered row instance."""
467
476
 
468
- def render(
477
+ def render( # ty: ignore[invalid-method-override]
469
478
  self,
470
479
  i: int | None = None,
471
480
  fields: list[Field] | None = None,
@@ -569,7 +578,7 @@ class TypedSet(Set): # pragma: no cover
569
578
  Count returns an int.
570
579
  """
571
580
  result = super().count(distinct, cache)
572
- return t.cast(int, result)
581
+ return result
573
582
 
574
583
  def select(self, *fields: t.Any, **attributes: t.Any) -> TypedRows[T_MetaInstance]:
575
584
  """
@@ -198,7 +198,7 @@ class TableMeta(type):
198
198
  Allow dict notation to get a column of this table (-> Field instance).
199
199
  """
200
200
  table = self._ensure_table_defined()
201
- return t.cast(Field, table[item])
201
+ return table[item]
202
202
 
203
203
  def __str__(self) -> str:
204
204
  """
@@ -766,7 +766,7 @@ class TableMeta(type):
766
766
  http://web2py.com/books/default/chapter/29/06/the-database-abstraction-layer?search=export_to_csv_file#One-to-mt.Any-relation
767
767
  """
768
768
  table = self._ensure_table_defined()
769
- return t.cast(Expression, table.on(query))
769
+ return table.on(query)
770
770
 
771
771
  def with_alias(self: t.Type[T_MetaInstance], alias: str) -> t.Type[T_MetaInstance]:
772
772
  """
@@ -1321,7 +1321,7 @@ class TypedTable(_TypedTable, metaclass=TableMeta):
1321
1321
  """
1322
1322
  table = cls._ensure_table_defined()
1323
1323
  result = table.as_dict(flat, sanitize)
1324
- return t.cast(AnyDict, result)
1324
+ return result
1325
1325
 
1326
1326
  @classmethod
1327
1327
  def as_typeddict(
@@ -1441,7 +1441,7 @@ class TypedTable(_TypedTable, metaclass=TableMeta):
1441
1441
  - dumps the row info if it's an instance (see _as_xml)
1442
1442
  """
1443
1443
  table = cls._ensure_table_defined()
1444
- return t.cast(str, table.as_xml(sanitize))
1444
+ return table.as_xml(sanitize)
1445
1445
 
1446
1446
  @classmethod
1447
1447
  def as_yaml(cls, sanitize: bool = True) -> str:
@@ -1453,7 +1453,7 @@ class TypedTable(_TypedTable, metaclass=TableMeta):
1453
1453
  - dumps the row info if it's an instance (see _as_yaml)
1454
1454
  """
1455
1455
  table = cls._ensure_table_defined()
1456
- return t.cast(str, table.as_yaml(sanitize))
1456
+ return table.as_yaml(sanitize)
1457
1457
 
1458
1458
  def _as_dict(
1459
1459
  self,
@@ -1483,7 +1483,7 @@ class TypedTable(_TypedTable, metaclass=TableMeta):
1483
1483
 
1484
1484
  result[relationship] = data
1485
1485
 
1486
- return t.cast(AnyDict, result)
1486
+ return result
1487
1487
 
1488
1488
  def _as_json(
1489
1489
  self,
@@ -1494,9 +1494,11 @@ class TypedTable(_TypedTable, metaclass=TableMeta):
1494
1494
  data = self._as_dict()
1495
1495
  return as_json.encode(data, default=default, indent=indent, **kwargs)
1496
1496
 
1497
- def _as_xml(self, sanitize: bool = True) -> str: # pragma: no cover
1497
+ def _as_xml(self, sanitize: bool = True) -> str: # noqa: ARG002 # pragma: no cover
1498
1498
  row = self._ensure_matching_row()
1499
- return t.cast(str, row.as_xml(sanitize))
1499
+ # pydal's Row.as_xml has no sanitize flag (its first parameter is the
1500
+ # element name), so the argument is intentionally not forwarded.
1501
+ return row.as_xml()
1500
1502
 
1501
1503
  # def _as_yaml(self, sanitize: bool = True) -> str:
1502
1504
  # row = self._ensure_matching_row()
@@ -116,7 +116,7 @@ class TableProtocol(t.Protocol): # pragma: no cover
116
116
  Tables have table[field] syntax.
117
117
  """
118
118
 
119
- def on(self, query: "QueryLike") -> "Expression | _Expression":
119
+ def on(self, query: "QueryLike") -> "Expression":
120
120
  """
121
121
  PyDAL `Table.on(query)` helper used in join callbacks.
122
122
  """
@@ -151,78 +151,18 @@ class FileSystemLike(t.Protocol): # pragma: no cover
151
151
  # same for typing and runtime:
152
152
  Row: t.TypeAlias = _Row
153
153
  Validator: t.TypeAlias = _Validator
154
- Table: t.TypeAlias = _Table
155
154
  Reference: t.TypeAlias = _Reference
156
155
 
157
- # has to be (fake) subclass during type-checking:
158
- if t.TYPE_CHECKING:
159
-
160
- class Query(_Query):
161
- """Pydal Query object. Makes mypy happy."""
162
-
163
- class Expression(_Expression):
164
- """Pydal Expression object. Makes mypy happy."""
165
-
166
- class Set(_Set):
167
- """Pydal Set object. Makes mypy happy."""
168
-
169
- class Select(_Select):
170
- """Pydal Select object. Makes mypy happy."""
171
-
172
- class OpRow:
173
- """
174
- Pydal OpRow object for typing (otherwise mypy thinks it's Any).
175
- """
176
-
177
- def __getattr__(self, name: str) -> t.Any:
178
- """Dynamic attribute access (e.g. row.email)."""
179
-
180
- def __getitem__(self, item: str) -> t.Any:
181
- """row.item syntax."""
182
-
183
- def __setitem__(self, key: str, value: t.Any) -> None:
184
- """row.item = key syntax."""
185
-
186
- def get(self, key: str, default: t.Any = None) -> t.Any:
187
- """Dictionary-like get used by update hooks."""
188
-
189
- def keys(self) -> t.Iterable[str]:
190
- """Dictionary-like key access."""
191
-
192
- def items(self) -> t.Iterable[tuple[str, t.Any]]:
193
- """Dictionary-like item iteration."""
194
-
195
- def values(self) -> t.Iterable[t.Any]:
196
- """Dictionary-like value iteration."""
197
-
198
- class Field(_Field):
199
- """Pydal Field object. Make mypy happy."""
200
-
201
- _rname: str
202
-
203
- def __eq__(self, other: t.Any) -> Query: # ty: ignore[invalid-method-override]
204
- """Comparing fields produces a PyDAL query."""
205
- return t.cast(Query, super().__eq__(other))
206
-
207
- def __ne__(self, other: t.Any) -> Query: # ty: ignore[invalid-method-override]
208
- """Comparing fields produces a PyDAL query."""
209
- return t.cast(Query, super().__ne__(other))
210
-
211
- def __hash__(self) -> int:
212
- """Keep fields hashable after overriding equality."""
213
- return super().__hash__()
214
-
215
- class Rows(_Rows):
216
- def column(self, column: t.Any = None) -> list[t.Any]: ...
217
-
218
- else:
219
- Query: t.TypeAlias = _Query
220
- Expression: t.TypeAlias = _Expression
221
- Set: t.TypeAlias = _Set
222
- Select: t.TypeAlias = _Select
223
- Rows: t.TypeAlias = _Rows
224
- OpRow: t.TypeAlias = _OpRow
225
- Field: t.TypeAlias = _Field
156
+ # pydal ships PEP 561 stubs (pydal-stubs), so these need no type-checking-only
157
+ # subclass to carry extra declarations; plain aliases keep one name per concept.
158
+ Query: t.TypeAlias = _Query
159
+ Expression: t.TypeAlias = _Expression
160
+ Set: t.TypeAlias = _Set
161
+ Select: t.TypeAlias = _Select
162
+ Table: t.TypeAlias = _Table
163
+ Field: t.TypeAlias = _Field
164
+ Rows: t.TypeAlias = _Rows
165
+ OpRow: t.TypeAlias = _OpRow
226
166
 
227
167
 
228
168
  # ---------------------------------------------------------------------------
@@ -414,7 +354,7 @@ type T_Field = t.Union["TypedField[t.Any]", "Table", t.Type["TypedTable"]]
414
354
  # checked while its dynamic attribute fallback covers model-specific fields.
415
355
  type P_Table = TableProtocol
416
356
 
417
- type QueryLike = Query | _Query | bool
357
+ type QueryLike = Query | bool
418
358
 
419
359
  type Condition = t.Callable[[type["TypedTable"], type["TypedTable"]], QueryLike] | None
420
360
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes