TypeDAL 4.9.3__tar.gz → 4.9.4__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.3 → typedal-4.9.4}/CHANGELOG.md +6 -0
  2. {typedal-4.9.3 → typedal-4.9.4}/PKG-INFO +1 -1
  3. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/__about__.py +1 -1
  4. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/query_builder.py +45 -1
  5. {typedal-4.9.3 → typedal-4.9.4}/tests/test_query_builder.py +43 -0
  6. {typedal-4.9.3 → typedal-4.9.4}/.crush/.gitignore +0 -0
  7. {typedal-4.9.3 → typedal-4.9.4}/.crush/crush.db-shm +0 -0
  8. {typedal-4.9.3 → typedal-4.9.4}/.crush/crush.db-wal +0 -0
  9. {typedal-4.9.3 → typedal-4.9.4}/.crush/init +0 -0
  10. {typedal-4.9.3 → typedal-4.9.4}/.crush/logs/crush.log +0 -0
  11. {typedal-4.9.3 → typedal-4.9.4}/.github/workflows/su6.yml +0 -0
  12. {typedal-4.9.3 → typedal-4.9.4}/.gitignore +0 -0
  13. {typedal-4.9.3 → typedal-4.9.4}/.readthedocs.yml +0 -0
  14. {typedal-4.9.3 → typedal-4.9.4}/README.md +0 -0
  15. {typedal-4.9.3 → typedal-4.9.4}/coverage.svg +0 -0
  16. {typedal-4.9.3 → typedal-4.9.4}/docs/10_advanced_apis.md +0 -0
  17. {typedal-4.9.3 → typedal-4.9.4}/docs/1_getting_started.md +0 -0
  18. {typedal-4.9.3 → typedal-4.9.4}/docs/2_defining_tables.md +0 -0
  19. {typedal-4.9.3 → typedal-4.9.4}/docs/3_building_queries.md +0 -0
  20. {typedal-4.9.3 → typedal-4.9.4}/docs/4_relationships.md +0 -0
  21. {typedal-4.9.3 → typedal-4.9.4}/docs/5_py4web.md +0 -0
  22. {typedal-4.9.3 → typedal-4.9.4}/docs/6_migrations.md +0 -0
  23. {typedal-4.9.3 → typedal-4.9.4}/docs/7_configuration.md +0 -0
  24. {typedal-4.9.3 → typedal-4.9.4}/docs/8_mixins.md +0 -0
  25. {typedal-4.9.3 → typedal-4.9.4}/docs/9_memoization.md +0 -0
  26. {typedal-4.9.3 → typedal-4.9.4}/docs/css/code_blocks.css +0 -0
  27. {typedal-4.9.3 → typedal-4.9.4}/docs/index.md +0 -0
  28. {typedal-4.9.3 → typedal-4.9.4}/docs/requirements.txt +0 -0
  29. {typedal-4.9.3 → typedal-4.9.4}/example_new.py +0 -0
  30. {typedal-4.9.3 → typedal-4.9.4}/example_old.py +0 -0
  31. {typedal-4.9.3 → typedal-4.9.4}/mkdocs.yml +0 -0
  32. {typedal-4.9.3 → typedal-4.9.4}/pyproject.toml +0 -0
  33. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/__init__.py +0 -0
  34. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/caching.py +0 -0
  35. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/cli.py +0 -0
  36. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/config.py +0 -0
  37. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/constants.py +0 -0
  38. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/core.py +0 -0
  39. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/define.py +0 -0
  40. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/enum_helpers.py +0 -0
  41. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/fields.py +0 -0
  42. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/for_py4web.py +0 -0
  43. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/for_web2py.py +0 -0
  44. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/helpers.py +0 -0
  45. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/mixins.py +0 -0
  46. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/py.typed +0 -0
  47. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/relationships.py +0 -0
  48. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/rows.py +0 -0
  49. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/serializers/as_json.py +0 -0
  50. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/serializers/typescript.py +0 -0
  51. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/tables.py +0 -0
  52. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/types.py +0 -0
  53. {typedal-4.9.3 → typedal-4.9.4}/src/typedal/web2py_py4web_shared.py +0 -0
  54. {typedal-4.9.3 → typedal-4.9.4}/tasks.py +0 -0
  55. {typedal-4.9.3 → typedal-4.9.4}/tests/__init__.py +0 -0
  56. {typedal-4.9.3 → typedal-4.9.4}/tests/configs/simple.toml +0 -0
  57. {typedal-4.9.3 → typedal-4.9.4}/tests/configs/valid.env +0 -0
  58. {typedal-4.9.3 → typedal-4.9.4}/tests/configs/valid.toml +0 -0
  59. {typedal-4.9.3 → typedal-4.9.4}/tests/conftest.py +0 -0
  60. {typedal-4.9.3 → typedal-4.9.4}/tests/py314_tests.py +0 -0
  61. {typedal-4.9.3 → typedal-4.9.4}/tests/test_cli.py +0 -0
  62. {typedal-4.9.3 → typedal-4.9.4}/tests/test_config.py +0 -0
  63. {typedal-4.9.3 → typedal-4.9.4}/tests/test_docs_examples.py +0 -0
  64. {typedal-4.9.3 → typedal-4.9.4}/tests/test_helpers.py +0 -0
  65. {typedal-4.9.3 → typedal-4.9.4}/tests/test_json.py +0 -0
  66. {typedal-4.9.3 → typedal-4.9.4}/tests/test_main.py +0 -0
  67. {typedal-4.9.3 → typedal-4.9.4}/tests/test_mixins.py +0 -0
  68. {typedal-4.9.3 → typedal-4.9.4}/tests/test_mypy.py +0 -0
  69. {typedal-4.9.3 → typedal-4.9.4}/tests/test_orm.py +0 -0
  70. {typedal-4.9.3 → typedal-4.9.4}/tests/test_py4web.py +0 -0
  71. {typedal-4.9.3 → typedal-4.9.4}/tests/test_relationships.py +0 -0
  72. {typedal-4.9.3 → typedal-4.9.4}/tests/test_row.py +0 -0
  73. {typedal-4.9.3 → typedal-4.9.4}/tests/test_stats.py +0 -0
  74. {typedal-4.9.3 → typedal-4.9.4}/tests/test_table.py +0 -0
  75. {typedal-4.9.3 → typedal-4.9.4}/tests/test_typescript.py +0 -0
  76. {typedal-4.9.3 → typedal-4.9.4}/tests/test_typing_mypy.md +0 -0
  77. {typedal-4.9.3 → typedal-4.9.4}/tests/test_typing_pyright.md +0 -0
  78. {typedal-4.9.3 → typedal-4.9.4}/tests/test_web2py.py +0 -0
  79. {typedal-4.9.3 → typedal-4.9.4}/tests/test_xx_others.py +0 -0
  80. {typedal-4.9.3 → typedal-4.9.4}/tests/timings.py +0 -0
@@ -2,6 +2,12 @@
2
2
 
3
3
  <!--next-version-placeholder-->
4
4
 
5
+ ## v4.9.4 (2026-07-03)
6
+
7
+ ### Fix
8
+
9
+ * **query_builder:** Include orderby fields in distinct-id subquery for paginated joins ([`d343a43`](https://github.com/trialandsuccess/TypeDAL/commit/d343a4326fdbf93fb5fd8b5db061ea5b3355e3de))
10
+
5
11
  ## v4.9.3 (2026-07-03)
6
12
 
7
13
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TypeDAL
3
- Version: 4.9.3
3
+ Version: 4.9.4
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__ = "4.9.3"
8
+ __version__ = "4.9.4"
@@ -818,6 +818,46 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
818
818
 
819
819
  return joins
820
820
 
821
+ def _selectable_orderby_fields(self, orderby: OrderBy | t.Iterable[OrderBy] | None) -> list[OrderBy]:
822
+ """Extract field values from pydal orderby expressions."""
823
+ if not orderby:
824
+ return []
825
+
826
+ if isinstance(orderby, (list, tuple, set)):
827
+ return [field for item in orderby for field in self._selectable_orderby_fields(item)]
828
+
829
+ if isinstance(orderby, pydal.objects.Field):
830
+ return [orderby]
831
+
832
+ fields = []
833
+ first = getattr(orderby, "first", None)
834
+ second = getattr(orderby, "second", None)
835
+
836
+ if first is not None:
837
+ fields.extend(self._selectable_orderby_fields(first))
838
+ if second is not None:
839
+ fields.extend(self._selectable_orderby_fields(second))
840
+
841
+ return fields
842
+
843
+ def _select_distinct_ids_with_orderby_fields(self, query: Query, select_kwargs: SelectKwargs) -> str:
844
+ db = self._get_db()
845
+ model = self.model
846
+ select_args: list[OrderBy] = [model.id]
847
+ seen = {str(model.id)}
848
+
849
+ for field in self._selectable_orderby_fields(select_kwargs.get("orderby")):
850
+ key = str(field)
851
+ if key not in seen:
852
+ select_args.append(field)
853
+ seen.add(key)
854
+
855
+ ids = db(query)._select(*select_args, **select_kwargs).rstrip(";")
856
+ id_column = getattr(model.id, "_raw_rname", model.id.name)
857
+ return f'SELECT "{id_column}" FROM ({ids}) AS typedal_paginate_ids' # nosec:
858
+ # id_column originates from code
859
+ # ids is a safe subquery, originating from code
860
+
821
861
  def _apply_limitby_optimization(
822
862
  self,
823
863
  query: Query,
@@ -839,7 +879,11 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
839
879
  kwargs["join"] = joins
840
880
  kwargs["distinct"] = True
841
881
 
842
- ids = db(query)._select(model.id, **kwargs)
882
+ if joins and kwargs.get("orderby"):
883
+ ids = self._select_distinct_ids_with_orderby_fields(query, kwargs)
884
+ else:
885
+ ids = db(query)._select(model.id, **kwargs)
886
+
843
887
  query = model.id.belongs(ids)
844
888
  metadata["ids"] = ids
845
889
 
@@ -519,6 +519,10 @@ def test_orderby():
519
519
  base_qt = TestQueryTable.select(TestQueryTable.id, TestQueryTable.number)
520
520
 
521
521
  assert base_qt.count() == 5
522
+ assert base_qt._selectable_orderby_fields(None) == []
523
+
524
+ composite_fields = base_qt._selectable_orderby_fields(TestQueryTable.number | TestQueryTable.other)
525
+ assert composite_fields == [TestQueryTable.number, TestQueryTable.other]
522
526
 
523
527
  rows1 = base_qt.orderby(TestQueryTable.id).paginate(limit=3, page=1)
524
528
  rows2 = base_qt.select(orderby=TestQueryTable.id, limitby=(0, 3)).collect()
@@ -602,6 +606,45 @@ def test_select_kwargs_use_rname_psql(dal_psql: TypeDAL):
602
606
  assert "some_table.some_name" in sql
603
607
 
604
608
 
609
+ def test_paginate_inner_joins_with_related_orderby_psql(dal_psql: TypeDAL):
610
+ db = dal_psql
611
+
612
+ @db.define()
613
+ class Method(TypedTable):
614
+ gid = TypedField(str)
615
+ name = TypedField(str)
616
+
617
+ @db.define()
618
+ class Supplier(TypedTable):
619
+ gid = TypedField(str)
620
+ name = TypedField(str)
621
+
622
+ @db.define()
623
+ class Product(TypedTable):
624
+ name = TypedField(str)
625
+ method: Method
626
+ supplier: Supplier
627
+
628
+ supplier = Supplier.insert(gid="supplier", name="Supplier")
629
+ slow = Method.insert(gid="slow", name="Slow")
630
+ fast = Method.insert(gid="fast", name="Fast")
631
+ Product.insert(name="Product slow", method=slow, supplier=supplier)
632
+ Product.insert(name="Product fast", method=fast, supplier=supplier)
633
+ db.commit()
634
+
635
+ builder = Product.join("method", method="inner").join("supplier", method="inner")
636
+ method_relation = builder.relationships["method"]
637
+ method_alias = method_relation.get_table(db).with_alias(f"method_{hash(method_relation)}")
638
+ builder = builder.orderby(method_alias.name)
639
+ builder = builder.select(Method.gid, Method.name)
640
+ builder = builder.select(Supplier.gid, Supplier.name)
641
+
642
+ page = builder.paginate(page=1, limit=1)
643
+
644
+ assert len(page) == 1
645
+ assert page.first().method.name == "Fast"
646
+
647
+
605
648
  def test_execute():
606
649
  _setup_data()
607
650
 
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