TypeDAL 4.9.8b2__tar.gz → 4.9.9__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.9.8b2 → typedal-4.9.9}/CHANGELOG.md +5 -4
- {typedal-4.9.8b2 → typedal-4.9.9}/PKG-INFO +1 -1
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/__about__.py +1 -1
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/core.py +18 -4
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/types.py +16 -3
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_main.py +44 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/.crush/.gitignore +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/.crush/crush.db-shm +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/.crush/crush.db-wal +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/.crush/init +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/.crush/logs/crush.log +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/.github/workflows/su6.yml +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/.gitignore +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/.readthedocs.yml +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/README.md +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/coverage.svg +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/docs/10_advanced_apis.md +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/docs/1_getting_started.md +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/docs/2_defining_tables.md +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/docs/3_building_queries.md +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/docs/4_relationships.md +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/docs/5_py4web.md +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/docs/6_migrations.md +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/docs/7_configuration.md +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/docs/8_mixins.md +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/docs/9_memoization.md +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/docs/css/code_blocks.css +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/docs/index.md +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/docs/requirements.txt +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/example_new.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/example_old.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/mkdocs.yml +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/pyproject.toml +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/__init__.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/caching.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/cli.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/config.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/constants.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/define.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/enum_helpers.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/fields.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/for_py4web.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/for_web2py.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/helpers.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/mixins.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/py.typed +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/query_builder.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/relationships.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/rows.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/serializers/as_json.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/serializers/typescript.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/tables.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/web2py_py4web_shared.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tasks.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/__init__.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/configs/simple.toml +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/configs/valid.env +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/configs/valid.toml +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/conftest.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/py314_tests.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_cli.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_config.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_docs_examples.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_helpers.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_json.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_mixins.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_mypy.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_orm.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_py4web.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_query_builder.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_relationships.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_row.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_stats.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_table.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_typescript.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_typing_mypy.md +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_typing_pyright.md +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_web2py.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_xx_others.py +0 -0
- {typedal-4.9.8b2 → typedal-4.9.9}/tests/timings.py +0 -0
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
<!--next-version-placeholder-->
|
|
4
4
|
|
|
5
|
-
## v4.9.
|
|
5
|
+
## v4.9.9 (2026-07-17)
|
|
6
6
|
|
|
7
7
|
### Fix
|
|
8
8
|
|
|
9
|
-
* **
|
|
9
|
+
* **typing:** Support dynamic fields in relationship callbacks ([`60c3921`](https://github.com/trialandsuccess/TypeDAL/commit/60c39214ba448d6a4b96fde21055a1f7a08263be))
|
|
10
10
|
|
|
11
|
-
## v4.9.8
|
|
11
|
+
## v4.9.8 (2026-07-14)
|
|
12
12
|
|
|
13
13
|
### Fix
|
|
14
14
|
|
|
15
|
-
* **core:**
|
|
15
|
+
* **core:** Properly clean up all TypeDAL tables, fields, hooks, validators, and dialect expressions when closing the database ([`3a3fe7b`](https://github.com/trialandsuccess/TypeDAL/commit/3a3fe7b3387476a9ee50f85d492dea067f218069), [`469e0cb`](https://github.com/trialandsuccess/TypeDAL/commit/469e0cbc5b4f1584849040735ced0232adc18b65), [`d848cf7`](https://github.com/trialandsuccess/TypeDAL/commit/d848cf72ca0ecee24ba857d6b5e03d3cacce7693))
|
|
16
|
+
|
|
16
17
|
|
|
17
18
|
## v4.9.7 (2026-07-14)
|
|
18
19
|
|
|
@@ -342,16 +342,28 @@ class TypeDAL(_TypeDALBase):
|
|
|
342
342
|
_purge_dialect_expressions(adapter)
|
|
343
343
|
|
|
344
344
|
for table_name in tuple(getattr(self, "tables", ())):
|
|
345
|
-
table = getattr(self, table_name, None)
|
|
346
|
-
if table is None:
|
|
345
|
+
table: Table | None = getattr(self, table_name, None)
|
|
346
|
+
if table is None: # pragma: no cover
|
|
347
347
|
continue
|
|
348
348
|
|
|
349
|
+
for hook_name in (
|
|
350
|
+
"_before_insert",
|
|
351
|
+
"_after_insert",
|
|
352
|
+
"_before_update",
|
|
353
|
+
"_after_update",
|
|
354
|
+
"_before_delete",
|
|
355
|
+
"_after_delete",
|
|
356
|
+
):
|
|
357
|
+
hooks = getattr(table, hook_name, None)
|
|
358
|
+
if isinstance(hooks, list):
|
|
359
|
+
hooks.clear()
|
|
360
|
+
|
|
349
361
|
if hasattr(table, "_db"):
|
|
350
362
|
table._db = None
|
|
351
363
|
|
|
352
364
|
for field_name in getattr(table, "fields", ()):
|
|
353
|
-
field: Field = getattr(table, field_name, None)
|
|
354
|
-
if field is None:
|
|
365
|
+
field: Field | None = getattr(table, field_name, None)
|
|
366
|
+
if field is None: # pragma: no cover
|
|
355
367
|
continue
|
|
356
368
|
|
|
357
369
|
if hasattr(field, "_db"):
|
|
@@ -362,6 +374,8 @@ class TypeDAL(_TypeDALBase):
|
|
|
362
374
|
field.table = None
|
|
363
375
|
if hasattr(field, "_table"):
|
|
364
376
|
field._table = None
|
|
377
|
+
if hasattr(field, "requires"):
|
|
378
|
+
field.requires = []
|
|
365
379
|
|
|
366
380
|
def try_define[T: t.Any](self, model: t.Type[T], verbose: bool = False) -> t.Type[T]:
|
|
367
381
|
"""
|
|
@@ -97,6 +97,19 @@ class TableProtocol(t.Protocol): # pragma: no cover
|
|
|
97
97
|
|
|
98
98
|
id: "TypedField[int]"
|
|
99
99
|
|
|
100
|
+
def __getattr__(self, name: str) -> t.Any:
|
|
101
|
+
"""
|
|
102
|
+
Allow model-specific fields in relationship callbacks.
|
|
103
|
+
|
|
104
|
+
Type checkers cannot infer the owning table from a relationship's
|
|
105
|
+
class-body assignment, and forward references can also obscure the
|
|
106
|
+
related table. PyDAL exposes table fields dynamically, so arbitrary
|
|
107
|
+
field access must remain permissive here. Because ``__getattr__``
|
|
108
|
+
returns ``Any``, type checkers do not validate field names in
|
|
109
|
+
relationship callbacks; for example, ``self.fakefield`` is accepted
|
|
110
|
+
even when no such field exists.
|
|
111
|
+
"""
|
|
112
|
+
|
|
100
113
|
def __getitem__(self, item: str) -> "Field":
|
|
101
114
|
"""
|
|
102
115
|
Tables have table[field] syntax.
|
|
@@ -399,9 +412,9 @@ type T_Query = t.Union[
|
|
|
399
412
|
|
|
400
413
|
type T_Field = t.Union["TypedField[t.Any]", "Table", t.Type["TypedTable"]]
|
|
401
414
|
|
|
402
|
-
#
|
|
403
|
-
#
|
|
404
|
-
type P_Table =
|
|
415
|
+
# Table-like callback parameter. The protocol keeps common table operations
|
|
416
|
+
# checked while its dynamic attribute fallback covers model-specific fields.
|
|
417
|
+
type P_Table = TableProtocol
|
|
405
418
|
|
|
406
419
|
type QueryLike = Query | _Query | bool
|
|
407
420
|
|
|
@@ -3,12 +3,15 @@ import gc
|
|
|
3
3
|
import re
|
|
4
4
|
import sys
|
|
5
5
|
import typing
|
|
6
|
+
import types
|
|
6
7
|
import weakref
|
|
8
|
+
from contextlib import contextmanager
|
|
7
9
|
from copy import copy
|
|
8
10
|
from sqlite3 import IntegrityError
|
|
9
11
|
from typing import ForwardRef
|
|
10
12
|
|
|
11
13
|
import pytest
|
|
14
|
+
from pydal.validators import IS_NOT_IN_DB
|
|
12
15
|
|
|
13
16
|
from src.typedal import TypeDAL, TypedRows
|
|
14
17
|
from src.typedal.__about__ import __version__
|
|
@@ -48,6 +51,47 @@ def test_database_is_garbage_collected_after_close():
|
|
|
48
51
|
assert db_ref() is None
|
|
49
52
|
|
|
50
53
|
|
|
54
|
+
def test_database_is_garbage_collected_after_context_manager():
|
|
55
|
+
|
|
56
|
+
@contextmanager
|
|
57
|
+
def typedal_context():
|
|
58
|
+
db = TypeDAL("sqlite:memory", enable_typedal_caching=False)
|
|
59
|
+
try:
|
|
60
|
+
yield db
|
|
61
|
+
db.commit()
|
|
62
|
+
except:
|
|
63
|
+
db.rollback()
|
|
64
|
+
finally:
|
|
65
|
+
db.close()
|
|
66
|
+
|
|
67
|
+
def in_scope():
|
|
68
|
+
with typedal_context() as db:
|
|
69
|
+
@db.define
|
|
70
|
+
class RelatedTable(TypedTable):
|
|
71
|
+
value = TypedField(str)
|
|
72
|
+
|
|
73
|
+
@db.define
|
|
74
|
+
class TemporaryTable(TypedTable):
|
|
75
|
+
related: RelatedTable
|
|
76
|
+
value = TypedField(str)
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def __on_define__(cls, db):
|
|
80
|
+
cls.id.requires = IS_NOT_IN_DB(db, cls.id)
|
|
81
|
+
cls.after_insert(lambda _row, _id, db=db: None)
|
|
82
|
+
|
|
83
|
+
query = TemporaryTable.where(id=0)
|
|
84
|
+
assert not query.paginate(limit=1)
|
|
85
|
+
|
|
86
|
+
return weakref.ref(db)
|
|
87
|
+
|
|
88
|
+
db_ref = in_scope()
|
|
89
|
+
|
|
90
|
+
gc.collect()
|
|
91
|
+
|
|
92
|
+
assert db_ref() is None
|
|
93
|
+
|
|
94
|
+
|
|
51
95
|
def test_database_with_auth_user_is_garbage_collected_after_close():
|
|
52
96
|
db = TypeDAL("sqlite:memory", enable_typedal_caching=False)
|
|
53
97
|
db.define(AuthUser, redefine=True)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|