TypeDAL 4.6.3__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.
Files changed (74) hide show
  1. {typedal-4.6.3 → typedal-4.6.4}/CHANGELOG.md +6 -0
  2. {typedal-4.6.3 → typedal-4.6.4}/PKG-INFO +1 -1
  3. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/__about__.py +1 -1
  4. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/cli.py +2 -1
  5. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/fields.py +3 -1
  6. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/query_builder.py +48 -5
  7. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/rows.py +28 -16
  8. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/tables.py +14 -1
  9. {typedal-4.6.3 → typedal-4.6.4}/tests/test_query_builder.py +58 -0
  10. {typedal-4.6.3 → typedal-4.6.4}/tests/test_row.py +25 -0
  11. typedal-4.6.3/.crush/.gitignore +0 -1
  12. typedal-4.6.3/.crush/crush.db-shm +0 -0
  13. typedal-4.6.3/.crush/crush.db-wal +0 -0
  14. typedal-4.6.3/.crush/init +0 -0
  15. typedal-4.6.3/.crush/logs/crush.log +0 -19
  16. {typedal-4.6.3 → typedal-4.6.4}/.github/workflows/su6.yml +0 -0
  17. {typedal-4.6.3 → typedal-4.6.4}/.gitignore +0 -0
  18. {typedal-4.6.3 → typedal-4.6.4}/.readthedocs.yml +0 -0
  19. {typedal-4.6.3 → typedal-4.6.4}/README.md +0 -0
  20. {typedal-4.6.3 → typedal-4.6.4}/coverage.svg +0 -0
  21. {typedal-4.6.3 → typedal-4.6.4}/docs/10_advanced_apis.md +0 -0
  22. {typedal-4.6.3 → typedal-4.6.4}/docs/1_getting_started.md +0 -0
  23. {typedal-4.6.3 → typedal-4.6.4}/docs/2_defining_tables.md +0 -0
  24. {typedal-4.6.3 → typedal-4.6.4}/docs/3_building_queries.md +0 -0
  25. {typedal-4.6.3 → typedal-4.6.4}/docs/4_relationships.md +0 -0
  26. {typedal-4.6.3 → typedal-4.6.4}/docs/5_py4web.md +0 -0
  27. {typedal-4.6.3 → typedal-4.6.4}/docs/6_migrations.md +0 -0
  28. {typedal-4.6.3 → typedal-4.6.4}/docs/7_configuration.md +0 -0
  29. {typedal-4.6.3 → typedal-4.6.4}/docs/8_mixins.md +0 -0
  30. {typedal-4.6.3 → typedal-4.6.4}/docs/9_memoization.md +0 -0
  31. {typedal-4.6.3 → typedal-4.6.4}/docs/css/code_blocks.css +0 -0
  32. {typedal-4.6.3 → typedal-4.6.4}/docs/index.md +0 -0
  33. {typedal-4.6.3 → typedal-4.6.4}/docs/requirements.txt +0 -0
  34. {typedal-4.6.3 → typedal-4.6.4}/example_new.py +0 -0
  35. {typedal-4.6.3 → typedal-4.6.4}/example_old.py +0 -0
  36. {typedal-4.6.3 → typedal-4.6.4}/mkdocs.yml +0 -0
  37. {typedal-4.6.3 → typedal-4.6.4}/pyproject.toml +0 -0
  38. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/__init__.py +0 -0
  39. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/caching.py +0 -0
  40. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/config.py +0 -0
  41. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/constants.py +0 -0
  42. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/core.py +0 -0
  43. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/define.py +0 -0
  44. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/for_py4web.py +0 -0
  45. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/for_web2py.py +0 -0
  46. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/helpers.py +0 -0
  47. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/mixins.py +0 -0
  48. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/py.typed +0 -0
  49. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/relationships.py +0 -0
  50. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/serializers/as_json.py +0 -0
  51. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/types.py +0 -0
  52. {typedal-4.6.3 → typedal-4.6.4}/src/typedal/web2py_py4web_shared.py +0 -0
  53. {typedal-4.6.3 → typedal-4.6.4}/tasks.py +0 -0
  54. {typedal-4.6.3 → typedal-4.6.4}/tests/__init__.py +0 -0
  55. {typedal-4.6.3 → typedal-4.6.4}/tests/configs/simple.toml +0 -0
  56. {typedal-4.6.3 → typedal-4.6.4}/tests/configs/valid.env +0 -0
  57. {typedal-4.6.3 → typedal-4.6.4}/tests/configs/valid.toml +0 -0
  58. {typedal-4.6.3 → typedal-4.6.4}/tests/py314_tests.py +0 -0
  59. {typedal-4.6.3 → typedal-4.6.4}/tests/test_cli.py +0 -0
  60. {typedal-4.6.3 → typedal-4.6.4}/tests/test_config.py +0 -0
  61. {typedal-4.6.3 → typedal-4.6.4}/tests/test_docs_examples.py +0 -0
  62. {typedal-4.6.3 → typedal-4.6.4}/tests/test_helpers.py +0 -0
  63. {typedal-4.6.3 → typedal-4.6.4}/tests/test_json.py +0 -0
  64. {typedal-4.6.3 → typedal-4.6.4}/tests/test_main.py +0 -0
  65. {typedal-4.6.3 → typedal-4.6.4}/tests/test_mixins.py +0 -0
  66. {typedal-4.6.3 → typedal-4.6.4}/tests/test_mypy.py +0 -0
  67. {typedal-4.6.3 → typedal-4.6.4}/tests/test_orm.py +0 -0
  68. {typedal-4.6.3 → typedal-4.6.4}/tests/test_py4web.py +0 -0
  69. {typedal-4.6.3 → typedal-4.6.4}/tests/test_relationships.py +0 -0
  70. {typedal-4.6.3 → typedal-4.6.4}/tests/test_stats.py +0 -0
  71. {typedal-4.6.3 → typedal-4.6.4}/tests/test_table.py +0 -0
  72. {typedal-4.6.3 → typedal-4.6.4}/tests/test_web2py.py +0 -0
  73. {typedal-4.6.3 → typedal-4.6.4}/tests/test_xx_others.py +0 -0
  74. {typedal-4.6.3 → typedal-4.6.4}/tests/timings.py +0 -0
@@ -2,6 +2,12 @@
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
+
5
11
  ## v4.6.3 (2026-03-19)
6
12
 
7
13
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TypeDAL
3
- Version: 4.6.3
3
+ Version: 4.6.4
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.6.3"
8
+ __version__ = "4.6.4"
@@ -390,7 +390,8 @@ def fake_migrations(
390
390
 
391
391
  previously_migrated = (
392
392
  db(
393
- db.ewh_implemented_features.name.belongs(to_fake) & (db.ewh_implemented_features.installed == True) # noqa E712
393
+ db.ewh_implemented_features.name.belongs(to_fake)
394
+ & (db.ewh_implemented_features.installed == True) # noqa E712
394
395
  )
395
396
  .select(db.ewh_implemented_features.name)
396
397
  .column("name")
@@ -373,7 +373,9 @@ def UploadField(**kw: t.Unpack[FieldSettings]) -> TypedField[str]:
373
373
  Upload = UploadField
374
374
 
375
375
 
376
- def ReferenceField[T_subclass: (TypedTable, Table)](
376
+ def ReferenceField[
377
+ T_subclass: (TypedTable, Table)
378
+ ](
377
379
  other_table: str | t.Type[TypedTable] | TypedTable | Table | T_subclass,
378
380
  **kw: t.Unpack[FieldSettings],
379
381
  ) -> TypedField[int]:
@@ -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(self, metadata: Metadata) -> "TypedRows[T_MetaInstance] | None":
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] = self.model(main)
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
- def _get_id(row: Row) -> int:
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
- return cls(rows, model, metadata=metadata)
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
  """
@@ -1153,7 +1164,9 @@ class TypedTable(_TypedTable, metaclass=TableMeta):
1153
1164
 
1154
1165
  relation_row = row[relation_name]
1155
1166
 
1156
- if isinstance(relation_row, list):
1167
+ if relation_row is None:
1168
+ row[relation_name] = None
1169
+ elif isinstance(relation_row, list):
1157
1170
  # list of rows
1158
1171
  combined = []
1159
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
 
@@ -307,3 +307,28 @@ def test_render():
307
307
  }
308
308
  ],
309
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
@@ -1 +0,0 @@
1
- *
Binary file
Binary file
typedal-4.6.3/.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