TypeDAL 3.17.0__tar.gz → 3.17.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.
Potentially problematic release.
This version of TypeDAL might be problematic. Click here for more details.
- {typedal-3.17.0 → typedal-3.17.2}/CHANGELOG.md +12 -0
- {typedal-3.17.0 → typedal-3.17.2}/PKG-INFO +1 -1
- {typedal-3.17.0 → typedal-3.17.2}/src/typedal/__about__.py +1 -1
- {typedal-3.17.0 → typedal-3.17.2}/src/typedal/core.py +26 -3
- {typedal-3.17.0 → typedal-3.17.2}/src/typedal/helpers.py +24 -4
- {typedal-3.17.0 → typedal-3.17.2}/src/typedal/types.py +3 -1
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_helpers.py +5 -1
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_query_builder.py +10 -3
- {typedal-3.17.0 → typedal-3.17.2}/.github/workflows/su6.yml +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/.gitignore +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/.readthedocs.yml +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/README.md +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/coverage.svg +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/docs/1_getting_started.md +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/docs/2_defining_tables.md +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/docs/3_building_queries.md +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/docs/4_relationships.md +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/docs/5_py4web.md +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/docs/6_migrations.md +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/docs/7_mixins.md +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/docs/css/code_blocks.css +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/docs/index.md +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/docs/requirements.txt +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/example_new.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/example_old.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/mkdocs.yml +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/pyproject.toml +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/src/typedal/__init__.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/src/typedal/caching.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/src/typedal/cli.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/src/typedal/config.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/src/typedal/fields.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/src/typedal/for_py4web.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/src/typedal/for_web2py.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/src/typedal/mixins.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/src/typedal/py.typed +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/src/typedal/serializers/as_json.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/src/typedal/web2py_py4web_shared.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/__init__.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/configs/simple.toml +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/configs/valid.env +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/configs/valid.toml +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_cli.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_config.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_docs_examples.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_json.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_main.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_mixins.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_mypy.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_orm.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_py4web.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_relationships.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_row.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_stats.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_table.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_web2py.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/test_xx_others.py +0 -0
- {typedal-3.17.0 → typedal-3.17.2}/tests/timings.py +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
<!--next-version-placeholder-->
|
|
4
4
|
|
|
5
|
+
## v3.17.2 (2025-09-24)
|
|
6
|
+
|
|
7
|
+
### Fix
|
|
8
|
+
|
|
9
|
+
* Support .orderby as alias for .select(orderby= ([`be561c5`](https://github.com/trialandsuccess/TypeDAL/commit/be561c500c573a3a45b22f250fe1d2b473b56cc3))
|
|
10
|
+
|
|
11
|
+
## v3.17.1 (2025-09-20)
|
|
12
|
+
|
|
13
|
+
### Fix
|
|
14
|
+
|
|
15
|
+
* Smarter adapt so Fields and Tables can also be safely inserted in `sql_expression` ([`d5cad6a`](https://github.com/trialandsuccess/TypeDAL/commit/d5cad6a13ddf50ec6e5762075fbeae1e44067da6))
|
|
16
|
+
|
|
5
17
|
## v3.17.0 (2025-09-20)
|
|
6
18
|
|
|
7
19
|
### Feature
|
|
@@ -60,6 +60,7 @@ from .types import (
|
|
|
60
60
|
FieldSettings,
|
|
61
61
|
Metadata,
|
|
62
62
|
OpRow,
|
|
63
|
+
OrderBy,
|
|
63
64
|
PaginateDict,
|
|
64
65
|
Pagination,
|
|
65
66
|
Query,
|
|
@@ -835,10 +836,10 @@ class TypeDAL(pydal.DAL): # type: ignore
|
|
|
835
836
|
def sql_expression(
|
|
836
837
|
self,
|
|
837
838
|
sql_fragment: str,
|
|
838
|
-
*raw_args:
|
|
839
|
+
*raw_args: Any,
|
|
839
840
|
output_type: str | None = None,
|
|
840
|
-
**raw_kwargs:
|
|
841
|
-
) ->
|
|
841
|
+
**raw_kwargs: Any,
|
|
842
|
+
) -> Expression:
|
|
842
843
|
"""
|
|
843
844
|
Creates a pydal Expression object representing a raw SQL fragment.
|
|
844
845
|
|
|
@@ -1146,6 +1147,12 @@ class TableMeta(type):
|
|
|
1146
1147
|
"""
|
|
1147
1148
|
return QueryBuilder(self).where(*a, **kw)
|
|
1148
1149
|
|
|
1150
|
+
def orderby(self: Type[T_MetaInstance], *fields: OrderBy) -> "QueryBuilder[T_MetaInstance]":
|
|
1151
|
+
"""
|
|
1152
|
+
See QueryBuilder.orderby!
|
|
1153
|
+
"""
|
|
1154
|
+
return QueryBuilder(self).orderby(*fields)
|
|
1155
|
+
|
|
1149
1156
|
def cache(self: Type[T_MetaInstance], *deps: Any, **kwargs: Any) -> "QueryBuilder[T_MetaInstance]":
|
|
1150
1157
|
"""
|
|
1151
1158
|
See QueryBuilder.cache!
|
|
@@ -2678,6 +2685,22 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
|
|
|
2678
2685
|
"""
|
|
2679
2686
|
return self._extend(select_args=list(fields), select_kwargs=options)
|
|
2680
2687
|
|
|
2688
|
+
def orderby(self, *fields: OrderBy) -> "QueryBuilder[T_MetaInstance]":
|
|
2689
|
+
"""
|
|
2690
|
+
Order the query results by specified fields.
|
|
2691
|
+
|
|
2692
|
+
Args:
|
|
2693
|
+
fields: field(s) to order by. Supported:
|
|
2694
|
+
table.name - sort by name, ascending
|
|
2695
|
+
~table.name - sort by name, descending
|
|
2696
|
+
<random> - sort randomly
|
|
2697
|
+
table.name|table.id - sort by two fields (first name, then id)
|
|
2698
|
+
|
|
2699
|
+
Returns:
|
|
2700
|
+
QueryBuilder: A new QueryBuilder instance with the ordering applied.
|
|
2701
|
+
"""
|
|
2702
|
+
return self.select(orderby=fields)
|
|
2703
|
+
|
|
2681
2704
|
def where(
|
|
2682
2705
|
self,
|
|
2683
2706
|
*queries_or_lambdas: Query | typing.Callable[[Type[T_MetaInstance]], Query] | dict[str, Any],
|
|
@@ -337,6 +337,26 @@ class classproperty:
|
|
|
337
337
|
return self.fget(owner)
|
|
338
338
|
|
|
339
339
|
|
|
340
|
+
def smarter_adapt(db: TypeDAL, placeholder: Any) -> str:
|
|
341
|
+
"""
|
|
342
|
+
Smarter adaptation of placeholder to quote if needed.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
db: Database object.
|
|
346
|
+
placeholder: Placeholder object.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
Quoted placeholder if needed, except for numbers (smart_adapt logic)
|
|
350
|
+
or fields/tables (use already quoted rname).
|
|
351
|
+
"""
|
|
352
|
+
return typing.cast(
|
|
353
|
+
str,
|
|
354
|
+
getattr(placeholder, "sql_shortref", None) # for tables
|
|
355
|
+
or getattr(placeholder, "sqlsafe", None) # for fields
|
|
356
|
+
or db._adapter.smart_adapt(placeholder), # for others
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
|
|
340
360
|
def sql_escape(db: TypeDAL, sql_fragment: str, *raw_args: Any, **raw_kwargs: Any) -> str:
|
|
341
361
|
"""
|
|
342
362
|
Generates escaped SQL fragments with placeholders.
|
|
@@ -358,18 +378,18 @@ def sql_escape(db: TypeDAL, sql_fragment: str, *raw_args: Any, **raw_kwargs: Any
|
|
|
358
378
|
|
|
359
379
|
elif raw_args:
|
|
360
380
|
# list
|
|
361
|
-
return sql_fragment % tuple(db
|
|
381
|
+
return sql_fragment % tuple(smarter_adapt(db, placeholder) for placeholder in raw_args)
|
|
362
382
|
else:
|
|
363
383
|
# dict
|
|
364
|
-
return sql_fragment % {key: db
|
|
384
|
+
return sql_fragment % {key: smarter_adapt(db, placeholder) for key, placeholder in raw_kwargs.items()}
|
|
365
385
|
|
|
366
386
|
|
|
367
387
|
def sql_expression(
|
|
368
388
|
db: TypeDAL,
|
|
369
389
|
sql_fragment: str,
|
|
370
|
-
*raw_args:
|
|
390
|
+
*raw_args: Any,
|
|
371
391
|
output_type: str | None = None,
|
|
372
|
-
**raw_kwargs:
|
|
392
|
+
**raw_kwargs: Any,
|
|
373
393
|
) -> Expression:
|
|
374
394
|
"""
|
|
375
395
|
Creates a pydal Expression object representing a raw SQL fragment.
|
|
@@ -218,6 +218,8 @@ class CacheFn(typing.Protocol):
|
|
|
218
218
|
CacheModel = typing.Callable[[str, CacheFn, int], Rows]
|
|
219
219
|
CacheTuple = tuple[CacheModel, int]
|
|
220
220
|
|
|
221
|
+
OrderBy: typing.TypeAlias = Expression | str
|
|
222
|
+
|
|
221
223
|
|
|
222
224
|
class SelectKwargs(TypedDict, total=False):
|
|
223
225
|
"""
|
|
@@ -226,7 +228,7 @@ class SelectKwargs(TypedDict, total=False):
|
|
|
226
228
|
|
|
227
229
|
join: Optional[list[Expression]]
|
|
228
230
|
left: Optional[list[Expression]]
|
|
229
|
-
orderby:
|
|
231
|
+
orderby: OrderBy | typing.Iterable[OrderBy] | None
|
|
230
232
|
limitby: Optional[tuple[int, int]]
|
|
231
233
|
distinct: bool | Field | Expression
|
|
232
234
|
orderby_on_limitby: bool
|
|
@@ -223,7 +223,7 @@ def test_sql_expression():
|
|
|
223
223
|
expr1 = sql_expression(database, "date('now') > %s", "2025-01-01")
|
|
224
224
|
expr2 = database.sql_expression("date('now') > %(value)s", value="2025-01-01")
|
|
225
225
|
|
|
226
|
-
assert expr1 == expr2
|
|
226
|
+
assert str(expr1) == str(expr2)
|
|
227
227
|
assert str(expr1) == "date('now') > '2025-01-01'"
|
|
228
228
|
# past -> should yield result
|
|
229
229
|
result = database(expr1).select(TestSqlExpression.value, expr2)[0]
|
|
@@ -240,3 +240,7 @@ def test_sql_expression():
|
|
|
240
240
|
# far future -> should not yield result
|
|
241
241
|
result3 = database(expr3).select(TestSqlExpression.value, expr3).as_list()
|
|
242
242
|
assert not result3
|
|
243
|
+
|
|
244
|
+
# test quoting fields and tables:
|
|
245
|
+
assert str(database.sql_expression("LOWER(%s)", TestSqlExpression.value)) == 'LOWER("test_sql_expression"."value")'
|
|
246
|
+
assert str(database.sql_expression("LOWER(%s.value)", TestSqlExpression)) == 'LOWER("test_sql_expression".value)'
|
|
@@ -447,7 +447,7 @@ def test_orderby():
|
|
|
447
447
|
|
|
448
448
|
assert base_qt.count() == 5
|
|
449
449
|
|
|
450
|
-
rows1 = base_qt.
|
|
450
|
+
rows1 = base_qt.orderby(TestQueryTable.id).paginate(limit=3, page=1)
|
|
451
451
|
rows2 = base_qt.select(orderby=TestQueryTable.id, limitby=(0, 3)).collect()
|
|
452
452
|
assert [_.id for _ in rows1] == [_.id for _ in rows2] == [1, 2, 3]
|
|
453
453
|
|
|
@@ -467,10 +467,12 @@ def test_orderby():
|
|
|
467
467
|
rows2 = joined_qt.select(orderby=TestQueryTable.id, limitby=(0, 3)).collect()
|
|
468
468
|
assert [_.id for _ in rows1] == [_.id for _ in rows2] == [1, 2, 3]
|
|
469
469
|
|
|
470
|
-
rows1 = joined_qt.
|
|
471
|
-
rows2 = joined_qt.select(orderby=TestQueryTable.number, limitby=(0, 3)).collect()
|
|
470
|
+
rows1 = joined_qt.orderby(TestQueryTable.number, TestQueryTable.other).paginate(limit=3, page=1)
|
|
471
|
+
rows2 = joined_qt.select(orderby=TestQueryTable.number | TestQueryTable.other, limitby=(0, 3)).collect()
|
|
472
472
|
assert [_.number for _ in rows1] == [_.number for _ in rows2] == [0, 1, 2]
|
|
473
473
|
|
|
474
|
+
assert rows1.metadata["sql"] == rows2.metadata["sql"]
|
|
475
|
+
|
|
474
476
|
rows1 = joined_qt.select(orderby=~TestQueryTable.number).paginate(limit=3, page=1)
|
|
475
477
|
rows2 = joined_qt.select(orderby=~TestQueryTable.number, limitby=(0, 3)).collect()
|
|
476
478
|
assert [_.number for _ in rows1] == [_.number for _ in rows2] == [4, 3, 2]
|
|
@@ -479,6 +481,11 @@ def test_orderby():
|
|
|
479
481
|
rows2 = joined_qt.select(orderby=~TestQueryTable.number, limitby=(0, 100)).collect()
|
|
480
482
|
assert [_.number for _ in rows1] == [_.number for _ in rows2] == [4, 3, 2, 1, 0]
|
|
481
483
|
|
|
484
|
+
assert (
|
|
485
|
+
TestQueryTable.orderby(TestQueryTable.yet_another, TestQueryTable.number).to_sql()
|
|
486
|
+
== TestQueryTable.select(orderby=TestQueryTable.yet_another | TestQueryTable.number).to_sql()
|
|
487
|
+
)
|
|
488
|
+
|
|
482
489
|
|
|
483
490
|
def test_execute():
|
|
484
491
|
_setup_data()
|
|
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
|