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.

Files changed (58) hide show
  1. {typedal-3.17.0 → typedal-3.17.2}/CHANGELOG.md +12 -0
  2. {typedal-3.17.0 → typedal-3.17.2}/PKG-INFO +1 -1
  3. {typedal-3.17.0 → typedal-3.17.2}/src/typedal/__about__.py +1 -1
  4. {typedal-3.17.0 → typedal-3.17.2}/src/typedal/core.py +26 -3
  5. {typedal-3.17.0 → typedal-3.17.2}/src/typedal/helpers.py +24 -4
  6. {typedal-3.17.0 → typedal-3.17.2}/src/typedal/types.py +3 -1
  7. {typedal-3.17.0 → typedal-3.17.2}/tests/test_helpers.py +5 -1
  8. {typedal-3.17.0 → typedal-3.17.2}/tests/test_query_builder.py +10 -3
  9. {typedal-3.17.0 → typedal-3.17.2}/.github/workflows/su6.yml +0 -0
  10. {typedal-3.17.0 → typedal-3.17.2}/.gitignore +0 -0
  11. {typedal-3.17.0 → typedal-3.17.2}/.readthedocs.yml +0 -0
  12. {typedal-3.17.0 → typedal-3.17.2}/README.md +0 -0
  13. {typedal-3.17.0 → typedal-3.17.2}/coverage.svg +0 -0
  14. {typedal-3.17.0 → typedal-3.17.2}/docs/1_getting_started.md +0 -0
  15. {typedal-3.17.0 → typedal-3.17.2}/docs/2_defining_tables.md +0 -0
  16. {typedal-3.17.0 → typedal-3.17.2}/docs/3_building_queries.md +0 -0
  17. {typedal-3.17.0 → typedal-3.17.2}/docs/4_relationships.md +0 -0
  18. {typedal-3.17.0 → typedal-3.17.2}/docs/5_py4web.md +0 -0
  19. {typedal-3.17.0 → typedal-3.17.2}/docs/6_migrations.md +0 -0
  20. {typedal-3.17.0 → typedal-3.17.2}/docs/7_mixins.md +0 -0
  21. {typedal-3.17.0 → typedal-3.17.2}/docs/css/code_blocks.css +0 -0
  22. {typedal-3.17.0 → typedal-3.17.2}/docs/index.md +0 -0
  23. {typedal-3.17.0 → typedal-3.17.2}/docs/requirements.txt +0 -0
  24. {typedal-3.17.0 → typedal-3.17.2}/example_new.py +0 -0
  25. {typedal-3.17.0 → typedal-3.17.2}/example_old.py +0 -0
  26. {typedal-3.17.0 → typedal-3.17.2}/mkdocs.yml +0 -0
  27. {typedal-3.17.0 → typedal-3.17.2}/pyproject.toml +0 -0
  28. {typedal-3.17.0 → typedal-3.17.2}/src/typedal/__init__.py +0 -0
  29. {typedal-3.17.0 → typedal-3.17.2}/src/typedal/caching.py +0 -0
  30. {typedal-3.17.0 → typedal-3.17.2}/src/typedal/cli.py +0 -0
  31. {typedal-3.17.0 → typedal-3.17.2}/src/typedal/config.py +0 -0
  32. {typedal-3.17.0 → typedal-3.17.2}/src/typedal/fields.py +0 -0
  33. {typedal-3.17.0 → typedal-3.17.2}/src/typedal/for_py4web.py +0 -0
  34. {typedal-3.17.0 → typedal-3.17.2}/src/typedal/for_web2py.py +0 -0
  35. {typedal-3.17.0 → typedal-3.17.2}/src/typedal/mixins.py +0 -0
  36. {typedal-3.17.0 → typedal-3.17.2}/src/typedal/py.typed +0 -0
  37. {typedal-3.17.0 → typedal-3.17.2}/src/typedal/serializers/as_json.py +0 -0
  38. {typedal-3.17.0 → typedal-3.17.2}/src/typedal/web2py_py4web_shared.py +0 -0
  39. {typedal-3.17.0 → typedal-3.17.2}/tests/__init__.py +0 -0
  40. {typedal-3.17.0 → typedal-3.17.2}/tests/configs/simple.toml +0 -0
  41. {typedal-3.17.0 → typedal-3.17.2}/tests/configs/valid.env +0 -0
  42. {typedal-3.17.0 → typedal-3.17.2}/tests/configs/valid.toml +0 -0
  43. {typedal-3.17.0 → typedal-3.17.2}/tests/test_cli.py +0 -0
  44. {typedal-3.17.0 → typedal-3.17.2}/tests/test_config.py +0 -0
  45. {typedal-3.17.0 → typedal-3.17.2}/tests/test_docs_examples.py +0 -0
  46. {typedal-3.17.0 → typedal-3.17.2}/tests/test_json.py +0 -0
  47. {typedal-3.17.0 → typedal-3.17.2}/tests/test_main.py +0 -0
  48. {typedal-3.17.0 → typedal-3.17.2}/tests/test_mixins.py +0 -0
  49. {typedal-3.17.0 → typedal-3.17.2}/tests/test_mypy.py +0 -0
  50. {typedal-3.17.0 → typedal-3.17.2}/tests/test_orm.py +0 -0
  51. {typedal-3.17.0 → typedal-3.17.2}/tests/test_py4web.py +0 -0
  52. {typedal-3.17.0 → typedal-3.17.2}/tests/test_relationships.py +0 -0
  53. {typedal-3.17.0 → typedal-3.17.2}/tests/test_row.py +0 -0
  54. {typedal-3.17.0 → typedal-3.17.2}/tests/test_stats.py +0 -0
  55. {typedal-3.17.0 → typedal-3.17.2}/tests/test_table.py +0 -0
  56. {typedal-3.17.0 → typedal-3.17.2}/tests/test_web2py.py +0 -0
  57. {typedal-3.17.0 → typedal-3.17.2}/tests/test_xx_others.py +0 -0
  58. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TypeDAL
3
- Version: 3.17.0
3
+ Version: 3.17.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
@@ -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__ = "3.17.0"
8
+ __version__ = "3.17.2"
@@ -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: str,
839
+ *raw_args: Any,
839
840
  output_type: str | None = None,
840
- **raw_kwargs: str,
841
- ) -> str:
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._adapter.adapt(placeholder) for placeholder in raw_args)
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._adapter.adapt(placeholder) for key, placeholder in raw_kwargs.items()}
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: str,
390
+ *raw_args: Any,
371
391
  output_type: str | None = None,
372
- **raw_kwargs: str,
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: Optional[Expression | str | Table]
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.select(orderby=TestQueryTable.id).paginate(limit=3, page=1)
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.select(orderby=TestQueryTable.number).paginate(limit=3, page=1)
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