TypeDAL 3.10.5__tar.gz → 3.11.1__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 (59) hide show
  1. {typedal-3.10.5 → typedal-3.11.1}/CHANGELOG.md +14 -0
  2. {typedal-3.10.5 → typedal-3.11.1}/PKG-INFO +2 -2
  3. typedal-3.11.1/coverage.svg +1 -0
  4. {typedal-3.10.5 → typedal-3.11.1}/src/typedal/__about__.py +1 -1
  5. {typedal-3.10.5 → typedal-3.11.1}/src/typedal/caching.py +1 -1
  6. {typedal-3.10.5 → typedal-3.11.1}/src/typedal/core.py +43 -9
  7. {typedal-3.10.5 → typedal-3.11.1}/src/typedal/for_py4web.py +2 -2
  8. {typedal-3.10.5 → typedal-3.11.1}/tests/test_query_builder.py +3 -0
  9. {typedal-3.10.5 → typedal-3.11.1}/tests/test_table.py +1 -0
  10. {typedal-3.10.5 → typedal-3.11.1}/tests/timings.py +1 -0
  11. typedal-3.10.5/coverage.svg +0 -1
  12. {typedal-3.10.5 → typedal-3.11.1}/.github/workflows/su6.yml +0 -0
  13. {typedal-3.10.5 → typedal-3.11.1}/.gitignore +0 -0
  14. {typedal-3.10.5 → typedal-3.11.1}/.readthedocs.yml +0 -0
  15. {typedal-3.10.5 → typedal-3.11.1}/README.md +0 -0
  16. {typedal-3.10.5 → typedal-3.11.1}/docs/1_getting_started.md +0 -0
  17. {typedal-3.10.5 → typedal-3.11.1}/docs/2_defining_tables.md +0 -0
  18. {typedal-3.10.5 → typedal-3.11.1}/docs/3_building_queries.md +0 -0
  19. {typedal-3.10.5 → typedal-3.11.1}/docs/4_relationships.md +0 -0
  20. {typedal-3.10.5 → typedal-3.11.1}/docs/5_py4web.md +0 -0
  21. {typedal-3.10.5 → typedal-3.11.1}/docs/6_migrations.md +0 -0
  22. {typedal-3.10.5 → typedal-3.11.1}/docs/7_mixins.md +0 -0
  23. {typedal-3.10.5 → typedal-3.11.1}/docs/css/code_blocks.css +0 -0
  24. {typedal-3.10.5 → typedal-3.11.1}/docs/index.md +0 -0
  25. {typedal-3.10.5 → typedal-3.11.1}/docs/requirements.txt +0 -0
  26. {typedal-3.10.5 → typedal-3.11.1}/example_new.py +0 -0
  27. {typedal-3.10.5 → typedal-3.11.1}/example_old.py +0 -0
  28. {typedal-3.10.5 → typedal-3.11.1}/mkdocs.yml +0 -0
  29. {typedal-3.10.5 → typedal-3.11.1}/pyproject.toml +0 -0
  30. {typedal-3.10.5 → typedal-3.11.1}/src/typedal/__init__.py +0 -0
  31. {typedal-3.10.5 → typedal-3.11.1}/src/typedal/cli.py +0 -0
  32. {typedal-3.10.5 → typedal-3.11.1}/src/typedal/config.py +0 -0
  33. {typedal-3.10.5 → typedal-3.11.1}/src/typedal/fields.py +0 -0
  34. {typedal-3.10.5 → typedal-3.11.1}/src/typedal/for_web2py.py +0 -0
  35. {typedal-3.10.5 → typedal-3.11.1}/src/typedal/helpers.py +0 -0
  36. {typedal-3.10.5 → typedal-3.11.1}/src/typedal/mixins.py +0 -0
  37. {typedal-3.10.5 → typedal-3.11.1}/src/typedal/py.typed +0 -0
  38. {typedal-3.10.5 → typedal-3.11.1}/src/typedal/serializers/as_json.py +0 -0
  39. {typedal-3.10.5 → typedal-3.11.1}/src/typedal/types.py +0 -0
  40. {typedal-3.10.5 → typedal-3.11.1}/src/typedal/web2py_py4web_shared.py +0 -0
  41. {typedal-3.10.5 → typedal-3.11.1}/tests/__init__.py +0 -0
  42. {typedal-3.10.5 → typedal-3.11.1}/tests/configs/simple.toml +0 -0
  43. {typedal-3.10.5 → typedal-3.11.1}/tests/configs/valid.env +0 -0
  44. {typedal-3.10.5 → typedal-3.11.1}/tests/configs/valid.toml +0 -0
  45. {typedal-3.10.5 → typedal-3.11.1}/tests/test_cli.py +0 -0
  46. {typedal-3.10.5 → typedal-3.11.1}/tests/test_config.py +0 -0
  47. {typedal-3.10.5 → typedal-3.11.1}/tests/test_docs_examples.py +0 -0
  48. {typedal-3.10.5 → typedal-3.11.1}/tests/test_helpers.py +0 -0
  49. {typedal-3.10.5 → typedal-3.11.1}/tests/test_json.py +0 -0
  50. {typedal-3.10.5 → typedal-3.11.1}/tests/test_main.py +0 -0
  51. {typedal-3.10.5 → typedal-3.11.1}/tests/test_mixins.py +0 -0
  52. {typedal-3.10.5 → typedal-3.11.1}/tests/test_mypy.py +0 -0
  53. {typedal-3.10.5 → typedal-3.11.1}/tests/test_orm.py +0 -0
  54. {typedal-3.10.5 → typedal-3.11.1}/tests/test_py4web.py +0 -0
  55. {typedal-3.10.5 → typedal-3.11.1}/tests/test_relationships.py +0 -0
  56. {typedal-3.10.5 → typedal-3.11.1}/tests/test_row.py +0 -0
  57. {typedal-3.10.5 → typedal-3.11.1}/tests/test_stats.py +0 -0
  58. {typedal-3.10.5 → typedal-3.11.1}/tests/test_web2py.py +0 -0
  59. {typedal-3.10.5 → typedal-3.11.1}/tests/test_xx_others.py +0 -0
@@ -2,6 +2,20 @@
2
2
 
3
3
  <!--next-version-placeholder-->
4
4
 
5
+ ## v3.11.1 (2025-04-25)
6
+
7
+ ### Fix
8
+
9
+ * Improved typing (`mypy` is happy again, py4web DAL should be typed better now) ([`b3199cb`](https://github.com/trialandsuccess/TypeDAL/commit/b3199cb423f643b1826fedab45bcaa3063fdc02d))
10
+ * `repr(rows)` crashed when `rows` contained no data ([`cc3b2d0`](https://github.com/trialandsuccess/TypeDAL/commit/cc3b2d0bd332faee579e6fdfb7068bd511f22093))
11
+
12
+ ## v3.11.0 (2025-04-25)
13
+
14
+ ### Fix
15
+
16
+ * Add `._count()` to get sql for `.count()` ([`6a336e9`](https://github.com/trialandsuccess/TypeDAL/commit/6a336e903017a2fcfac9bea313f150e5af8d77de))
17
+ * Add `.exists()` method on querybuilder to check if there are any rows (using `.count()`) ([`7e54e64`](https://github.com/trialandsuccess/TypeDAL/commit/7e54e64461e04f6e65a9aa4d1736585f088bc9d1))
18
+
5
19
  ## v3.10.5 (2025-04-22)
6
20
 
7
21
  ### Fix
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: TypeDAL
3
- Version: 3.10.5
3
+ Version: 3.11.1
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
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="122" height="20" role="img" aria-label="coverage: 100.00%"><title>coverage: 100.00%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="122" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="61" height="20" fill="#4c1"/><rect width="122" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">coverage</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">coverage</text><text aria-hidden="true" x="905" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">100.00%</text><text x="905" y="140" transform="scale(.1)" fill="#fff" textLength="510">100.00%</text></g></svg>
@@ -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.10.5"
8
+ __version__ = "3.11.1"
@@ -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(),
@@ -189,7 +189,7 @@ class Relationship(typing.Generic[To_Type]):
189
189
  and_code = inspect.getsource(c_and).strip()
190
190
  src_code += " AND " + and_code
191
191
  else:
192
- cls_name = self._type if isinstance(self._type, str) else self._type.__name__ # type: ignore
192
+ cls_name = self._type if isinstance(self._type, str) else self._type.__name__
193
193
  src_code = f"to {cls_name} (missing condition)"
194
194
 
195
195
  join = f":{self.join}" if self.join else ""
@@ -1041,6 +1041,12 @@ class TableMeta(type):
1041
1041
  """
1042
1042
  return QueryBuilder(self).count()
1043
1043
 
1044
+ def exists(self: Type[T_MetaInstance]) -> bool:
1045
+ """
1046
+ See QueryBuilder.exists!
1047
+ """
1048
+ return QueryBuilder(self).exists()
1049
+
1044
1050
  def first(self: Type[T_MetaInstance]) -> T_MetaInstance | None:
1045
1051
  """
1046
1052
  See QueryBuilder.first!
@@ -1264,9 +1270,9 @@ class TypedField(Expression, typing.Generic[T_Value]): # pragma: no cover
1264
1270
 
1265
1271
  def __init__(
1266
1272
  self,
1267
- _type: Type[T_Value] | types.UnionType = str,
1273
+ _type: Type[T_Value] | types.UnionType = str, # type: ignore
1268
1274
  /,
1269
- **settings: Unpack[FieldSettings], # type: ignore
1275
+ **settings: Unpack[FieldSettings],
1270
1276
  ) -> None:
1271
1277
  """
1272
1278
  Typed version of pydal.Field, which will be converted to a normal Field in the background.
@@ -2788,14 +2794,10 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2788
2794
  """
2789
2795
  yield from self.collect()
2790
2796
 
2791
- def count(self, distinct: bool = None) -> int:
2792
- """
2793
- Return the amount of rows matching the current query.
2794
- """
2795
- db = self._get_db()
2797
+ def __count(self, db: TypeDAL, distinct: bool = None) -> Query:
2798
+ # internal, shared logic between .count and ._count
2796
2799
  model = self.model
2797
2800
  query = self.query
2798
-
2799
2801
  for key, relation in self.relationships.items():
2800
2802
  if (not relation.condition or relation.join != "inner") and not distinct:
2801
2803
  continue
@@ -2806,8 +2808,40 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2806
2808
  other = other.with_alias(f"{key}_{hash(relation)}")
2807
2809
  query &= relation.condition(model, other)
2808
2810
 
2811
+ return query
2812
+
2813
+ def count(self, distinct: bool = None) -> int:
2814
+ """
2815
+ Return the amount of rows matching the current query.
2816
+ """
2817
+ db = self._get_db()
2818
+ query = self.__count(db, distinct=distinct)
2819
+
2809
2820
  return db(query).count(distinct)
2810
2821
 
2822
+ def _count(self, distinct: bool = None) -> str:
2823
+ """
2824
+ Return the SQL for .count().
2825
+ """
2826
+ db = self._get_db()
2827
+ query = self.__count(db, distinct=distinct)
2828
+
2829
+ return typing.cast(
2830
+ str,
2831
+ db(query)._count(distinct)
2832
+ )
2833
+
2834
+ def exists(self) -> bool:
2835
+ """
2836
+ Determines if any records exist matching the current query.
2837
+
2838
+ Returns True if one or more records exist; otherwise, False.
2839
+
2840
+ Returns:
2841
+ bool: A boolean indicating whether any records exist.
2842
+ """
2843
+ return bool(self.count())
2844
+
2811
2845
  def __paginate(
2812
2846
  self,
2813
2847
  limit: int,
@@ -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)
@@ -318,6 +318,9 @@ def test_complex_join():
318
318
 
319
319
  assert builder.count() == 4 == len(builder.collect())
320
320
 
321
+ count_sql = builder._count()
322
+ assert "COUNT(" in count_sql
323
+
321
324
  sql = builder._collect()
322
325
  assert "JOIN" in sql
323
326
  assert "LEFT" not in sql
@@ -123,6 +123,7 @@ def test_both_styles_for_class():
123
123
 
124
124
  assert db(old_style).count() == 0
125
125
  assert NewStyle.count() == 0
126
+ assert not NewStyle.exists()
126
127
 
127
128
  with pytest.raises(Exception):
128
129
  old_style.insert(string_field=123, int_field="abc")
@@ -89,6 +89,7 @@ def selects_pydal():
89
89
 
90
90
  def selects_typedal():
91
91
  assert TimeTable.count()
92
+ assert TimeTable.exists()
92
93
  assert TimeTable(1)
93
94
  assert TimeTable(id=1)
94
95
  assert TimeTable.where(TimeTable.id == 1).first()
@@ -1 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="122" height="20" role="img" aria-label="coverage: 100.00%"><title>coverage: 100.00%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="122" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="61" height="20" fill="#4c1"/><rect width="122" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">coverage</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">coverage</text><text aria-hidden="true" x="905" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">100.00%</text><text x="905" y="140" transform="scale(.1)" fill="#fff" textLength="510">100.00%</text></g></svg>
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