TypeDAL 4.1.0__tar.gz → 4.2.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.
- {typedal-4.1.0 → typedal-4.2.0}/CHANGELOG.md +6 -0
- {typedal-4.1.0 → typedal-4.2.0}/PKG-INFO +1 -1
- {typedal-4.1.0 → typedal-4.2.0}/example_old.py +0 -2
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/__about__.py +1 -1
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/cli.py +1 -2
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/query_builder.py +19 -5
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_config.py +0 -3
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_mypy.py +0 -1
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_query_builder.py +32 -20
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_relationships.py +27 -26
- {typedal-4.1.0 → typedal-4.2.0}/.github/workflows/su6.yml +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/.gitignore +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/.readthedocs.yml +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/README.md +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/coverage.svg +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/docs/1_getting_started.md +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/docs/2_defining_tables.md +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/docs/3_building_queries.md +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/docs/4_relationships.md +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/docs/5_py4web.md +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/docs/6_migrations.md +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/docs/7_configuration.md +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/docs/8_mixins.md +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/docs/css/code_blocks.css +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/docs/index.md +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/docs/requirements.txt +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/example_new.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/mkdocs.yml +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/pyproject.toml +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/__init__.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/caching.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/config.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/constants.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/core.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/define.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/fields.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/for_py4web.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/for_web2py.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/helpers.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/mixins.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/py.typed +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/relationships.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/rows.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/serializers/as_json.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/tables.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/types.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/src/typedal/web2py_py4web_shared.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/__init__.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/configs/simple.toml +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/configs/valid.env +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/configs/valid.toml +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/py314_tests.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_cli.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_docs_examples.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_helpers.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_json.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_main.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_mixins.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_orm.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_py4web.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_row.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_stats.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_table.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_web2py.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/test_xx_others.py +0 -0
- {typedal-4.1.0 → typedal-4.2.0}/tests/timings.py +0 -0
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
<!--next-version-placeholder-->
|
|
4
4
|
|
|
5
|
+
## v4.2.0 (2025-11-28)
|
|
6
|
+
|
|
7
|
+
### Feature
|
|
8
|
+
|
|
9
|
+
* Minimal support for using querybuilder on old-style pydal tables ([`ec8baeb`](https://github.com/trialandsuccess/TypeDAL/commit/ec8baebbbaae5a2cb5d48254997a6322a0670d7d))
|
|
10
|
+
|
|
5
11
|
## v4.1.0 (2025-11-26)
|
|
6
12
|
|
|
7
13
|
### Feature
|
|
@@ -392,8 +392,7 @@ def fake_migrations(
|
|
|
392
392
|
|
|
393
393
|
previously_migrated = (
|
|
394
394
|
db(
|
|
395
|
-
db.ewh_implemented_features.name.belongs(to_fake)
|
|
396
|
-
& (db.ewh_implemented_features.installed == True) # noqa E712
|
|
395
|
+
db.ewh_implemented_features.name.belongs(to_fake) & (db.ewh_implemented_features.installed == True) # noqa E712
|
|
397
396
|
)
|
|
398
397
|
.select(db.ewh_implemented_features.name)
|
|
399
398
|
.column("name")
|
|
@@ -22,7 +22,7 @@ from .helpers import (
|
|
|
22
22
|
normalize_table_keys,
|
|
23
23
|
throw,
|
|
24
24
|
)
|
|
25
|
-
from .tables import TypedTable
|
|
25
|
+
from .tables import TableMeta, TypedTable
|
|
26
26
|
from .types import (
|
|
27
27
|
CacheMetadata,
|
|
28
28
|
Condition,
|
|
@@ -67,7 +67,8 @@ class QueryBuilder(t.Generic[T_MetaInstance]):
|
|
|
67
67
|
MyTable.where(...) -> QueryBuilder[MyTable]
|
|
68
68
|
"""
|
|
69
69
|
self.model = model
|
|
70
|
-
table =
|
|
70
|
+
table = self._ensure_table_defined()
|
|
71
|
+
|
|
71
72
|
default_query = table.id > 0
|
|
72
73
|
self.query = add_query or default_query
|
|
73
74
|
self.select_args = select_args or []
|
|
@@ -75,6 +76,14 @@ class QueryBuilder(t.Generic[T_MetaInstance]):
|
|
|
75
76
|
self.relationships = relationships or {}
|
|
76
77
|
self.metadata = metadata or {}
|
|
77
78
|
|
|
79
|
+
def _ensure_table_defined(self):
|
|
80
|
+
model = self.model
|
|
81
|
+
if hasattr(model, "_ensure_table_defined"):
|
|
82
|
+
return model._ensure_table_defined()
|
|
83
|
+
else:
|
|
84
|
+
# already a pydal table
|
|
85
|
+
return model
|
|
86
|
+
|
|
78
87
|
def __str__(self) -> str:
|
|
79
88
|
"""
|
|
80
89
|
Simple string representation for the query builder.
|
|
@@ -99,7 +108,7 @@ class QueryBuilder(t.Generic[T_MetaInstance]):
|
|
|
99
108
|
"""
|
|
100
109
|
Querybuilder is truthy if it has t.Any conditions.
|
|
101
110
|
"""
|
|
102
|
-
table = self.
|
|
111
|
+
table = self._ensure_table_defined()
|
|
103
112
|
default_query = table.id > 0
|
|
104
113
|
return any(
|
|
105
114
|
[
|
|
@@ -191,7 +200,7 @@ class QueryBuilder(t.Generic[T_MetaInstance]):
|
|
|
191
200
|
.where(lambda table: table.id == 5, lambda table: table.id == 6) == (table.id == 5) | (table.id=6)
|
|
192
201
|
"""
|
|
193
202
|
new_query = self.query
|
|
194
|
-
table = self.
|
|
203
|
+
table = self._ensure_table_defined()
|
|
195
204
|
|
|
196
205
|
queries_or_lambdas = (
|
|
197
206
|
*queries_or_lambdas,
|
|
@@ -531,6 +540,11 @@ class QueryBuilder(t.Generic[T_MetaInstance]):
|
|
|
531
540
|
if _to is None:
|
|
532
541
|
_to = TypedRows
|
|
533
542
|
|
|
543
|
+
if not isinstance(self.model, TableMeta):
|
|
544
|
+
# tried to use querybuilder with a non-typedal table,
|
|
545
|
+
# fallback to execute:
|
|
546
|
+
return self.execute(add_id=add_id)
|
|
547
|
+
|
|
534
548
|
db = self._get_db()
|
|
535
549
|
metadata = self.metadata.copy()
|
|
536
550
|
|
|
@@ -792,7 +806,7 @@ class QueryBuilder(t.Generic[T_MetaInstance]):
|
|
|
792
806
|
Transform the raw rows into Typed Table model instances with nested relationships.
|
|
793
807
|
"""
|
|
794
808
|
db = self._get_db()
|
|
795
|
-
main_table = self.
|
|
809
|
+
main_table = self._ensure_table_defined()
|
|
796
810
|
|
|
797
811
|
# id: Model
|
|
798
812
|
records: dict[t.Any, T_MetaInstance] = {}
|
|
@@ -6,10 +6,7 @@ import uuid
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
8
|
import pytest
|
|
9
|
-
|
|
10
|
-
# from contextlib import chdir
|
|
11
9
|
from contextlib_chdir import chdir
|
|
12
|
-
from pydal2sql import generate_sql
|
|
13
10
|
from testcontainers.postgres import PostgresContainer
|
|
14
11
|
|
|
15
12
|
from src.typedal import TypeDAL, TypedField, TypedTable
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import inspect
|
|
2
|
-
import typing
|
|
3
|
-
|
|
4
1
|
import pytest
|
|
5
2
|
from pydal.objects import Query
|
|
6
3
|
|
|
7
4
|
from src.typedal import TypeDAL, TypedField, TypedTable, relationship
|
|
8
|
-
from typedal
|
|
5
|
+
from typedal import QueryBuilder
|
|
9
6
|
|
|
10
7
|
db = TypeDAL("sqlite:memory")
|
|
11
8
|
|
|
@@ -17,7 +14,9 @@ class TestQueryTable(TypedTable):
|
|
|
17
14
|
yet_another = TypedField(list[str], default=["something", "and", "other", "things"])
|
|
18
15
|
|
|
19
16
|
relations = relationship(
|
|
20
|
-
list["TestRelationship"],
|
|
17
|
+
list["TestRelationship"],
|
|
18
|
+
condition=lambda self, other: self.id == other.querytable,
|
|
19
|
+
join="left",
|
|
21
20
|
)
|
|
22
21
|
|
|
23
22
|
|
|
@@ -47,21 +46,21 @@ def test_query_type():
|
|
|
47
46
|
|
|
48
47
|
|
|
49
48
|
"""
|
|
50
|
-
SELECT "test_query_table"."id"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
FROM "test_query_table"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
WHERE ("test_query_table"."id" IN (SELECT "test_query_table"."id"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
ORDER BY "test_query_table"."number" DESC;
|
|
49
|
+
SELECT "test_query_table"."id"
|
|
50
|
+
, "test_query_table"."number"
|
|
51
|
+
, "relations_8106139955393"."id"
|
|
52
|
+
, "relations_8106139955393"."name"
|
|
53
|
+
, "relations_8106139955393"."value"
|
|
54
|
+
, "relations_8106139955393"."querytable"
|
|
55
|
+
FROM "test_query_table"
|
|
56
|
+
LEFT JOIN "test_relationship" AS "relations_8106139955393"
|
|
57
|
+
ON ("relations_8106139955393"."querytable" = "test_query_table"."id")
|
|
58
|
+
WHERE ("test_query_table"."id" IN (SELECT "test_query_table"."id"
|
|
59
|
+
FROM "test_query_table"
|
|
60
|
+
WHERE ("test_query_table"."id" > 0)
|
|
61
|
+
ORDER BY "test_query_table"."id"
|
|
62
|
+
LIMIT 3 OFFSET 0))
|
|
63
|
+
ORDER BY "test_query_table"."number" DESC;
|
|
65
64
|
"""
|
|
66
65
|
|
|
67
66
|
|
|
@@ -532,3 +531,16 @@ def test_collect_with_extra_fields():
|
|
|
532
531
|
|
|
533
532
|
with pytest.raises(HTTP):
|
|
534
533
|
TestRelationship.where(TestRelationship.id == 3245892384).first_or_fail(HTTP(404))
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def test_minimal_functionality_on_pydal_style_tables():
|
|
537
|
+
_setup_data()
|
|
538
|
+
|
|
539
|
+
qb1 = TestQueryTable.where(number=2).collect()
|
|
540
|
+
qb2 = QueryBuilder(db.test_query_table).where(number=2).collect()
|
|
541
|
+
|
|
542
|
+
assert len(qb1) == len(qb2)
|
|
543
|
+
assert qb1.first().id == qb2.first().id
|
|
544
|
+
|
|
545
|
+
assert qb2
|
|
546
|
+
assert len(qb2) == 1
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import contextlib
|
|
2
|
-
import json
|
|
3
2
|
import time
|
|
4
3
|
import types
|
|
5
4
|
import typing
|
|
@@ -102,8 +101,7 @@ class Tagged(TypedTable): # pivot table
|
|
|
102
101
|
|
|
103
102
|
|
|
104
103
|
@db.define()
|
|
105
|
-
class Empty(TypedTable):
|
|
106
|
-
...
|
|
104
|
+
class Empty(TypedTable): ...
|
|
107
105
|
|
|
108
106
|
|
|
109
107
|
def _setup_data():
|
|
@@ -314,8 +312,7 @@ def test_typedal_way():
|
|
|
314
312
|
author1 = User.where(id=4).join("articles").first()
|
|
315
313
|
|
|
316
314
|
assert (
|
|
317
|
-
|
|
318
|
-
dict(author1)["articles"]) == 2
|
|
315
|
+
len(author1.as_dict()["articles"]) == len(author1.__dict__["articles"]) == len(dict(author1)["articles"]) == 2
|
|
319
316
|
)
|
|
320
317
|
|
|
321
318
|
|
|
@@ -483,12 +480,12 @@ def test_caching():
|
|
|
483
480
|
cached_user_only2 = User.join().cache(User.id).collect_or_fail()
|
|
484
481
|
|
|
485
482
|
assert (
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
483
|
+
len(uncached2)
|
|
484
|
+
== len(uncached)
|
|
485
|
+
== len(cached2)
|
|
486
|
+
== len(cached)
|
|
487
|
+
== len(cached_user_only2)
|
|
488
|
+
== len(cached_user_only)
|
|
492
489
|
)
|
|
493
490
|
|
|
494
491
|
assert uncached.as_json() == uncached2.as_json() == cached.as_json() == cached2.as_json()
|
|
@@ -496,9 +493,9 @@ def test_caching():
|
|
|
496
493
|
assert cached.first().gid == cached2.first().gid
|
|
497
494
|
|
|
498
495
|
assert (
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
496
|
+
[_.name for _ in uncached2.first().roles]
|
|
497
|
+
== [_.name for _ in cached.first().roles]
|
|
498
|
+
== [_.name for _ in cached2.first().roles]
|
|
502
499
|
)
|
|
503
500
|
|
|
504
501
|
assert not uncached2.metadata.get("cache", {}).get("enabled")
|
|
@@ -636,27 +633,31 @@ def test_caching_dependencies():
|
|
|
636
633
|
|
|
637
634
|
def test_illegal():
|
|
638
635
|
with pytest.raises(ValueError), pytest.warns(UserWarning):
|
|
636
|
+
|
|
639
637
|
class HasRelationship:
|
|
640
638
|
something = relationship("...", condition=lambda: 1, on=lambda: 2)
|
|
641
639
|
|
|
642
640
|
with pytest.raises(ValueError), pytest.warns(UserWarning):
|
|
643
641
|
Tag.join(Tag.articles, condition=lambda: 1, on=lambda: 2)
|
|
644
642
|
|
|
643
|
+
|
|
645
644
|
def test_join_relationship_custom_on():
|
|
646
645
|
_setup_data()
|
|
647
646
|
|
|
648
|
-
rows1 = Tag.join(
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
647
|
+
rows1 = Tag.join(
|
|
648
|
+
Tag.articles,
|
|
649
|
+
condition=lambda tag, article: (Tagged.tag == tag.id) & (article.gid == Tagged.entity) & (article.author == 3),
|
|
650
|
+
method="inner",
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
rows2 = Tag.join(
|
|
654
|
+
Tag.articles,
|
|
655
|
+
on=lambda tag, article: [
|
|
656
|
+
tagged := Tagged.unique_alias(),
|
|
657
|
+
(tagged.tag == tag.id) & (article.gid == tagged.entity) & (article.author == 3),
|
|
658
|
+
],
|
|
659
|
+
method="inner",
|
|
660
|
+
)
|
|
660
661
|
|
|
661
662
|
assert all([row.articles for row in rows1])
|
|
662
663
|
assert all([row.articles for row in rows2])
|
|
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
|