TypeDAL 4.9.0__tar.gz → 4.9.2__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.9.0 → typedal-4.9.2}/CHANGELOG.md +17 -0
- {typedal-4.9.0 → typedal-4.9.2}/PKG-INFO +2 -1
- {typedal-4.9.0 → typedal-4.9.2}/docs/2_defining_tables.md +21 -0
- {typedal-4.9.0 → typedal-4.9.2}/docs/3_building_queries.md +22 -0
- {typedal-4.9.0 → typedal-4.9.2}/pyproject.toml +1 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/__about__.py +1 -1
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/fields.py +14 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/query_builder.py +44 -6
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/tables.py +3 -1
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/types.py +12 -3
- typedal-4.9.2/tests/conftest.py +30 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_config.py +0 -17
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_helpers.py +1 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_query_builder.py +44 -1
- typedal-4.9.0/.crush/.gitignore +0 -1
- typedal-4.9.0/.crush/crush.db-shm +0 -0
- typedal-4.9.0/.crush/crush.db-wal +0 -0
- typedal-4.9.0/.crush/init +0 -0
- typedal-4.9.0/.crush/logs/crush.log +0 -37
- {typedal-4.9.0 → typedal-4.9.2}/.github/workflows/su6.yml +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/.gitignore +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/.readthedocs.yml +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/README.md +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/coverage.svg +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/docs/10_advanced_apis.md +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/docs/1_getting_started.md +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/docs/4_relationships.md +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/docs/5_py4web.md +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/docs/6_migrations.md +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/docs/7_configuration.md +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/docs/8_mixins.md +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/docs/9_memoization.md +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/docs/css/code_blocks.css +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/docs/index.md +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/docs/requirements.txt +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/example_new.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/example_old.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/mkdocs.yml +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/__init__.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/caching.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/cli.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/config.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/constants.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/core.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/define.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/enum_helpers.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/for_py4web.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/for_web2py.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/helpers.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/mixins.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/py.typed +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/relationships.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/rows.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/serializers/as_json.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/serializers/typescript.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/src/typedal/web2py_py4web_shared.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tasks.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/__init__.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/configs/simple.toml +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/configs/valid.env +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/configs/valid.toml +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/py314_tests.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_cli.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_docs_examples.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_json.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_main.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_mixins.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_mypy.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_orm.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_py4web.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_relationships.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_row.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_stats.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_table.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_typescript.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_typing_mypy.md +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_typing_pyright.md +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_web2py.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/test_xx_others.py +0 -0
- {typedal-4.9.0 → typedal-4.9.2}/tests/timings.py +0 -0
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
<!--next-version-placeholder-->
|
|
4
4
|
|
|
5
|
+
## v4.9.2 (2026-07-02)
|
|
6
|
+
|
|
7
|
+
### Fix
|
|
8
|
+
|
|
9
|
+
* **query-builder:** Normalize field rnames in select options ([`5fe46d9`](https://github.com/trialandsuccess/TypeDAL/commit/5fe46d9b4acd3081ff6e87cf7f9a45b8f4aa876b))
|
|
10
|
+
|
|
11
|
+
## v4.9.1 (2026-07-02)
|
|
12
|
+
|
|
13
|
+
### Fix
|
|
14
|
+
|
|
15
|
+
* **typing:** `select(distinct=` can also be a str ([`dc05389`](https://github.com/trialandsuccess/TypeDAL/commit/dc05389e89102cfb5de4a3b174857b8a7866a005))
|
|
16
|
+
* Improved typing for Permissions ([`236e4a8`](https://github.com/trialandsuccess/TypeDAL/commit/236e4a8ebfc42ed438dc8f626c192c6baca52f02))
|
|
17
|
+
|
|
18
|
+
### Documentation
|
|
19
|
+
|
|
20
|
+
* Added info about `permissions` in docs ([`a5a9a85`](https://github.com/trialandsuccess/TypeDAL/commit/a5a9a85bb737acf5d5521e8923df56b3608b1ef5))
|
|
21
|
+
|
|
5
22
|
## v4.9.0 (2026-06-12)
|
|
6
23
|
|
|
7
24
|
### Feature
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: TypeDAL
|
|
3
|
-
Version: 4.9.
|
|
3
|
+
Version: 4.9.2
|
|
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
|
|
@@ -43,6 +43,7 @@ Requires-Dist: ewok; extra == 'dev'
|
|
|
43
43
|
Requires-Dist: hatch; extra == 'dev'
|
|
44
44
|
Requires-Dist: mkdocs; extra == 'dev'
|
|
45
45
|
Requires-Dist: mkdocs-dracula-theme; extra == 'dev'
|
|
46
|
+
Requires-Dist: psycopg2-binary; extra == 'dev'
|
|
46
47
|
Requires-Dist: pydantic<3; extra == 'dev'
|
|
47
48
|
Requires-Dist: pyright<1.1.400; extra == 'dev'
|
|
48
49
|
Requires-Dist: pytest-typing; extra == 'dev'
|
|
@@ -89,6 +89,27 @@ Important constraints and behavior:
|
|
|
89
89
|
| `Field('name', 'string', required=True)` | `name: str` | `name: TypedField[str]` | `name = TypedField(str, required=True)` | `name = StringField(required=True)` |
|
|
90
90
|
| `Field('name', 'text', required=False)` | `name: typing.Optional[str]` or <br/> <code>name: str | None</code> | `name: TypedField[typing.Optional[str]]` or <br/> <code>name: TypedField[str | None]</code> | `name = TypedField(str, type="text", required=False)` | `name = StringField(required=False)` |
|
|
91
91
|
|
|
92
|
+
### Table permissions
|
|
93
|
+
|
|
94
|
+
You can restrict table-level operations when defining a model:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
@db.define(permissions={"read": True, "insert": False, "update": True, "delete": False})
|
|
98
|
+
class Article(TypedTable):
|
|
99
|
+
title: str
|
|
100
|
+
body: str
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Permissions are currently available for:
|
|
104
|
+
|
|
105
|
+
- `read`
|
|
106
|
+
- `insert`
|
|
107
|
+
- `update`
|
|
108
|
+
- `delete`
|
|
109
|
+
|
|
110
|
+
Any permission you do not specify is treated as allowed by default.
|
|
111
|
+
Use this when a table should exist in the schema but should not be writable, or when you want to prevent reads from a given model entirely.
|
|
112
|
+
|
|
92
113
|
# Hooks
|
|
93
114
|
|
|
94
115
|
Some logic can be added when data is added/edited/deleted from the database.
|
|
@@ -219,6 +219,28 @@ Be aware doing this might break some caching functionality!
|
|
|
219
219
|
|
|
220
220
|
**Note:** For caching function results (instead of just query results), see [9. Function Memoization](./9_memoization.md).
|
|
221
221
|
|
|
222
|
+
### permissions
|
|
223
|
+
|
|
224
|
+
TypeDAL lets you override permissions on a query builder without changing the underlying table definition.
|
|
225
|
+
This is useful when a query should be more restrictive or more permissive than the model defaults.
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
# deny reads for this query
|
|
229
|
+
restricted = Article.permissions(read=False).where(id=1)
|
|
230
|
+
|
|
231
|
+
# allow a write operation only for this chain
|
|
232
|
+
Article.where(id=1).permissions(update=True, delete=False).update(title="New title")
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The same method is available on `TypedTable` as a convenience wrapper:
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
Article.permissions(read=False).where(id=1).collect()
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Permissions merge across the table definition and the query chain, with the most restrictive value winning.
|
|
242
|
+
Supported flags are `read`, `insert`, `update`, and `delete`.
|
|
243
|
+
|
|
222
244
|
### Collecting
|
|
223
245
|
|
|
224
246
|
The Query Builder has a few operations that don't return a new builder instance:
|
|
@@ -268,6 +268,20 @@ class TypedField[T_Value](Expression): # pragma: no cover
|
|
|
268
268
|
return t.cast(Expression, self._field.lower())
|
|
269
269
|
|
|
270
270
|
|
|
271
|
+
def rname(field: TypedField[t.Any] | Field) -> str:
|
|
272
|
+
"""
|
|
273
|
+
Return the full rname (table and field).
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
table = field._table
|
|
277
|
+
inner_field = field._field if isinstance(field, TypedField) else field
|
|
278
|
+
|
|
279
|
+
if not (table and inner_field):
|
|
280
|
+
raise ValueError("missing table or inner field on this 'field'")
|
|
281
|
+
|
|
282
|
+
return "%s.%s" % (table._rname, inner_field._rname)
|
|
283
|
+
|
|
284
|
+
|
|
271
285
|
def is_typed_field(cls: t.Any) -> t.TypeGuard["TypedField[t.Any]"]:
|
|
272
286
|
"""
|
|
273
287
|
Is `cls` an instance or subclass of TypedField?
|
|
@@ -152,7 +152,22 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
152
152
|
"""
|
|
153
153
|
Return a clone of this builder with permission overrides merged in.
|
|
154
154
|
"""
|
|
155
|
-
return self._extend(permissions=
|
|
155
|
+
return self._extend(permissions=permissions)
|
|
156
|
+
|
|
157
|
+
def _normalize_select_option(
|
|
158
|
+
self, value: str | Field | Expression | bool | t.Iterable[str | Field]
|
|
159
|
+
) -> str | bool | list[str]:
|
|
160
|
+
# currently only used for 'distinct' since orderby, ... are patched by pydal itself in select()
|
|
161
|
+
if isinstance(value, bool):
|
|
162
|
+
return value
|
|
163
|
+
|
|
164
|
+
if isinstance(value, (list, tuple, set)):
|
|
165
|
+
return list(self._normalize_select_option(val) for val in value)
|
|
166
|
+
|
|
167
|
+
if rname := getattr(value, "_rname", None):
|
|
168
|
+
return str(rname)
|
|
169
|
+
|
|
170
|
+
return str(value)
|
|
156
171
|
|
|
157
172
|
def select(self, *fields: t.Any, **options: t.Unpack[SelectKwargs]) -> "QueryBuilder[T_MetaInstance]":
|
|
158
173
|
"""
|
|
@@ -179,6 +194,11 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
179
194
|
left: othertable.on(query) - do a LEFT JOIN. Using TypeDAL relationships with .join() is recommended!
|
|
180
195
|
cache: cache the query result to speed up repeated queries; e.g. (cache=(cache.ram, 3600), cacheable=True)
|
|
181
196
|
"""
|
|
197
|
+
|
|
198
|
+
for key in ("distinct",):
|
|
199
|
+
if options.get(key):
|
|
200
|
+
options[key] = self._normalize_select_option(options[key])
|
|
201
|
+
|
|
182
202
|
return self._extend(select_args=list(fields), select_kwargs=options)
|
|
183
203
|
|
|
184
204
|
def orderby(self, *fields: OrderBy) -> "QueryBuilder[T_MetaInstance]":
|
|
@@ -272,7 +292,10 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
272
292
|
return self._extend(overwrite_query=new_query)
|
|
273
293
|
|
|
274
294
|
def _parse_relationships(
|
|
275
|
-
self,
|
|
295
|
+
self,
|
|
296
|
+
fields: t.Iterable[str | t.Type[TypedTable]],
|
|
297
|
+
method: JOIN_OPTIONS = None,
|
|
298
|
+
**update: t.Any,
|
|
276
299
|
) -> dict[str, Relationship[t.Any]]:
|
|
277
300
|
"""
|
|
278
301
|
Parse relationship fields into a dict of base relationships with nested relationships.
|
|
@@ -766,7 +789,11 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
766
789
|
return joins
|
|
767
790
|
|
|
768
791
|
def _build_inner_joins_recursive(
|
|
769
|
-
self,
|
|
792
|
+
self,
|
|
793
|
+
relation: Relationship[t.Any],
|
|
794
|
+
parent_table: t.Type[_TypedTable],
|
|
795
|
+
key: str,
|
|
796
|
+
parent_key: str = "",
|
|
770
797
|
) -> list[t.Any]:
|
|
771
798
|
"""Recursively build inner joins for a relationship and its nested relationships."""
|
|
772
799
|
db = self._get_db()
|
|
@@ -876,13 +903,21 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
876
903
|
# todo: add additional test, deduplicate
|
|
877
904
|
nested_key = f"{parent_key}.{nested_name}" if parent_key else f"{key}.{nested_name}"
|
|
878
905
|
select_args = self._process_relationship_for_left_join(
|
|
879
|
-
nested,
|
|
906
|
+
nested,
|
|
907
|
+
nested_name,
|
|
908
|
+
select_args,
|
|
909
|
+
left_joins,
|
|
910
|
+
other,
|
|
911
|
+
nested_key,
|
|
880
912
|
)
|
|
881
913
|
|
|
882
914
|
return select_args
|
|
883
915
|
|
|
884
916
|
def _ensure_relationship_fields(
|
|
885
|
-
self,
|
|
917
|
+
self,
|
|
918
|
+
select_args: list[t.Any],
|
|
919
|
+
other: t.Type[TypedTable],
|
|
920
|
+
select_fields: str,
|
|
886
921
|
) -> list[t.Any]:
|
|
887
922
|
"""Ensure required fields from relationship table are selected."""
|
|
888
923
|
if f"{other}." not in select_fields:
|
|
@@ -895,7 +930,10 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
895
930
|
return select_args
|
|
896
931
|
|
|
897
932
|
def _update_select_args_with_alias(
|
|
898
|
-
self,
|
|
933
|
+
self,
|
|
934
|
+
select_args: list[t.Any],
|
|
935
|
+
pre_alias: str,
|
|
936
|
+
other: t.Type[TypedTable],
|
|
899
937
|
) -> list[t.Any]:
|
|
900
938
|
"""Update select_args to use aliased table names."""
|
|
901
939
|
post_alias = str(other).split(" AS ")[-1]
|
|
@@ -370,7 +370,9 @@ class TableMeta(type):
|
|
|
370
370
|
"""
|
|
371
371
|
return QueryBuilder(self).cache(*deps, **kwargs)
|
|
372
372
|
|
|
373
|
-
def permissions(
|
|
373
|
+
def permissions(
|
|
374
|
+
self: t.Type[T_MetaInstance], **permissions: t.Unpack[Permissions]
|
|
375
|
+
) -> "QueryBuilder[T_MetaInstance]":
|
|
374
376
|
"""
|
|
375
377
|
See QueryBuilder.permissions!
|
|
376
378
|
"""
|
|
@@ -46,7 +46,14 @@ type AnyDict = dict[str, t.Any]
|
|
|
46
46
|
|
|
47
47
|
PermissionType = t.Literal["read", "insert", "update", "delete"]
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
|
|
50
|
+
# type Permissions = dict[PermissionType, bool]
|
|
51
|
+
class Permissions(t.TypedDict):
|
|
52
|
+
# note: extra source of truth because the dynamic dict doesn't work for all type checkers
|
|
53
|
+
read: bool
|
|
54
|
+
insert: bool
|
|
55
|
+
update: bool
|
|
56
|
+
delete: bool
|
|
50
57
|
|
|
51
58
|
|
|
52
59
|
def merge_permissions(*permission_sets: Permissions | None) -> Permissions:
|
|
@@ -64,7 +71,7 @@ def merge_permissions(*permission_sets: Permissions | None) -> Permissions:
|
|
|
64
71
|
|
|
65
72
|
for key in permission_types:
|
|
66
73
|
if key in permission_set:
|
|
67
|
-
merged[key] = merged[key] and bool(permission_set[key])
|
|
74
|
+
merged[key] = merged[key] and bool(permission_set[key]) # type: ignore
|
|
68
75
|
|
|
69
76
|
return t.cast(Permissions, merged)
|
|
70
77
|
|
|
@@ -181,6 +188,8 @@ class Reference(_Reference):
|
|
|
181
188
|
class Field(_Field):
|
|
182
189
|
"""Pydal Field object. Make mypy happy."""
|
|
183
190
|
|
|
191
|
+
_rname: str
|
|
192
|
+
|
|
184
193
|
|
|
185
194
|
class Rows(_Rows):
|
|
186
195
|
"""Pydal Rows object. Make mypy happy."""
|
|
@@ -276,7 +285,7 @@ class SelectKwargs(t.TypedDict, total=False):
|
|
|
276
285
|
groupby: "GroupBy | t.Iterable[GroupBy] | None"
|
|
277
286
|
having: "Having | None"
|
|
278
287
|
limitby: t.Optional[tuple[int, int]]
|
|
279
|
-
distinct: bool | Field | Expression
|
|
288
|
+
distinct: bool | Field | Expression | str | t.Iterable[str]
|
|
280
289
|
orderby_on_limitby: bool
|
|
281
290
|
cacheable: bool
|
|
282
291
|
cache: "CacheTuple"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from testcontainers.postgres import PostgresContainer
|
|
5
|
+
|
|
6
|
+
from src.typedal import TypeDAL
|
|
7
|
+
|
|
8
|
+
postgres = PostgresContainer(
|
|
9
|
+
dbname="postgres",
|
|
10
|
+
username="someuser",
|
|
11
|
+
password="somepass",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture(scope="module", autouse=True)
|
|
16
|
+
def psql(request):
|
|
17
|
+
postgres.ports = {
|
|
18
|
+
5432: 9631, # as set in valid.env
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
request.addfinalizer(postgres.stop)
|
|
22
|
+
postgres.start()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def dal_psql(psql):
|
|
27
|
+
conn_str = postgres.get_connection_url()
|
|
28
|
+
uri = "postgres://" + conn_str.split("://")[-1]
|
|
29
|
+
with tempfile.TemporaryDirectory() as d:
|
|
30
|
+
yield TypeDAL(uri, attempts=1, migrate=True, enable_typedal_caching=False, folder=d)
|
|
@@ -9,7 +9,6 @@ from pathlib import Path
|
|
|
9
9
|
|
|
10
10
|
import pytest
|
|
11
11
|
from contextlib_chdir import chdir
|
|
12
|
-
from testcontainers.postgres import PostgresContainer
|
|
13
12
|
|
|
14
13
|
from src.typedal import TypeDAL, TypedField, TypedTable
|
|
15
14
|
from src.typedal.config import (
|
|
@@ -20,22 +19,6 @@ from src.typedal.config import (
|
|
|
20
19
|
)
|
|
21
20
|
from src.typedal.fields import PointField, TimestampField, UUIDField
|
|
22
21
|
|
|
23
|
-
postgres = PostgresContainer(
|
|
24
|
-
dbname="postgres",
|
|
25
|
-
username="someuser",
|
|
26
|
-
password="somepass",
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@pytest.fixture(scope="module", autouse=True)
|
|
31
|
-
def psql(request):
|
|
32
|
-
postgres.ports = {
|
|
33
|
-
5432: 9631, # as set in valid.env
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
request.addfinalizer(postgres.stop)
|
|
37
|
-
postgres.start()
|
|
38
|
-
|
|
39
22
|
|
|
40
23
|
@pytest.fixture
|
|
41
24
|
def at_temp_dir():
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import pytest
|
|
2
|
-
from pydal.objects import Query
|
|
2
|
+
from pydal.objects import Field, Query
|
|
3
3
|
|
|
4
4
|
from src.typedal import TypeDAL, TypedField, TypedTable, relationship
|
|
5
5
|
from typedal import QueryBuilder
|
|
6
|
+
from typedal.fields import rname
|
|
6
7
|
|
|
7
8
|
db = TypeDAL("sqlite:memory")
|
|
8
9
|
|
|
@@ -559,6 +560,48 @@ def test_orderby():
|
|
|
559
560
|
)
|
|
560
561
|
|
|
561
562
|
|
|
563
|
+
def test_select_kwargs_use_rname_psql(dal_psql: TypeDAL):
|
|
564
|
+
db = dal_psql
|
|
565
|
+
|
|
566
|
+
@db.define(rname="some_table")
|
|
567
|
+
class Sometable(TypedTable):
|
|
568
|
+
name = TypedField(str, rname="some_name")
|
|
569
|
+
|
|
570
|
+
Sometable.insert(name="B")
|
|
571
|
+
Sometable.insert(name="A")
|
|
572
|
+
Sometable.insert(name="A")
|
|
573
|
+
db.commit()
|
|
574
|
+
|
|
575
|
+
# default orderby/distinct
|
|
576
|
+
|
|
577
|
+
orderby_sql = Sometable.select(orderby=Sometable.name).to_sql().lower()
|
|
578
|
+
|
|
579
|
+
assert "sometable.name" not in orderby_sql
|
|
580
|
+
assert "some_table.some_name" in orderby_sql
|
|
581
|
+
|
|
582
|
+
distinct_sql1 = Sometable.select(Sometable.name, distinct=Sometable.name).to_sql().lower()
|
|
583
|
+
distinct_sql2 = Sometable.select(Sometable.name, distinct=(Sometable.name, Sometable.id)).to_sql().lower()
|
|
584
|
+
distinct_sql3 = Sometable.select(Sometable.name, distinct=("name", "id")).to_sql().lower()
|
|
585
|
+
|
|
586
|
+
for sql in (distinct_sql1, distinct_sql2, distinct_sql3):
|
|
587
|
+
assert "sometable.name" not in sql
|
|
588
|
+
assert "some_table.some_name" in sql
|
|
589
|
+
|
|
590
|
+
builder = Sometable.select(orderby=Sometable.name)
|
|
591
|
+
existing_orderby: Field = builder.select_kwargs.get("orderby")
|
|
592
|
+
|
|
593
|
+
existing_orderby_name = rname(existing_orderby)
|
|
594
|
+
sometable_name = rname(Sometable.name)
|
|
595
|
+
sometable_name_pydal = rname(db.sometable.name)
|
|
596
|
+
|
|
597
|
+
with pytest.raises(ValueError):
|
|
598
|
+
rname(TypedField(str, rname="some_name"))
|
|
599
|
+
|
|
600
|
+
for sql in (existing_orderby_name, sometable_name, sometable_name_pydal):
|
|
601
|
+
assert "sometable.name" not in sql
|
|
602
|
+
assert "some_table.some_name" in sql
|
|
603
|
+
|
|
604
|
+
|
|
562
605
|
def test_execute():
|
|
563
606
|
_setup_data()
|
|
564
607
|
|
typedal-4.9.0/.crush/.gitignore
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
*
|
|
Binary file
|
|
Binary file
|
typedal-4.9.0/.crush/init
DELETED
|
File without changes
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
{"time":"2026-01-20T14:19:21.359487106+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-20T14:19:21.518115889+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-20T14:19:21.52626179+01:00","level":"INFO","msg":"OK 20250424200609_initial.sql (1.52ms)"}
|
|
4
|
-
{"time":"2026-01-20T14:19:21.526651475+01:00","level":"INFO","msg":"OK 20250515105448_add_summary_message_id.sql (330.71µs)"}
|
|
5
|
-
{"time":"2026-01-20T14:19:21.526995425+01:00","level":"INFO","msg":"OK 20250624000000_add_created_at_indexes.sql (325.29µs)"}
|
|
6
|
-
{"time":"2026-01-20T14:19:21.527303588+01:00","level":"INFO","msg":"OK 20250627000000_add_provider_to_messages.sql (293.15µs)"}
|
|
7
|
-
{"time":"2026-01-20T14:19:21.52790648+01:00","level":"INFO","msg":"OK 20250810000000_add_is_summary_message.sql (495.71µs)"}
|
|
8
|
-
{"time":"2026-01-20T14:19:21.528316924+01:00","level":"INFO","msg":"OK 20250812000000_add_todos_to_sessions.sql (389.38µs)"}
|
|
9
|
-
{"time":"2026-01-20T14:19:21.528324939+01:00","level":"INFO","msg":"goose: successfully migrated database to version: 20250812000000"}
|
|
10
|
-
{"time":"2026-01-20T14:19:21.528356959+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"}
|
|
11
|
-
{"time":"2026-01-20T14:19:21.52843831+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":102},"msg":"Initializing MCP clients"}
|
|
12
|
-
{"time":"2026-01-20T14:21:06.413595152+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"}
|
|
13
|
-
{"time":"2026-01-20T14:21:14.539289381+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"}
|
|
14
|
-
{"time":"2026-01-20T14:21:15.710688424+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."}
|
|
15
|
-
{"time":"2026-01-20T14:21:17.08089755+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"}
|
|
16
|
-
{"time":"2026-01-20T14:21:17.151106375+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"}
|
|
17
|
-
{"time":"2026-01-20T14:22:05.503761296+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"}
|
|
18
|
-
{"time":"2026-01-20T14:26:05.030425805+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"}
|
|
19
|
-
{"time":"2026-01-20T14:36:26.778665189+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"}
|
|
20
|
-
{"time":"2026-01-20T14:38:51.314473736+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"}
|
|
21
|
-
{"time":"2026-01-20T15:02:22.602405814+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":403},"msg":"Shutdown took 6.62949ms"}
|
|
22
|
-
{"time":"2026-01-20T15:02:23.719585648+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"}
|
|
23
|
-
{"time":"2026-01-20T15:02:23.881789126+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"}
|
|
24
|
-
{"time":"2026-01-20T15:02:23.884044691+01:00","level":"INFO","msg":"goose: no migrations to run. current version: 20250812000000"}
|
|
25
|
-
{"time":"2026-01-20T15:02:23.884082792+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"}
|
|
26
|
-
{"time":"2026-01-20T15:02:23.884273487+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":102},"msg":"Initializing MCP clients"}
|
|
27
|
-
{"time":"2026-01-20T15:04:13.024457515+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"}
|
|
28
|
-
{"time":"2026-01-20T15:04:14.232072885+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."}
|
|
29
|
-
{"time":"2026-01-20T15:11:24.612161916+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":403},"msg":"Shutdown took 9.537141ms"}
|
|
30
|
-
{"time":"2026-01-20T15:11:25.263960575+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"}
|
|
31
|
-
{"time":"2026-01-20T15:11:25.431990051+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"}
|
|
32
|
-
{"time":"2026-01-20T15:11:25.434282035+01:00","level":"INFO","msg":"goose: no migrations to run. current version: 20250812000000"}
|
|
33
|
-
{"time":"2026-01-20T15:11:25.434366662+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"}
|
|
34
|
-
{"time":"2026-01-20T15:11:25.434579879+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":102},"msg":"Initializing MCP clients"}
|
|
35
|
-
{"time":"2026-01-20T15:24:40.456010578+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"}
|
|
36
|
-
{"time":"2026-01-20T15:24:51.673808433+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."}
|
|
37
|
-
{"time":"2026-01-20T15:26:03.868420566+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).Cancel","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":907},"msg":"Request cancellation initiated","session_id":"4c2d1684-8db2-4ffd-83bb-9c08fe850f29"}
|
|
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
|