TypeDAL 3.11.0__tar.gz → 3.12.0__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.11.0 → typedal-3.12.0}/CHANGELOG.md +13 -0
  2. {typedal-3.11.0 → typedal-3.12.0}/PKG-INFO +1 -1
  3. {typedal-3.11.0 → typedal-3.12.0}/docs/4_relationships.md +8 -2
  4. {typedal-3.11.0 → typedal-3.12.0}/src/typedal/__about__.py +1 -1
  5. {typedal-3.11.0 → typedal-3.12.0}/src/typedal/caching.py +1 -1
  6. {typedal-3.11.0 → typedal-3.12.0}/src/typedal/core.py +24 -7
  7. {typedal-3.11.0 → typedal-3.12.0}/src/typedal/for_py4web.py +2 -2
  8. {typedal-3.11.0 → typedal-3.12.0}/tests/test_relationships.py +5 -5
  9. {typedal-3.11.0 → typedal-3.12.0}/.github/workflows/su6.yml +0 -0
  10. {typedal-3.11.0 → typedal-3.12.0}/.gitignore +0 -0
  11. {typedal-3.11.0 → typedal-3.12.0}/.readthedocs.yml +0 -0
  12. {typedal-3.11.0 → typedal-3.12.0}/README.md +0 -0
  13. {typedal-3.11.0 → typedal-3.12.0}/coverage.svg +0 -0
  14. {typedal-3.11.0 → typedal-3.12.0}/docs/1_getting_started.md +0 -0
  15. {typedal-3.11.0 → typedal-3.12.0}/docs/2_defining_tables.md +0 -0
  16. {typedal-3.11.0 → typedal-3.12.0}/docs/3_building_queries.md +0 -0
  17. {typedal-3.11.0 → typedal-3.12.0}/docs/5_py4web.md +0 -0
  18. {typedal-3.11.0 → typedal-3.12.0}/docs/6_migrations.md +0 -0
  19. {typedal-3.11.0 → typedal-3.12.0}/docs/7_mixins.md +0 -0
  20. {typedal-3.11.0 → typedal-3.12.0}/docs/css/code_blocks.css +0 -0
  21. {typedal-3.11.0 → typedal-3.12.0}/docs/index.md +0 -0
  22. {typedal-3.11.0 → typedal-3.12.0}/docs/requirements.txt +0 -0
  23. {typedal-3.11.0 → typedal-3.12.0}/example_new.py +0 -0
  24. {typedal-3.11.0 → typedal-3.12.0}/example_old.py +0 -0
  25. {typedal-3.11.0 → typedal-3.12.0}/mkdocs.yml +0 -0
  26. {typedal-3.11.0 → typedal-3.12.0}/pyproject.toml +0 -0
  27. {typedal-3.11.0 → typedal-3.12.0}/src/typedal/__init__.py +0 -0
  28. {typedal-3.11.0 → typedal-3.12.0}/src/typedal/cli.py +0 -0
  29. {typedal-3.11.0 → typedal-3.12.0}/src/typedal/config.py +0 -0
  30. {typedal-3.11.0 → typedal-3.12.0}/src/typedal/fields.py +0 -0
  31. {typedal-3.11.0 → typedal-3.12.0}/src/typedal/for_web2py.py +0 -0
  32. {typedal-3.11.0 → typedal-3.12.0}/src/typedal/helpers.py +0 -0
  33. {typedal-3.11.0 → typedal-3.12.0}/src/typedal/mixins.py +0 -0
  34. {typedal-3.11.0 → typedal-3.12.0}/src/typedal/py.typed +0 -0
  35. {typedal-3.11.0 → typedal-3.12.0}/src/typedal/serializers/as_json.py +0 -0
  36. {typedal-3.11.0 → typedal-3.12.0}/src/typedal/types.py +0 -0
  37. {typedal-3.11.0 → typedal-3.12.0}/src/typedal/web2py_py4web_shared.py +0 -0
  38. {typedal-3.11.0 → typedal-3.12.0}/tests/__init__.py +0 -0
  39. {typedal-3.11.0 → typedal-3.12.0}/tests/configs/simple.toml +0 -0
  40. {typedal-3.11.0 → typedal-3.12.0}/tests/configs/valid.env +0 -0
  41. {typedal-3.11.0 → typedal-3.12.0}/tests/configs/valid.toml +0 -0
  42. {typedal-3.11.0 → typedal-3.12.0}/tests/test_cli.py +0 -0
  43. {typedal-3.11.0 → typedal-3.12.0}/tests/test_config.py +0 -0
  44. {typedal-3.11.0 → typedal-3.12.0}/tests/test_docs_examples.py +0 -0
  45. {typedal-3.11.0 → typedal-3.12.0}/tests/test_helpers.py +0 -0
  46. {typedal-3.11.0 → typedal-3.12.0}/tests/test_json.py +0 -0
  47. {typedal-3.11.0 → typedal-3.12.0}/tests/test_main.py +0 -0
  48. {typedal-3.11.0 → typedal-3.12.0}/tests/test_mixins.py +0 -0
  49. {typedal-3.11.0 → typedal-3.12.0}/tests/test_mypy.py +0 -0
  50. {typedal-3.11.0 → typedal-3.12.0}/tests/test_orm.py +0 -0
  51. {typedal-3.11.0 → typedal-3.12.0}/tests/test_py4web.py +0 -0
  52. {typedal-3.11.0 → typedal-3.12.0}/tests/test_query_builder.py +0 -0
  53. {typedal-3.11.0 → typedal-3.12.0}/tests/test_row.py +0 -0
  54. {typedal-3.11.0 → typedal-3.12.0}/tests/test_stats.py +0 -0
  55. {typedal-3.11.0 → typedal-3.12.0}/tests/test_table.py +0 -0
  56. {typedal-3.11.0 → typedal-3.12.0}/tests/test_web2py.py +0 -0
  57. {typedal-3.11.0 → typedal-3.12.0}/tests/test_xx_others.py +0 -0
  58. {typedal-3.11.0 → typedal-3.12.0}/tests/timings.py +0 -0
@@ -2,6 +2,19 @@
2
2
 
3
3
  <!--next-version-placeholder-->
4
4
 
5
+ ## v3.12.0 (2025-04-25)
6
+
7
+ ### Feature
8
+
9
+ * Support adding a `unique_alias` which improves working with multiple joins. ([`8b2112c`](https://github.com/trialandsuccess/TypeDAL/commit/8b2112cf9c97a00a9610e1af6e80e05fcc296aad))
10
+
11
+ ## v3.11.1 (2025-04-25)
12
+
13
+ ### Fix
14
+
15
+ * Improved typing (`mypy` is happy again, py4web DAL should be typed better now) ([`b3199cb`](https://github.com/trialandsuccess/TypeDAL/commit/b3199cb423f643b1826fedab45bcaa3063fdc02d))
16
+ * `repr(rows)` crashed when `rows` contained no data ([`cc3b2d0`](https://github.com/trialandsuccess/TypeDAL/commit/cc3b2d0bd332faee579e6fdfb7068bd511f22093))
17
+
5
18
  ## v3.11.0 (2025-04-25)
6
19
 
7
20
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TypeDAL
3
- Version: 3.11.0
3
+ Version: 3.12.0
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
@@ -92,16 +92,22 @@ Adding the `Relationship["Sidekick"]` hint is optional, but recommended to impro
92
92
  Setting up a relationship that uses a junction/pivot table is slightly harder.
93
93
 
94
94
  ```python
95
+
96
+ # with `unique_alias()` which is better if you have multiple joins:
97
+
95
98
  @db.define()
96
99
  class Post(TypedTable):
97
100
  title: str
98
101
  author: Author
99
102
 
100
103
  tags = relationship(list["Tag"], on=lambda post, tag: [
101
- Tagged.on(Tagged.post == post.id),
102
- tag.on(tag.id == Tagged.tag),
104
+ # post and tag already have a unique alias, create one for tagged here:
105
+ tagged := Tagged.unique_alias(),
106
+ tagged.on(tagged.post == post.id),
107
+ tag.on(tag.id == tagged.tag),
103
108
  ])
104
109
 
110
+ # without unique alias:
105
111
 
106
112
  @db.define()
107
113
  class Tag(TypedTable):
@@ -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.11.0"
8
+ __version__ = "3.12.0"
@@ -385,7 +385,7 @@ def _calculate_stats(db: "TypeDAL", query: Query) -> GenericStats:
385
385
  sum_len_field = _TypedalCache.data.len().sum()
386
386
  size_row = db(query).select(sum_len_field).first()
387
387
 
388
- size = size_row[sum_len_field] if size_row else 0 # type: ignore
388
+ size = size_row[sum_len_field] if size_row else 0
389
389
 
390
390
  return {
391
391
  "entries": _TypedalCache.where(query).count(),
@@ -11,6 +11,7 @@ import math
11
11
  import sys
12
12
  import types
13
13
  import typing
14
+ import uuid
14
15
  import warnings
15
16
  from collections import defaultdict
16
17
  from copy import copy
@@ -189,7 +190,7 @@ class Relationship(typing.Generic[To_Type]):
189
190
  and_code = inspect.getsource(c_and).strip()
190
191
  src_code += " AND " + and_code
191
192
  else:
192
- cls_name = self._type if isinstance(self._type, str) else self._type.__name__ # type: ignore
193
+ cls_name = self._type if isinstance(self._type, str) else self._type.__name__
193
194
  src_code = f"to {cls_name} (missing condition)"
194
195
 
195
196
  join = f":{self.join}" if self.join else ""
@@ -1179,11 +1180,21 @@ class TableMeta(type):
1179
1180
  Useful for joins when joining the same table multiple times.
1180
1181
 
1181
1182
  See Also:
1182
- http://web2py.com/books/default/chapter/29/06/the-database-abstraction-layer?search=export_to_csv_file#One-to-many-relation
1183
+ http://web2py.com/books/default/chapter/29/06/the-database-abstraction-layer#One-to-many-relation
1183
1184
  """
1184
1185
  table = self._ensure_table_defined()
1185
1186
  return typing.cast(Type[T_MetaInstance], table.with_alias(alias))
1186
1187
 
1188
+ def unique_alias(self: Type[T_MetaInstance]) -> Type[T_MetaInstance]:
1189
+ """
1190
+ Generates a unique alias for this table.
1191
+
1192
+ Useful for joins when joining the same table multiple times
1193
+ and you don't want to keep track of aliases yourself.
1194
+ """
1195
+ key = f"{self.__name__.lower()}_{hash(uuid.uuid4())}"
1196
+ return self.with_alias(key)
1197
+
1187
1198
  # hooks:
1188
1199
  def before_insert(
1189
1200
  cls: Type[T_MetaInstance],
@@ -1270,9 +1281,9 @@ class TypedField(Expression, typing.Generic[T_Value]): # pragma: no cover
1270
1281
 
1271
1282
  def __init__(
1272
1283
  self,
1273
- _type: Type[T_Value] | types.UnionType = str,
1284
+ _type: Type[T_Value] | types.UnionType = str, # type: ignore
1274
1285
  /,
1275
- **settings: Unpack[FieldSettings], # type: ignore
1286
+ **settings: Unpack[FieldSettings],
1276
1287
  ) -> None:
1277
1288
  """
1278
1289
  Typed version of pydal.Field, which will be converted to a normal Field in the background.
@@ -2683,6 +2694,12 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2683
2694
  if not isinstance(on, list): # pragma: no cover
2684
2695
  on = [on]
2685
2696
 
2697
+ on = [
2698
+ _
2699
+ for _ in on
2700
+ # only allow Expressions (query and such):
2701
+ if isinstance(_, pydal.objects.Expression)
2702
+ ]
2686
2703
  left.extend(on)
2687
2704
  elif method == "left":
2688
2705
  # .on not given, generate it:
@@ -2794,7 +2811,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2794
2811
  """
2795
2812
  yield from self.collect()
2796
2813
 
2797
- def __count(self, db: TypeDAL, distinct: bool = None):
2814
+ def __count(self, db: TypeDAL, distinct: bool = None) -> Query:
2798
2815
  # internal, shared logic between .count and ._count
2799
2816
  model = self.model
2800
2817
  query = self.query
@@ -2819,14 +2836,14 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2819
2836
 
2820
2837
  return db(query).count(distinct)
2821
2838
 
2822
- def _count(self, distinct: bool = None):
2839
+ def _count(self, distinct: bool = None) -> str:
2823
2840
  """
2824
2841
  Return the SQL for .count().
2825
2842
  """
2826
2843
  db = self._get_db()
2827
2844
  query = self.__count(db, distinct=distinct)
2828
2845
 
2829
- return db(query)._count(distinct)
2846
+ return typing.cast(str, db(query)._count(distinct))
2830
2847
 
2831
2848
  def exists(self) -> bool:
2832
2849
  """
@@ -22,9 +22,9 @@ class Fixture(_Fixture): # type: ignore
22
22
 
23
23
 
24
24
  class PY4WEB_DAL_SINGLETON(MetaDAL):
25
- _instances: typing.ClassVar[typing.MutableMapping[str, AnyType]] = {}
25
+ _instances: typing.ClassVar[typing.MutableMapping[str, TypeDAL]] = {}
26
26
 
27
- def __call__(cls, uri: typing.Optional[str] = None, *args: typing.Any, **kwargs: typing.Any) -> AnyType:
27
+ def __call__(cls, uri: typing.Optional[str] = None, *args: typing.Any, **kwargs: typing.Any) -> TypeDAL:
28
28
  db_uid = kwargs.get("db_uid", hashlib_md5(repr(uri or (args, kwargs))).hexdigest())
29
29
  if db_uid not in cls._instances:
30
30
  cls._instances[db_uid] = super().__call__(uri, *args, **kwargs)
@@ -25,10 +25,11 @@ class TaggableMixin:
25
25
  list["Tag"],
26
26
  # lambda self, _: (Tagged.entity == self.gid) & (Tagged.tag == Tag.id)
27
27
  # doing an .on with and & inside can lead to a cross join,
28
- # for relationships with pivot tables a manual on query is prefered:
29
- on=lambda entity, _tag: [
30
- Tagged.on(Tagged.entity == entity.gid),
31
- Tag.on((Tagged.tag == Tag.id)),
28
+ # for relationships with pivot tables a manual on query with aliases is prefered:
29
+ on=lambda entity, tag: [
30
+ tagged := Tagged.unique_alias(),
31
+ tagged.on(tagged.entity == entity.gid),
32
+ tag.on((tagged.tag == tag.id)),
32
33
  ],
33
34
  )
34
35
  # tags = relationship(list["Tag"], tagged)
@@ -186,7 +187,6 @@ def test_typedal_way():
186
187
  Empty.first_or_fail()
187
188
 
188
189
  # user through article: 1 - many
189
-
190
190
  all_articles = Article.join().collect().as_dict()
191
191
 
192
192
  assert all_articles[3]["final_editor"]["name"] == "Editor 1"
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