TypeDAL 3.8.4__tar.gz → 3.8.5__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.8.4 → typedal-3.8.5}/CHANGELOG.md +7 -0
- {typedal-3.8.4 → typedal-3.8.5}/PKG-INFO +1 -1
- {typedal-3.8.4 → typedal-3.8.5}/example_new.py +3 -1
- {typedal-3.8.4 → typedal-3.8.5}/src/typedal/__about__.py +1 -1
- {typedal-3.8.4 → typedal-3.8.5}/src/typedal/core.py +30 -6
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_query_builder.py +14 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_relationships.py +53 -7
- {typedal-3.8.4 → typedal-3.8.5}/.github/workflows/su6.yml +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/.gitignore +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/.readthedocs.yml +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/README.md +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/coverage.svg +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/docs/1_getting_started.md +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/docs/2_defining_tables.md +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/docs/3_building_queries.md +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/docs/4_relationships.md +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/docs/5_py4web.md +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/docs/6_migrations.md +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/docs/7_mixins.md +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/docs/css/code_blocks.css +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/docs/index.md +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/docs/requirements.txt +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/example_old.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/mkdocs.yml +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/pyproject.toml +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/src/typedal/__init__.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/src/typedal/caching.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/src/typedal/cli.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/src/typedal/config.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/src/typedal/fields.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/src/typedal/for_py4web.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/src/typedal/for_web2py.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/src/typedal/helpers.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/src/typedal/mixins.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/src/typedal/py.typed +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/src/typedal/serializers/as_json.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/src/typedal/types.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/src/typedal/web2py_py4web_shared.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/__init__.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/configs/simple.toml +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/configs/valid.env +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/configs/valid.toml +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_cli.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_config.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_docs_examples.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_helpers.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_json.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_main.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_mixins.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_mypy.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_orm.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_py4web.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_row.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_stats.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_table.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_web2py.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/test_xx_others.py +0 -0
- {typedal-3.8.4 → typedal-3.8.5}/tests/timings.py +0 -0
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
<!--next-version-placeholder-->
|
|
4
4
|
|
|
5
|
+
## v3.8.5 (2024-10-24)
|
|
6
|
+
|
|
7
|
+
### Fix
|
|
8
|
+
|
|
9
|
+
* Use right 'timestamp' field ([`2aedb02`](https://github.com/trialandsuccess/TypeDAL/commit/2aedb027b4490f8284253e9c7f427b0660b6bc13))
|
|
10
|
+
* Allow specifying a field to Builder.count(...); support selecting extra fields (e.g. MyField.count()) ([`ce28a79`](https://github.com/trialandsuccess/TypeDAL/commit/ce28a7995a6d817424462f8b18383c85fa349ba4))
|
|
11
|
+
|
|
5
12
|
## v3.8.4 (2024-10-24)
|
|
6
13
|
|
|
7
14
|
### Fix
|
|
@@ -8,6 +8,8 @@ from src.typedal.fields import TextField
|
|
|
8
8
|
from src.typedal.helpers import utcnow
|
|
9
9
|
from pydal.validators import IS_NOT_EMPTY
|
|
10
10
|
|
|
11
|
+
from typedal.fields import TimestampField
|
|
12
|
+
|
|
11
13
|
db = TypeDAL("sqlite:memory")
|
|
12
14
|
|
|
13
15
|
|
|
@@ -20,7 +22,7 @@ class Person(TypedTable):
|
|
|
20
22
|
age = TypedField(int, default=18, requires=IS_NOT_EMPTY())
|
|
21
23
|
nicknames: list[str]
|
|
22
24
|
|
|
23
|
-
ts =
|
|
25
|
+
ts = TimestampField()
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
assert db.person._format == "%(name)s"
|
|
@@ -1500,7 +1500,14 @@ class TypedTable(_TypedTable, metaclass=TableMeta):
|
|
|
1500
1500
|
return None # type: ignore
|
|
1501
1501
|
|
|
1502
1502
|
inst._row = row
|
|
1503
|
-
|
|
1503
|
+
|
|
1504
|
+
if hasattr(row, "id"):
|
|
1505
|
+
inst.__dict__.update(row)
|
|
1506
|
+
else:
|
|
1507
|
+
# deal with _extra (and possibly others?)
|
|
1508
|
+
# Row <{actual: {}, _extra: ...}>
|
|
1509
|
+
inst.__dict__.update(row[str(cls)])
|
|
1510
|
+
|
|
1504
1511
|
inst._setup_instance_methods()
|
|
1505
1512
|
return inst
|
|
1506
1513
|
|
|
@@ -1828,7 +1835,22 @@ class TypedRows(typing.Collection[T_MetaInstance], Rows):
|
|
|
1828
1835
|
`metadata` can be any (un)structured data
|
|
1829
1836
|
`model` is a Typed Table class
|
|
1830
1837
|
"""
|
|
1831
|
-
|
|
1838
|
+
|
|
1839
|
+
def _get_id(row: Row) -> int:
|
|
1840
|
+
"""
|
|
1841
|
+
Try to find the id field in a row.
|
|
1842
|
+
|
|
1843
|
+
If _extra exists, the row changes:
|
|
1844
|
+
<Row {'test_relationship': {'id': 1}, '_extra': {'COUNT("test_relationship"."querytable")': 8}}>
|
|
1845
|
+
"""
|
|
1846
|
+
if idx := getattr(row, "id", None):
|
|
1847
|
+
return typing.cast(int, idx)
|
|
1848
|
+
elif main := getattr(row, str(model), None):
|
|
1849
|
+
return typing.cast(int, main.id)
|
|
1850
|
+
else: # pragma: no cover
|
|
1851
|
+
raise NotImplementedError(f"`id` could not be found for {row}")
|
|
1852
|
+
|
|
1853
|
+
records = records or {_get_id(row): model(row) for row in rows}
|
|
1832
1854
|
super().__init__(rows.db, records, rows.colnames, rows.compact, rows.response, rows.fields)
|
|
1833
1855
|
self.model = model
|
|
1834
1856
|
self.metadata = metadata or {}
|
|
@@ -2721,7 +2743,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2721
2743
|
"""
|
|
2722
2744
|
yield from self.collect()
|
|
2723
2745
|
|
|
2724
|
-
def count(self) -> int:
|
|
2746
|
+
def count(self, distinct: bool = None) -> int:
|
|
2725
2747
|
"""
|
|
2726
2748
|
Return the amount of rows matching the current query.
|
|
2727
2749
|
"""
|
|
@@ -2730,14 +2752,16 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2730
2752
|
query = self.query
|
|
2731
2753
|
|
|
2732
2754
|
for key, relation in self.relationships.items():
|
|
2733
|
-
if not relation.condition or relation.join != "inner":
|
|
2755
|
+
if (not relation.condition or relation.join != "inner") and not distinct:
|
|
2734
2756
|
continue
|
|
2735
2757
|
|
|
2736
2758
|
other = relation.get_table(db)
|
|
2737
|
-
|
|
2759
|
+
if not distinct:
|
|
2760
|
+
# todo: can this lead to other issues?
|
|
2761
|
+
other = other.with_alias(f"{key}_{hash(relation)}")
|
|
2738
2762
|
query &= relation.condition(model, other)
|
|
2739
2763
|
|
|
2740
|
-
return db(query).count()
|
|
2764
|
+
return db(query).count(distinct)
|
|
2741
2765
|
|
|
2742
2766
|
def __paginate(
|
|
2743
2767
|
self,
|
|
@@ -471,3 +471,17 @@ def test_column():
|
|
|
471
471
|
|
|
472
472
|
assert len(rows) == 4
|
|
473
473
|
assert set(rows) == {33}
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def test_collect_with_extra_fields():
|
|
477
|
+
_setup_data()
|
|
478
|
+
builder = TestRelationship.select(TestRelationship.id, TestRelationship.name, TestRelationship.querytable.count())
|
|
479
|
+
|
|
480
|
+
assert builder.execute()
|
|
481
|
+
|
|
482
|
+
row = builder.first_or_fail()
|
|
483
|
+
|
|
484
|
+
assert row.id
|
|
485
|
+
assert row.name
|
|
486
|
+
assert row._extra
|
|
487
|
+
assert row[TestRelationship.querytable.count()]
|
|
@@ -43,7 +43,7 @@ class Role(TypedTable, TaggableMixin):
|
|
|
43
43
|
@db.define()
|
|
44
44
|
class User(TypedTable, TaggableMixin):
|
|
45
45
|
gid = TypedField(str, default=uuid4)
|
|
46
|
-
name: str
|
|
46
|
+
name: TypedField[str]
|
|
47
47
|
roles: TypedField[list[Role]]
|
|
48
48
|
main_role = TypedField(Role)
|
|
49
49
|
extra_roles = TypedField(list[Role])
|
|
@@ -110,6 +110,11 @@ def _setup_data():
|
|
|
110
110
|
]
|
|
111
111
|
)
|
|
112
112
|
|
|
113
|
+
# no relationships:
|
|
114
|
+
new_author = User.insert(name="Untagged Author", roles=[], main_role=writer, extra_roles=[])
|
|
115
|
+
untagged1 = Article.insert(title="Untagged Article 1", author=new_author)
|
|
116
|
+
untagged2 = Article.insert(title="Untagged Article 2", author=new_author)
|
|
117
|
+
|
|
113
118
|
# articles
|
|
114
119
|
|
|
115
120
|
article1, article2 = Article.bulk_insert(
|
|
@@ -184,11 +189,11 @@ def test_typedal_way():
|
|
|
184
189
|
|
|
185
190
|
all_articles = Article.join().collect().as_dict()
|
|
186
191
|
|
|
187
|
-
assert all_articles[
|
|
188
|
-
assert all_articles[
|
|
192
|
+
assert all_articles[3]["final_editor"]["name"] == "Editor 1"
|
|
193
|
+
assert all_articles[4]["secondary_author"]["name"] == "Editor 1"
|
|
189
194
|
|
|
190
|
-
assert all_articles[
|
|
191
|
-
assert all_articles[
|
|
195
|
+
assert all_articles[3]["secondary_author"] is None
|
|
196
|
+
assert all_articles[4]["final_editor"] is None
|
|
192
197
|
|
|
193
198
|
assert Article.first_or_fail()
|
|
194
199
|
|
|
@@ -244,7 +249,7 @@ def test_typedal_way():
|
|
|
244
249
|
|
|
245
250
|
users = User.join().collect()
|
|
246
251
|
|
|
247
|
-
assert len(users) ==
|
|
252
|
+
assert len(users) == 4 # reader, writer, editor, untagged
|
|
248
253
|
|
|
249
254
|
# get by id:
|
|
250
255
|
reader = users[1]
|
|
@@ -533,6 +538,47 @@ def test_caching_dependencies():
|
|
|
533
538
|
|
|
534
539
|
def test_illegal():
|
|
535
540
|
with pytest.raises(ValueError), pytest.warns(UserWarning):
|
|
536
|
-
|
|
537
541
|
class HasRelationship:
|
|
538
542
|
something = relationship("...", condition=lambda: 1, on=lambda: 2)
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def test_join_with_select():
|
|
547
|
+
_setup_data()
|
|
548
|
+
|
|
549
|
+
builder = User.select(User.id, User.gid, Article.id, Article.gid).where(id=2).join("articles")
|
|
550
|
+
user = builder.first_or_fail()
|
|
551
|
+
|
|
552
|
+
assert user.id
|
|
553
|
+
assert user.gid
|
|
554
|
+
assert not user.name
|
|
555
|
+
assert user.articles[0].id
|
|
556
|
+
assert user.articles[0].gid
|
|
557
|
+
assert not hasattr(user.articles[0], "title")
|
|
558
|
+
|
|
559
|
+
for user in builder.paginate(limit=1, page=1):
|
|
560
|
+
assert user.id
|
|
561
|
+
assert user.gid
|
|
562
|
+
assert not user.name
|
|
563
|
+
assert user.articles[0].id
|
|
564
|
+
assert user.articles[0].gid
|
|
565
|
+
assert not hasattr(user.articles[0], "title")
|
|
566
|
+
|
|
567
|
+
for user in builder.collect():
|
|
568
|
+
assert user.id
|
|
569
|
+
assert user.gid
|
|
570
|
+
assert not user.name
|
|
571
|
+
assert user.articles[0].id
|
|
572
|
+
assert user.articles[0].gid
|
|
573
|
+
assert not hasattr(user.articles[0], "title")
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def test_count_with_join():
|
|
577
|
+
_setup_data()
|
|
578
|
+
|
|
579
|
+
# 0. count via select:
|
|
580
|
+
row = User.select(User.id, User.gid, Article.id, Article.gid).where(id=4).join("articles").first_or_fail()
|
|
581
|
+
assert len(row.articles) == 2
|
|
582
|
+
|
|
583
|
+
assert User.where(id=4).join("articles").count(User.id) == 1
|
|
584
|
+
assert User.where(id=4).join("articles").count(Article.id) == 2
|
|
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
|