sqlmodel-ext 0.5.0__py3-none-any.whl → 0.5.2__py3-none-any.whl

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 (32) hide show
  1. sqlmodel_ext/__init__.py +4 -1
  2. sqlmodel_ext/_compat.py +4 -2
  3. sqlmodel_ext/_exceptions.py +1 -1
  4. sqlmodel_ext/_sa_type.py +20 -8
  5. sqlmodel_ext/_type_unwrap.py +10 -2
  6. sqlmodel_ext/base.py +267 -119
  7. sqlmodel_ext/check_derived.py +8 -8
  8. sqlmodel_ext/dependencies.py +178 -0
  9. sqlmodel_ext/field_types/__init__.py +27 -39
  10. sqlmodel_ext/field_types/_internal/path.py +22 -10
  11. sqlmodel_ext/field_types/dialects/postgresql/array.py +27 -13
  12. sqlmodel_ext/field_types/dialects/postgresql/jsonb_types.py +4 -5
  13. sqlmodel_ext/field_types/dialects/postgresql/numpy_vector.py +40 -24
  14. sqlmodel_ext/field_types/mixins/module_name_mixin.py +2 -2
  15. sqlmodel_ext/field_types/url.py +6 -6
  16. sqlmodel_ext/mixins/cached_table.py +239 -266
  17. sqlmodel_ext/mixins/migration_cache_invalidation.py +6 -6
  18. sqlmodel_ext/mixins/polymorphic.py +36 -23
  19. sqlmodel_ext/mixins/relation_preload.py +16 -16
  20. sqlmodel_ext/mixins/resource_quota.py +9 -6
  21. sqlmodel_ext/mixins/table.py +44 -28
  22. sqlmodel_ext/mixins/trgm_searchable.py +1 -1
  23. sqlmodel_ext/pagination.py +72 -39
  24. sqlmodel_ext/relation_load_checker.py +21 -15
  25. sqlmodel_ext/session.py +60 -33
  26. sqlmodel_ext/unset.py +1 -3
  27. {sqlmodel_ext-0.5.0.dist-info → sqlmodel_ext-0.5.2.dist-info}/METADATA +7 -6
  28. sqlmodel_ext-0.5.2.dist-info/RECORD +50 -0
  29. sqlmodel_ext-0.5.0.dist-info/RECORD +0 -49
  30. {sqlmodel_ext-0.5.0.dist-info → sqlmodel_ext-0.5.2.dist-info}/WHEEL +0 -0
  31. {sqlmodel_ext-0.5.0.dist-info → sqlmodel_ext-0.5.2.dist-info}/entry_points.txt +0 -0
  32. {sqlmodel_ext-0.5.0.dist-info → sqlmodel_ext-0.5.2.dist-info}/licenses/LICENSE +0 -0
sqlmodel_ext/__init__.py CHANGED
@@ -20,7 +20,7 @@ Quick start::
20
20
  user = await user.save(session)
21
21
  users = await User.get(session, fetch_mode="all")
22
22
  """
23
- __version__ = "0.5.0"
23
+ __version__ = "0.5.2"
24
24
 
25
25
  # Every re-export below uses the redundant ``X as X`` form: the package ships
26
26
  # ``py.typed``, and type checkers treat a plain ``from m import X`` in a typed
@@ -66,6 +66,9 @@ from sqlmodel_ext.pagination import (
66
66
  TableViewRequest as TableViewRequest,
67
67
  )
68
68
 
69
+ # FastAPI dependency for query-parameter DTOs (cross-field errors -> 422; requires the `fastapi` extra when called)
70
+ from sqlmodel_ext.dependencies import query_dependency as query_dependency
71
+
69
72
  # Mixins
70
73
  from sqlmodel_ext.mixins import (
71
74
  # Table
sqlmodel_ext/_compat.py CHANGED
@@ -9,7 +9,9 @@ Import this module for side effects before using SQLModelBase.
9
9
  import sys
10
10
 
11
11
  if sys.version_info >= (3, 14):
12
- import annotationlib # noqa: F401
12
+ # The checker analyzes at pythonVersion 3.12 (the lowest supported version), where this
13
+ # version-gated block is statically unreachable; it does run on 3.14+.
14
+ import annotationlib # noqa: F401 # pyright: ignore[reportUnreachable]
13
15
  import types
14
16
  from enum import StrEnum
15
17
  from typing import Literal, Union, get_args, get_origin
@@ -101,7 +103,7 @@ if sys.version_info >= (3, 14):
101
103
  if non_none_args:
102
104
  first_arg = non_none_args[0]
103
105
  # Check for Annotated SA type metadata first (e.g. Array[T] | None).
104
- # Array[T] desugars to Annotated[list[T], _ArrayTypeHandler(T)];
106
+ # Array[T] desugars to Annotated[list[T], ArrayTypeHandler(T)];
105
107
  # wrapping with | None produces Union[Annotated[...], None],
106
108
  # so the SA type must be extracted from the inner Annotated.
107
109
  first_arg_type_name = type(first_arg).__name__
@@ -12,5 +12,5 @@ class RecordNotFoundError(Exception):
12
12
  status_code: int = 404
13
13
 
14
14
  def __init__(self, detail: str = "Not found"):
15
- self.detail = detail
15
+ self.detail: str = detail
16
16
  super().__init__(detail)
sqlmodel_ext/_sa_type.py CHANGED
@@ -2,15 +2,27 @@
2
2
  Internal helpers for extracting SQLAlchemy types from annotations.
3
3
 
4
4
  These are used by the metaclass to inject sa_type into Field definitions.
5
+
6
+ Internal protocol, not part of the public API: the functions carry public names
7
+ only because :mod:`sqlmodel_ext.base` (the metaclass) imports them across module
8
+ boundaries; the module itself stays private (``_sa_type``).
5
9
  """
6
10
  import sys
7
11
  import typing
8
- from typing import Any, Mapping, get_args, get_origin, get_type_hints
12
+ from collections.abc import Mapping
13
+ from typing import Any, get_args, get_origin, get_type_hints
14
+
9
15
 
10
- from sqlalchemy.orm import Mapped
16
+ def _null_schema_handler(_source_type: Any) -> None:
17
+ """
18
+ Stand-in for pydantic's ``GetCoreSchemaHandler`` when probing a type's
19
+ ``__get_pydantic_core_schema__`` for ``metadata['sa_type']``: the probe
20
+ only reads the metadata and never needs the delegated inner schema.
21
+ """
22
+ return None
11
23
 
12
24
 
13
- def _extract_sa_type_from_annotation(annotation: Any) -> Any | None:
25
+ def extract_sa_type_from_annotation(annotation: Any) -> Any | None:
14
26
  """
15
27
  Extract SQLAlchemy type from a type annotation.
16
28
 
@@ -40,7 +52,7 @@ def _extract_sa_type_from_annotation(annotation: Any) -> Any | None:
40
52
  try:
41
53
  schema = item.__get_pydantic_core_schema__(
42
54
  annotation,
43
- lambda x: None,
55
+ _null_schema_handler,
44
56
  )
45
57
  if isinstance(schema, dict) and 'metadata' in schema:
46
58
  sa_type = schema['metadata'].get('sa_type')
@@ -54,7 +66,7 @@ def _extract_sa_type_from_annotation(annotation: Any) -> Any | None:
54
66
  try:
55
67
  schema = annotation.__get_pydantic_core_schema__(
56
68
  annotation,
57
- lambda x: None,
69
+ _null_schema_handler,
58
70
  )
59
71
  if isinstance(schema, dict) and 'metadata' in schema:
60
72
  sa_type = schema['metadata'].get('sa_type')
@@ -66,7 +78,7 @@ def _extract_sa_type_from_annotation(annotation: Any) -> Any | None:
66
78
  return None
67
79
 
68
80
 
69
- def _resolve_annotations(attrs: dict[str, Any]) -> tuple[
81
+ def resolve_annotations(attrs: dict[str, Any]) -> tuple[
70
82
  dict[str, Any],
71
83
  dict[str, str],
72
84
  Mapping[str, Any],
@@ -112,7 +124,7 @@ def _resolve_annotations(attrs: dict[str, Any]) -> tuple[
112
124
  return dict(evaluated), {}, module_globals, localns
113
125
 
114
126
 
115
- def _evaluate_annotation_from_string(
127
+ def evaluate_annotation_from_string(
116
128
  field_name: str,
117
129
  annotation_strings: dict[str, str],
118
130
  current_type: Any,
@@ -129,7 +141,7 @@ def _evaluate_annotation_from_string(
129
141
  return current_type
130
142
 
131
143
  expr = annotation_strings.get(field_name)
132
- if not expr or not isinstance(expr, str):
144
+ if not expr:
133
145
  return current_type
134
146
 
135
147
  try:
@@ -17,7 +17,15 @@ from a ``response_model`` to its DTO / ORM classes).
17
17
  """
18
18
  import types
19
19
  import typing
20
- from typing import Annotated, Any, Union
20
+ from typing import Annotated, Any, Final
21
+
22
+ # ``typing.Union`` as a runtime value: the ``get_origin()`` of ``Optional[X]`` /
23
+ # ``Union[X, Y]`` on Python < 3.14 (``X | Y`` has origin ``types.UnionType``).
24
+ # reportDeprecated targets spelling *annotations* with ``Union`` (use ``X | Y``);
25
+ # an identity-comparison target has no non-deprecated spelling, so the single
26
+ # reference lives here and every origin check in the package compares against
27
+ # this name (internal protocol, not part of the public API).
28
+ TYPING_UNION: Final = typing.Union # pyright: ignore[reportDeprecated]
21
29
 
22
30
 
23
31
  def unwrap_to_class(hint: Any) -> type | None:
@@ -41,7 +49,7 @@ def unwrap_to_class(hint: Any) -> type | None:
41
49
  return unwrap_to_class(args[0])
42
50
 
43
51
  # X | None (UnionType) or Optional[X] (Union[X, None])
44
- if origin is types.UnionType or origin is Union: # pyright: ignore[reportDeprecated]
52
+ if origin is types.UnionType or origin is TYPING_UNION:
45
53
  args = typing.get_args(hint)
46
54
  non_none = [a for a in args if a is not type(None)]
47
55
  if len(non_none) == 1: