TypeDAL 4.8.2__tar.gz → 4.8.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 (75) hide show
  1. {typedal-4.8.2 → typedal-4.8.4}/CHANGELOG.md +12 -0
  2. {typedal-4.8.2 → typedal-4.8.4}/PKG-INFO +1 -1
  3. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/__about__.py +1 -1
  4. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/caching.py +4 -0
  5. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/core.py +1 -0
  6. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/define.py +5 -6
  7. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/enum_helpers.py +8 -0
  8. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/fields.py +39 -0
  9. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/mixins.py +1 -1
  10. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/relationships.py +15 -5
  11. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/rows.py +3 -3
  12. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/types.py +15 -2
  13. {typedal-4.8.2 → typedal-4.8.4}/typing_again.py +5 -0
  14. {typedal-4.8.2 → typedal-4.8.4}/.github/workflows/su6.yml +0 -0
  15. {typedal-4.8.2 → typedal-4.8.4}/.gitignore +0 -0
  16. {typedal-4.8.2 → typedal-4.8.4}/.readthedocs.yml +0 -0
  17. {typedal-4.8.2 → typedal-4.8.4}/README.md +0 -0
  18. {typedal-4.8.2 → typedal-4.8.4}/coverage.svg +0 -0
  19. {typedal-4.8.2 → typedal-4.8.4}/docs/10_advanced_apis.md +0 -0
  20. {typedal-4.8.2 → typedal-4.8.4}/docs/1_getting_started.md +0 -0
  21. {typedal-4.8.2 → typedal-4.8.4}/docs/2_defining_tables.md +0 -0
  22. {typedal-4.8.2 → typedal-4.8.4}/docs/3_building_queries.md +0 -0
  23. {typedal-4.8.2 → typedal-4.8.4}/docs/4_relationships.md +0 -0
  24. {typedal-4.8.2 → typedal-4.8.4}/docs/5_py4web.md +0 -0
  25. {typedal-4.8.2 → typedal-4.8.4}/docs/6_migrations.md +0 -0
  26. {typedal-4.8.2 → typedal-4.8.4}/docs/7_configuration.md +0 -0
  27. {typedal-4.8.2 → typedal-4.8.4}/docs/8_mixins.md +0 -0
  28. {typedal-4.8.2 → typedal-4.8.4}/docs/9_memoization.md +0 -0
  29. {typedal-4.8.2 → typedal-4.8.4}/docs/css/code_blocks.css +0 -0
  30. {typedal-4.8.2 → typedal-4.8.4}/docs/index.md +0 -0
  31. {typedal-4.8.2 → typedal-4.8.4}/docs/requirements.txt +0 -0
  32. {typedal-4.8.2 → typedal-4.8.4}/example_new.py +0 -0
  33. {typedal-4.8.2 → typedal-4.8.4}/example_old.py +0 -0
  34. {typedal-4.8.2 → typedal-4.8.4}/mkdocs.yml +0 -0
  35. {typedal-4.8.2 → typedal-4.8.4}/pyproject.toml +0 -0
  36. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/__init__.py +0 -0
  37. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/cli.py +0 -0
  38. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/config.py +0 -0
  39. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/constants.py +0 -0
  40. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/for_py4web.py +0 -0
  41. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/for_web2py.py +0 -0
  42. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/helpers.py +0 -0
  43. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/py.typed +0 -0
  44. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/query_builder.py +0 -0
  45. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/serializers/as_json.py +0 -0
  46. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/serializers/typescript.py +0 -0
  47. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/tables.py +0 -0
  48. {typedal-4.8.2 → typedal-4.8.4}/src/typedal/web2py_py4web_shared.py +0 -0
  49. {typedal-4.8.2 → typedal-4.8.4}/tasks.py +0 -0
  50. {typedal-4.8.2 → typedal-4.8.4}/tests/__init__.py +0 -0
  51. {typedal-4.8.2 → typedal-4.8.4}/tests/configs/simple.toml +0 -0
  52. {typedal-4.8.2 → typedal-4.8.4}/tests/configs/valid.env +0 -0
  53. {typedal-4.8.2 → typedal-4.8.4}/tests/configs/valid.toml +0 -0
  54. {typedal-4.8.2 → typedal-4.8.4}/tests/py314_tests.py +0 -0
  55. {typedal-4.8.2 → typedal-4.8.4}/tests/test_cli.py +0 -0
  56. {typedal-4.8.2 → typedal-4.8.4}/tests/test_config.py +0 -0
  57. {typedal-4.8.2 → typedal-4.8.4}/tests/test_docs_examples.py +0 -0
  58. {typedal-4.8.2 → typedal-4.8.4}/tests/test_helpers.py +0 -0
  59. {typedal-4.8.2 → typedal-4.8.4}/tests/test_json.py +0 -0
  60. {typedal-4.8.2 → typedal-4.8.4}/tests/test_main.py +0 -0
  61. {typedal-4.8.2 → typedal-4.8.4}/tests/test_mixins.py +0 -0
  62. {typedal-4.8.2 → typedal-4.8.4}/tests/test_mypy.py +0 -0
  63. {typedal-4.8.2 → typedal-4.8.4}/tests/test_orm.py +0 -0
  64. {typedal-4.8.2 → typedal-4.8.4}/tests/test_py4web.py +0 -0
  65. {typedal-4.8.2 → typedal-4.8.4}/tests/test_query_builder.py +0 -0
  66. {typedal-4.8.2 → typedal-4.8.4}/tests/test_relationships.py +0 -0
  67. {typedal-4.8.2 → typedal-4.8.4}/tests/test_row.py +0 -0
  68. {typedal-4.8.2 → typedal-4.8.4}/tests/test_stats.py +0 -0
  69. {typedal-4.8.2 → typedal-4.8.4}/tests/test_table.py +0 -0
  70. {typedal-4.8.2 → typedal-4.8.4}/tests/test_typescript.py +0 -0
  71. {typedal-4.8.2 → typedal-4.8.4}/tests/test_typing_mypy.md +0 -0
  72. {typedal-4.8.2 → typedal-4.8.4}/tests/test_typing_pyright.md +0 -0
  73. {typedal-4.8.2 → typedal-4.8.4}/tests/test_web2py.py +0 -0
  74. {typedal-4.8.2 → typedal-4.8.4}/tests/test_xx_others.py +0 -0
  75. {typedal-4.8.2 → typedal-4.8.4}/tests/timings.py +0 -0
@@ -2,6 +2,18 @@
2
2
 
3
3
  <!--next-version-placeholder-->
4
4
 
5
+ ## v4.8.4 (2026-05-28)
6
+
7
+ ### Fix
8
+
9
+ * Improved typing on OpRow; docstrings ([`064b454`](https://github.com/trialandsuccess/TypeDAL/commit/064b4546ceb709ee6124d4a749919c4193fb3752))
10
+
11
+ ## v4.8.3 (2026-05-25)
12
+
13
+ ### Fix
14
+
15
+ * Improved typing hints on TypedField properties ([`55b5ce4`](https://github.com/trialandsuccess/TypeDAL/commit/55b5ce4ae2657ae43b9cfa19dbe90e4386609495))
16
+
5
17
  ## v4.8.2 (2026-05-20)
6
18
 
7
19
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TypeDAL
3
- Version: 4.8.2
3
+ Version: 4.8.4
4
4
  Summary: Typing support for PyDAL
5
5
  Project-URL: Documentation, https://typedal.readthedocs.io/
6
6
  Project-URL: Issues, https://github.com/trialandsuccess/TypeDAL/issues
@@ -5,4 +5,4 @@ This file contains the Version info for this package.
5
5
  # SPDX-FileCopyrightText: 2023-present Robin van der Noord <robinvandernoord@gmail.com>
6
6
  #
7
7
  # SPDX-License-Identifier: MIT
8
- __version__ = "4.8.2"
8
+ __version__ = "4.8.4"
@@ -416,6 +416,8 @@ def _expired_and_valid_query() -> tuple[str, str]:
416
416
 
417
417
 
418
418
  class Stats[T](t.TypedDict):
419
+ """Typed container for total/valid/expired metric groups."""
420
+
419
421
  total: T
420
422
  valid: T
421
423
  expired: T
@@ -578,6 +580,7 @@ def memoize[T: t.Any](
578
580
  # Cache miss - compute result
579
581
 
580
582
  def track_execute(_qb: "QueryBuilder[t.Any]", raw: Rows) -> None:
583
+ """Collect table/id dependencies from executed raw query results."""
581
584
  # find dependant table+id combinations, includes relationships:
582
585
  deps.update(_determine_dependencies_auto(raw))
583
586
 
@@ -591,6 +594,7 @@ def memoize[T: t.Any](
591
594
  deps.update({(table, 0) for table in related_tables})
592
595
 
593
596
  def track_collect(qb: "QueryBuilder[t.Any]", _: TypedRows[t.Any], raw: Rows) -> None:
597
+ """Collect dependencies from `.collect()` hooks by reusing execute tracking."""
594
598
  return track_execute(qb, raw)
595
599
 
596
600
  # hooks every .collect() to track extra dependencies
@@ -349,6 +349,7 @@ class TypeDAL(_TypeDALBase):
349
349
  """
350
350
 
351
351
  def wrapper(cls: t.Type[T]) -> t.Type[T]:
352
+ """Define and return a TypedTable class bound to this DB instance."""
352
353
  return self._builder.define(cls, **kwargs)
353
354
 
354
355
  if maybe_cls:
@@ -30,12 +30,7 @@ from .helpers import (
30
30
  )
31
31
  from .relationships import Relationship, to_relationship
32
32
  from .tables import TypedTable
33
- from .types import (
34
- Field,
35
- T_annotation,
36
- Table,
37
- _Types,
38
- )
33
+ from .types import Field, T_annotation, Table, _Types
39
34
 
40
35
  try:
41
36
  # python 3.14+
@@ -46,12 +41,16 @@ except ImportError: # pragma: no cover
46
41
 
47
42
 
48
43
  class IS_IN_ENUM(Validator):
44
+ """Validator that accepts only values present in a specific enum."""
45
+
49
46
  def __init__(self, etype: type[enum.Enum], error_message: str = "value not allowed"):
47
+ """Initialize the enum validator."""
50
48
  super().__init__()
51
49
  self.etype = etype
52
50
  self.error_message = error_message
53
51
 
54
52
  def validate(self, value: t.Any, _record_id: int | None = None) -> t.Any:
53
+ """Validate and normalize an enum-compatible value."""
55
54
  if value not in self.etype:
56
55
  raise ValidationError(self.translator(self.error_message))
57
56
 
@@ -1,3 +1,5 @@
1
+ """Helpers for parsing enum values and preserving invalid enum input context."""
2
+
1
3
  from __future__ import annotations
2
4
 
3
5
  import enum
@@ -7,6 +9,8 @@ from dataclasses import dataclass
7
9
 
8
10
  @dataclass(frozen=True)
9
11
  class InvalidEnumValue:
12
+ """Sentinel payload used when a raw value cannot be parsed into an enum member."""
13
+
10
14
  enum_type: type[enum.Enum]
11
15
  raw: t.Any
12
16
  value: None = None
@@ -19,6 +23,7 @@ class InvalidEnumValue:
19
23
 
20
24
 
21
25
  def enum_value_type(enum_type: type[enum.Enum]) -> type[t.Any]:
26
+ """Return the shared Python type of all enum member values."""
22
27
  values = [member.value for member in enum_type]
23
28
  if not values: # pragma: no cover
24
29
  raise TypeError(f"Enum {enum_type.__name__} has no members.")
@@ -32,6 +37,7 @@ def enum_value_type(enum_type: type[enum.Enum]) -> type[t.Any]:
32
37
 
33
38
 
34
39
  def parse_enum_value(enum_type: type[enum.Enum], raw: t.Any) -> enum.Enum | InvalidEnumValue | None:
40
+ """Parse a raw value into an enum member or return an invalid sentinel."""
35
41
  if raw is None: # pragma: no cover
36
42
  return None
37
43
 
@@ -43,6 +49,8 @@ def parse_enum_value(enum_type: type[enum.Enum], raw: t.Any) -> enum.Enum | Inva
43
49
 
44
50
 
45
51
  def make_enum_filter_out(enum_type: type[enum.Enum]) -> t.Callable[[t.Any], enum.Enum | InvalidEnumValue | None]:
52
+ """Build a parser callback for enum values."""
53
+
46
54
  def _filter_out(raw: t.Any) -> enum.Enum | InvalidEnumValue | None:
47
55
  return parse_enum_value(enum_type, raw)
48
56
 
@@ -18,9 +18,11 @@ from pydal.objects import Table
18
18
 
19
19
  from .core import TypeDAL
20
20
  from .types import (
21
+ AnyCallable,
21
22
  Expression,
22
23
  Field,
23
24
  FieldSettings,
25
+ FileSystemLike,
24
26
  Query,
25
27
  T_annotation,
26
28
  T_MetaInstance,
@@ -50,7 +52,44 @@ class TypedField[T_Value](Expression): # pragma: no cover
50
52
  _type: T_annotation
51
53
  kwargs: t.Any
52
54
 
55
+ # Typed hints for common pydal.Field attrs forwarded via __getattr__ after bind().
56
+ type: str | type | Table | Field | SQLCustomType
57
+ type_name: str
58
+ length: int
59
+ default: T_Value
60
+ required: bool
61
+ ondelete: str
62
+ onupdate: str
63
+ notnull: bool
64
+ unique: bool
65
+ regex: str | None
66
+ options: list[t.Any] | AnyCallable | None
67
+ uploadfield: bool | str
68
+ uploadfolder: str | None
69
+ uploadseparate: bool
70
+ uploadfs: FileSystemLike | None
71
+ widget: AnyCallable | None
72
+ label: str
73
+ comment: str | None
74
+ writable: bool
75
+ readable: bool
76
+ searchable: bool
77
+ listable: bool
78
+ update: T_Value | AnyCallable | None
79
+ authorize: AnyCallable | None
80
+ autodelete: bool
53
81
  requires: Validator | t.Iterable[Validator]
82
+ represent: t.Callable[[T_Value, TypedTable | None], t.Any]
83
+ compute: AnyCallable | None
84
+ custom_store: AnyCallable | None
85
+ custom_retrieve: AnyCallable | None
86
+ custom_retrieve_file_properties: AnyCallable | None
87
+ custom_delete: AnyCallable | None
88
+ filter_in: AnyCallable | None
89
+ filter_out: AnyCallable | None
90
+ custom_qualifier: AnyCallable | None
91
+ map_none: T_Value | None
92
+ _raw_rname: str | None
54
93
 
55
94
  # NOTE: for the logic of converting a TypedField into a pydal Field, see TypeDAL._to_field
56
95
 
@@ -190,7 +190,7 @@ class SlugMixin(Mixin):
190
190
 
191
191
  @classmethod
192
192
  def __generate_slug_before_insert(cls, row: OpRow) -> None:
193
- if row.get("slug"): # type: ignore
193
+ if row.get("slug"):
194
194
  # manually set -> skip
195
195
  return None
196
196
 
@@ -208,22 +208,32 @@ class Relationship[To_Type]:
208
208
  @t.overload
209
209
  def __get__(
210
210
  self: "Relationship[list[_RelTable]]", instance: None, owner: t.Type["TypedTable"]
211
- ) -> "Relationship[list[_RelTable]]": ...
211
+ ) -> "Relationship[list[_RelTable]]":
212
+ """Return the descriptor itself when accessed on the class."""
213
+ ...
212
214
 
213
215
  @t.overload
214
216
  def __get__(
215
217
  self: "Relationship[list[_RelTable]]", instance: "TypedTable", owner: t.Type["TypedTable"]
216
- ) -> list[_RelTable]: ...
218
+ ) -> list[_RelTable]:
219
+ """Return related rows for list-valued relationships."""
220
+ ...
217
221
 
218
222
  @t.overload
219
223
  def __get__(
220
224
  self: "Relationship[_RelValue]", instance: None, owner: t.Type["TypedTable"]
221
- ) -> "Relationship[_RelValue]": ...
225
+ ) -> "Relationship[_RelValue]":
226
+ """Return the descriptor itself when accessed on the class."""
227
+ ...
222
228
 
223
229
  @t.overload
224
230
  def __get__(
225
- self: "Relationship[_RelValue]", instance: "TypedTable", owner: t.Type["TypedTable"]
226
- ) -> _RelValue: ...
231
+ self: "Relationship[_RelValue]",
232
+ instance: "TypedTable",
233
+ owner: t.Type["TypedTable"],
234
+ ) -> _RelValue:
235
+ """Return the related object for single-valued relationships."""
236
+ ...
227
237
 
228
238
  def __get__(
229
239
  self,
@@ -61,7 +61,6 @@ class TypedRows(t.Collection[T_MetaInstance], Rows):
61
61
  `metadata` can be t.Any (un)structured data
62
62
  `model` is a Typed Table class
63
63
  """
64
-
65
64
  records = records or {self._get_id(row, model): model(row) for row in rows}
66
65
  raw = raw or {}
67
66
 
@@ -433,8 +432,9 @@ class TypedRows(t.Collection[T_MetaInstance], Rows):
433
432
  fields: list[Field] | None = None,
434
433
  ) -> t.Generator[T_MetaInstance, None, None] | T_MetaInstance:
435
434
  """
436
- Takes an index and returns a copy of the indexed row with values
437
- transformed via the "represent" attributes of the associated fields.
435
+ Take an index and return a copy of the indexed row with values.
436
+
437
+ Values are transformed via the "represent" attributes of the associated fields.
438
438
 
439
439
  Args:
440
440
  i: index. If not specified, a generator is returned for iteration
@@ -62,7 +62,7 @@ class TableProtocol(t.Protocol): # pragma: no cover
62
62
 
63
63
  def on(self, query: "QueryLike") -> "Expression | _Expression":
64
64
  """
65
- pydal Table.on(query) helper used in join callbacks.
65
+ PyDAL `Table.on(query)` helper used in join callbacks.
66
66
  """
67
67
 
68
68
 
@@ -112,13 +112,26 @@ if t.TYPE_CHECKING:
112
112
  Pydal OpRow object for typing (otherwise mypy thinks it's Any).
113
113
  """
114
114
 
115
+ def __getattr__(self, name: str) -> t.Any:
116
+ """Dynamic attribute access (e.g. row.email)."""
117
+
115
118
  def __getitem__(self, item: str) -> t.Any:
116
119
  """row.item syntax."""
117
120
 
118
121
  def __setitem__(self, key: str, value: t.Any) -> None:
119
122
  """row.item = key syntax."""
120
123
 
121
- # more methods could be added
124
+ def get(self, key: str, default: t.Any = None) -> t.Any:
125
+ """Dictionary-like get used by update hooks."""
126
+
127
+ def keys(self) -> t.Iterable[str]:
128
+ """Dictionary-like key access."""
129
+
130
+ def items(self) -> t.Iterable[tuple[str, t.Any]]:
131
+ """Dictionary-like item iteration."""
132
+
133
+ def values(self) -> t.Iterable[t.Any]:
134
+ """Dictionary-like value iteration."""
122
135
 
123
136
  else:
124
137
 
@@ -33,3 +33,8 @@ reveal_type(row.rel) # expected: OtherTable | None; pycharm: Relationship[Other
33
33
 
34
34
  reveal_type(MyTable.multiple) # expected: Relationship[list[OtherTable]]; pycharm: Relationship[list[OtherTable]]
35
35
  reveal_type(row.multiple) # expected: list[OtherTable]; pycharm: Relationship[list[OtherTable]]
36
+
37
+
38
+
39
+ MyTable.field.label = "Test Label"
40
+ MyTable.field.represent = lambda value, row: "Test Repr"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes