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.
Files changed (80) hide show
  1. {typedal-4.9.0 → typedal-4.9.2}/CHANGELOG.md +17 -0
  2. {typedal-4.9.0 → typedal-4.9.2}/PKG-INFO +2 -1
  3. {typedal-4.9.0 → typedal-4.9.2}/docs/2_defining_tables.md +21 -0
  4. {typedal-4.9.0 → typedal-4.9.2}/docs/3_building_queries.md +22 -0
  5. {typedal-4.9.0 → typedal-4.9.2}/pyproject.toml +1 -0
  6. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/__about__.py +1 -1
  7. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/fields.py +14 -0
  8. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/query_builder.py +44 -6
  9. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/tables.py +3 -1
  10. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/types.py +12 -3
  11. typedal-4.9.2/tests/conftest.py +30 -0
  12. {typedal-4.9.0 → typedal-4.9.2}/tests/test_config.py +0 -17
  13. {typedal-4.9.0 → typedal-4.9.2}/tests/test_helpers.py +1 -0
  14. {typedal-4.9.0 → typedal-4.9.2}/tests/test_query_builder.py +44 -1
  15. typedal-4.9.0/.crush/.gitignore +0 -1
  16. typedal-4.9.0/.crush/crush.db-shm +0 -0
  17. typedal-4.9.0/.crush/crush.db-wal +0 -0
  18. typedal-4.9.0/.crush/init +0 -0
  19. typedal-4.9.0/.crush/logs/crush.log +0 -37
  20. {typedal-4.9.0 → typedal-4.9.2}/.github/workflows/su6.yml +0 -0
  21. {typedal-4.9.0 → typedal-4.9.2}/.gitignore +0 -0
  22. {typedal-4.9.0 → typedal-4.9.2}/.readthedocs.yml +0 -0
  23. {typedal-4.9.0 → typedal-4.9.2}/README.md +0 -0
  24. {typedal-4.9.0 → typedal-4.9.2}/coverage.svg +0 -0
  25. {typedal-4.9.0 → typedal-4.9.2}/docs/10_advanced_apis.md +0 -0
  26. {typedal-4.9.0 → typedal-4.9.2}/docs/1_getting_started.md +0 -0
  27. {typedal-4.9.0 → typedal-4.9.2}/docs/4_relationships.md +0 -0
  28. {typedal-4.9.0 → typedal-4.9.2}/docs/5_py4web.md +0 -0
  29. {typedal-4.9.0 → typedal-4.9.2}/docs/6_migrations.md +0 -0
  30. {typedal-4.9.0 → typedal-4.9.2}/docs/7_configuration.md +0 -0
  31. {typedal-4.9.0 → typedal-4.9.2}/docs/8_mixins.md +0 -0
  32. {typedal-4.9.0 → typedal-4.9.2}/docs/9_memoization.md +0 -0
  33. {typedal-4.9.0 → typedal-4.9.2}/docs/css/code_blocks.css +0 -0
  34. {typedal-4.9.0 → typedal-4.9.2}/docs/index.md +0 -0
  35. {typedal-4.9.0 → typedal-4.9.2}/docs/requirements.txt +0 -0
  36. {typedal-4.9.0 → typedal-4.9.2}/example_new.py +0 -0
  37. {typedal-4.9.0 → typedal-4.9.2}/example_old.py +0 -0
  38. {typedal-4.9.0 → typedal-4.9.2}/mkdocs.yml +0 -0
  39. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/__init__.py +0 -0
  40. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/caching.py +0 -0
  41. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/cli.py +0 -0
  42. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/config.py +0 -0
  43. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/constants.py +0 -0
  44. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/core.py +0 -0
  45. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/define.py +0 -0
  46. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/enum_helpers.py +0 -0
  47. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/for_py4web.py +0 -0
  48. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/for_web2py.py +0 -0
  49. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/helpers.py +0 -0
  50. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/mixins.py +0 -0
  51. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/py.typed +0 -0
  52. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/relationships.py +0 -0
  53. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/rows.py +0 -0
  54. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/serializers/as_json.py +0 -0
  55. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/serializers/typescript.py +0 -0
  56. {typedal-4.9.0 → typedal-4.9.2}/src/typedal/web2py_py4web_shared.py +0 -0
  57. {typedal-4.9.0 → typedal-4.9.2}/tasks.py +0 -0
  58. {typedal-4.9.0 → typedal-4.9.2}/tests/__init__.py +0 -0
  59. {typedal-4.9.0 → typedal-4.9.2}/tests/configs/simple.toml +0 -0
  60. {typedal-4.9.0 → typedal-4.9.2}/tests/configs/valid.env +0 -0
  61. {typedal-4.9.0 → typedal-4.9.2}/tests/configs/valid.toml +0 -0
  62. {typedal-4.9.0 → typedal-4.9.2}/tests/py314_tests.py +0 -0
  63. {typedal-4.9.0 → typedal-4.9.2}/tests/test_cli.py +0 -0
  64. {typedal-4.9.0 → typedal-4.9.2}/tests/test_docs_examples.py +0 -0
  65. {typedal-4.9.0 → typedal-4.9.2}/tests/test_json.py +0 -0
  66. {typedal-4.9.0 → typedal-4.9.2}/tests/test_main.py +0 -0
  67. {typedal-4.9.0 → typedal-4.9.2}/tests/test_mixins.py +0 -0
  68. {typedal-4.9.0 → typedal-4.9.2}/tests/test_mypy.py +0 -0
  69. {typedal-4.9.0 → typedal-4.9.2}/tests/test_orm.py +0 -0
  70. {typedal-4.9.0 → typedal-4.9.2}/tests/test_py4web.py +0 -0
  71. {typedal-4.9.0 → typedal-4.9.2}/tests/test_relationships.py +0 -0
  72. {typedal-4.9.0 → typedal-4.9.2}/tests/test_row.py +0 -0
  73. {typedal-4.9.0 → typedal-4.9.2}/tests/test_stats.py +0 -0
  74. {typedal-4.9.0 → typedal-4.9.2}/tests/test_table.py +0 -0
  75. {typedal-4.9.0 → typedal-4.9.2}/tests/test_typescript.py +0 -0
  76. {typedal-4.9.0 → typedal-4.9.2}/tests/test_typing_mypy.md +0 -0
  77. {typedal-4.9.0 → typedal-4.9.2}/tests/test_typing_pyright.md +0 -0
  78. {typedal-4.9.0 → typedal-4.9.2}/tests/test_web2py.py +0 -0
  79. {typedal-4.9.0 → typedal-4.9.2}/tests/test_xx_others.py +0 -0
  80. {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.0
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 &#124; None</code> | `name: TypedField[typing.Optional[str]]` or <br/> <code>name: TypedField[str &#124; 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:
@@ -97,6 +97,7 @@ dev = [
97
97
  "contextlib-chdir",
98
98
  "testcontainers",
99
99
  "pydantic < 3",
100
+ "psycopg2-binary",
100
101
  # depends on ->
101
102
  "requests<2.32",
102
103
  # mypy:
@@ -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.9.0"
8
+ __version__ = "4.9.2"
@@ -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=t.cast(Permissions, 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, fields: t.Iterable[str | t.Type[TypedTable]], method: JOIN_OPTIONS = None, **update: t.Any
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, relation: Relationship[t.Any], parent_table: t.Type[_TypedTable], key: str, parent_key: str = ""
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, nested_name, select_args, left_joins, other, nested_key
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, select_args: list[t.Any], other: t.Type[TypedTable], select_fields: str
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, select_args: list[t.Any], pre_alias: str, other: t.Type[TypedTable]
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(self: t.Type[T_MetaInstance], **permissions: bool) -> "QueryBuilder[T_MetaInstance]":
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
- type Permissions = dict[PermissionType, bool]
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():
@@ -274,6 +274,7 @@ def test_sql_expression_314():
274
274
 
275
275
  test_sql_expression_314(database)
276
276
 
277
+
277
278
  def test_merge_permissions():
278
279
  combined = merge_permissions({"read": True, "insert": False}, {"read": False, "insert": False})
279
280
  assert len(combined) == 4
@@ -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
 
@@ -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