TypeDAL 3.13.0__tar.gz → 3.14.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.13.0 → typedal-3.14.0}/CHANGELOG.md +12 -0
  2. {typedal-3.13.0 → typedal-3.14.0}/PKG-INFO +2 -2
  3. {typedal-3.13.0 → typedal-3.14.0}/src/typedal/__about__.py +1 -1
  4. {typedal-3.13.0 → typedal-3.14.0}/src/typedal/__init__.py +1 -1
  5. {typedal-3.13.0 → typedal-3.14.0}/src/typedal/cli.py +3 -2
  6. {typedal-3.13.0 → typedal-3.14.0}/src/typedal/core.py +39 -14
  7. {typedal-3.13.0 → typedal-3.14.0}/src/typedal/for_py4web.py +1 -2
  8. {typedal-3.13.0 → typedal-3.14.0}/src/typedal/helpers.py +5 -1
  9. {typedal-3.13.0 → typedal-3.14.0}/src/typedal/mixins.py +5 -1
  10. {typedal-3.13.0 → typedal-3.14.0}/src/typedal/serializers/as_json.py +1 -1
  11. {typedal-3.13.0 → typedal-3.14.0}/tests/test_main.py +5 -0
  12. {typedal-3.13.0 → typedal-3.14.0}/tests/test_query_builder.py +2 -0
  13. {typedal-3.13.0 → typedal-3.14.0}/.github/workflows/su6.yml +0 -0
  14. {typedal-3.13.0 → typedal-3.14.0}/.gitignore +0 -0
  15. {typedal-3.13.0 → typedal-3.14.0}/.readthedocs.yml +0 -0
  16. {typedal-3.13.0 → typedal-3.14.0}/README.md +0 -0
  17. {typedal-3.13.0 → typedal-3.14.0}/coverage.svg +0 -0
  18. {typedal-3.13.0 → typedal-3.14.0}/docs/1_getting_started.md +0 -0
  19. {typedal-3.13.0 → typedal-3.14.0}/docs/2_defining_tables.md +0 -0
  20. {typedal-3.13.0 → typedal-3.14.0}/docs/3_building_queries.md +0 -0
  21. {typedal-3.13.0 → typedal-3.14.0}/docs/4_relationships.md +0 -0
  22. {typedal-3.13.0 → typedal-3.14.0}/docs/5_py4web.md +0 -0
  23. {typedal-3.13.0 → typedal-3.14.0}/docs/6_migrations.md +0 -0
  24. {typedal-3.13.0 → typedal-3.14.0}/docs/7_mixins.md +0 -0
  25. {typedal-3.13.0 → typedal-3.14.0}/docs/css/code_blocks.css +0 -0
  26. {typedal-3.13.0 → typedal-3.14.0}/docs/index.md +0 -0
  27. {typedal-3.13.0 → typedal-3.14.0}/docs/requirements.txt +0 -0
  28. {typedal-3.13.0 → typedal-3.14.0}/example_new.py +0 -0
  29. {typedal-3.13.0 → typedal-3.14.0}/example_old.py +0 -0
  30. {typedal-3.13.0 → typedal-3.14.0}/mkdocs.yml +0 -0
  31. {typedal-3.13.0 → typedal-3.14.0}/pyproject.toml +0 -0
  32. {typedal-3.13.0 → typedal-3.14.0}/src/typedal/caching.py +0 -0
  33. {typedal-3.13.0 → typedal-3.14.0}/src/typedal/config.py +0 -0
  34. {typedal-3.13.0 → typedal-3.14.0}/src/typedal/fields.py +0 -0
  35. {typedal-3.13.0 → typedal-3.14.0}/src/typedal/for_web2py.py +0 -0
  36. {typedal-3.13.0 → typedal-3.14.0}/src/typedal/py.typed +0 -0
  37. {typedal-3.13.0 → typedal-3.14.0}/src/typedal/types.py +0 -0
  38. {typedal-3.13.0 → typedal-3.14.0}/src/typedal/web2py_py4web_shared.py +0 -0
  39. {typedal-3.13.0 → typedal-3.14.0}/tests/__init__.py +0 -0
  40. {typedal-3.13.0 → typedal-3.14.0}/tests/configs/simple.toml +0 -0
  41. {typedal-3.13.0 → typedal-3.14.0}/tests/configs/valid.env +0 -0
  42. {typedal-3.13.0 → typedal-3.14.0}/tests/configs/valid.toml +0 -0
  43. {typedal-3.13.0 → typedal-3.14.0}/tests/test_cli.py +0 -0
  44. {typedal-3.13.0 → typedal-3.14.0}/tests/test_config.py +0 -0
  45. {typedal-3.13.0 → typedal-3.14.0}/tests/test_docs_examples.py +0 -0
  46. {typedal-3.13.0 → typedal-3.14.0}/tests/test_helpers.py +0 -0
  47. {typedal-3.13.0 → typedal-3.14.0}/tests/test_json.py +0 -0
  48. {typedal-3.13.0 → typedal-3.14.0}/tests/test_mixins.py +0 -0
  49. {typedal-3.13.0 → typedal-3.14.0}/tests/test_mypy.py +0 -0
  50. {typedal-3.13.0 → typedal-3.14.0}/tests/test_orm.py +0 -0
  51. {typedal-3.13.0 → typedal-3.14.0}/tests/test_py4web.py +0 -0
  52. {typedal-3.13.0 → typedal-3.14.0}/tests/test_relationships.py +0 -0
  53. {typedal-3.13.0 → typedal-3.14.0}/tests/test_row.py +0 -0
  54. {typedal-3.13.0 → typedal-3.14.0}/tests/test_stats.py +0 -0
  55. {typedal-3.13.0 → typedal-3.14.0}/tests/test_table.py +0 -0
  56. {typedal-3.13.0 → typedal-3.14.0}/tests/test_web2py.py +0 -0
  57. {typedal-3.13.0 → typedal-3.14.0}/tests/test_xx_others.py +0 -0
  58. {typedal-3.13.0 → typedal-3.14.0}/tests/timings.py +0 -0
@@ -2,6 +2,18 @@
2
2
 
3
3
  <!--next-version-placeholder-->
4
4
 
5
+ ## v3.14.0 (2025-05-15)
6
+
7
+ ### Feature
8
+
9
+ * `db.find_model` to get the registered TypedTable class for a specific 'table_name' ([`c645303`](https://github.com/trialandsuccess/TypeDAL/commit/c645303a85c96735f48eafaaeb20f867b977686f))
10
+
11
+ ## v3.13.1 (2025-04-28)
12
+
13
+ ### Fix
14
+
15
+ * Pass select kwargs via `.column()` - so you can do e.g. `distinct=True` ([`e5bc168`](https://github.com/trialandsuccess/TypeDAL/commit/e5bc168b90d6a5d214f049de0ef31e544214cc23))
16
+
5
17
  ## v3.13.0 (2025-04-28)
6
18
 
7
19
  ### Feature
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: TypeDAL
3
- Version: 3.13.0
3
+ Version: 3.14.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
@@ -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.13.0"
8
+ __version__ = "3.14.0"
@@ -10,4 +10,4 @@ try:
10
10
  except ImportError: # pragma: no cover
11
11
  P4W_DAL = None # type: ignore
12
12
 
13
- __all__ = ["TypeDAL", "TypedTable", "TypedField", "TypedRows", "fields", "Relationship", "relationship"]
13
+ __all__ = ["Relationship", "TypeDAL", "TypedField", "TypedRows", "TypedTable", "fields", "relationship"]
@@ -185,7 +185,7 @@ def setup(
185
185
  data.pop(prop, None)
186
186
  continue
187
187
 
188
- if minimal and getattr(config, prop, None) not in (None, "") or is_optional(annotation):
188
+ if (minimal and getattr(config, prop, None) not in (None, "")) or is_optional(annotation):
189
189
  # property already present or not required, SKIP!
190
190
  data[prop] = getattr(config, prop, None)
191
191
  continue
@@ -392,7 +392,8 @@ def fake_migrations(
392
392
 
393
393
  previously_migrated = (
394
394
  db(
395
- db.ewh_implemented_features.name.belongs(to_fake) & (db.ewh_implemented_features.installed == True) # noqa E712
395
+ db.ewh_implemented_features.name.belongs(to_fake)
396
+ & (db.ewh_implemented_features.installed == True) # noqa E712
396
397
  )
397
398
  .select(db.ewh_implemented_features.name)
398
399
  .column("name")
@@ -93,10 +93,8 @@ def is_typed_field(cls: Any) -> typing.TypeGuard["TypedField[Any]"]:
93
93
 
94
94
  Deprecated
95
95
  """
96
- return (
97
- isinstance(cls, TypedField)
98
- or isinstance(typing.get_origin(cls), type)
99
- and issubclass(typing.get_origin(cls), TypedField)
96
+ return isinstance(cls, TypedField) or (
97
+ isinstance(typing.get_origin(cls), type) and issubclass(typing.get_origin(cls), TypedField)
100
98
  )
101
99
 
102
100
 
@@ -698,11 +696,30 @@ class TypeDAL(pydal.DAL): # type: ignore
698
696
  """
699
697
  Allows dynamically accessing a table by its name as a string.
700
698
 
699
+ If you need the TypedTable class instead of the pydal table, use find_model instead.
700
+
701
701
  Example:
702
702
  db['users'] -> user
703
703
  """
704
704
  return typing.cast(Table, super().__getitem__(str(key)))
705
705
 
706
+ def find_model(self, table_name: str) -> Type["TypedTable"] | None:
707
+ """
708
+ Retrieves a mapped table class by its name.
709
+
710
+ This method searches for a table class matching the given table name
711
+ in the defined class map dictionary. If a match is found, the corresponding
712
+ table class is returned; otherwise, None is returned, indicating that no
713
+ table class matches the input name.
714
+
715
+ Args:
716
+ table_name: The name of the table to retrieve the mapped class for.
717
+
718
+ Returns:
719
+ The mapped table class if it exists, otherwise None.
720
+ """
721
+ return self._class_map.get(table_name, None)
722
+
706
723
  @classmethod
707
724
  def _build_field(cls, name: str, _type: str, **kw: Any) -> Field:
708
725
  # return Field(name, _type, **{**cls.default_kwargs, **kw})
@@ -1018,6 +1035,14 @@ class TableMeta(type):
1018
1035
  """
1019
1036
  return QueryBuilder(self).select(*a, **kw)
1020
1037
 
1038
+ def column(self: Type[T_MetaInstance], field: "TypedField[T] | T", **options: Unpack[SelectKwargs]) -> list[T]:
1039
+ """
1040
+ Get all values in a specific column.
1041
+
1042
+ Shortcut for `.select(field).execute().column(field)`.
1043
+ """
1044
+ return QueryBuilder(self).select(field, **options).execute().column(field)
1045
+
1021
1046
  def paginate(self: Type[T_MetaInstance], limit: int, page: int = 1) -> "PaginatedRows[T_MetaInstance]":
1022
1047
  """
1023
1048
  See QueryBuilder.paginate!
@@ -2695,24 +2720,24 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2695
2720
  return save_to_cache(typed_rows, rows)
2696
2721
 
2697
2722
  @typing.overload
2698
- def column(self, field: TypedField[T]) -> list[T]:
2723
+ def column(self, field: TypedField[T], **options: Unpack[SelectKwargs]) -> list[T]:
2699
2724
  """
2700
2725
  If a typedfield is passed, the output type can be safely determined.
2701
2726
  """
2702
2727
 
2703
2728
  @typing.overload
2704
- def column(self, field: T) -> list[T]:
2729
+ def column(self, field: T, **options: Unpack[SelectKwargs]) -> list[T]:
2705
2730
  """
2706
2731
  Otherwise, the output type is loosely determined (assumes `field: type` or Any).
2707
2732
  """
2708
2733
 
2709
- def column(self, field: TypedField[T] | T) -> list[T]:
2734
+ def column(self, field: TypedField[T] | T, **options: Unpack[SelectKwargs]) -> list[T]:
2710
2735
  """
2711
2736
  Get all values in a specific column.
2712
2737
 
2713
2738
  Shortcut for `.select(field).execute().column(field)`.
2714
2739
  """
2715
- return self.select(field).execute().column(field)
2740
+ return self.select(field, **options).execute().column(field)
2716
2741
 
2717
2742
  def _handle_relationships_pre_select(
2718
2743
  self,
@@ -2878,7 +2903,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2878
2903
 
2879
2904
  return _to(rows, self.model, records, metadata=metadata)
2880
2905
 
2881
- def collect_or_fail(self, exception: Exception = None) -> "TypedRows[T_MetaInstance]":
2906
+ def collect_or_fail(self, exception: typing.Optional[Exception] = None) -> "TypedRows[T_MetaInstance]":
2882
2907
  """
2883
2908
  Call .collect() and raise an error if nothing found.
2884
2909
 
@@ -2898,7 +2923,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2898
2923
  """
2899
2924
  yield from self.collect()
2900
2925
 
2901
- def __count(self, db: TypeDAL, distinct: bool = None) -> Query:
2926
+ def __count(self, db: TypeDAL, distinct: typing.Optional[bool] = None) -> Query:
2902
2927
  # internal, shared logic between .count and ._count
2903
2928
  model = self.model
2904
2929
  query = self.query
@@ -2914,7 +2939,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2914
2939
 
2915
2940
  return query
2916
2941
 
2917
- def count(self, distinct: bool = None) -> int:
2942
+ def count(self, distinct: typing.Optional[bool] = None) -> int:
2918
2943
  """
2919
2944
  Return the amount of rows matching the current query.
2920
2945
  """
@@ -2923,7 +2948,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2923
2948
 
2924
2949
  return db(query).count(distinct)
2925
2950
 
2926
- def _count(self, distinct: bool = None) -> str:
2951
+ def _count(self, distinct: typing.Optional[bool] = None) -> str:
2927
2952
  """
2928
2953
  Return the SQL for .count().
2929
2954
  """
@@ -3022,7 +3047,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
3022
3047
  def _first(self) -> str:
3023
3048
  return self._paginate(page=1, limit=1)
3024
3049
 
3025
- def first_or_fail(self, exception: Exception = None, verbose: bool = False) -> T_MetaInstance:
3050
+ def first_or_fail(self, exception: typing.Optional[Exception] = None, verbose: bool = False) -> T_MetaInstance:
3026
3051
  """
3027
3052
  Call .first() and raise an error if nothing found.
3028
3053
 
@@ -3110,7 +3135,7 @@ class TypedSet(pydal.objects.Set): # type: ignore # pragma: no cover
3110
3135
  This class is not actually used, only 'cast' by TypeDAL.__call__
3111
3136
  """
3112
3137
 
3113
- def count(self, distinct: bool = None, cache: AnyDict = None) -> int:
3138
+ def count(self, distinct: typing.Optional[bool] = None, cache: AnyDict = None) -> int:
3114
3139
  """
3115
3140
  Count returns an int.
3116
3141
  """
@@ -5,7 +5,6 @@ ONLY USE IN COMBINATION WITH PY4WEB!
5
5
  import typing
6
6
 
7
7
  import threadsafevariable
8
- from configuraptor.abs import AnyType
9
8
  from py4web.core import ICECUBE
10
9
  from py4web.core import Fixture as _Fixture
11
10
  from pydal.base import MetaDAL, hashlib_md5
@@ -68,8 +67,8 @@ def setup_py4web_tables(db: TypeDAL) -> None:
68
67
 
69
68
 
70
69
  __all__ = [
70
+ "DAL",
71
71
  "AuthUser",
72
72
  "Fixture",
73
- "DAL",
74
73
  "setup_py4web_tables",
75
74
  ]
@@ -55,7 +55,7 @@ def all_dict(cls: type) -> AnyDict:
55
55
  return dict(ChainMap(*(c.__dict__ for c in reversed_mro(cls)))) # type: ignore
56
56
 
57
57
 
58
- def all_annotations(cls: type, _except: typing.Iterable[str] = None) -> dict[str, type]:
58
+ def all_annotations(cls: type, _except: typing.Optional[typing.Iterable[str]] = None) -> dict[str, type]:
59
59
  """
60
60
  Wrapper around `_all_annotations` that filters away any keys in _except.
61
61
 
@@ -306,6 +306,10 @@ def get_field(field: "TypedField[typing.Any] | Field") -> "Field":
306
306
 
307
307
 
308
308
  class classproperty:
309
+ """
310
+ Combination of @classmethod and @property.
311
+ """
312
+
309
313
  def __init__(self, fget: typing.Callable[..., typing.Any]) -> None:
310
314
  """
311
315
  Initialize the classproperty.
@@ -163,7 +163,11 @@ class SlugMixin(Mixin):
163
163
  ) # set via init subclass
164
164
 
165
165
  def __init_subclass__(
166
- cls, slug_field: str = None, slug_suffix_length: int = 0, slug_suffix: Optional[int] = None, **kw: Any
166
+ cls,
167
+ slug_field: typing.Optional[str] = None,
168
+ slug_suffix_length: int = 0,
169
+ slug_suffix: Optional[int] = None,
170
+ **kw: Any,
167
171
  ) -> None:
168
172
  """
169
173
  Bind 'slug field' option to be used later (on_define).
@@ -68,7 +68,7 @@ class SerializedJson(ConfigurableJsonEncoder):
68
68
  return _rules.get(_type, JSONRule(transform=self._default) if with_default else None)
69
69
 
70
70
 
71
- def encode(something: Any, indent: int = None, **kw: Any) -> str:
71
+ def encode(something: Any, indent: typing.Optional[int] = None, **kw: Any) -> str:
72
72
  """
73
73
  Encode anything to JSON with some improved defaults.
74
74
  """
@@ -166,6 +166,11 @@ def test_mixed_defines(capsys):
166
166
 
167
167
  assert SecondNewSyntax(1).location == "Rotterdam"
168
168
 
169
+ # test find_model:
170
+ assert db.find_model("old_syntax") is None
171
+ assert db.find_model("first_new_syntax") is FirstNewSyntax
172
+ assert db.find_model("second_new_syntax") is SecondNewSyntax
173
+
169
174
 
170
175
  def test_dont_allow_bool_in_query():
171
176
  with pytest.raises(ValueError):
@@ -483,6 +483,8 @@ def test_column():
483
483
  assert len(rows) == 4
484
484
  assert set(rows) == {33}
485
485
 
486
+ assert TestRelationship.column(TestRelationship.value, distinct=True, orderby=~TestRelationship.value) == [33, 3]
487
+
486
488
 
487
489
  def test_collect_with_extra_fields():
488
490
  _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