sqlmodel-ext 0.5.2__py3-none-any.whl → 0.5.4__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 (47) hide show
  1. sqlmodel_ext/__init__.py +1 -1
  2. sqlmodel_ext/_compat.py +261 -261
  3. sqlmodel_ext/_exceptions.py +16 -16
  4. sqlmodel_ext/_type_unwrap.py +117 -117
  5. sqlmodel_ext/_utils.py +5 -5
  6. sqlmodel_ext/base.py +2178 -2178
  7. sqlmodel_ext/check_derived.py +565 -565
  8. sqlmodel_ext/constants.py +76 -76
  9. sqlmodel_ext/dependencies.py +178 -178
  10. sqlmodel_ext/derived_decls.py +818 -818
  11. sqlmodel_ext/field_types/__init__.py +997 -997
  12. sqlmodel_ext/field_types/_internal/path.py +72 -72
  13. sqlmodel_ext/field_types/_ssrf.py +81 -81
  14. sqlmodel_ext/field_types/dialects/__init__.py +7 -7
  15. sqlmodel_ext/field_types/dialects/postgresql/__init__.py +47 -47
  16. sqlmodel_ext/field_types/dialects/postgresql/array.py +232 -232
  17. sqlmodel_ext/field_types/dialects/postgresql/exceptions.py +38 -38
  18. sqlmodel_ext/field_types/dialects/postgresql/jsonb_types.py +266 -266
  19. sqlmodel_ext/field_types/dialects/postgresql/numpy_vector.py +443 -443
  20. sqlmodel_ext/field_types/ip_address.py +86 -86
  21. sqlmodel_ext/field_types/mixins/__init__.py +1 -1
  22. sqlmodel_ext/field_types/mixins/module_name_mixin.py +33 -33
  23. sqlmodel_ext/field_types/url.py +170 -170
  24. sqlmodel_ext/mixins/__init__.py +79 -79
  25. sqlmodel_ext/mixins/_uuid.py +69 -69
  26. sqlmodel_ext/mixins/cached_table.py +18 -3
  27. sqlmodel_ext/mixins/constants.py +17 -17
  28. sqlmodel_ext/mixins/exceptions.py +84 -84
  29. sqlmodel_ext/mixins/info_response.py +47 -47
  30. sqlmodel_ext/mixins/migration_cache_invalidation.py +243 -243
  31. sqlmodel_ext/mixins/mixin_table_scan.py +96 -96
  32. sqlmodel_ext/mixins/optimistic_lock.py +161 -161
  33. sqlmodel_ext/mixins/relation_preload.py +918 -916
  34. sqlmodel_ext/mixins/resource_quota.py +318 -318
  35. sqlmodel_ext/mixins/table.py +2041 -2036
  36. sqlmodel_ext/mixins/trgm_searchable.py +230 -230
  37. sqlmodel_ext/pagination.py +291 -291
  38. sqlmodel_ext/relation_load_checker.py +6367 -6367
  39. sqlmodel_ext/select.py +471 -466
  40. sqlmodel_ext/session.py +633 -633
  41. sqlmodel_ext/unset.py +189 -189
  42. {sqlmodel_ext-0.5.2.dist-info → sqlmodel_ext-0.5.4.dist-info}/METADATA +4 -4
  43. sqlmodel_ext-0.5.4.dist-info/RECORD +50 -0
  44. {sqlmodel_ext-0.5.2.dist-info → sqlmodel_ext-0.5.4.dist-info}/licenses/LICENSE +21 -21
  45. sqlmodel_ext-0.5.2.dist-info/RECORD +0 -50
  46. {sqlmodel_ext-0.5.2.dist-info → sqlmodel_ext-0.5.4.dist-info}/WHEEL +0 -0
  47. {sqlmodel_ext-0.5.2.dist-info → sqlmodel_ext-0.5.4.dist-info}/entry_points.txt +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.2"
23
+ __version__ = "0.5.4"
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
sqlmodel_ext/_compat.py CHANGED
@@ -1,261 +1,261 @@
1
- """
2
- Python 3.14+ compatibility patches for SQLModel.
3
-
4
- This module applies monkey-patches to fix SQLModel's compatibility issues
5
- with Python 3.14 (PEP 649 deferred evaluation of annotations).
6
-
7
- Import this module for side effects before using SQLModelBase.
8
- """
9
- import sys
10
-
11
- if sys.version_info >= (3, 14):
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]
15
- import types
16
- from enum import StrEnum
17
- from typing import Literal, Union, get_args, get_origin
18
-
19
- from pydantic_core import PydanticUndefined
20
- from sqlalchemy import String
21
- from sqlalchemy.orm.attributes import InstrumentedAttribute
22
-
23
- # JSONB with fallback to generic JSON for non-PostgreSQL databases
24
- try:
25
- from sqlalchemy.dialects.postgresql import JSONB
26
- except ImportError:
27
- from sqlalchemy.types import JSON as JSONB # type: ignore[assignment]
28
-
29
- # Monkey-patch 1: Fix get_sqlalchemy_type for Python 3.14
30
- import sqlmodel.main
31
- _original_get_sqlalchemy_type = sqlmodel.main.get_sqlalchemy_type
32
-
33
- def _get_explicit_sa_type(field):
34
- """
35
- Extract an explicit ``sa_type`` from a SQLModel/Pydantic FieldInfo.
36
-
37
- Matches upstream ``sqlmodel.main._get_sqlmodel_field_value`` semantics:
38
- prefers ``FieldInfoMetadata.sa_type`` then falls back to the direct
39
- ``field.sa_type`` attribute. Returns ``PydanticUndefined`` when unset.
40
- """
41
- # Prefer FieldInfoMetadata.sa_type (set via Field(sa_type=...))
42
- metadata = getattr(field, 'metadata', None)
43
- if metadata:
44
- for item in metadata:
45
- sa_type = getattr(item, 'sa_type', PydanticUndefined)
46
- if sa_type is not PydanticUndefined:
47
- return sa_type
48
- # Fallback to direct attribute
49
- return getattr(field, 'sa_type', PydanticUndefined)
50
-
51
- def _patched_get_sqlalchemy_type(field):
52
- """
53
- Fix SQLModel's get_sqlalchemy_type for Python 3.14 type issues.
54
-
55
- Handles ForwardRef, ClassVar, Literal, Mapped, and custom types
56
- that cause issubclass errors under PEP 649.
57
- """
58
- # Respect explicit sa_type from Field(sa_type=...) — must take
59
- # precedence over annotation-based auto-detection, matching upstream
60
- # SQLModel behavior (sqlmodel/main.py::get_sqlalchemy_type checks
61
- # sa_type before everything else). Without this, fields like
62
- # ``options: list[str] | None = Field(sa_type=JSON)`` incorrectly
63
- # get JSONB from the Union[list, None] -> JSONB branch below,
64
- # breaking non-PostgreSQL dialects (e.g., SQLite in tests).
65
- explicit_sa_type = _get_explicit_sa_type(field)
66
- if explicit_sa_type is not PydanticUndefined:
67
- return explicit_sa_type
68
-
69
- # Check field.metadata (Pydantic-processed Annotated types)
70
- metadata = getattr(field, 'metadata', None)
71
- if metadata:
72
- # Pass field.annotation as source_type so handlers that need the element type
73
- # (e.g. _PendingArrayHandler extracting T from list[T] to compute ARRAY(T))
74
- # can resolve. handler=None is fine because the sa_type extraction path only
75
- # reads metadata['sa_type'] and never invokes the schema itself.
76
- field_annotation = getattr(field, 'annotation', None)
77
- for metadata_item in metadata:
78
- if hasattr(metadata_item, '__get_pydantic_core_schema__'):
79
- try:
80
- schema = metadata_item.__get_pydantic_core_schema__(field_annotation, None)
81
- if isinstance(schema, dict) and 'metadata' in schema:
82
- sa_type = schema['metadata'].get('sa_type')
83
- if sa_type is not None:
84
- return sa_type
85
- except (TypeError, AttributeError, KeyError):
86
- pass
87
-
88
- # Check InstrumentedAttribute defaults
89
- default = getattr(field, 'default', None)
90
- if default is not None:
91
- if isinstance(default, InstrumentedAttribute):
92
- return None
93
-
94
- annotation = getattr(field, 'annotation', None)
95
- if annotation is not None:
96
- # Handle Union[list/dict/..., None] types -> JSONB
97
- origin = get_origin(annotation)
98
-
99
- is_union = origin is Union or isinstance(annotation, types.UnionType)
100
- if is_union:
101
- args = get_args(annotation)
102
- non_none_args = [a for a in args if a is not type(None)]
103
- if non_none_args:
104
- first_arg = non_none_args[0]
105
- # Check for Annotated SA type metadata first (e.g. Array[T] | None).
106
- # Array[T] desugars to Annotated[list[T], ArrayTypeHandler(T)];
107
- # wrapping with | None produces Union[Annotated[...], None],
108
- # so the SA type must be extracted from the inner Annotated.
109
- first_arg_type_name = type(first_arg).__name__
110
- if first_arg_type_name in ('AnnotatedAlias', '_AnnotatedAlias'):
111
- inner_args = get_args(first_arg)
112
- for md in inner_args[1:]:
113
- if hasattr(md, '__get_pydantic_core_schema__'):
114
- try:
115
- # Pass first_arg (the full Annotated alias) as source_type so
116
- # handlers like _PendingArrayHandler can extract the element type.
117
- schema = md.__get_pydantic_core_schema__(first_arg, None)
118
- if isinstance(schema, dict) and 'metadata' in schema:
119
- sa_type = schema['metadata'].get('sa_type')
120
- if sa_type is not None:
121
- return sa_type
122
- except (TypeError, AttributeError, KeyError):
123
- pass
124
- first_origin = get_origin(first_arg)
125
- # Plain list/dict/tuple/set (no Annotated SA type) -> JSONB
126
- if first_origin in (list, dict, tuple, set):
127
- return JSONB
128
-
129
- # Handle Literal[StrEnum.MEMBER] types -> String
130
- if origin is Literal:
131
- literal_args = get_args(annotation)
132
- if literal_args:
133
- if all(isinstance(arg, StrEnum) for arg in literal_args):
134
- return String
135
-
136
- # Check __sqlmodel_sa_type__ attribute
137
- if hasattr(annotation, '__sqlmodel_sa_type__'):
138
- return annotation.__sqlmodel_sa_type__
139
-
140
- # Check __get_pydantic_core_schema__ for custom types
141
- if hasattr(annotation, '__get_pydantic_core_schema__'):
142
- try:
143
- schema = annotation.__get_pydantic_core_schema__(annotation, lambda x: None)
144
- if isinstance(schema, dict) and 'metadata' in schema:
145
- sa_type = schema['metadata'].get('sa_type')
146
- if sa_type is not None:
147
- return sa_type
148
- except (TypeError, AttributeError, KeyError):
149
- pass
150
-
151
- import typing
152
- from sqlalchemy.orm import Mapped
153
- anno_type_name = type(annotation).__name__
154
-
155
- # ForwardRef: Relationship field annotations
156
- if anno_type_name == 'ForwardRef':
157
- return None
158
-
159
- # AnnotatedAlias: check for sa_type metadata
160
- if anno_type_name in ('AnnotatedAlias', '_AnnotatedAlias'):
161
- args = get_args(annotation)
162
- for md in args[1:]:
163
- if hasattr(md, '__get_pydantic_core_schema__'):
164
- try:
165
- # Pass annotation (the full Annotated form) as source_type so
166
- # handlers needing the element type (_PendingArrayHandler etc.)
167
- # can extract T from list[T].
168
- schema = md.__get_pydantic_core_schema__(annotation, None)
169
- if isinstance(schema, dict) and 'metadata' in schema:
170
- sa_type = schema['metadata'].get('sa_type')
171
- if sa_type is not None:
172
- return sa_type
173
- except (TypeError, AttributeError, KeyError):
174
- pass
175
-
176
- # _GenericAlias or GenericAlias: typing generic types
177
- if anno_type_name in ('_GenericAlias', 'GenericAlias'):
178
- local_origin = get_origin(annotation)
179
-
180
- if local_origin is typing.ClassVar:
181
- return None
182
-
183
- if local_origin in (list, dict, tuple, set):
184
- field_info = getattr(field, 'field_info', None)
185
- if field_info is None:
186
- return None
187
-
188
- # Mapped type handling
189
- if 'Mapped' in anno_type_name or 'Mapped' in str(annotation):
190
- return None
191
-
192
- if get_origin(annotation) is Mapped:
193
- return None
194
- try:
195
- if annotation is Mapped or isinstance(annotation, type) and issubclass(annotation, Mapped):
196
- return None
197
- except TypeError:
198
- pass
199
-
200
- return _original_get_sqlalchemy_type(field)
201
-
202
- sqlmodel.main.get_sqlalchemy_type = _patched_get_sqlalchemy_type
203
-
204
- # Monkey-patch 2: Fix InstrumentedAttribute defaults in inherited table classes
205
- import sqlmodel._compat as _compat
206
- from pydantic_core import PydanticUndefined as Undefined
207
- from sqlalchemy.orm import attributes as _sa_attributes
208
-
209
- _original_sqlmodel_table_construct = _compat.sqlmodel_table_construct
210
-
211
- def _patched_sqlmodel_table_construct(self_instance, values):
212
- """
213
- Fix sqlmodel_table_construct to skip InstrumentedAttribute defaults.
214
-
215
- In polymorphic inherited table classes, inherited Relationship fields
216
- may have InstrumentedAttribute as their default value, causing errors.
217
- """
218
- cls = type(self_instance)
219
-
220
- fields_to_set = {}
221
-
222
- for name, field in cls.model_fields.items():
223
- if name in values:
224
- fields_to_set[name] = values[name]
225
- continue
226
-
227
- if isinstance(field.default, _sa_attributes.InstrumentedAttribute):
228
- continue
229
-
230
- if field.default is not Undefined:
231
- fields_to_set[name] = field.default
232
- elif field.default_factory is not None:
233
- fields_to_set[name] = field.get_default(call_default_factory=True)
234
-
235
- for key, value in fields_to_set.items():
236
- if not isinstance(value, _sa_attributes.InstrumentedAttribute):
237
- setattr(self_instance, key, value)
238
-
239
- object.__setattr__(self_instance, '__pydantic_fields_set__', set(values.keys()))
240
- if not cls.__pydantic_root_model__:
241
- _extra = None
242
- if cls.model_config.get('extra') == 'allow':
243
- _extra = {}
244
- for k, v in values.items():
245
- if k not in cls.model_fields:
246
- _extra[k] = v
247
- object.__setattr__(self_instance, '__pydantic_extra__', _extra)
248
-
249
- if cls.__pydantic_post_init__:
250
- self_instance.model_post_init(None)
251
- elif not cls.__pydantic_root_model__:
252
- object.__setattr__(self_instance, '__pydantic_private__', None)
253
-
254
- for key in self_instance.__sqlmodel_relationships__:
255
- value = values.get(key, Undefined)
256
- if value is not Undefined:
257
- setattr(self_instance, key, value)
258
-
259
- return self_instance
260
-
261
- _compat.sqlmodel_table_construct = _patched_sqlmodel_table_construct
1
+ """
2
+ Python 3.14+ compatibility patches for SQLModel.
3
+
4
+ This module applies monkey-patches to fix SQLModel's compatibility issues
5
+ with Python 3.14 (PEP 649 deferred evaluation of annotations).
6
+
7
+ Import this module for side effects before using SQLModelBase.
8
+ """
9
+ import sys
10
+
11
+ if sys.version_info >= (3, 14):
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]
15
+ import types
16
+ from enum import StrEnum
17
+ from typing import Literal, Union, get_args, get_origin
18
+
19
+ from pydantic_core import PydanticUndefined
20
+ from sqlalchemy import String
21
+ from sqlalchemy.orm.attributes import InstrumentedAttribute
22
+
23
+ # JSONB with fallback to generic JSON for non-PostgreSQL databases
24
+ try:
25
+ from sqlalchemy.dialects.postgresql import JSONB
26
+ except ImportError:
27
+ from sqlalchemy.types import JSON as JSONB # type: ignore[assignment]
28
+
29
+ # Monkey-patch 1: Fix get_sqlalchemy_type for Python 3.14
30
+ import sqlmodel.main
31
+ _original_get_sqlalchemy_type = sqlmodel.main.get_sqlalchemy_type
32
+
33
+ def _get_explicit_sa_type(field):
34
+ """
35
+ Extract an explicit ``sa_type`` from a SQLModel/Pydantic FieldInfo.
36
+
37
+ Matches upstream ``sqlmodel.main._get_sqlmodel_field_value`` semantics:
38
+ prefers ``FieldInfoMetadata.sa_type`` then falls back to the direct
39
+ ``field.sa_type`` attribute. Returns ``PydanticUndefined`` when unset.
40
+ """
41
+ # Prefer FieldInfoMetadata.sa_type (set via Field(sa_type=...))
42
+ metadata = getattr(field, 'metadata', None)
43
+ if metadata:
44
+ for item in metadata:
45
+ sa_type = getattr(item, 'sa_type', PydanticUndefined)
46
+ if sa_type is not PydanticUndefined:
47
+ return sa_type
48
+ # Fallback to direct attribute
49
+ return getattr(field, 'sa_type', PydanticUndefined)
50
+
51
+ def _patched_get_sqlalchemy_type(field):
52
+ """
53
+ Fix SQLModel's get_sqlalchemy_type for Python 3.14 type issues.
54
+
55
+ Handles ForwardRef, ClassVar, Literal, Mapped, and custom types
56
+ that cause issubclass errors under PEP 649.
57
+ """
58
+ # Respect explicit sa_type from Field(sa_type=...) — must take
59
+ # precedence over annotation-based auto-detection, matching upstream
60
+ # SQLModel behavior (sqlmodel/main.py::get_sqlalchemy_type checks
61
+ # sa_type before everything else). Without this, fields like
62
+ # ``options: list[str] | None = Field(sa_type=JSON)`` incorrectly
63
+ # get JSONB from the Union[list, None] -> JSONB branch below,
64
+ # breaking non-PostgreSQL dialects (e.g., SQLite in tests).
65
+ explicit_sa_type = _get_explicit_sa_type(field)
66
+ if explicit_sa_type is not PydanticUndefined:
67
+ return explicit_sa_type
68
+
69
+ # Check field.metadata (Pydantic-processed Annotated types)
70
+ metadata = getattr(field, 'metadata', None)
71
+ if metadata:
72
+ # Pass field.annotation as source_type so handlers that need the element type
73
+ # (e.g. _PendingArrayHandler extracting T from list[T] to compute ARRAY(T))
74
+ # can resolve. handler=None is fine because the sa_type extraction path only
75
+ # reads metadata['sa_type'] and never invokes the schema itself.
76
+ field_annotation = getattr(field, 'annotation', None)
77
+ for metadata_item in metadata:
78
+ if hasattr(metadata_item, '__get_pydantic_core_schema__'):
79
+ try:
80
+ schema = metadata_item.__get_pydantic_core_schema__(field_annotation, None)
81
+ if isinstance(schema, dict) and 'metadata' in schema:
82
+ sa_type = schema['metadata'].get('sa_type')
83
+ if sa_type is not None:
84
+ return sa_type
85
+ except (TypeError, AttributeError, KeyError):
86
+ pass
87
+
88
+ # Check InstrumentedAttribute defaults
89
+ default = getattr(field, 'default', None)
90
+ if default is not None:
91
+ if isinstance(default, InstrumentedAttribute):
92
+ return None
93
+
94
+ annotation = getattr(field, 'annotation', None)
95
+ if annotation is not None:
96
+ # Handle Union[list/dict/..., None] types -> JSONB
97
+ origin = get_origin(annotation)
98
+
99
+ is_union = origin is Union or isinstance(annotation, types.UnionType)
100
+ if is_union:
101
+ args = get_args(annotation)
102
+ non_none_args = [a for a in args if a is not type(None)]
103
+ if non_none_args:
104
+ first_arg = non_none_args[0]
105
+ # Check for Annotated SA type metadata first (e.g. Array[T] | None).
106
+ # Array[T] desugars to Annotated[list[T], ArrayTypeHandler(T)];
107
+ # wrapping with | None produces Union[Annotated[...], None],
108
+ # so the SA type must be extracted from the inner Annotated.
109
+ first_arg_type_name = type(first_arg).__name__
110
+ if first_arg_type_name in ('AnnotatedAlias', '_AnnotatedAlias'):
111
+ inner_args = get_args(first_arg)
112
+ for md in inner_args[1:]:
113
+ if hasattr(md, '__get_pydantic_core_schema__'):
114
+ try:
115
+ # Pass first_arg (the full Annotated alias) as source_type so
116
+ # handlers like _PendingArrayHandler can extract the element type.
117
+ schema = md.__get_pydantic_core_schema__(first_arg, None)
118
+ if isinstance(schema, dict) and 'metadata' in schema:
119
+ sa_type = schema['metadata'].get('sa_type')
120
+ if sa_type is not None:
121
+ return sa_type
122
+ except (TypeError, AttributeError, KeyError):
123
+ pass
124
+ first_origin = get_origin(first_arg)
125
+ # Plain list/dict/tuple/set (no Annotated SA type) -> JSONB
126
+ if first_origin in (list, dict, tuple, set):
127
+ return JSONB
128
+
129
+ # Handle Literal[StrEnum.MEMBER] types -> String
130
+ if origin is Literal:
131
+ literal_args = get_args(annotation)
132
+ if literal_args:
133
+ if all(isinstance(arg, StrEnum) for arg in literal_args):
134
+ return String
135
+
136
+ # Check __sqlmodel_sa_type__ attribute
137
+ if hasattr(annotation, '__sqlmodel_sa_type__'):
138
+ return annotation.__sqlmodel_sa_type__
139
+
140
+ # Check __get_pydantic_core_schema__ for custom types
141
+ if hasattr(annotation, '__get_pydantic_core_schema__'):
142
+ try:
143
+ schema = annotation.__get_pydantic_core_schema__(annotation, lambda x: None)
144
+ if isinstance(schema, dict) and 'metadata' in schema:
145
+ sa_type = schema['metadata'].get('sa_type')
146
+ if sa_type is not None:
147
+ return sa_type
148
+ except (TypeError, AttributeError, KeyError):
149
+ pass
150
+
151
+ import typing
152
+ from sqlalchemy.orm import Mapped
153
+ anno_type_name = type(annotation).__name__
154
+
155
+ # ForwardRef: Relationship field annotations
156
+ if anno_type_name == 'ForwardRef':
157
+ return None
158
+
159
+ # AnnotatedAlias: check for sa_type metadata
160
+ if anno_type_name in ('AnnotatedAlias', '_AnnotatedAlias'):
161
+ args = get_args(annotation)
162
+ for md in args[1:]:
163
+ if hasattr(md, '__get_pydantic_core_schema__'):
164
+ try:
165
+ # Pass annotation (the full Annotated form) as source_type so
166
+ # handlers needing the element type (_PendingArrayHandler etc.)
167
+ # can extract T from list[T].
168
+ schema = md.__get_pydantic_core_schema__(annotation, None)
169
+ if isinstance(schema, dict) and 'metadata' in schema:
170
+ sa_type = schema['metadata'].get('sa_type')
171
+ if sa_type is not None:
172
+ return sa_type
173
+ except (TypeError, AttributeError, KeyError):
174
+ pass
175
+
176
+ # _GenericAlias or GenericAlias: typing generic types
177
+ if anno_type_name in ('_GenericAlias', 'GenericAlias'):
178
+ local_origin = get_origin(annotation)
179
+
180
+ if local_origin is typing.ClassVar:
181
+ return None
182
+
183
+ if local_origin in (list, dict, tuple, set):
184
+ field_info = getattr(field, 'field_info', None)
185
+ if field_info is None:
186
+ return None
187
+
188
+ # Mapped type handling
189
+ if 'Mapped' in anno_type_name or 'Mapped' in str(annotation):
190
+ return None
191
+
192
+ if get_origin(annotation) is Mapped:
193
+ return None
194
+ try:
195
+ if annotation is Mapped or isinstance(annotation, type) and issubclass(annotation, Mapped):
196
+ return None
197
+ except TypeError:
198
+ pass
199
+
200
+ return _original_get_sqlalchemy_type(field)
201
+
202
+ sqlmodel.main.get_sqlalchemy_type = _patched_get_sqlalchemy_type
203
+
204
+ # Monkey-patch 2: Fix InstrumentedAttribute defaults in inherited table classes
205
+ import sqlmodel._compat as _compat
206
+ from pydantic_core import PydanticUndefined as Undefined
207
+ from sqlalchemy.orm import attributes as _sa_attributes
208
+
209
+ _original_sqlmodel_table_construct = _compat.sqlmodel_table_construct
210
+
211
+ def _patched_sqlmodel_table_construct(self_instance, values):
212
+ """
213
+ Fix sqlmodel_table_construct to skip InstrumentedAttribute defaults.
214
+
215
+ In polymorphic inherited table classes, inherited Relationship fields
216
+ may have InstrumentedAttribute as their default value, causing errors.
217
+ """
218
+ cls = type(self_instance)
219
+
220
+ fields_to_set = {}
221
+
222
+ for name, field in cls.model_fields.items():
223
+ if name in values:
224
+ fields_to_set[name] = values[name]
225
+ continue
226
+
227
+ if isinstance(field.default, _sa_attributes.InstrumentedAttribute):
228
+ continue
229
+
230
+ if field.default is not Undefined:
231
+ fields_to_set[name] = field.default
232
+ elif field.default_factory is not None:
233
+ fields_to_set[name] = field.get_default(call_default_factory=True)
234
+
235
+ for key, value in fields_to_set.items():
236
+ if not isinstance(value, _sa_attributes.InstrumentedAttribute):
237
+ setattr(self_instance, key, value)
238
+
239
+ object.__setattr__(self_instance, '__pydantic_fields_set__', set(values.keys()))
240
+ if not cls.__pydantic_root_model__:
241
+ _extra = None
242
+ if cls.model_config.get('extra') == 'allow':
243
+ _extra = {}
244
+ for k, v in values.items():
245
+ if k not in cls.model_fields:
246
+ _extra[k] = v
247
+ object.__setattr__(self_instance, '__pydantic_extra__', _extra)
248
+
249
+ if cls.__pydantic_post_init__:
250
+ self_instance.model_post_init(None)
251
+ elif not cls.__pydantic_root_model__:
252
+ object.__setattr__(self_instance, '__pydantic_private__', None)
253
+
254
+ for key in self_instance.__sqlmodel_relationships__:
255
+ value = values.get(key, Undefined)
256
+ if value is not Undefined:
257
+ setattr(self_instance, key, value)
258
+
259
+ return self_instance
260
+
261
+ _compat.sqlmodel_table_construct = _patched_sqlmodel_table_construct
@@ -1,16 +1,16 @@
1
- """Exceptions for sqlmodel_ext."""
2
-
3
-
4
- class RecordNotFoundError(Exception):
5
- """
6
- Raised when a database record is not found.
7
-
8
- Attributes:
9
- status_code: HTTP-compatible status code (404)
10
- detail: Human-readable error message
11
- """
12
- status_code: int = 404
13
-
14
- def __init__(self, detail: str = "Not found"):
15
- self.detail: str = detail
16
- super().__init__(detail)
1
+ """Exceptions for sqlmodel_ext."""
2
+
3
+
4
+ class RecordNotFoundError(Exception):
5
+ """
6
+ Raised when a database record is not found.
7
+
8
+ Attributes:
9
+ status_code: HTTP-compatible status code (404)
10
+ detail: Human-readable error message
11
+ """
12
+ status_code: int = 404
13
+
14
+ def __init__(self, detail: str = "Not found"):
15
+ self.detail: str = detail
16
+ super().__init__(detail)