TypeDAL 4.6.2__tar.gz → 4.6.4__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.6.2 → typedal-4.6.4}/CHANGELOG.md +12 -0
- {typedal-4.6.2 → typedal-4.6.4}/PKG-INFO +1 -1
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/__about__.py +1 -1
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/query_builder.py +48 -5
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/rows.py +28 -16
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/tables.py +15 -1
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_query_builder.py +58 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_row.py +45 -0
- typedal-4.6.2/.crush/.gitignore +0 -1
- typedal-4.6.2/.crush/crush.db-shm +0 -0
- typedal-4.6.2/.crush/crush.db-wal +0 -0
- typedal-4.6.2/.crush/init +0 -0
- typedal-4.6.2/.crush/logs/crush.log +0 -19
- {typedal-4.6.2 → typedal-4.6.4}/.github/workflows/su6.yml +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/.gitignore +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/.readthedocs.yml +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/README.md +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/coverage.svg +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/docs/10_advanced_apis.md +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/docs/1_getting_started.md +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/docs/2_defining_tables.md +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/docs/3_building_queries.md +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/docs/4_relationships.md +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/docs/5_py4web.md +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/docs/6_migrations.md +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/docs/7_configuration.md +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/docs/8_mixins.md +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/docs/9_memoization.md +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/docs/css/code_blocks.css +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/docs/index.md +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/docs/requirements.txt +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/example_new.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/example_old.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/mkdocs.yml +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/pyproject.toml +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/__init__.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/caching.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/cli.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/config.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/constants.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/core.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/define.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/fields.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/for_py4web.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/for_web2py.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/helpers.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/mixins.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/py.typed +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/relationships.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/serializers/as_json.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/types.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/src/typedal/web2py_py4web_shared.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tasks.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/__init__.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/configs/simple.toml +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/configs/valid.env +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/configs/valid.toml +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/py314_tests.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_cli.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_config.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_docs_examples.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_helpers.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_json.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_main.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_mixins.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_mypy.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_orm.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_py4web.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_relationships.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_stats.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_table.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_web2py.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/test_xx_others.py +0 -0
- {typedal-4.6.2 → typedal-4.6.4}/tests/timings.py +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
<!--next-version-placeholder-->
|
|
4
4
|
|
|
5
|
+
## v4.6.4 (2026-03-30)
|
|
6
|
+
|
|
7
|
+
### Fix
|
|
8
|
+
|
|
9
|
+
* Add collect_into to allow instantiating results into different model classes + fix render ([#8](https://github.com/trialandsuccess/TypeDAL/issues/8)) ([`bc5b5b7`](https://github.com/trialandsuccess/TypeDAL/commit/bc5b5b78ce033b068c21f468932a4b2f3adfeeac))
|
|
10
|
+
|
|
11
|
+
## v4.6.3 (2026-03-19)
|
|
12
|
+
|
|
13
|
+
### Fix
|
|
14
|
+
|
|
15
|
+
* `as_dict()` and similar changed behavior after doing `.render()` ([`8241503`](https://github.com/trialandsuccess/TypeDAL/commit/8241503073c5a9fd76cc898048f6c7796de3ce7a))
|
|
16
|
+
|
|
5
17
|
## v4.6.2 (2026-03-13)
|
|
6
18
|
|
|
7
19
|
### Fix
|
|
@@ -33,6 +33,7 @@ from .types import (
|
|
|
33
33
|
OnQuery,
|
|
34
34
|
OrderBy,
|
|
35
35
|
Query,
|
|
36
|
+
Row,
|
|
36
37
|
Rows,
|
|
37
38
|
SelectKwargs,
|
|
38
39
|
T_MetaInstance,
|
|
@@ -517,7 +518,11 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
517
518
|
"""
|
|
518
519
|
return self.to_sql()
|
|
519
520
|
|
|
520
|
-
def _collect_cached(
|
|
521
|
+
def _collect_cached(
|
|
522
|
+
self,
|
|
523
|
+
metadata: Metadata,
|
|
524
|
+
into: t.Type[_TypedTable],
|
|
525
|
+
) -> "TypedRows[T_MetaInstance] | None":
|
|
521
526
|
expires_at = metadata["cache"].get("expires_at")
|
|
522
527
|
metadata["cache"] |= {
|
|
523
528
|
# key is partly dependant on cache metadata but not these:
|
|
@@ -529,6 +534,7 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
529
534
|
|
|
530
535
|
_, key = create_and_hash_cache_key(
|
|
531
536
|
self.model,
|
|
537
|
+
f"{into.__module__}.{into.__qualname__}",
|
|
532
538
|
metadata,
|
|
533
539
|
self.query,
|
|
534
540
|
self.select_args,
|
|
@@ -566,12 +572,15 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
566
572
|
verbose: bool = False,
|
|
567
573
|
_to: t.Type["TypedRows[t.Any]"] = None,
|
|
568
574
|
add_id: bool = True,
|
|
575
|
+
_into: t.Type[_TypedTable] | None = None,
|
|
576
|
+
_init: t.Callable[[_TypedTable, Row], None] | None = None,
|
|
569
577
|
) -> "TypedRows[T_MetaInstance]":
|
|
570
578
|
"""
|
|
571
579
|
Execute the built query and turn it into model instances, while handling relationships.
|
|
572
580
|
"""
|
|
573
581
|
if _to is None:
|
|
574
582
|
_to = TypedRows
|
|
583
|
+
into = _into or self.model
|
|
575
584
|
|
|
576
585
|
if not isinstance(self.model, TableMeta):
|
|
577
586
|
# tried to use querybuilder with a non-typedal table,
|
|
@@ -585,7 +594,7 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
585
594
|
|
|
586
595
|
metadata: Metadata = self.metadata.copy()
|
|
587
596
|
|
|
588
|
-
if metadata.get("cache", {}).get("enabled") and (result := self._collect_cached(metadata)):
|
|
597
|
+
if metadata.get("cache", {}).get("enabled") and (result := self._collect_cached(metadata, into)):
|
|
589
598
|
return result
|
|
590
599
|
|
|
591
600
|
query, select_args, select_kwargs = self._before_query(metadata, add_id=add_id)
|
|
@@ -609,12 +618,12 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
609
618
|
|
|
610
619
|
if not self.relationships:
|
|
611
620
|
# easy
|
|
612
|
-
typed_rows = _to.from_rows(rows, self.model, metadata=metadata)
|
|
621
|
+
typed_rows = _to.from_rows(rows, self.model, metadata=metadata, into=into, init=_init)
|
|
613
622
|
else:
|
|
614
623
|
# harder: try to match rows to the belonging objects
|
|
615
624
|
# assume structure of {'table': <data>} per row.
|
|
616
625
|
# if that's not the case, return default behavior again
|
|
617
|
-
typed_rows = self._collect_with_relationships(rows, metadata=metadata, _to=_to)
|
|
626
|
+
typed_rows = self._collect_with_relationships(rows, metadata=metadata, _to=_to, _into=into, _init=_init)
|
|
618
627
|
|
|
619
628
|
for fn_after in db._after_collect:
|
|
620
629
|
fn_after(self, typed_rows, rows)
|
|
@@ -622,6 +631,35 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
622
631
|
# only saves if requested in metadata:
|
|
623
632
|
return save_to_cache(typed_rows, rows)
|
|
624
633
|
|
|
634
|
+
def collect_into[T_Into: _TypedTable](
|
|
635
|
+
self,
|
|
636
|
+
into: t.Type[T_Into],
|
|
637
|
+
verbose: bool = False,
|
|
638
|
+
add_id: bool = True,
|
|
639
|
+
init: t.Callable[[T_Into, Row], None] | None = None,
|
|
640
|
+
) -> "TypedRows[T_Into]":
|
|
641
|
+
"""
|
|
642
|
+
Execute the built query and instantiate root records as another model class.
|
|
643
|
+
"""
|
|
644
|
+
self._validate_collect_into_model(into)
|
|
645
|
+
_init = t.cast(t.Callable[[_TypedTable, Row], None] | None, init)
|
|
646
|
+
rows = self.collect(verbose=verbose, add_id=add_id, _into=into, _init=_init)
|
|
647
|
+
return t.cast("TypedRows[T_Into]", rows)
|
|
648
|
+
|
|
649
|
+
def _validate_collect_into_model(self, into: t.Type[t.Any]) -> None:
|
|
650
|
+
if not isinstance(into, TableMeta):
|
|
651
|
+
raise TypeError("collect_into expects a TypedTable class")
|
|
652
|
+
|
|
653
|
+
source = self.model._ensure_table_defined()
|
|
654
|
+
target = into._ensure_table_defined()
|
|
655
|
+
|
|
656
|
+
if source is target or str(source) == str(target):
|
|
657
|
+
return
|
|
658
|
+
|
|
659
|
+
raise ValueError(
|
|
660
|
+
f"collect_into target '{into.__name__}' must be bound to table '{source}', got '{target}'",
|
|
661
|
+
)
|
|
662
|
+
|
|
625
663
|
@t.overload
|
|
626
664
|
def column[T: t.Any](self, field: TypedField[T], **options: t.Unpack[SelectKwargs]) -> list[T]:
|
|
627
665
|
"""
|
|
@@ -843,12 +881,15 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
843
881
|
rows: Rows,
|
|
844
882
|
metadata: Metadata,
|
|
845
883
|
_to: t.Type["TypedRows[T_MetaInstance]"],
|
|
884
|
+
_into: t.Type[_TypedTable] | None = None,
|
|
885
|
+
_init: t.Callable[[_TypedTable, Row], None] | None = None,
|
|
846
886
|
) -> "TypedRows[T_MetaInstance]":
|
|
847
887
|
"""
|
|
848
888
|
Transform the raw rows into Typed Table model instances with nested relationships.
|
|
849
889
|
"""
|
|
850
890
|
db = self._get_db()
|
|
851
891
|
main_table = self._ensure_table_defined()
|
|
892
|
+
into = _into or self.model
|
|
852
893
|
|
|
853
894
|
# id: Model
|
|
854
895
|
records: dict[t.Any, T_MetaInstance] = {}
|
|
@@ -866,7 +907,9 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
866
907
|
raw_per_id[main_id].append(normalize_table_keys(row))
|
|
867
908
|
|
|
868
909
|
if main_id not in records:
|
|
869
|
-
records[main_id] =
|
|
910
|
+
records[main_id] = t.cast(T_MetaInstance, into(main))
|
|
911
|
+
if _init:
|
|
912
|
+
_init(t.cast(_TypedTable, records[main_id]), row)
|
|
870
913
|
records[main_id]._with = list(self.relationships.keys())
|
|
871
914
|
|
|
872
915
|
# Setup all relationship defaults (once)
|
|
@@ -62,21 +62,7 @@ class TypedRows(t.Collection[T_MetaInstance], Rows):
|
|
|
62
62
|
`model` is a Typed Table class
|
|
63
63
|
"""
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
"""
|
|
67
|
-
Try to find the id field in a row.
|
|
68
|
-
|
|
69
|
-
If _extra exists, the row changes:
|
|
70
|
-
<Row {'test_relationship': {'id': 1}, '_extra': {'COUNT("test_relationship"."querytable")': 8}}>
|
|
71
|
-
"""
|
|
72
|
-
if idx := getattr(row, "id", None):
|
|
73
|
-
return t.cast(int, idx)
|
|
74
|
-
elif main := getattr(row, str(model), None):
|
|
75
|
-
return t.cast(int, main.id)
|
|
76
|
-
else: # pragma: no cover
|
|
77
|
-
raise NotImplementedError(f"`id` could not be found for {row}")
|
|
78
|
-
|
|
79
|
-
records = records or {_get_id(row): model(row) for row in rows}
|
|
65
|
+
records = records or {self._get_id(row, model): model(row) for row in rows}
|
|
80
66
|
raw = raw or {}
|
|
81
67
|
|
|
82
68
|
for idx, entity in records.items():
|
|
@@ -87,6 +73,21 @@ class TypedRows(t.Collection[T_MetaInstance], Rows):
|
|
|
87
73
|
self.metadata = metadata or {}
|
|
88
74
|
self.colnames = rows.colnames
|
|
89
75
|
|
|
76
|
+
@staticmethod
|
|
77
|
+
def _get_id(row: Row, model: t.Type[t.Any]) -> int:
|
|
78
|
+
"""
|
|
79
|
+
Try to find the id field in a row.
|
|
80
|
+
|
|
81
|
+
If _extra exists, the row changes:
|
|
82
|
+
<Row {'test_relationship': {'id': 1}, '_extra': {'COUNT("test_relationship"."querytable")': 8}}>
|
|
83
|
+
"""
|
|
84
|
+
if idx := getattr(row, "id", None):
|
|
85
|
+
return t.cast(int, idx)
|
|
86
|
+
elif main := getattr(row, str(model), None):
|
|
87
|
+
return t.cast(int, main.id)
|
|
88
|
+
else: # pragma: no cover
|
|
89
|
+
raise NotImplementedError(f"`id` could not be found for {row}")
|
|
90
|
+
|
|
90
91
|
def __len__(self) -> int:
|
|
91
92
|
"""
|
|
92
93
|
Return the count of rows.
|
|
@@ -374,11 +375,22 @@ class TypedRows(t.Collection[T_MetaInstance], Rows):
|
|
|
374
375
|
rows: Rows,
|
|
375
376
|
model: t.Type[T_MetaInstance],
|
|
376
377
|
metadata: Metadata = None,
|
|
378
|
+
into: t.Type[_TypedTable] | None = None,
|
|
379
|
+
init: t.Callable[[_TypedTable, Row], None] | None = None,
|
|
377
380
|
) -> "TypedRows[T_MetaInstance]":
|
|
378
381
|
"""
|
|
379
382
|
Internal method to convert a Rows object to a TypedRows.
|
|
380
383
|
"""
|
|
381
|
-
|
|
384
|
+
target_model = into or model
|
|
385
|
+
|
|
386
|
+
def build(row: Row) -> T_MetaInstance:
|
|
387
|
+
instance = t.cast(T_MetaInstance, target_model(row))
|
|
388
|
+
if init:
|
|
389
|
+
init(instance, row)
|
|
390
|
+
return instance
|
|
391
|
+
|
|
392
|
+
records = {cls._get_id(row, model): build(row) for row in rows}
|
|
393
|
+
return cls(rows, model, records=records, metadata=metadata)
|
|
382
394
|
|
|
383
395
|
def __getstate__(self) -> AnyDict:
|
|
384
396
|
"""
|
|
@@ -386,6 +386,17 @@ class TableMeta(type):
|
|
|
386
386
|
"""
|
|
387
387
|
return QueryBuilder(self).collect(verbose=verbose)
|
|
388
388
|
|
|
389
|
+
def collect_into[T_Into: _TypedTable](
|
|
390
|
+
self: t.Type[_TypedTable],
|
|
391
|
+
into: t.Type[T_Into],
|
|
392
|
+
verbose: bool = False,
|
|
393
|
+
init: t.Callable[[T_Into, Row], None] | None = None,
|
|
394
|
+
) -> "TypedRows[T_Into]":
|
|
395
|
+
"""
|
|
396
|
+
See QueryBuilder.collect_into!
|
|
397
|
+
"""
|
|
398
|
+
return QueryBuilder(self).collect_into(into=into, verbose=verbose, init=init)
|
|
399
|
+
|
|
389
400
|
@property
|
|
390
401
|
def ALL(cls) -> pydal.objects.SQLALL:
|
|
391
402
|
"""
|
|
@@ -1104,6 +1115,7 @@ class TypedTable(_TypedTable, metaclass=TableMeta):
|
|
|
1104
1115
|
# then create a new (more empty) row object:
|
|
1105
1116
|
state["_row"] = Row(json.loads(state["_row"]))
|
|
1106
1117
|
self.__dict__ |= state
|
|
1118
|
+
self._setup_instance_methods()
|
|
1107
1119
|
|
|
1108
1120
|
@classmethod
|
|
1109
1121
|
def _sql(cls) -> str:
|
|
@@ -1152,7 +1164,9 @@ class TypedTable(_TypedTable, metaclass=TableMeta):
|
|
|
1152
1164
|
|
|
1153
1165
|
relation_row = row[relation_name]
|
|
1154
1166
|
|
|
1155
|
-
if
|
|
1167
|
+
if relation_row is None:
|
|
1168
|
+
row[relation_name] = None
|
|
1169
|
+
elif isinstance(relation_row, list):
|
|
1156
1170
|
# list of rows
|
|
1157
1171
|
combined = []
|
|
1158
1172
|
|
|
@@ -28,6 +28,10 @@ class TestRelationship(TypedTable):
|
|
|
28
28
|
querytable: TestQueryTable
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
class TestQueryTableBound(TestQueryTable):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
31
35
|
class Undefined(TypedTable):
|
|
32
36
|
value: int
|
|
33
37
|
|
|
@@ -210,6 +214,60 @@ def test_select():
|
|
|
210
214
|
assert other.value
|
|
211
215
|
|
|
212
216
|
|
|
217
|
+
def test_collect_into():
|
|
218
|
+
_setup_data()
|
|
219
|
+
|
|
220
|
+
rows = TestQueryTable.where(lambda row: row.number < 3).collect_into(TestQueryTableBound)
|
|
221
|
+
first = rows.first()
|
|
222
|
+
|
|
223
|
+
assert first
|
|
224
|
+
assert isinstance(first, TestQueryTableBound)
|
|
225
|
+
assert rows.model is TestQueryTable
|
|
226
|
+
|
|
227
|
+
joined = TestQueryTable.join("relations").where(id=1).collect_into(TestQueryTableBound)
|
|
228
|
+
joined_first = joined.first()
|
|
229
|
+
|
|
230
|
+
assert joined_first
|
|
231
|
+
assert isinstance(joined_first, TestQueryTableBound)
|
|
232
|
+
assert isinstance(joined_first.relations[0], TestRelationship)
|
|
233
|
+
|
|
234
|
+
marker = object()
|
|
235
|
+
|
|
236
|
+
def bind(sticker: TestQueryTableBound, _row):
|
|
237
|
+
sticker.item = marker
|
|
238
|
+
|
|
239
|
+
bound_rows = TestQueryTable.where(lambda row: row.number < 3).collect_into(TestQueryTableBound, init=bind)
|
|
240
|
+
assert all(getattr(row, "item", None) is marker for row in bound_rows)
|
|
241
|
+
|
|
242
|
+
calls: list[int] = []
|
|
243
|
+
|
|
244
|
+
def bind_once_per_root(sticker: TestQueryTableBound, _row):
|
|
245
|
+
calls.append(sticker.id)
|
|
246
|
+
|
|
247
|
+
TestQueryTable.join("relations").where(id=1).collect_into(TestQueryTableBound, init=bind_once_per_root)
|
|
248
|
+
assert calls == [1]
|
|
249
|
+
|
|
250
|
+
with pytest.raises(TypeError):
|
|
251
|
+
TestQueryTable.collect_into(dict) # type: ignore[arg-type]
|
|
252
|
+
|
|
253
|
+
with pytest.raises(ValueError):
|
|
254
|
+
TestQueryTable.collect_into(TestRelationship)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def test_collect_into_cache_isolation():
|
|
258
|
+
_setup_data()
|
|
259
|
+
|
|
260
|
+
regular = TestQueryTable.where(id=1).cache().collect()
|
|
261
|
+
remapped_fresh = TestQueryTable.where(id=1).cache().collect_into(TestQueryTableBound)
|
|
262
|
+
remapped_cached = TestQueryTable.where(id=1).cache().collect_into(TestQueryTableBound)
|
|
263
|
+
|
|
264
|
+
assert regular.metadata["cache"]["status"] == "fresh"
|
|
265
|
+
assert remapped_fresh.metadata["cache"]["status"] == "fresh"
|
|
266
|
+
assert remapped_cached.metadata["cache"]["status"] == "cached"
|
|
267
|
+
|
|
268
|
+
assert isinstance(remapped_cached.first(), TestQueryTableBound)
|
|
269
|
+
|
|
270
|
+
|
|
213
271
|
def test_paginate():
|
|
214
272
|
_setup_data()
|
|
215
273
|
|
|
@@ -266,6 +266,15 @@ def test_render():
|
|
|
266
266
|
assert rendered_two.normal == "123"
|
|
267
267
|
assert rendered_two.list_field == "abc, def"
|
|
268
268
|
assert rendered_two.related.also_normal == "321"
|
|
269
|
+
assert json.loads(rendered_two.as_json()) == {
|
|
270
|
+
"id": rendered_two.id,
|
|
271
|
+
"normal": "123",
|
|
272
|
+
"list_field": "abc, def",
|
|
273
|
+
"related": {
|
|
274
|
+
"id": rendered_two.related.id,
|
|
275
|
+
"also_normal": "321",
|
|
276
|
+
},
|
|
277
|
+
}
|
|
269
278
|
|
|
270
279
|
# test list:
|
|
271
280
|
|
|
@@ -287,3 +296,39 @@ def test_render():
|
|
|
287
296
|
assert rendered_four.normal == "123"
|
|
288
297
|
assert rendered_four.list_field == "abc, def"
|
|
289
298
|
assert rendered_four.related_list[0].also_normal == "321"
|
|
299
|
+
assert json.loads(rendered_four.as_json()) == {
|
|
300
|
+
"id": rendered_four.id,
|
|
301
|
+
"normal": "123",
|
|
302
|
+
"list_field": "abc, def",
|
|
303
|
+
"related_list": [
|
|
304
|
+
{
|
|
305
|
+
"id": rendered_four.related_list[0].id,
|
|
306
|
+
"also_normal": "321",
|
|
307
|
+
}
|
|
308
|
+
],
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def test_render_with_none_single_relationship_row():
|
|
313
|
+
@db.define()
|
|
314
|
+
class RelatedTableNone(TypedTable):
|
|
315
|
+
value: str
|
|
316
|
+
|
|
317
|
+
@db.define()
|
|
318
|
+
class RenderTableNone(TypedTable):
|
|
319
|
+
normal: str
|
|
320
|
+
related = relationship(
|
|
321
|
+
RelatedTableNone,
|
|
322
|
+
condition=lambda this, that: this.normal == that.value,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
RenderTableNone.insert(normal="no-match")
|
|
326
|
+
|
|
327
|
+
row = RenderTableNone.select().join("related").first()
|
|
328
|
+
|
|
329
|
+
assert row
|
|
330
|
+
assert row.related is None
|
|
331
|
+
|
|
332
|
+
rendered = row.render()
|
|
333
|
+
|
|
334
|
+
assert rendered.related is None
|
typedal-4.6.2/.crush/.gitignore
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
*
|
|
Binary file
|
|
Binary file
|
typedal-4.6.2/.crush/init
DELETED
|
File without changes
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{"time":"2026-03-13T20:12:28.275088774+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":55},"msg":"Fetching providers from Catwalk"}
|
|
2
|
-
{"time":"2026-03-13T20:12:28.486572096+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":63},"msg":"Catwalk providers not modified"}
|
|
3
|
-
{"time":"2026-03-13T20:12:28.486667656+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/config.(*Config).configureProviders","file":"github.com/charmbracelet/crush/internal/config/load.go","line":296},"msg":"Skipping provider due to missing API key","provider":"anthropic"}
|
|
4
|
-
{"time":"2026-03-13T20:12:28.497862335+01:00","level":"INFO","msg":"OK 20250424200609_initial.sql (1.55ms)"}
|
|
5
|
-
{"time":"2026-03-13T20:12:28.498184106+01:00","level":"INFO","msg":"OK 20250515105448_add_summary_message_id.sql (300.3µs)"}
|
|
6
|
-
{"time":"2026-03-13T20:12:28.498510046+01:00","level":"INFO","msg":"OK 20250624000000_add_created_at_indexes.sql (298.08µs)"}
|
|
7
|
-
{"time":"2026-03-13T20:12:28.498811912+01:00","level":"INFO","msg":"OK 20250627000000_add_provider_to_messages.sql (289.51µs)"}
|
|
8
|
-
{"time":"2026-03-13T20:12:28.499108738+01:00","level":"INFO","msg":"OK 20250810000000_add_is_summary_message.sql (245.48µs)"}
|
|
9
|
-
{"time":"2026-03-13T20:12:28.499362065+01:00","level":"INFO","msg":"OK 20250812000000_add_todos_to_sessions.sql (241.95µs)"}
|
|
10
|
-
{"time":"2026-03-13T20:12:28.499726903+01:00","level":"INFO","msg":"OK 20260127000000_add_read_files_table.sql (351.05µs)"}
|
|
11
|
-
{"time":"2026-03-13T20:12:28.499731494+01:00","level":"INFO","msg":"goose: successfully migrated database to version: 20260127000000"}
|
|
12
|
-
{"time":"2026-03-13T20:12:28.50106094+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools/mcp.Initialize","file":"github.com/charmbracelet/crush/internal/agent/tools/mcp/init.go","line":167},"msg":"Initializing MCP clients"}
|
|
13
|
-
{"time":"2026-03-13T20:12:31.915239786+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":55},"msg":"Fetching providers from Catwalk"}
|
|
14
|
-
{"time":"2026-03-13T20:12:32.150078123+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":63},"msg":"Catwalk providers not modified"}
|
|
15
|
-
{"time":"2026-03-13T20:12:32.150303604+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/config.(*Config).configureProviders","file":"github.com/charmbracelet/crush/internal/config/load.go","line":296},"msg":"Skipping provider due to missing API key","provider":"anthropic"}
|
|
16
|
-
{"time":"2026-03-13T20:12:32.15507598+01:00","level":"INFO","msg":"goose: no migrations to run. current version: 20260127000000"}
|
|
17
|
-
{"time":"2026-03-13T20:12:32.161646966+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools/mcp.Initialize","file":"github.com/charmbracelet/crush/internal/agent/tools/mcp/init.go","line":167},"msg":"Initializing MCP clients"}
|
|
18
|
-
{"time":"2026-03-13T20:15:20.65020982+01:00","level":"ERROR","source":{"function":"github.com/charmbracelet/x/powernap/pkg/lsp.startServerProcess.func1","file":"github.com/charmbracelet/x/powernap@v0.1.3/pkg/lsp/client.go","line":650},"msg":"Language server stderr","command":"ty","output":"2026-03-13 20:15:20.650149482 INFO Version: 0.0.20\n"}
|
|
19
|
-
{"time":"2026-03-13T20:15:20.651255948+01:00","level":"ERROR","source":{"function":"github.com/charmbracelet/x/powernap/pkg/lsp.startServerProcess.func1","file":"github.com/charmbracelet/x/powernap@v0.1.3/pkg/lsp/client.go","line":650},"msg":"Language server stderr","command":"ty","output":"2026-03-13 20:15:20.651183033 ERROR Invalid client response: did not contain a result or error (method=workspace/configuration)\n"}
|
|
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
|