TypeDAL 4.7.1__tar.gz → 4.7.2__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.
- {typedal-4.7.1 → typedal-4.7.2}/CHANGELOG.md +6 -0
- {typedal-4.7.1 → typedal-4.7.2}/PKG-INFO +1 -1
- {typedal-4.7.1 → typedal-4.7.2}/docs/2_defining_tables.md +31 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/__about__.py +1 -1
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/define.py +3 -2
- typedal-4.7.2/src/typedal/enum_helpers.py +43 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_main.py +41 -10
- typedal-4.7.1/release_4_7.md +0 -139
- {typedal-4.7.1 → typedal-4.7.2}/.crush/.gitignore +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/.crush/init +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/.crush/logs/crush.log +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/.github/workflows/su6.yml +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/.gitignore +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/.readthedocs.yml +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/README.md +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/coverage.svg +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/docs/10_advanced_apis.md +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/docs/1_getting_started.md +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/docs/3_building_queries.md +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/docs/4_relationships.md +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/docs/5_py4web.md +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/docs/6_migrations.md +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/docs/7_configuration.md +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/docs/8_mixins.md +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/docs/9_memoization.md +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/docs/css/code_blocks.css +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/docs/index.md +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/docs/requirements.txt +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/example_new.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/example_old.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/mkdocs.yml +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/pyproject.toml +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/__init__.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/caching.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/cli.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/config.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/constants.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/core.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/fields.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/for_py4web.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/for_web2py.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/helpers.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/mixins.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/py.typed +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/query_builder.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/relationships.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/rows.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/serializers/as_json.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/tables.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/types.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/src/typedal/web2py_py4web_shared.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tasks.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/__init__.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/configs/simple.toml +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/configs/valid.env +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/configs/valid.toml +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/py314_tests.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_cli.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_config.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_docs_examples.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_helpers.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_json.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_mixins.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_mypy.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_orm.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_py4web.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_query_builder.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_relationships.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_row.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_stats.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_table.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_web2py.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/test_xx_others.py +0 -0
- {typedal-4.7.1 → typedal-4.7.2}/tests/timings.py +0 -0
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
<!--next-version-placeholder-->
|
|
4
4
|
|
|
5
|
+
## v4.7.2 (2026-04-20)
|
|
6
|
+
|
|
7
|
+
### Fix
|
|
8
|
+
|
|
9
|
+
* **enums:** Add safe enum parsing helper and docs for mixed-type rejection + invalid DB sentinel ([`33ee169`](https://github.com/trialandsuccess/TypeDAL/commit/33ee169b61522cf7a863e29c68822a3b0e07d376))
|
|
10
|
+
|
|
5
11
|
## v4.7.1 (2026-04-20)
|
|
6
12
|
|
|
7
13
|
### Fix
|
|
@@ -51,6 +51,37 @@ Any keyword arguments you would pass to `db.define_table`, you can also pass to
|
|
|
51
51
|
| `Field('name', 'big-id')` | × | × | × | × |
|
|
52
52
|
| `Field('name', 'big-reference')` | × | × | × | × |
|
|
53
53
|
|
|
54
|
+
### Enum fields
|
|
55
|
+
|
|
56
|
+
TypeDAL supports `enum.Enum` subclasses directly (including `enum.StrEnum` and `enum.IntEnum`):
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import enum
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class Status(enum.StrEnum):
|
|
63
|
+
DRAFT = "draft"
|
|
64
|
+
PUBLISHED = "published"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Priority(enum.IntEnum):
|
|
68
|
+
LOW = 1
|
|
69
|
+
HIGH = 2
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@db.define()
|
|
73
|
+
class Article(TypedTable):
|
|
74
|
+
status: Status
|
|
75
|
+
priority: Priority
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Important constraints and behavior:
|
|
79
|
+
|
|
80
|
+
- All enum member values in one enum must share the same underlying Python type for DB fields.
|
|
81
|
+
Mixed enums (for example `str` + `int` in one enum class) raise `TypeError` when defining the table.
|
|
82
|
+
- Reading rows with invalid enum values in the database does not crash. Those values are returned as
|
|
83
|
+
`typedal.enum_helpers.InvalidEnumValue`, where `.value` is `None`.
|
|
84
|
+
|
|
54
85
|
### Making a field required/optional
|
|
55
86
|
|
|
56
87
|
| pydal | typedal (native python type) | typedal (using TypedField annotation) | typedal (using TypedField) | typedal (using specific Field) |
|
|
@@ -17,6 +17,7 @@ from pydal.validators import IS_IN_SET, ValidationError, Validator
|
|
|
17
17
|
|
|
18
18
|
from .constants import BASIC_MAPPINGS
|
|
19
19
|
from .core import TypeDAL, evaluate_forward_reference, resolve_annotation
|
|
20
|
+
from .enum_helpers import enum_value_type, make_enum_filter_out
|
|
20
21
|
from .fields import TypedField, is_typed_field
|
|
21
22
|
from .helpers import (
|
|
22
23
|
all_annotations,
|
|
@@ -178,10 +179,10 @@ class TableDefinitionBuilder:
|
|
|
178
179
|
_child_type = type(t.get_args(ftype)[0])
|
|
179
180
|
return self.annotation_to_pydal_fieldtype(_child_type, mut_kw)
|
|
180
181
|
elif isinstance(ftype, type) and issubclass(ftype, enum.Enum):
|
|
181
|
-
|
|
182
|
-
_child_type = type(_values[0])
|
|
182
|
+
_child_type = enum_value_type(ftype)
|
|
183
183
|
# mut_kw.setdefault("requires", [IS_IN_SET(_values)])
|
|
184
184
|
mut_kw.setdefault("requires", [IS_IN_ENUM(ftype)])
|
|
185
|
+
mut_kw.setdefault("filter_out", make_enum_filter_out(ftype))
|
|
185
186
|
return self.annotation_to_pydal_fieldtype(_child_type, mut_kw)
|
|
186
187
|
elif isinstance(ftype, types.GenericAlias) and t.get_origin(ftype) in (list, TypedField):
|
|
187
188
|
# list[str] -> str -> string -> list:string
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import enum
|
|
4
|
+
import typing as t
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class InvalidEnumValue:
|
|
10
|
+
enum_type: type[enum.Enum]
|
|
11
|
+
raw: t.Any
|
|
12
|
+
value: None = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def enum_value_type(enum_type: type[enum.Enum]) -> type[t.Any]:
|
|
16
|
+
values = [member.value for member in enum_type]
|
|
17
|
+
if not values: # pragma: no cover
|
|
18
|
+
raise TypeError(f"Enum {enum_type.__name__} has no members.")
|
|
19
|
+
|
|
20
|
+
first_type = type(values[0])
|
|
21
|
+
if any(type(value) is not first_type for value in values):
|
|
22
|
+
raise TypeError(
|
|
23
|
+
f"Enum {enum_type.__name__} has mixed value types; all values must share one type for DB fields.",
|
|
24
|
+
)
|
|
25
|
+
return first_type
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def parse_enum_value(enum_type: type[enum.Enum], raw: t.Any) -> enum.Enum | InvalidEnumValue | None:
|
|
29
|
+
if raw is None: # pragma: no cover
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
if isinstance(raw, enum_type): # pragma: no cover
|
|
33
|
+
return raw
|
|
34
|
+
|
|
35
|
+
value_map = {str(member.value): member for member in enum_type}
|
|
36
|
+
return value_map.get(str(raw), InvalidEnumValue(enum_type=enum_type, raw=raw))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def make_enum_filter_out(enum_type: type[enum.Enum]) -> t.Callable[[t.Any], enum.Enum | InvalidEnumValue | None]:
|
|
40
|
+
def _filter_out(raw: t.Any) -> enum.Enum | InvalidEnumValue | None:
|
|
41
|
+
return parse_enum_value(enum_type, raw)
|
|
42
|
+
|
|
43
|
+
return _filter_out
|
|
@@ -10,6 +10,7 @@ import pytest
|
|
|
10
10
|
|
|
11
11
|
from src.typedal import TypedRows
|
|
12
12
|
from src.typedal.__about__ import __version__
|
|
13
|
+
from src.typedal.enum_helpers import InvalidEnumValue
|
|
13
14
|
from src.typedal.fields import *
|
|
14
15
|
|
|
15
16
|
|
|
@@ -193,14 +194,12 @@ def test_dont_allow_bool_in_query():
|
|
|
193
194
|
|
|
194
195
|
def test_invalid_union():
|
|
195
196
|
with pytest.raises(NotImplementedError):
|
|
196
|
-
|
|
197
197
|
@db.define
|
|
198
198
|
class Invalid(TypedTable):
|
|
199
199
|
valid: int | None
|
|
200
200
|
invalid: int | str
|
|
201
201
|
|
|
202
202
|
with pytest.raises(NotImplementedError):
|
|
203
|
-
|
|
204
203
|
@db.define
|
|
205
204
|
class Invalid(TypedTable):
|
|
206
205
|
valid: list[int]
|
|
@@ -278,7 +277,6 @@ def test_typedfield_to_field_type():
|
|
|
278
277
|
optional_two = TypedField(str | None)
|
|
279
278
|
|
|
280
279
|
with pytest.raises(NotImplementedError):
|
|
281
|
-
|
|
282
280
|
@db.define()
|
|
283
281
|
class Invalid(TypedTable):
|
|
284
282
|
third = TypedField(dict[str, int]) # not supported
|
|
@@ -592,7 +590,8 @@ def test_forward_reference_class_314():
|
|
|
592
590
|
class WithFakeRef(TypedTable):
|
|
593
591
|
fwd: Fake
|
|
594
592
|
|
|
595
|
-
class Future(TypedTable):
|
|
593
|
+
class Future(TypedTable):
|
|
594
|
+
...
|
|
596
595
|
|
|
597
596
|
# note: this still has to be defined first because otherwise pydal can't create a database relation!:
|
|
598
597
|
assert db.define(Future)
|
|
@@ -643,34 +642,52 @@ def test_reorder_fields():
|
|
|
643
642
|
|
|
644
643
|
|
|
645
644
|
def test_literal_enum_fields():
|
|
646
|
-
class
|
|
645
|
+
class MixedEnum(enum.Enum):
|
|
647
646
|
FIRST = "first"
|
|
648
647
|
SECOND = 2
|
|
649
648
|
|
|
649
|
+
with pytest.raises(TypeError, match="mixed value types"):
|
|
650
|
+
@db.define()
|
|
651
|
+
class MixedLiteralTable(TypedTable):
|
|
652
|
+
enum_one: MixedEnum
|
|
653
|
+
|
|
654
|
+
class TestStrEnum(enum.StrEnum):
|
|
655
|
+
FIRST = "first"
|
|
656
|
+
SECOND = "second"
|
|
657
|
+
|
|
658
|
+
class TestIntEnum(enum.IntEnum):
|
|
659
|
+
FIRST = 1
|
|
660
|
+
SECOND = 2
|
|
661
|
+
|
|
650
662
|
@db.define()
|
|
651
663
|
class LiteralTable(TypedTable):
|
|
652
664
|
lit_one: t.Literal["first", "second"]
|
|
653
665
|
lit_two = TypedField(t.Literal["first", "second"])
|
|
654
666
|
|
|
655
|
-
enum_one:
|
|
656
|
-
enum_two = TypedField(
|
|
667
|
+
enum_one: TestStrEnum
|
|
668
|
+
enum_two = TypedField(TestIntEnum)
|
|
657
669
|
|
|
658
670
|
# should be ok
|
|
659
671
|
row, err = LiteralTable.validate_and_insert(
|
|
660
672
|
lit_one="first",
|
|
661
673
|
lit_two="second",
|
|
662
|
-
enum_one=
|
|
674
|
+
enum_one=TestStrEnum.FIRST,
|
|
663
675
|
enum_two=2,
|
|
664
676
|
)
|
|
665
677
|
assert not err, "unexpected error"
|
|
666
678
|
assert row, "expected row"
|
|
667
679
|
|
|
680
|
+
assert isinstance(row.enum_one, TestStrEnum)
|
|
681
|
+
assert row.enum_one.value == "first"
|
|
682
|
+
assert isinstance(row.enum_two, TestIntEnum)
|
|
683
|
+
assert row.enum_two == TestIntEnum.SECOND
|
|
684
|
+
|
|
668
685
|
# should error on lit_one
|
|
669
686
|
row, err = LiteralTable.validate_and_insert(
|
|
670
687
|
lit_one="wrong",
|
|
671
688
|
lit_two="wronger",
|
|
672
|
-
enum_one=
|
|
673
|
-
enum_two
|
|
689
|
+
enum_one="invalid",
|
|
690
|
+
enum_two=-1,
|
|
674
691
|
)
|
|
675
692
|
assert not row, "unexpected row"
|
|
676
693
|
assert err, "expected err"
|
|
@@ -679,3 +696,17 @@ def test_literal_enum_fields():
|
|
|
679
696
|
assert "lit_two" in err
|
|
680
697
|
assert "enum_one" in err
|
|
681
698
|
assert "enum_two" in err
|
|
699
|
+
|
|
700
|
+
idx = db.executesql("""
|
|
701
|
+
INSERT INTO literal_table(lit_one, lit_two, enum_one, enum_two)
|
|
702
|
+
VALUES ('fake', 'fake', 'fake', 999)
|
|
703
|
+
RETURNING id
|
|
704
|
+
""")[0][0]
|
|
705
|
+
|
|
706
|
+
invalid_row = LiteralTable(idx)
|
|
707
|
+
assert isinstance(invalid_row.enum_one, InvalidEnumValue)
|
|
708
|
+
assert invalid_row.enum_one.raw == "fake"
|
|
709
|
+
assert invalid_row.enum_one.value is None
|
|
710
|
+
assert isinstance(invalid_row.enum_two, InvalidEnumValue)
|
|
711
|
+
assert invalid_row.enum_two.raw == 999
|
|
712
|
+
assert invalid_row.enum_two.value is None
|
typedal-4.7.1/release_4_7.md
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
# TypeDAL 4.7: A literal improvement to Enums
|
|
2
|
-
|
|
3
|
-
**Release date:** 20-04-2026
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
TypeDAL v4.7 introduces native support for `typing.Literal` and `enum.Enum` as field types.
|
|
8
|
-
No more manually wiring `IS_IN_SET` validators for constrained values.
|
|
9
|
-
|
|
10
|
-
## What's New
|
|
11
|
-
|
|
12
|
-
### `typing.Literal` fields
|
|
13
|
-
|
|
14
|
-
Declare a field with a fixed set of allowed string/integer values and TypeDAL automatically infers the database type and
|
|
15
|
-
attaches an `IS_IN_SET` validator:
|
|
16
|
-
|
|
17
|
-
```python
|
|
18
|
-
from typing import Literal
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@db.define()
|
|
22
|
-
class Ticket(TypedTable):
|
|
23
|
-
status: Literal["open", "in_progress", "closed"]
|
|
24
|
-
priority: Literal[1, 2, 3]
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### `Enum` fields
|
|
28
|
-
|
|
29
|
-
Use Python `Enum` classes directly. TypeDAL extracts the values, infers the underlying type, and applies an `IS_IN_ENUM`
|
|
30
|
-
validator:
|
|
31
|
-
|
|
32
|
-
```python
|
|
33
|
-
import enum
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class Color(enum.Enum):
|
|
37
|
-
RED = "red"
|
|
38
|
-
GREEN = "green"
|
|
39
|
-
BLUE = "blue"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@db.define()
|
|
43
|
-
class Palette(TypedTable):
|
|
44
|
-
color: Color
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
The `IS_IN_ENUM` validator accepts both enum members and their raw values:
|
|
48
|
-
|
|
49
|
-
```python
|
|
50
|
-
row = Palette(color=Color.RED) # via enum member
|
|
51
|
-
row = Palette(color="red") # via raw value
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Other changes since v4.6
|
|
55
|
-
|
|
56
|
-
These fixes landed incrementally between v4.6 and now (shipped as 4.6.1–4.6.4):
|
|
57
|
-
Included here as a recap for users upgrading from 4.6.0 directly to 4.7.
|
|
58
|
-
|
|
59
|
-
### collect_into
|
|
60
|
-
|
|
61
|
-
Collect query results into a different model class. A practical use case is
|
|
62
|
-
returning a safe API-facing view from the same underlying table:
|
|
63
|
-
|
|
64
|
-
The `init` callback is optional and only needed for per-row runtime enrichment.
|
|
65
|
-
Without an explicit `.select(...)`, TypeDAL maps fields declared on the target model.
|
|
66
|
-
|
|
67
|
-
```python
|
|
68
|
-
@db.define()
|
|
69
|
-
class User(TypedTable):
|
|
70
|
-
id: int
|
|
71
|
-
email: str
|
|
72
|
-
password_hash: str
|
|
73
|
-
is_active: bool
|
|
74
|
-
|
|
75
|
-
class PublicUser(TypedTable):
|
|
76
|
-
"""Same users table, but safe for API responses."""
|
|
77
|
-
id: int
|
|
78
|
-
email: str
|
|
79
|
-
is_active: bool
|
|
80
|
-
# note: no password_hash
|
|
81
|
-
profile_url: str | None = None
|
|
82
|
-
|
|
83
|
-
def enrich_profile_url(row: PublicUser, _raw):
|
|
84
|
-
row.profile_url = f"/users/{row.id}"
|
|
85
|
-
|
|
86
|
-
rows = (
|
|
87
|
-
User.where(is_active=True)
|
|
88
|
-
# note: `init` is optional
|
|
89
|
-
.collect_into(PublicUser, init=enrich_profile_url)
|
|
90
|
-
)
|
|
91
|
-
row = rows.first()
|
|
92
|
-
assert isinstance(row, PublicUser)
|
|
93
|
-
assert hasattr(row, "profile_url")
|
|
94
|
-
assert "password_hash" not in row
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
The cache is isolated per model class, so `.cache().collect_into(PublicUser)`
|
|
98
|
-
and a regular `.cache().collect()` do not interfere.
|
|
99
|
-
|
|
100
|
-
### render() → as_dict() consistency
|
|
101
|
-
|
|
102
|
-
As of this fix, calling `.render()` on a row also affects subsequent
|
|
103
|
-
`.as_dict()` output:
|
|
104
|
-
|
|
105
|
-
```python
|
|
106
|
-
row = db.article(1).render()
|
|
107
|
-
row.as_dict() # now returns the rendered representation, not raw pydal dict
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### Pydantic visibility / readable=False
|
|
111
|
-
|
|
112
|
-
Fields marked with `readable=False` are now excluded from generated Pydantic
|
|
113
|
-
schemas, matching pydal/py4web behavior. Visibility and lazy-load filtering has
|
|
114
|
-
also been moved into the schema-converter path so it applies consistently to
|
|
115
|
-
FastAPI endpoints.
|
|
116
|
-
|
|
117
|
-
### Mixin typing without MRO conflicts
|
|
118
|
-
|
|
119
|
-
Mixin-based table classes now type-check properly in mypy and PyCharm while
|
|
120
|
-
keeping runtime MRO clean:
|
|
121
|
-
|
|
122
|
-
```python
|
|
123
|
-
class SearchMixin(Mixin):
|
|
124
|
-
@classmethod
|
|
125
|
-
def by_title(cls, title: str):
|
|
126
|
-
return cls.where(title=title)
|
|
127
|
-
|
|
128
|
-
@db.define()
|
|
129
|
-
class SearchableTable(TypedTable, SearchMixin):
|
|
130
|
-
title: str
|
|
131
|
-
|
|
132
|
-
def search(table: type[SearchMixin], q: str):
|
|
133
|
-
table.by_title(q) # fully typed now
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## 📜 Changelog
|
|
137
|
-
|
|
138
|
-
For all details, see the full changelog:
|
|
139
|
-
**[CHANGELOG.md](https://github.com/trialandsuccess/TypeDAL/blob/master/CHANGELOG.md)**
|
|
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
|
|
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
|