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.
Files changed (80) hide show
  1. {typedal-4.9.8b2 → typedal-4.9.9}/CHANGELOG.md +5 -4
  2. {typedal-4.9.8b2 → typedal-4.9.9}/PKG-INFO +1 -1
  3. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/__about__.py +1 -1
  4. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/core.py +18 -4
  5. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/types.py +16 -3
  6. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_main.py +44 -0
  7. {typedal-4.9.8b2 → typedal-4.9.9}/.crush/.gitignore +0 -0
  8. {typedal-4.9.8b2 → typedal-4.9.9}/.crush/crush.db-shm +0 -0
  9. {typedal-4.9.8b2 → typedal-4.9.9}/.crush/crush.db-wal +0 -0
  10. {typedal-4.9.8b2 → typedal-4.9.9}/.crush/init +0 -0
  11. {typedal-4.9.8b2 → typedal-4.9.9}/.crush/logs/crush.log +0 -0
  12. {typedal-4.9.8b2 → typedal-4.9.9}/.github/workflows/su6.yml +0 -0
  13. {typedal-4.9.8b2 → typedal-4.9.9}/.gitignore +0 -0
  14. {typedal-4.9.8b2 → typedal-4.9.9}/.readthedocs.yml +0 -0
  15. {typedal-4.9.8b2 → typedal-4.9.9}/README.md +0 -0
  16. {typedal-4.9.8b2 → typedal-4.9.9}/coverage.svg +0 -0
  17. {typedal-4.9.8b2 → typedal-4.9.9}/docs/10_advanced_apis.md +0 -0
  18. {typedal-4.9.8b2 → typedal-4.9.9}/docs/1_getting_started.md +0 -0
  19. {typedal-4.9.8b2 → typedal-4.9.9}/docs/2_defining_tables.md +0 -0
  20. {typedal-4.9.8b2 → typedal-4.9.9}/docs/3_building_queries.md +0 -0
  21. {typedal-4.9.8b2 → typedal-4.9.9}/docs/4_relationships.md +0 -0
  22. {typedal-4.9.8b2 → typedal-4.9.9}/docs/5_py4web.md +0 -0
  23. {typedal-4.9.8b2 → typedal-4.9.9}/docs/6_migrations.md +0 -0
  24. {typedal-4.9.8b2 → typedal-4.9.9}/docs/7_configuration.md +0 -0
  25. {typedal-4.9.8b2 → typedal-4.9.9}/docs/8_mixins.md +0 -0
  26. {typedal-4.9.8b2 → typedal-4.9.9}/docs/9_memoization.md +0 -0
  27. {typedal-4.9.8b2 → typedal-4.9.9}/docs/css/code_blocks.css +0 -0
  28. {typedal-4.9.8b2 → typedal-4.9.9}/docs/index.md +0 -0
  29. {typedal-4.9.8b2 → typedal-4.9.9}/docs/requirements.txt +0 -0
  30. {typedal-4.9.8b2 → typedal-4.9.9}/example_new.py +0 -0
  31. {typedal-4.9.8b2 → typedal-4.9.9}/example_old.py +0 -0
  32. {typedal-4.9.8b2 → typedal-4.9.9}/mkdocs.yml +0 -0
  33. {typedal-4.9.8b2 → typedal-4.9.9}/pyproject.toml +0 -0
  34. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/__init__.py +0 -0
  35. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/caching.py +0 -0
  36. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/cli.py +0 -0
  37. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/config.py +0 -0
  38. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/constants.py +0 -0
  39. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/define.py +0 -0
  40. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/enum_helpers.py +0 -0
  41. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/fields.py +0 -0
  42. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/for_py4web.py +0 -0
  43. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/for_web2py.py +0 -0
  44. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/helpers.py +0 -0
  45. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/mixins.py +0 -0
  46. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/py.typed +0 -0
  47. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/query_builder.py +0 -0
  48. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/relationships.py +0 -0
  49. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/rows.py +0 -0
  50. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/serializers/as_json.py +0 -0
  51. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/serializers/typescript.py +0 -0
  52. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/tables.py +0 -0
  53. {typedal-4.9.8b2 → typedal-4.9.9}/src/typedal/web2py_py4web_shared.py +0 -0
  54. {typedal-4.9.8b2 → typedal-4.9.9}/tasks.py +0 -0
  55. {typedal-4.9.8b2 → typedal-4.9.9}/tests/__init__.py +0 -0
  56. {typedal-4.9.8b2 → typedal-4.9.9}/tests/configs/simple.toml +0 -0
  57. {typedal-4.9.8b2 → typedal-4.9.9}/tests/configs/valid.env +0 -0
  58. {typedal-4.9.8b2 → typedal-4.9.9}/tests/configs/valid.toml +0 -0
  59. {typedal-4.9.8b2 → typedal-4.9.9}/tests/conftest.py +0 -0
  60. {typedal-4.9.8b2 → typedal-4.9.9}/tests/py314_tests.py +0 -0
  61. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_cli.py +0 -0
  62. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_config.py +0 -0
  63. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_docs_examples.py +0 -0
  64. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_helpers.py +0 -0
  65. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_json.py +0 -0
  66. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_mixins.py +0 -0
  67. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_mypy.py +0 -0
  68. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_orm.py +0 -0
  69. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_py4web.py +0 -0
  70. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_query_builder.py +0 -0
  71. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_relationships.py +0 -0
  72. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_row.py +0 -0
  73. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_stats.py +0 -0
  74. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_table.py +0 -0
  75. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_typescript.py +0 -0
  76. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_typing_mypy.md +0 -0
  77. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_typing_pyright.md +0 -0
  78. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_web2py.py +0 -0
  79. {typedal-4.9.8b2 → typedal-4.9.9}/tests/test_xx_others.py +0 -0
  80. {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.8-beta.2 (2026-07-14)
5
+ ## v4.9.9 (2026-07-17)
6
6
 
7
7
  ### Fix
8
8
 
9
- * **core:** Fully detach tables and fields when closing TypeDAL ([`469e0cb`](https://github.com/trialandsuccess/TypeDAL/commit/469e0cbc5b4f1584849040735ced0232adc18b65))
9
+ * **typing:** Support dynamic fields in relationship callbacks ([`60c3921`](https://github.com/trialandsuccess/TypeDAL/commit/60c39214ba448d6a4b96fde21055a1f7a08263be))
10
10
 
11
- ## v4.9.8-beta.1 (2026-07-14)
11
+ ## v4.9.8 (2026-07-14)
12
12
 
13
13
  ### Fix
14
14
 
15
- * **core:** Purge dialect expressions when closing TypeDAL ([`d848cf7`](https://github.com/trialandsuccess/TypeDAL/commit/d848cf72ca0ecee24ba857d6b5e03d3cacce7693))
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TypeDAL
3
- Version: 4.9.8b2
3
+ Version: 4.9.9
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.9.8-beta.2"
8
+ __version__ = "4.9.9"
@@ -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
- # table-ish parameter:
403
- # Use protocol typing so checkers know `.id` exists in relationship callbacks.
404
- type P_Table = t.Union[t.Type["_TypedTable"], TableProtocol]
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