TypeDAL 4.4.4__tar.gz → 4.4.6__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.4.4 → typedal-4.4.6}/CHANGELOG.md +17 -0
- {typedal-4.4.4 → typedal-4.4.6}/PKG-INFO +44 -11
- {typedal-4.4.4 → typedal-4.4.6}/README.md +43 -10
- typedal-4.4.6/docs/10_advanced_apis.md +76 -0
- {typedal-4.4.4 → typedal-4.4.6}/docs/2_defining_tables.md +2 -2
- {typedal-4.4.4 → typedal-4.4.6}/docs/3_building_queries.md +8 -3
- {typedal-4.4.4 → typedal-4.4.6}/docs/4_relationships.md +24 -1
- {typedal-4.4.4 → typedal-4.4.6}/docs/7_configuration.md +1 -1
- {typedal-4.4.4 → typedal-4.4.6}/docs/9_memoization.md +5 -0
- {typedal-4.4.4 → typedal-4.4.6}/docs/index.md +1 -0
- {typedal-4.4.4 → typedal-4.4.6}/mkdocs.yml +2 -1
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/__about__.py +1 -1
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/caching.py +1 -1
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/config.py +1 -3
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/core.py +7 -7
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/define.py +3 -3
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/mixins.py +1 -1
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/query_builder.py +9 -4
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/relationships.py +28 -5
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_query_builder.py +8 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_relationships.py +4 -3
- typedal-4.4.4/.crush/.gitignore +0 -1
- typedal-4.4.4/.crush/crush.db-shm +0 -0
- typedal-4.4.4/.crush/crush.db-wal +0 -0
- typedal-4.4.4/.crush/init +0 -0
- typedal-4.4.4/.crush/logs/crush.log +0 -34
- {typedal-4.4.4 → typedal-4.4.6}/.github/workflows/su6.yml +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/.gitignore +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/.readthedocs.yml +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/coverage.svg +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/docs/1_getting_started.md +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/docs/5_py4web.md +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/docs/6_migrations.md +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/docs/8_mixins.md +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/docs/css/code_blocks.css +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/docs/requirements.txt +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/example_new.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/example_old.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/pyproject.toml +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/__init__.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/cli.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/constants.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/fields.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/for_py4web.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/for_web2py.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/helpers.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/py.typed +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/rows.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/serializers/as_json.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/tables.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/types.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/src/typedal/web2py_py4web_shared.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tasks.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/__init__.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/configs/simple.toml +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/configs/valid.env +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/configs/valid.toml +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/py314_tests.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_cli.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_config.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_docs_examples.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_helpers.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_json.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_main.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_mixins.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_mypy.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_orm.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_py4web.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_row.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_stats.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_table.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_web2py.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/test_xx_others.py +0 -0
- {typedal-4.4.4 → typedal-4.4.6}/tests/timings.py +0 -0
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
<!--next-version-placeholder-->
|
|
4
4
|
|
|
5
|
+
## v4.4.6 (2026-03-05)
|
|
6
|
+
|
|
7
|
+
### Fix
|
|
8
|
+
|
|
9
|
+
* Support .first() when using querybuilder on pydal-style tables. ([`d75f254`](https://github.com/trialandsuccess/TypeDAL/commit/d75f25440f0dcca51991bc047bf7ce8d479f942b))
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* Make docs up-to-date with recently introduced features, improve reading flow for new users ([`64bb7be`](https://github.com/trialandsuccess/TypeDAL/commit/64bb7be220c5f1a5998940b320565b5a3dbca988))
|
|
14
|
+
|
|
15
|
+
## v4.4.5 (2026-02-27)
|
|
16
|
+
|
|
17
|
+
### Fix
|
|
18
|
+
|
|
19
|
+
* Add `Ref` type so you can do forward references in types, ([`3240a74`](https://github.com/trialandsuccess/TypeDAL/commit/3240a747a4dd63a887464c0a748c68907d38d7d0))
|
|
20
|
+
* Pass known classes as namespace so `col: "ForwardRef"` works in 3.13 too ([`b655da8`](https://github.com/trialandsuccess/TypeDAL/commit/b655da812d7ea014b95adf73974ca301a6c42ca2))
|
|
21
|
+
|
|
5
22
|
## v4.4.4 (2026-02-25)
|
|
6
23
|
|
|
7
24
|
### Fix
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: TypeDAL
|
|
3
|
-
Version: 4.4.
|
|
3
|
+
Version: 4.4.6
|
|
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
|
|
@@ -81,13 +81,49 @@ the underlying `db.define_table` pydal Tables.
|
|
|
81
81
|
e.g. `rows: TypedRows[SomeTable] = db(...).select()`. When using the QueryBuilder, a `TypedRows` instance is returned
|
|
82
82
|
by `.collect()`.
|
|
83
83
|
|
|
84
|
-
Version 2.0 also introduces more ORM-like
|
|
84
|
+
Version 2.0 also introduces more ORM-like functionality.
|
|
85
85
|
Most notably, a Typed Query Builder that sees your table classes as models with relationships to each other.
|
|
86
86
|
See [3. Building Queries](https://typedal.readthedocs.io/en/stable/3_building_queries/) for more
|
|
87
87
|
details.
|
|
88
88
|
|
|
89
|
+
## Quickstart
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
uv pip install typedal
|
|
93
|
+
# alternative:
|
|
94
|
+
pip install typedal
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from typedal import TypeDAL, TypedTable
|
|
99
|
+
|
|
100
|
+
db = TypeDAL("sqlite:memory")
|
|
101
|
+
# Alternatives:
|
|
102
|
+
# db = TypeDAL("sqlite://storage.sqlite")
|
|
103
|
+
# db = TypeDAL("postgres://user:password@localhost:5432/mydb")
|
|
104
|
+
# db = TypeDAL("mysql://user:password@localhost:3306/mydb")
|
|
105
|
+
# ...
|
|
106
|
+
|
|
107
|
+
@db.define()
|
|
108
|
+
class User(TypedTable):
|
|
109
|
+
name: str
|
|
110
|
+
age: int | None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
User.insert(name="Alice", age=30)
|
|
114
|
+
adults = User.where(User.age >= 18).collect()
|
|
115
|
+
print(adults.column("name")) # ['Alice']
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
If you are new to TypeDAL, start with:
|
|
119
|
+
|
|
120
|
+
1. [Getting Started](https://typedal.readthedocs.io/en/stable/1_getting_started/)
|
|
121
|
+
2. [Defining Tables](https://typedal.readthedocs.io/en/stable/2_defining_tables/)
|
|
122
|
+
3. [Building Queries](https://typedal.readthedocs.io/en/stable/3_building_queries/)
|
|
123
|
+
4. [Relationships](https://typedal.readthedocs.io/en/stable/4_relationships/)
|
|
124
|
+
|
|
89
125
|
## CLI
|
|
90
|
-
The
|
|
126
|
+
The TypeDAL CLI provides a convenient interface for generating SQL migrations for [edwh-migrate](https://github.com/educationwarehouse/migrate#readme)
|
|
91
127
|
from PyDAL or TypeDAL configurations using [pydal2sql](https://github.com/robinvandernoord/pydal2sql).
|
|
92
128
|
It offers various commands to streamline database management tasks.
|
|
93
129
|
|
|
@@ -252,7 +288,7 @@ row = db.table_name(id=1) # -> Any (Row)
|
|
|
252
288
|
# all:
|
|
253
289
|
all_rows = TableName.collect() # or .all()
|
|
254
290
|
# some:
|
|
255
|
-
# order of select and where is
|
|
291
|
+
# order of select and where is interchangeable here
|
|
256
292
|
rows = TableName.select(Tablename.id).where(TableName.id > 5).where(TableName.id < 50).collect()
|
|
257
293
|
# one:
|
|
258
294
|
row = TableName(id=1) # or .where(...).first()
|
|
@@ -330,13 +366,10 @@ db.commit() # this is usually done automatically but sometimes you want to manua
|
|
|
330
366
|
|
|
331
367
|
## Caveats
|
|
332
368
|
|
|
333
|
-
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
annotations are converted to string representations. This makes it very hard to re-evaluate the annotation into the
|
|
337
|
-
original type, since the variable scope is lost (and thus references to variables or other classes are ambiguous or
|
|
338
|
-
simply impossible to find).
|
|
369
|
+
- Some editors (notably PyCharm) cannot always distinguish class-level and instance-level access on the same symbol.
|
|
370
|
+
For example, `Model.somefield` is a field descriptor (query operations like `.belongs()`), while
|
|
371
|
+
`model.somefield` is the runtime value (for example `list[str]`).
|
|
339
372
|
- `TypedField` limitations; Since pydal implements some magic methods to perform queries, some features of typing will
|
|
340
373
|
not work on a typed field: `typing.Optional` or a union (`Field() | None`) will result in errors. The only way to make
|
|
341
374
|
a typedfield optional right now, would be to set `required=False` as an argument yourself. This is also a reason
|
|
342
|
-
why `typing.get_type_hints` is not a solution
|
|
375
|
+
why `typing.get_type_hints` is not a complete solution.
|
|
@@ -24,13 +24,49 @@ the underlying `db.define_table` pydal Tables.
|
|
|
24
24
|
e.g. `rows: TypedRows[SomeTable] = db(...).select()`. When using the QueryBuilder, a `TypedRows` instance is returned
|
|
25
25
|
by `.collect()`.
|
|
26
26
|
|
|
27
|
-
Version 2.0 also introduces more ORM-like
|
|
27
|
+
Version 2.0 also introduces more ORM-like functionality.
|
|
28
28
|
Most notably, a Typed Query Builder that sees your table classes as models with relationships to each other.
|
|
29
29
|
See [3. Building Queries](https://typedal.readthedocs.io/en/stable/3_building_queries/) for more
|
|
30
30
|
details.
|
|
31
31
|
|
|
32
|
+
## Quickstart
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uv pip install typedal
|
|
36
|
+
# alternative:
|
|
37
|
+
pip install typedal
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from typedal import TypeDAL, TypedTable
|
|
42
|
+
|
|
43
|
+
db = TypeDAL("sqlite:memory")
|
|
44
|
+
# Alternatives:
|
|
45
|
+
# db = TypeDAL("sqlite://storage.sqlite")
|
|
46
|
+
# db = TypeDAL("postgres://user:password@localhost:5432/mydb")
|
|
47
|
+
# db = TypeDAL("mysql://user:password@localhost:3306/mydb")
|
|
48
|
+
# ...
|
|
49
|
+
|
|
50
|
+
@db.define()
|
|
51
|
+
class User(TypedTable):
|
|
52
|
+
name: str
|
|
53
|
+
age: int | None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
User.insert(name="Alice", age=30)
|
|
57
|
+
adults = User.where(User.age >= 18).collect()
|
|
58
|
+
print(adults.column("name")) # ['Alice']
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
If you are new to TypeDAL, start with:
|
|
62
|
+
|
|
63
|
+
1. [Getting Started](https://typedal.readthedocs.io/en/stable/1_getting_started/)
|
|
64
|
+
2. [Defining Tables](https://typedal.readthedocs.io/en/stable/2_defining_tables/)
|
|
65
|
+
3. [Building Queries](https://typedal.readthedocs.io/en/stable/3_building_queries/)
|
|
66
|
+
4. [Relationships](https://typedal.readthedocs.io/en/stable/4_relationships/)
|
|
67
|
+
|
|
32
68
|
## CLI
|
|
33
|
-
The
|
|
69
|
+
The TypeDAL CLI provides a convenient interface for generating SQL migrations for [edwh-migrate](https://github.com/educationwarehouse/migrate#readme)
|
|
34
70
|
from PyDAL or TypeDAL configurations using [pydal2sql](https://github.com/robinvandernoord/pydal2sql).
|
|
35
71
|
It offers various commands to streamline database management tasks.
|
|
36
72
|
|
|
@@ -195,7 +231,7 @@ row = db.table_name(id=1) # -> Any (Row)
|
|
|
195
231
|
# all:
|
|
196
232
|
all_rows = TableName.collect() # or .all()
|
|
197
233
|
# some:
|
|
198
|
-
# order of select and where is
|
|
234
|
+
# order of select and where is interchangeable here
|
|
199
235
|
rows = TableName.select(Tablename.id).where(TableName.id > 5).where(TableName.id < 50).collect()
|
|
200
236
|
# one:
|
|
201
237
|
row = TableName(id=1) # or .where(...).first()
|
|
@@ -273,13 +309,10 @@ db.commit() # this is usually done automatically but sometimes you want to manua
|
|
|
273
309
|
|
|
274
310
|
## Caveats
|
|
275
311
|
|
|
276
|
-
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
annotations are converted to string representations. This makes it very hard to re-evaluate the annotation into the
|
|
280
|
-
original type, since the variable scope is lost (and thus references to variables or other classes are ambiguous or
|
|
281
|
-
simply impossible to find).
|
|
312
|
+
- Some editors (notably PyCharm) cannot always distinguish class-level and instance-level access on the same symbol.
|
|
313
|
+
For example, `Model.somefield` is a field descriptor (query operations like `.belongs()`), while
|
|
314
|
+
`model.somefield` is the runtime value (for example `list[str]`).
|
|
282
315
|
- `TypedField` limitations; Since pydal implements some magic methods to perform queries, some features of typing will
|
|
283
316
|
not work on a typed field: `typing.Optional` or a union (`Field() | None`) will result in errors. The only way to make
|
|
284
317
|
a typedfield optional right now, would be to set `required=False` as an argument yourself. This is also a reason
|
|
285
|
-
why `typing.get_type_hints` is not a solution
|
|
318
|
+
why `typing.get_type_hints` is not a complete solution.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# 10. Advanced APIs
|
|
2
|
+
|
|
3
|
+
This chapter documents a few public APIs that are useful in specific cases, but are not part of the default onboarding flow.
|
|
4
|
+
|
|
5
|
+
## QueryBuilder on old-style pyDAL tables
|
|
6
|
+
|
|
7
|
+
If you are migrating incrementally, you can still use TypeDAL's query builder on existing pyDAL tables:
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from typedal import QueryBuilder
|
|
11
|
+
|
|
12
|
+
rows = QueryBuilder(db.some_table).where(id=2).collect()
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This gives you partial builder ergonomics while keeping your existing table definitions.
|
|
16
|
+
|
|
17
|
+
> **Important:** support is intentionally limited for old-style tables.
|
|
18
|
+
> Internally, `.collect()` is effectively a passthrough to `.execute()` and returns regular pyDAL `Rows`.
|
|
19
|
+
> `.first()`/`.first_or_fail()` return a regular pyDAL `Row` in this mode.
|
|
20
|
+
|
|
21
|
+
### Verified working methods
|
|
22
|
+
|
|
23
|
+
- Query composition: `.where(...)`, `.select(...)`, `.orderby(...)`, `.groupby(...)`, `.having(...)`
|
|
24
|
+
- Execution/introspection: `.execute()`, `.collect()`, `.to_sql()`
|
|
25
|
+
- Row access: `.first()`, `.first_or_fail()`
|
|
26
|
+
- Pagination helpers: `.paginate()`, `.chunk()`
|
|
27
|
+
- Basic set operations: `.count()`, `.exists()`, `.update(...)`, `.delete(...)`, `.collect_or_fail()`
|
|
28
|
+
- `.cache(...).collect()` runs (returns pyDAL `Rows`)
|
|
29
|
+
|
|
30
|
+
### Verified unsupported methods
|
|
31
|
+
|
|
32
|
+
- `.join(...)`: **not supported** for old-style tables (depends on TypeDAL model relationship internals)
|
|
33
|
+
|
|
34
|
+
### Behavioral caveat
|
|
35
|
+
|
|
36
|
+
Legacy mode does not perform typed model mapping. Expect pyDAL `Rows`/`Row` outputs rather than typed entities.
|
|
37
|
+
|
|
38
|
+
If you need full QueryBuilder behavior (typed entities, relationships, typed joins, cache integration),
|
|
39
|
+
migrate that table to `TypedTable`.
|
|
40
|
+
|
|
41
|
+
## Upsert and validation helpers
|
|
42
|
+
|
|
43
|
+
`TypedTable` exposes convenience methods for common upsert/validation flows:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
# update if found, otherwise insert
|
|
47
|
+
user = User.update_or_insert(User.email == "a@example.com", email="a@example.com", name="Alice")
|
|
48
|
+
|
|
49
|
+
# validate before insert
|
|
50
|
+
created, errors = User.validate_and_insert(email="a@example.com")
|
|
51
|
+
|
|
52
|
+
# validate before update
|
|
53
|
+
updated, errors = User.validate_and_update(User.id == 1, name="Alice Updated")
|
|
54
|
+
|
|
55
|
+
# validate before update-or-insert
|
|
56
|
+
row, errors = User.validate_and_update_or_insert(User.email == "a@example.com", name="Alice")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Behavior notes:
|
|
60
|
+
|
|
61
|
+
- `update_or_insert(...)` returns the resulting instance.
|
|
62
|
+
- `validate_and_*` methods return `(instance_or_none, errors_or_none)`.
|
|
63
|
+
|
|
64
|
+
## Reordering table fields
|
|
65
|
+
|
|
66
|
+
You can reorder fields on a defined table with `reorder_fields`:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
# Keep listed fields first, keep all other fields after them
|
|
70
|
+
MyTable.reorder_fields(MyTable.id, MyTable.name)
|
|
71
|
+
|
|
72
|
+
# Keep only the listed fields
|
|
73
|
+
MyTable.reorder_fields(MyTable.id, MyTable.name, keep_others=False)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This is useful when you want deterministic field order for SQL generation, inspection, or exports.
|
|
@@ -40,7 +40,7 @@ Any keyword arguments you would pass to `db.define_table`, you can also pass to
|
|
|
40
40
|
| `Field('name', 'time')` | `name: datetime.time` | `name: TypedField[datetime.time]` | `name = TypedField(datetime.time)` | `name = TimeField()` |
|
|
41
41
|
| `Field('name', 'datetime')` | `name: datetime.datetime` | `name: TypedField[datetime.datetime]` | `name = TypedField(datetime.datetime)` | `name = DatetimeField()` |
|
|
42
42
|
| `Field('name', 'password')` | × | × | `name = TypedField(str, type="password")` | `name = PasswordField()` |
|
|
43
|
-
| `Field('name', 'upload')` | × | × | `name = TypedField(str, type="upload)`
|
|
43
|
+
| `Field('name', 'upload')` | × | × | `name = TypedField(str, type="upload")` | `name = UploadField()` |
|
|
44
44
|
| `Field('name', 'reference <table>')` | `name: Table` | `name: TypedField[Table]` | `name = TypedField(Table)` | `name = ReferenceField('table')` |
|
|
45
45
|
| `Field('name', 'list:string')` | `name: list[str]` | `name: TypedField[list[str]]` | `name = TypedField(list[str])` | `name = ListStringField()` |
|
|
46
46
|
| `Field('name', 'list:integer')` | `name: list[int]` | `name: TypedField[list[int]]` | `name = TypedField()` | `name = ListIntegerField()` |
|
|
@@ -104,4 +104,4 @@ row.delete_record() # to trigger
|
|
|
104
104
|
MyTable.where(...).delete() # to trigger
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
Now that we have some tables, it's time to actually query them! Let's go to [3. Building Queries](./3_building_queries.md) to learn how.
|
|
@@ -236,8 +236,8 @@ The Query Builder has a few operations that don't return a new builder instance:
|
|
|
236
236
|
- first: get the first entity matching your query, possibly with relationships loaded (if .join was used)
|
|
237
237
|
- first_or_fail: where `first` may return an empty result, this variant will raise an error if there are no results.
|
|
238
238
|
- to_sql: get the SQL string that would run, useful for debugging, subqueries and other advanced SQL operations.
|
|
239
|
-
- update: instead of selecting rows, update those matching the current query (see [
|
|
240
|
-
- delete: instead of selecting rows, delete those matching the current query (see [
|
|
239
|
+
- update: instead of selecting rows, update those matching the current query (see [Update](#update))
|
|
240
|
+
- delete: instead of selecting rows, delete those matching the current query (see [Delete](#delete))
|
|
241
241
|
|
|
242
242
|
Additionally, you can directly call `.all()`, `.collect()`, `.count()`, `.first()` on a model (e.g. `User.all()`).
|
|
243
243
|
|
|
@@ -264,7 +264,7 @@ person.update_record(name="New Name")
|
|
|
264
264
|
db(db.person.name == "Old Name").delete()
|
|
265
265
|
|
|
266
266
|
row = db.person(4)
|
|
267
|
-
row.
|
|
267
|
+
row.delete_record()
|
|
268
268
|
|
|
269
269
|
# typedal:
|
|
270
270
|
Person.where(id="Old Name").delete() # via query builder
|
|
@@ -272,3 +272,8 @@ Person.where(id="Old Name").delete() # via query builder
|
|
|
272
272
|
person = Person(4)
|
|
273
273
|
person.delete_record()
|
|
274
274
|
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
Need less-common query patterns (for example, using `QueryBuilder` on old-style pyDAL tables)?
|
|
279
|
+
See [10. Advanced APIs](./10_advanced_apis.md).
|
|
@@ -98,6 +98,30 @@ In this example, `Relationship["Sidekick"]` is added as an extra type hint, sinc
|
|
|
98
98
|
after the superhero class.
|
|
99
99
|
Adding the `Relationship["Sidekick"]` hint is optional, but recommended to improve editor support.
|
|
100
100
|
|
|
101
|
+
### Forward References with `Ref[...]` (typing helper)
|
|
102
|
+
|
|
103
|
+
For relationship targets, you can use `Ref[...]` as a typed replacement for the string form:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from typedal.relationships import Ref
|
|
107
|
+
|
|
108
|
+
bestie = relationship(Ref["BestFriend"], lambda user, bestie: user.id == bestie.friend)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
This is equivalent at runtime to:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
bestie = relationship("BestFriend", lambda user, bestie: user.id == bestie.friend)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`Ref[...]` is primarily for type-checking/editor support. It does not change runtime behavior.
|
|
118
|
+
|
|
119
|
+
For normal table fields, `Ref[...]` is not needed. Use standard forward-reference annotations, for example:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
owner: "User"
|
|
123
|
+
```
|
|
124
|
+
|
|
101
125
|
## Many-to-Many
|
|
102
126
|
|
|
103
127
|
Setting up a relationship that uses a junction/pivot table is slightly harder.
|
|
@@ -199,4 +223,3 @@ Depending on your setup:
|
|
|
199
223
|
- **Using py4web or web2py?** → [5. py4web & web2py](./5_py4web.md)
|
|
200
224
|
- **Ready to manage your database?** → [6. Migrations](./6_migrations.md)
|
|
201
225
|
- **Dive deeper into functionality?** → [8.: Mixins](./8_mixins.md)
|
|
202
|
-
|
|
@@ -55,7 +55,7 @@ noop = false
|
|
|
55
55
|
### Generating Migrations (pydal2sql)
|
|
56
56
|
|
|
57
57
|
- **`input`**: Path to your TypeDAL table definitions file
|
|
58
|
-
- **`output`**: Path to the generated migration `.py`
|
|
58
|
+
- **`output`**: Path to the generated migration `.py` file
|
|
59
59
|
- **`dialect`**: Database type: `sqlite`, `postgres`, `mysql`, etc. (if unclear from database uri)
|
|
60
60
|
- **`magic`**: Insert missing variables to prevent crashes (default: `true`).
|
|
61
61
|
See [pydal2sql docs](https://github.com/robinvandernoord/pydal2sql#configuration).
|
|
@@ -148,3 +148,8 @@ class SpecialTable(TypedTable):
|
|
|
148
148
|
```
|
|
149
149
|
|
|
150
150
|
**Warning:** Disabling this may break caching functionality for queries involving this table.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
Want to explore less common but useful APIs (like old-style pyDAL `QueryBuilder`, validation/upsert helpers, and field reordering)?
|
|
155
|
+
Continue with [10. Advanced APIs](./10_advanced_apis.md).
|
|
@@ -11,6 +11,7 @@ nav:
|
|
|
11
11
|
- 7. Configuration: 7_configuration.md
|
|
12
12
|
- 8. Mixins: 8_mixins.md
|
|
13
13
|
- 9. Function Memoization: 9_memoization.md
|
|
14
|
+
- 10. Advanced APIs: 10_advanced_apis.md
|
|
14
15
|
extra:
|
|
15
16
|
version:
|
|
16
17
|
default: stable
|
|
@@ -40,4 +41,4 @@ theme:
|
|
|
40
41
|
|
|
41
42
|
extra_css:
|
|
42
43
|
- https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/tokyo-night-dark.min.css
|
|
43
|
-
- css/code_blocks.css
|
|
44
|
+
- css/code_blocks.css
|
|
@@ -577,7 +577,7 @@ def memoize(
|
|
|
577
577
|
return cached, "cached"
|
|
578
578
|
# Cache miss - compute result
|
|
579
579
|
|
|
580
|
-
def track_execute(_qb: "QueryBuilder[t.Any]", raw: Rows):
|
|
580
|
+
def track_execute(_qb: "QueryBuilder[t.Any]", raw: Rows) -> None:
|
|
581
581
|
# find dependant table+id combinations, includes relationships:
|
|
582
582
|
deps.update(_determine_dependencies_auto(raw))
|
|
583
583
|
|
|
@@ -9,13 +9,11 @@ from pathlib import Path
|
|
|
9
9
|
|
|
10
10
|
import tomli
|
|
11
11
|
from configuraptor import TypedConfig, alias
|
|
12
|
-
from configuraptor.helpers import find_pyproject_toml
|
|
12
|
+
from configuraptor.helpers import expand_env_vars_into_toml_values, find_pyproject_toml
|
|
13
13
|
from dotenv import dotenv_values, find_dotenv
|
|
14
14
|
|
|
15
15
|
from .types import AnyDict
|
|
16
16
|
|
|
17
|
-
from configuraptor.helpers import expand_env_vars_into_toml_values
|
|
18
|
-
|
|
19
17
|
if t.TYPE_CHECKING:
|
|
20
18
|
from edwh_migrate import Config as MigrateConfig
|
|
21
19
|
from pydal2sql.typer_support import Config as P2SConfig
|
|
@@ -106,27 +106,27 @@ def evaluate_forward_reference(
|
|
|
106
106
|
return evaluate_forward_reference_314(fw_ref, namespace=namespace or {})
|
|
107
107
|
|
|
108
108
|
|
|
109
|
-
def resolve_annotation_313(ftype: str) -> type: # pragma: no cover
|
|
109
|
+
def resolve_annotation_313(ftype: str, namespace: dict[str, type] | None = None) -> type: # pragma: no cover
|
|
110
110
|
"""
|
|
111
111
|
Resolve an annotation that's in string representation.
|
|
112
112
|
|
|
113
113
|
Variant for Python 3.13
|
|
114
114
|
"""
|
|
115
115
|
fw_ref: ForwardRef = t.get_args(t.Type[ftype])[0]
|
|
116
|
-
return evaluate_forward_reference(fw_ref)
|
|
116
|
+
return evaluate_forward_reference(fw_ref, namespace=namespace)
|
|
117
117
|
|
|
118
118
|
|
|
119
|
-
def resolve_annotation_314(ftype: str) -> type: # pragma: no cover
|
|
119
|
+
def resolve_annotation_314(ftype: str, namespace: dict[str, type] | None = None) -> type: # pragma: no cover
|
|
120
120
|
"""
|
|
121
121
|
Resolve an annotation that's in string representation.
|
|
122
122
|
|
|
123
123
|
Variant for Python 3.14 + using annotationlib
|
|
124
124
|
"""
|
|
125
125
|
fw_ref = ForwardRef(ftype)
|
|
126
|
-
return evaluate_forward_reference(fw_ref)
|
|
126
|
+
return evaluate_forward_reference(fw_ref, namespace=namespace)
|
|
127
127
|
|
|
128
128
|
|
|
129
|
-
def resolve_annotation(ftype: str) -> type: # pragma: no cover
|
|
129
|
+
def resolve_annotation(ftype: str, namespace: dict[str, type] | None = None) -> type: # pragma: no cover
|
|
130
130
|
"""
|
|
131
131
|
Resolve an annotation that's in string representation.
|
|
132
132
|
|
|
@@ -135,9 +135,9 @@ def resolve_annotation(ftype: str) -> type: # pragma: no cover
|
|
|
135
135
|
if sys.version_info.major != 3:
|
|
136
136
|
raise EnvironmentError("Only python 3 is supported.")
|
|
137
137
|
elif sys.version_info.minor <= 13:
|
|
138
|
-
return resolve_annotation_313(ftype)
|
|
138
|
+
return resolve_annotation_313(ftype, namespace=namespace)
|
|
139
139
|
else:
|
|
140
|
-
return resolve_annotation_314(ftype)
|
|
140
|
+
return resolve_annotation_314(ftype, namespace=namespace)
|
|
141
141
|
|
|
142
142
|
|
|
143
143
|
class TypeDAL(pydal.DAL):
|
|
@@ -133,13 +133,13 @@ class TableDefinitionBuilder:
|
|
|
133
133
|
"""Convert Python type annotation to pydal field type string."""
|
|
134
134
|
ftype = t.cast(type, ftype_annotation) # cast from Type to type to make mypy happy)
|
|
135
135
|
|
|
136
|
+
known_classes = {table.__name__: table for table in self.class_map.values()}
|
|
137
|
+
|
|
136
138
|
if isinstance(ftype, str):
|
|
137
139
|
# extract type from string
|
|
138
|
-
ftype = resolve_annotation(ftype)
|
|
140
|
+
ftype = resolve_annotation(ftype, namespace=known_classes)
|
|
139
141
|
|
|
140
142
|
if isinstance(ftype, ForwardRef):
|
|
141
|
-
known_classes = {table.__name__: table for table in self.class_map.values()}
|
|
142
|
-
|
|
143
143
|
ftype = evaluate_forward_reference(ftype, namespace=known_classes)
|
|
144
144
|
|
|
145
145
|
if mapping := BASIC_MAPPINGS.get(ftype):
|
|
@@ -116,7 +116,7 @@ class HAS_UNIQUE_SLUG(IS_NOT_IN_DB):
|
|
|
116
116
|
if not value.strip():
|
|
117
117
|
raise ValidationError(self.translator(self.error_message))
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
tablename, fieldname = str(self.field).split(".")
|
|
120
120
|
table = self.dbset.db[tablename]
|
|
121
121
|
field = table[fieldname]
|
|
122
122
|
query = field == value
|
|
@@ -555,7 +555,7 @@ class QueryBuilder(t.Generic[T_MetaInstance]):
|
|
|
555
555
|
for fn_before in db._before_execute:
|
|
556
556
|
fn_before(self)
|
|
557
557
|
|
|
558
|
-
rows = db(query).select(*select_args, **select_kwargs)
|
|
558
|
+
rows: Rows = db(query).select(*select_args, **select_kwargs)
|
|
559
559
|
|
|
560
560
|
for fn_after in db._after_execute:
|
|
561
561
|
fn_after(self, rows)
|
|
@@ -1133,11 +1133,16 @@ class QueryBuilder(t.Generic[T_MetaInstance]):
|
|
|
1133
1133
|
|
|
1134
1134
|
Also adds paginate, since it would be a waste to select more rows than needed.
|
|
1135
1135
|
"""
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
else:
|
|
1136
|
+
row = self.paginate(page=1, limit=1, verbose=verbose).first()
|
|
1137
|
+
if not row:
|
|
1139
1138
|
return None
|
|
1140
1139
|
|
|
1140
|
+
if not isinstance(self.model, TableMeta):
|
|
1141
|
+
# old-style pydal table: keep pydal semantics and return raw Row
|
|
1142
|
+
return row
|
|
1143
|
+
|
|
1144
|
+
return self.model.from_row(row)
|
|
1145
|
+
|
|
1141
1146
|
def _first(self) -> str:
|
|
1142
1147
|
return self._paginate(page=1, limit=1)
|
|
1143
1148
|
|
|
@@ -5,17 +5,18 @@ Contains base functionality related to Relationships.
|
|
|
5
5
|
import inspect
|
|
6
6
|
import typing as t
|
|
7
7
|
import warnings
|
|
8
|
+
from typing import ForwardRef
|
|
8
9
|
|
|
9
10
|
import pydal.objects
|
|
10
11
|
|
|
11
12
|
from .config import LazyPolicy
|
|
12
13
|
from .constants import JOIN_OPTIONS
|
|
13
|
-
from .core import TypeDAL
|
|
14
|
+
from .core import TypeDAL, evaluate_forward_reference
|
|
14
15
|
from .fields import TypedField
|
|
15
16
|
from .helpers import extract_type_optional, looks_like, unwrap_type
|
|
16
17
|
from .types import Condition, OnQuery, T_Field
|
|
17
18
|
|
|
18
|
-
To_Type = t.TypeVar("To_Type")
|
|
19
|
+
To_Type = t.TypeVar("To_Type", bound="TypedTable")
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
# default lazy policy is defined at the TypeDAL() instance settings level
|
|
@@ -63,6 +64,10 @@ class Relationship(t.Generic[To_Type]):
|
|
|
63
64
|
self.condition_and = condition_and
|
|
64
65
|
self._lazy = lazy
|
|
65
66
|
|
|
67
|
+
if t.get_origin(_type) == Ref:
|
|
68
|
+
# unwrap Ref["City"] to ForwardRef("City") to be evaluated/stringified later on:
|
|
69
|
+
_type = t.get_args(_type)[0]
|
|
70
|
+
|
|
66
71
|
if args := t.get_args(_type):
|
|
67
72
|
self.table = unwrap_type(args[0])
|
|
68
73
|
self.multiple = True
|
|
@@ -70,6 +75,12 @@ class Relationship(t.Generic[To_Type]):
|
|
|
70
75
|
self.table = t.cast(type[TypedTable], _type)
|
|
71
76
|
self.multiple = False
|
|
72
77
|
|
|
78
|
+
if isinstance(self.table, ForwardRef):
|
|
79
|
+
try:
|
|
80
|
+
self.table = evaluate_forward_reference(self.table)
|
|
81
|
+
except Exception:
|
|
82
|
+
self.table = self.table.__forward_arg__
|
|
83
|
+
|
|
73
84
|
if isinstance(self.table, str):
|
|
74
85
|
self.table = TypeDAL.to_snake(self.table)
|
|
75
86
|
|
|
@@ -264,6 +275,18 @@ class Relationship(t.Generic[To_Type]):
|
|
|
264
275
|
return fallback_value
|
|
265
276
|
|
|
266
277
|
|
|
278
|
+
class Ref(t.Generic[To_Type]):
|
|
279
|
+
"""
|
|
280
|
+
Type-level forward reference wrapper.
|
|
281
|
+
|
|
282
|
+
Allows writing:
|
|
283
|
+
|
|
284
|
+
relationship(Ref["User"])
|
|
285
|
+
|
|
286
|
+
so that type checkers resolve the inner type correctly.
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
|
|
267
290
|
@t.overload
|
|
268
291
|
def relationship(
|
|
269
292
|
_type: type[list[To_Type]],
|
|
@@ -286,7 +309,7 @@ def relationship(
|
|
|
286
309
|
|
|
287
310
|
@t.overload
|
|
288
311
|
def relationship(
|
|
289
|
-
_type: t.Type[To_Type] | str,
|
|
312
|
+
_type: t.Type[To_Type] | str | t.Type[Ref[To_Type]],
|
|
290
313
|
condition: Condition = None,
|
|
291
314
|
*,
|
|
292
315
|
join: t.Literal["inner"],
|
|
@@ -308,7 +331,7 @@ def relationship(
|
|
|
308
331
|
|
|
309
332
|
@t.overload
|
|
310
333
|
def relationship(
|
|
311
|
-
_type: t.Type[To_Type] | str,
|
|
334
|
+
_type: t.Type[To_Type] | str | t.Type[Ref[To_Type]],
|
|
312
335
|
condition: Condition = None,
|
|
313
336
|
join: JOIN_OPTIONS = None,
|
|
314
337
|
on: OnQuery = None,
|
|
@@ -327,7 +350,7 @@ def relationship(
|
|
|
327
350
|
|
|
328
351
|
|
|
329
352
|
def relationship(
|
|
330
|
-
_type: type[list[To_Type]] | t.Type[To_Type] | str,
|
|
353
|
+
_type: type[list[To_Type]] | t.Type[To_Type] | str | t.Type[Ref[To_Type]],
|
|
331
354
|
condition: Condition = None,
|
|
332
355
|
join: JOIN_OPTIONS = None,
|
|
333
356
|
on: OnQuery = None,
|
|
@@ -526,6 +526,14 @@ def test_minimal_functionality_on_pydal_style_tables():
|
|
|
526
526
|
assert qb2
|
|
527
527
|
assert len(qb2) == 1
|
|
528
528
|
|
|
529
|
+
first = QueryBuilder(db.test_query_table).where(number=2).first()
|
|
530
|
+
assert first
|
|
531
|
+
assert first.id == qb1.first().id
|
|
532
|
+
|
|
533
|
+
first_or_fail = QueryBuilder(db.test_query_table).where(number=2).first_or_fail()
|
|
534
|
+
assert first_or_fail
|
|
535
|
+
assert first_or_fail.id == qb1.first().id
|
|
536
|
+
|
|
529
537
|
|
|
530
538
|
def test_before_after_collect(capsys):
|
|
531
539
|
_setup_data()
|
|
@@ -8,7 +8,7 @@ from uuid import uuid4
|
|
|
8
8
|
|
|
9
9
|
import pytest
|
|
10
10
|
|
|
11
|
-
from src.typedal import Relationship, TypeDAL, TypedField, TypedTable, relationship
|
|
11
|
+
from src.typedal import Relationship, TypeDAL, TypedField, TypedRows, TypedTable, relationship
|
|
12
12
|
from src.typedal.caching import (
|
|
13
13
|
_TypedalCache,
|
|
14
14
|
_TypedalCacheDependency,
|
|
@@ -16,8 +16,8 @@ from src.typedal.caching import (
|
|
|
16
16
|
clear_expired,
|
|
17
17
|
remove_cache,
|
|
18
18
|
)
|
|
19
|
+
from src.typedal.relationships import Ref
|
|
19
20
|
from src.typedal.serializers import as_json
|
|
20
|
-
from typedal import TypedRows
|
|
21
21
|
|
|
22
22
|
db = TypeDAL("sqlite:memory", lazy_policy="warn")
|
|
23
23
|
|
|
@@ -970,7 +970,8 @@ class Office(TypedTable):
|
|
|
970
970
|
city_id: City
|
|
971
971
|
company: "Company"
|
|
972
972
|
|
|
973
|
-
|
|
973
|
+
city = relationship(City, lambda office, city: office.city_id == city.id)
|
|
974
|
+
city_alternative = relationship(Ref["City"], lambda office, city: office.city_id == city.id)
|
|
974
975
|
|
|
975
976
|
|
|
976
977
|
class Company(TypedTable):
|
typedal-4.4.4/.crush/.gitignore
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
*
|
|
Binary file
|
|
Binary file
|
typedal-4.4.4/.crush/init
DELETED
|
File without changes
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
{"time":"2026-01-26T17:01:48.91963488+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-01-26T17:01:49.11634171+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-01-26T17:01:49.116589785+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/config.(*Config).configureProviders","file":"github.com/charmbracelet/crush/internal/config/load.go","line":259},"msg":"Skipping provider due to missing API key","provider":"anthropic"}
|
|
4
|
-
{"time":"2026-01-26T17:01:49.128053484+01:00","level":"INFO","msg":"OK 20250424200609_initial.sql (1.26ms)"}
|
|
5
|
-
{"time":"2026-01-26T17:01:49.128338994+01:00","level":"INFO","msg":"OK 20250515105448_add_summary_message_id.sql (263.8µs)"}
|
|
6
|
-
{"time":"2026-01-26T17:01:49.128678839+01:00","level":"INFO","msg":"OK 20250624000000_add_created_at_indexes.sql (325.2µs)"}
|
|
7
|
-
{"time":"2026-01-26T17:01:49.128984204+01:00","level":"INFO","msg":"OK 20250627000000_add_provider_to_messages.sql (291.73µs)"}
|
|
8
|
-
{"time":"2026-01-26T17:01:49.12929983+01:00","level":"INFO","msg":"OK 20250810000000_add_is_summary_message.sql (265.62µs)"}
|
|
9
|
-
{"time":"2026-01-26T17:01:49.129580779+01:00","level":"INFO","msg":"OK 20250812000000_add_todos_to_sessions.sql (268.09µs)"}
|
|
10
|
-
{"time":"2026-01-26T17:01:49.129585964+01:00","level":"INFO","msg":"goose: successfully migrated database to version: 20250812000000"}
|
|
11
|
-
{"time":"2026-01-26T17:01:49.129633405+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).initLSPClients","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":21},"msg":"LSP clients initialization started in background"}
|
|
12
|
-
{"time":"2026-01-26T17:01:49.129745943+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":110},"msg":"Initializing MCP clients"}
|
|
13
|
-
{"time":"2026-01-26T17:01:49.73470854+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).createAndStartLSPClient","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":76},"msg":"LSP client initialized","name":"python"}
|
|
14
|
-
{"time":"2026-01-26T17:02:52.962674187+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
|
|
15
|
-
{"time":"2026-01-26T17:02:57.3247847+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools.init.func1","file":"github.com/charmbracelet/crush/internal/agent/tools/rg.go","line":18},"msg":"Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower."}
|
|
16
|
-
{"time":"2026-01-26T17:11:33.432259554+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).Shutdown.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":507},"msg":"Shutdown took 6.286852ms"}
|
|
17
|
-
{"time":"2026-01-26T17:11:33.982816329+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"}
|
|
18
|
-
{"time":"2026-01-26T17:11:34.178260248+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"}
|
|
19
|
-
{"time":"2026-01-26T17:11:34.178494988+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/config.(*Config).configureProviders","file":"github.com/charmbracelet/crush/internal/config/load.go","line":259},"msg":"Skipping provider due to missing API key","provider":"anthropic"}
|
|
20
|
-
{"time":"2026-01-26T17:11:34.18216688+01:00","level":"INFO","msg":"goose: no migrations to run. current version: 20250812000000"}
|
|
21
|
-
{"time":"2026-01-26T17:11:34.182194111+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).initLSPClients","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":21},"msg":"LSP clients initialization started in background"}
|
|
22
|
-
{"time":"2026-01-26T17:11:34.182335616+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":110},"msg":"Initializing MCP clients"}
|
|
23
|
-
{"time":"2026-01-26T17:11:34.786142525+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).createAndStartLSPClient","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":76},"msg":"LSP client initialized","name":"python"}
|
|
24
|
-
{"time":"2026-01-26T17:11:43.042930337+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
|
|
25
|
-
{"time":"2026-01-26T17:11:44.911909276+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools.init.func1","file":"github.com/charmbracelet/crush/internal/agent/tools/rg.go","line":18},"msg":"Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower."}
|
|
26
|
-
{"time":"2026-01-26T18:10:19.727802782+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"}
|
|
27
|
-
{"time":"2026-01-26T18:10:19.926137244+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"}
|
|
28
|
-
{"time":"2026-01-26T18:10:19.926245207+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/config.(*Config).configureProviders","file":"github.com/charmbracelet/crush/internal/config/load.go","line":259},"msg":"Skipping provider due to missing API key","provider":"anthropic"}
|
|
29
|
-
{"time":"2026-01-26T18:10:19.927808467+01:00","level":"INFO","msg":"goose: no migrations to run. current version: 20250812000000"}
|
|
30
|
-
{"time":"2026-01-26T18:10:19.927858435+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).initLSPClients","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":21},"msg":"LSP clients initialization started in background"}
|
|
31
|
-
{"time":"2026-01-26T18:10:19.928006013+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":110},"msg":"Initializing MCP clients"}
|
|
32
|
-
{"time":"2026-01-26T18:10:20.546676356+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).createAndStartLSPClient","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":76},"msg":"LSP client initialized","name":"python"}
|
|
33
|
-
{"time":"2026-01-26T18:11:07.640056766+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
|
|
34
|
-
{"time":"2026-01-26T18:11:11.201161834+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools.init.func1","file":"github.com/charmbracelet/crush/internal/agent/tools/rg.go","line":18},"msg":"Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower."}
|
|
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
|