TypeDAL 3.12.1__tar.gz → 3.13.0__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.
Potentially problematic release.
This version of TypeDAL might be problematic. Click here for more details.
- {typedal-3.12.1 → typedal-3.13.0}/CHANGELOG.md +12 -0
- {typedal-3.12.1 → typedal-3.13.0}/PKG-INFO +3 -3
- {typedal-3.12.1 → typedal-3.13.0}/pyproject.toml +1 -1
- {typedal-3.12.1 → typedal-3.13.0}/src/typedal/__about__.py +1 -1
- {typedal-3.12.1 → typedal-3.13.0}/src/typedal/core.py +87 -0
- {typedal-3.12.1 → typedal-3.13.0}/src/typedal/helpers.py +24 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_main.py +39 -0
- {typedal-3.12.1 → typedal-3.13.0}/.github/workflows/su6.yml +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/.gitignore +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/.readthedocs.yml +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/README.md +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/coverage.svg +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/docs/1_getting_started.md +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/docs/2_defining_tables.md +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/docs/3_building_queries.md +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/docs/4_relationships.md +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/docs/5_py4web.md +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/docs/6_migrations.md +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/docs/7_mixins.md +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/docs/css/code_blocks.css +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/docs/index.md +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/docs/requirements.txt +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/example_new.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/example_old.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/mkdocs.yml +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/src/typedal/__init__.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/src/typedal/caching.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/src/typedal/cli.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/src/typedal/config.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/src/typedal/fields.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/src/typedal/for_py4web.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/src/typedal/for_web2py.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/src/typedal/mixins.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/src/typedal/py.typed +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/src/typedal/serializers/as_json.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/src/typedal/types.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/src/typedal/web2py_py4web_shared.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/__init__.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/configs/simple.toml +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/configs/valid.env +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/configs/valid.toml +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_cli.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_config.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_docs_examples.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_helpers.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_json.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_mixins.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_mypy.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_orm.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_py4web.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_query_builder.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_relationships.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_row.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_stats.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_table.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_web2py.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/test_xx_others.py +0 -0
- {typedal-3.12.1 → typedal-3.13.0}/tests/timings.py +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
<!--next-version-placeholder-->
|
|
4
4
|
|
|
5
|
+
## v3.13.0 (2025-04-28)
|
|
6
|
+
|
|
7
|
+
### Feature
|
|
8
|
+
|
|
9
|
+
* Adding `_once` hooks ([`a69fbb3`](https://github.com/trialandsuccess/TypeDAL/commit/a69fbb361cdfec9352fca503206299c5bbb940d2))
|
|
10
|
+
|
|
11
|
+
## v3.12.2 (2025-04-25)
|
|
12
|
+
|
|
13
|
+
### Fix
|
|
14
|
+
|
|
15
|
+
* Pinned slugify at wrong version ([`470c545`](https://github.com/trialandsuccess/TypeDAL/commit/470c545fd503afbd8d787b92fdf977f5e610333a))
|
|
16
|
+
|
|
5
17
|
## v3.12.1 (2025-04-25)
|
|
6
18
|
|
|
7
19
|
### Fix
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: TypeDAL
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.13.0
|
|
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
|
|
@@ -20,7 +20,7 @@ Requires-Dist: configuraptor<2,>=1.26.2
|
|
|
20
20
|
Requires-Dist: dill<1
|
|
21
21
|
Requires-Dist: legacy-cgi; python_version >= '3.13'
|
|
22
22
|
Requires-Dist: pydal<=20250228.1
|
|
23
|
-
Requires-Dist: python-slugify<
|
|
23
|
+
Requires-Dist: python-slugify<9
|
|
24
24
|
Provides-Extra: all
|
|
25
25
|
Requires-Dist: edwh-migrate[full]>=0.8.0; extra == 'all'
|
|
26
26
|
Requires-Dist: py4web; extra == 'all'
|
|
@@ -5,6 +5,7 @@ Core functionality of TypeDAL.
|
|
|
5
5
|
import contextlib
|
|
6
6
|
import csv
|
|
7
7
|
import datetime as dt
|
|
8
|
+
import functools
|
|
8
9
|
import inspect
|
|
9
10
|
import json
|
|
10
11
|
import math
|
|
@@ -33,6 +34,7 @@ from .helpers import (
|
|
|
33
34
|
all_annotations,
|
|
34
35
|
all_dict,
|
|
35
36
|
as_lambda,
|
|
37
|
+
classproperty,
|
|
36
38
|
extract_type_optional,
|
|
37
39
|
filter_out,
|
|
38
40
|
instanciate,
|
|
@@ -796,6 +798,10 @@ class TypeDAL(pydal.DAL): # type: ignore
|
|
|
796
798
|
return to_snake(camel)
|
|
797
799
|
|
|
798
800
|
|
|
801
|
+
P = typing.ParamSpec("P")
|
|
802
|
+
R = typing.TypeVar("R")
|
|
803
|
+
|
|
804
|
+
|
|
799
805
|
class TableMeta(type):
|
|
800
806
|
"""
|
|
801
807
|
This metaclass contains functionality on table classes, that doesn't exist on its instances.
|
|
@@ -1196,6 +1202,19 @@ class TableMeta(type):
|
|
|
1196
1202
|
return self.with_alias(key)
|
|
1197
1203
|
|
|
1198
1204
|
# hooks:
|
|
1205
|
+
def _hook_once(
|
|
1206
|
+
cls: Type[T_MetaInstance], hooks: list[typing.Callable[P, R]], fn: typing.Callable[P, R]
|
|
1207
|
+
) -> Type[T_MetaInstance]:
|
|
1208
|
+
@functools.wraps(fn)
|
|
1209
|
+
def wraps(*a: P.args, **kw: P.kwargs) -> R:
|
|
1210
|
+
try:
|
|
1211
|
+
return fn(*a, **kw)
|
|
1212
|
+
finally:
|
|
1213
|
+
hooks.remove(wraps)
|
|
1214
|
+
|
|
1215
|
+
hooks.append(wraps)
|
|
1216
|
+
return cls
|
|
1217
|
+
|
|
1199
1218
|
def before_insert(
|
|
1200
1219
|
cls: Type[T_MetaInstance],
|
|
1201
1220
|
fn: typing.Callable[[T_MetaInstance], Optional[bool]] | typing.Callable[[OpRow], Optional[bool]],
|
|
@@ -1207,6 +1226,15 @@ class TableMeta(type):
|
|
|
1207
1226
|
cls._before_insert.append(fn)
|
|
1208
1227
|
return cls
|
|
1209
1228
|
|
|
1229
|
+
def before_insert_once(
|
|
1230
|
+
cls: Type[T_MetaInstance],
|
|
1231
|
+
fn: typing.Callable[[T_MetaInstance], Optional[bool]] | typing.Callable[[OpRow], Optional[bool]],
|
|
1232
|
+
) -> Type[T_MetaInstance]:
|
|
1233
|
+
"""
|
|
1234
|
+
Add a before insert hook that only fires once and then removes itself.
|
|
1235
|
+
"""
|
|
1236
|
+
return cls._hook_once(cls._before_insert, fn) # type: ignore
|
|
1237
|
+
|
|
1210
1238
|
def after_insert(
|
|
1211
1239
|
cls: Type[T_MetaInstance],
|
|
1212
1240
|
fn: (
|
|
@@ -1221,6 +1249,18 @@ class TableMeta(type):
|
|
|
1221
1249
|
cls._after_insert.append(fn)
|
|
1222
1250
|
return cls
|
|
1223
1251
|
|
|
1252
|
+
def after_insert_once(
|
|
1253
|
+
cls: Type[T_MetaInstance],
|
|
1254
|
+
fn: (
|
|
1255
|
+
typing.Callable[[T_MetaInstance, Reference], Optional[bool]]
|
|
1256
|
+
| typing.Callable[[OpRow, Reference], Optional[bool]]
|
|
1257
|
+
),
|
|
1258
|
+
) -> Type[T_MetaInstance]:
|
|
1259
|
+
"""
|
|
1260
|
+
Add an after insert hook that only fires once and then removes itself.
|
|
1261
|
+
"""
|
|
1262
|
+
return cls._hook_once(cls._after_insert, fn) # type: ignore
|
|
1263
|
+
|
|
1224
1264
|
def before_update(
|
|
1225
1265
|
cls: Type[T_MetaInstance],
|
|
1226
1266
|
fn: typing.Callable[[Set, T_MetaInstance], Optional[bool]] | typing.Callable[[Set, OpRow], Optional[bool]],
|
|
@@ -1232,6 +1272,15 @@ class TableMeta(type):
|
|
|
1232
1272
|
cls._before_update.append(fn)
|
|
1233
1273
|
return cls
|
|
1234
1274
|
|
|
1275
|
+
def before_update_once(
|
|
1276
|
+
cls,
|
|
1277
|
+
fn: typing.Callable[[Set, T_MetaInstance], Optional[bool]] | typing.Callable[[Set, OpRow], Optional[bool]],
|
|
1278
|
+
) -> Type[T_MetaInstance]:
|
|
1279
|
+
"""
|
|
1280
|
+
Add a before update hook that only fires once and then removes itself.
|
|
1281
|
+
"""
|
|
1282
|
+
return cls._hook_once(cls._before_update, fn) # type: ignore
|
|
1283
|
+
|
|
1235
1284
|
def after_update(
|
|
1236
1285
|
cls: Type[T_MetaInstance],
|
|
1237
1286
|
fn: typing.Callable[[Set, T_MetaInstance], Optional[bool]] | typing.Callable[[Set, OpRow], Optional[bool]],
|
|
@@ -1243,6 +1292,15 @@ class TableMeta(type):
|
|
|
1243
1292
|
cls._after_update.append(fn)
|
|
1244
1293
|
return cls
|
|
1245
1294
|
|
|
1295
|
+
def after_update_once(
|
|
1296
|
+
cls: Type[T_MetaInstance],
|
|
1297
|
+
fn: typing.Callable[[Set, T_MetaInstance], Optional[bool]] | typing.Callable[[Set, OpRow], Optional[bool]],
|
|
1298
|
+
) -> Type[T_MetaInstance]:
|
|
1299
|
+
"""
|
|
1300
|
+
Add an after update hook that only fires once and then removes itself.
|
|
1301
|
+
"""
|
|
1302
|
+
return cls._hook_once(cls._after_update, fn) # type: ignore
|
|
1303
|
+
|
|
1246
1304
|
def before_delete(cls: Type[T_MetaInstance], fn: typing.Callable[[Set], Optional[bool]]) -> Type[T_MetaInstance]:
|
|
1247
1305
|
"""
|
|
1248
1306
|
Add a before delete hook.
|
|
@@ -1251,6 +1309,15 @@ class TableMeta(type):
|
|
|
1251
1309
|
cls._before_delete.append(fn)
|
|
1252
1310
|
return cls
|
|
1253
1311
|
|
|
1312
|
+
def before_delete_once(
|
|
1313
|
+
cls: Type[T_MetaInstance],
|
|
1314
|
+
fn: typing.Callable[[Set], Optional[bool]],
|
|
1315
|
+
) -> Type[T_MetaInstance]:
|
|
1316
|
+
"""
|
|
1317
|
+
Add a before delete hook that only fires once and then removes itself.
|
|
1318
|
+
"""
|
|
1319
|
+
return cls._hook_once(cls._before_delete, fn)
|
|
1320
|
+
|
|
1254
1321
|
def after_delete(cls: Type[T_MetaInstance], fn: typing.Callable[[Set], Optional[bool]]) -> Type[T_MetaInstance]:
|
|
1255
1322
|
"""
|
|
1256
1323
|
Add an after delete hook.
|
|
@@ -1259,6 +1326,15 @@ class TableMeta(type):
|
|
|
1259
1326
|
cls._after_delete.append(fn)
|
|
1260
1327
|
return cls
|
|
1261
1328
|
|
|
1329
|
+
def after_delete_once(
|
|
1330
|
+
cls: Type[T_MetaInstance],
|
|
1331
|
+
fn: typing.Callable[[Set], Optional[bool]],
|
|
1332
|
+
) -> Type[T_MetaInstance]:
|
|
1333
|
+
"""
|
|
1334
|
+
Add an after delete hook that only fires once and then removes itself.
|
|
1335
|
+
"""
|
|
1336
|
+
return cls._hook_once(cls._after_delete, fn)
|
|
1337
|
+
|
|
1262
1338
|
|
|
1263
1339
|
class TypedField(Expression, typing.Generic[T_Value]): # pragma: no cover
|
|
1264
1340
|
"""
|
|
@@ -1482,6 +1558,17 @@ class _TypedTable:
|
|
|
1482
1558
|
where you need a reference to the current database, which may not exist yet when defining the model.
|
|
1483
1559
|
"""
|
|
1484
1560
|
|
|
1561
|
+
@classproperty
|
|
1562
|
+
def _hooks(cls) -> dict[str, list[typing.Callable[..., Optional[bool]]]]:
|
|
1563
|
+
return {
|
|
1564
|
+
"before_insert": cls._before_insert,
|
|
1565
|
+
"after_insert": cls._after_insert,
|
|
1566
|
+
"before_update": cls._before_update,
|
|
1567
|
+
"after_update": cls._after_update,
|
|
1568
|
+
"before_delete": cls._before_delete,
|
|
1569
|
+
"after_delete": cls._after_delete,
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1485
1572
|
|
|
1486
1573
|
class TypedTable(_TypedTable, metaclass=TableMeta):
|
|
1487
1574
|
"""
|
|
@@ -303,3 +303,27 @@ def get_field(field: "TypedField[typing.Any] | Field") -> "Field":
|
|
|
303
303
|
"Field",
|
|
304
304
|
field, # Table.field already is a Field, but cast to make sure the editor knows this too.
|
|
305
305
|
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class classproperty:
|
|
309
|
+
def __init__(self, fget: typing.Callable[..., typing.Any]) -> None:
|
|
310
|
+
"""
|
|
311
|
+
Initialize the classproperty.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
fget: A function that takes the class as an argument and returns a value.
|
|
315
|
+
"""
|
|
316
|
+
self.fget = fget
|
|
317
|
+
|
|
318
|
+
def __get__(self, obj: typing.Any, owner: typing.Type[T]) -> typing.Any:
|
|
319
|
+
"""
|
|
320
|
+
Retrieve the property value.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
obj: The instance of the class (unused).
|
|
324
|
+
owner: The class that owns the property.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
The value returned by the function.
|
|
328
|
+
"""
|
|
329
|
+
return self.fget(owner)
|
|
@@ -510,10 +510,49 @@ def test_hooks_duplicates():
|
|
|
510
510
|
HookedTableV3.after_insert(increase_counter_v2) # other hash
|
|
511
511
|
|
|
512
512
|
HookedTableV3.insert(name="Should increase counter twice")
|
|
513
|
+
assert counter == 3
|
|
514
|
+
|
|
515
|
+
for hook in HookedTableV3._hooks.values():
|
|
516
|
+
hook.clear()
|
|
513
517
|
|
|
518
|
+
HookedTableV3.insert(name="Should NOT increase counter")
|
|
514
519
|
assert counter == 3
|
|
515
520
|
|
|
516
521
|
|
|
522
|
+
def test_hooks_once():
|
|
523
|
+
@db.define()
|
|
524
|
+
class HookedTableV4(TypedTable):
|
|
525
|
+
name: str
|
|
526
|
+
|
|
527
|
+
counter = 0
|
|
528
|
+
|
|
529
|
+
def increase_counter_v2(_, __=None):
|
|
530
|
+
nonlocal counter
|
|
531
|
+
counter += 1
|
|
532
|
+
|
|
533
|
+
HookedTableV4.before_insert_once(increase_counter_v2)
|
|
534
|
+
HookedTableV4.after_insert_once(increase_counter_v2)
|
|
535
|
+
HookedTableV4.before_update_once(increase_counter_v2)
|
|
536
|
+
HookedTableV4.after_update_once(increase_counter_v2)
|
|
537
|
+
HookedTableV4.before_delete_once(increase_counter_v2)
|
|
538
|
+
HookedTableV4.after_delete_once(increase_counter_v2)
|
|
539
|
+
|
|
540
|
+
assert counter == 0
|
|
541
|
+
|
|
542
|
+
HookedTableV4.insert(name="1")
|
|
543
|
+
assert counter == 2
|
|
544
|
+
row = HookedTableV4.insert(name="2")
|
|
545
|
+
assert counter == 2
|
|
546
|
+
|
|
547
|
+
row.update_record(name="3")
|
|
548
|
+
assert counter == 4
|
|
549
|
+
row.update_record(name="4")
|
|
550
|
+
assert counter == 4
|
|
551
|
+
|
|
552
|
+
row.delete_record()
|
|
553
|
+
assert counter == 6
|
|
554
|
+
|
|
555
|
+
|
|
517
556
|
def test_try():
|
|
518
557
|
class SomeTableToRetry(TypedTable):
|
|
519
558
|
key: int
|
|
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
|