TypeDAL 3.13.1__tar.gz → 3.14.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 (58) hide show
  1. {typedal-3.13.1 → typedal-3.14.1}/CHANGELOG.md +12 -0
  2. {typedal-3.13.1 → typedal-3.14.1}/PKG-INFO +1 -1
  3. {typedal-3.13.1 → typedal-3.14.1}/src/typedal/__about__.py +1 -1
  4. {typedal-3.13.1 → typedal-3.14.1}/src/typedal/__init__.py +1 -1
  5. {typedal-3.13.1 → typedal-3.14.1}/src/typedal/cli.py +3 -2
  6. {typedal-3.13.1 → typedal-3.14.1}/src/typedal/core.py +27 -12
  7. {typedal-3.13.1 → typedal-3.14.1}/src/typedal/for_py4web.py +1 -2
  8. {typedal-3.13.1 → typedal-3.14.1}/src/typedal/helpers.py +5 -1
  9. {typedal-3.13.1 → typedal-3.14.1}/src/typedal/mixins.py +9 -1
  10. {typedal-3.13.1 → typedal-3.14.1}/src/typedal/serializers/as_json.py +1 -1
  11. {typedal-3.13.1 → typedal-3.14.1}/tests/test_main.py +5 -0
  12. {typedal-3.13.1 → typedal-3.14.1}/tests/test_mixins.py +8 -1
  13. {typedal-3.13.1 → typedal-3.14.1}/.github/workflows/su6.yml +0 -0
  14. {typedal-3.13.1 → typedal-3.14.1}/.gitignore +0 -0
  15. {typedal-3.13.1 → typedal-3.14.1}/.readthedocs.yml +0 -0
  16. {typedal-3.13.1 → typedal-3.14.1}/README.md +0 -0
  17. {typedal-3.13.1 → typedal-3.14.1}/coverage.svg +0 -0
  18. {typedal-3.13.1 → typedal-3.14.1}/docs/1_getting_started.md +0 -0
  19. {typedal-3.13.1 → typedal-3.14.1}/docs/2_defining_tables.md +0 -0
  20. {typedal-3.13.1 → typedal-3.14.1}/docs/3_building_queries.md +0 -0
  21. {typedal-3.13.1 → typedal-3.14.1}/docs/4_relationships.md +0 -0
  22. {typedal-3.13.1 → typedal-3.14.1}/docs/5_py4web.md +0 -0
  23. {typedal-3.13.1 → typedal-3.14.1}/docs/6_migrations.md +0 -0
  24. {typedal-3.13.1 → typedal-3.14.1}/docs/7_mixins.md +0 -0
  25. {typedal-3.13.1 → typedal-3.14.1}/docs/css/code_blocks.css +0 -0
  26. {typedal-3.13.1 → typedal-3.14.1}/docs/index.md +0 -0
  27. {typedal-3.13.1 → typedal-3.14.1}/docs/requirements.txt +0 -0
  28. {typedal-3.13.1 → typedal-3.14.1}/example_new.py +0 -0
  29. {typedal-3.13.1 → typedal-3.14.1}/example_old.py +0 -0
  30. {typedal-3.13.1 → typedal-3.14.1}/mkdocs.yml +0 -0
  31. {typedal-3.13.1 → typedal-3.14.1}/pyproject.toml +0 -0
  32. {typedal-3.13.1 → typedal-3.14.1}/src/typedal/caching.py +0 -0
  33. {typedal-3.13.1 → typedal-3.14.1}/src/typedal/config.py +0 -0
  34. {typedal-3.13.1 → typedal-3.14.1}/src/typedal/fields.py +0 -0
  35. {typedal-3.13.1 → typedal-3.14.1}/src/typedal/for_web2py.py +0 -0
  36. {typedal-3.13.1 → typedal-3.14.1}/src/typedal/py.typed +0 -0
  37. {typedal-3.13.1 → typedal-3.14.1}/src/typedal/types.py +0 -0
  38. {typedal-3.13.1 → typedal-3.14.1}/src/typedal/web2py_py4web_shared.py +0 -0
  39. {typedal-3.13.1 → typedal-3.14.1}/tests/__init__.py +0 -0
  40. {typedal-3.13.1 → typedal-3.14.1}/tests/configs/simple.toml +0 -0
  41. {typedal-3.13.1 → typedal-3.14.1}/tests/configs/valid.env +0 -0
  42. {typedal-3.13.1 → typedal-3.14.1}/tests/configs/valid.toml +0 -0
  43. {typedal-3.13.1 → typedal-3.14.1}/tests/test_cli.py +0 -0
  44. {typedal-3.13.1 → typedal-3.14.1}/tests/test_config.py +0 -0
  45. {typedal-3.13.1 → typedal-3.14.1}/tests/test_docs_examples.py +0 -0
  46. {typedal-3.13.1 → typedal-3.14.1}/tests/test_helpers.py +0 -0
  47. {typedal-3.13.1 → typedal-3.14.1}/tests/test_json.py +0 -0
  48. {typedal-3.13.1 → typedal-3.14.1}/tests/test_mypy.py +0 -0
  49. {typedal-3.13.1 → typedal-3.14.1}/tests/test_orm.py +0 -0
  50. {typedal-3.13.1 → typedal-3.14.1}/tests/test_py4web.py +0 -0
  51. {typedal-3.13.1 → typedal-3.14.1}/tests/test_query_builder.py +0 -0
  52. {typedal-3.13.1 → typedal-3.14.1}/tests/test_relationships.py +0 -0
  53. {typedal-3.13.1 → typedal-3.14.1}/tests/test_row.py +0 -0
  54. {typedal-3.13.1 → typedal-3.14.1}/tests/test_stats.py +0 -0
  55. {typedal-3.13.1 → typedal-3.14.1}/tests/test_table.py +0 -0
  56. {typedal-3.13.1 → typedal-3.14.1}/tests/test_web2py.py +0 -0
  57. {typedal-3.13.1 → typedal-3.14.1}/tests/test_xx_others.py +0 -0
  58. {typedal-3.13.1 → typedal-3.14.1}/tests/timings.py +0 -0
@@ -2,6 +2,18 @@
2
2
 
3
3
  <!--next-version-placeholder-->
4
4
 
5
+ ## v3.14.1 (2025-05-27)
6
+
7
+ ### Fix
8
+
9
+ * Don't force slug if already manually set ([`a33b2b5`](https://github.com/trialandsuccess/TypeDAL/commit/a33b2b523a56ae9adc6fb4c49a5708efe7950977))
10
+
11
+ ## v3.14.0 (2025-05-15)
12
+
13
+ ### Feature
14
+
15
+ * `db.find_model` to get the registered TypedTable class for a specific 'table_name' ([`c645303`](https://github.com/trialandsuccess/TypeDAL/commit/c645303a85c96735f48eafaaeb20f867b977686f))
16
+
5
17
  ## v3.13.1 (2025-04-28)
6
18
 
7
19
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: TypeDAL
3
- Version: 3.13.1
3
+ Version: 3.14.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
@@ -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.1"
8
+ __version__ = "3.14.1"
@@ -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})
@@ -1024,7 +1041,6 @@ class TableMeta(type):
1024
1041
 
1025
1042
  Shortcut for `.select(field).execute().column(field)`.
1026
1043
  """
1027
-
1028
1044
  return QueryBuilder(self).select(field, **options).execute().column(field)
1029
1045
 
1030
1046
  def paginate(self: Type[T_MetaInstance], limit: int, page: int = 1) -> "PaginatedRows[T_MetaInstance]":
@@ -2721,7 +2737,6 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2721
2737
 
2722
2738
  Shortcut for `.select(field).execute().column(field)`.
2723
2739
  """
2724
-
2725
2740
  return self.select(field, **options).execute().column(field)
2726
2741
 
2727
2742
  def _handle_relationships_pre_select(
@@ -2888,7 +2903,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2888
2903
 
2889
2904
  return _to(rows, self.model, records, metadata=metadata)
2890
2905
 
2891
- 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]":
2892
2907
  """
2893
2908
  Call .collect() and raise an error if nothing found.
2894
2909
 
@@ -2908,7 +2923,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2908
2923
  """
2909
2924
  yield from self.collect()
2910
2925
 
2911
- def __count(self, db: TypeDAL, distinct: bool = None) -> Query:
2926
+ def __count(self, db: TypeDAL, distinct: typing.Optional[bool] = None) -> Query:
2912
2927
  # internal, shared logic between .count and ._count
2913
2928
  model = self.model
2914
2929
  query = self.query
@@ -2924,7 +2939,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2924
2939
 
2925
2940
  return query
2926
2941
 
2927
- def count(self, distinct: bool = None) -> int:
2942
+ def count(self, distinct: typing.Optional[bool] = None) -> int:
2928
2943
  """
2929
2944
  Return the amount of rows matching the current query.
2930
2945
  """
@@ -2933,7 +2948,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
2933
2948
 
2934
2949
  return db(query).count(distinct)
2935
2950
 
2936
- def _count(self, distinct: bool = None) -> str:
2951
+ def _count(self, distinct: typing.Optional[bool] = None) -> str:
2937
2952
  """
2938
2953
  Return the SQL for .count().
2939
2954
  """
@@ -3032,7 +3047,7 @@ class QueryBuilder(typing.Generic[T_MetaInstance]):
3032
3047
  def _first(self) -> str:
3033
3048
  return self._paginate(page=1, limit=1)
3034
3049
 
3035
- 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:
3036
3051
  """
3037
3052
  Call .first() and raise an error if nothing found.
3038
3053
 
@@ -3120,7 +3135,7 @@ class TypedSet(pydal.objects.Set): # type: ignore # pragma: no cover
3120
3135
  This class is not actually used, only 'cast' by TypeDAL.__call__
3121
3136
  """
3122
3137
 
3123
- def count(self, distinct: bool = None, cache: AnyDict = None) -> int:
3138
+ def count(self, distinct: typing.Optional[bool] = None, cache: AnyDict = None) -> int:
3124
3139
  """
3125
3140
  Count returns an int.
3126
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).
@@ -193,6 +197,10 @@ class SlugMixin(Mixin):
193
197
 
194
198
  @classmethod
195
199
  def __generate_slug_before_insert(cls, row: OpRow) -> None:
200
+ if row.get("slug"):
201
+ # manually set -> skip
202
+ return None
203
+
196
204
  settings = cls.__settings__
197
205
 
198
206
  text_input = row[settings["slug_field"]]
@@ -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):
@@ -95,7 +95,7 @@ def test_slug(db):
95
95
  assert error is None
96
96
 
97
97
  assert row.name == "Two Words"
98
- assert row.slug.startswith("two-words")
98
+ assert row.slug.startswith("two-words-")
99
99
 
100
100
  row, error = TableWithSlugSuffix.validate_and_insert(name="Two Words")
101
101
  assert error is None
@@ -103,6 +103,13 @@ def test_slug(db):
103
103
  assert row.name == "Two Words"
104
104
  assert row.slug.startswith("two-words")
105
105
 
106
+ # test manual slug:
107
+ manual_slug = "some-other-slug-manually-defined"
108
+ # validate_and_insert will fail because 'slug' is not writable
109
+ row = TableWithMixins.insert(name="Some Name", slug=manual_slug)
110
+
111
+ assert row.slug == manual_slug
112
+
106
113
 
107
114
  def test_timestamps(db):
108
115
  row = TableWithTimestamps.insert(unrelated="Hi")
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