34
34
from sqlalchemy import Boolean , Column , Date , DateTime
35
35
from sqlalchemy import Enum as sa_Enum
36
36
from sqlalchemy import Float , ForeignKey , Integer , Interval , Numeric , inspect
37
+ from sqlalchemy .ext .hybrid import hybrid_property
37
38
from sqlalchemy .orm import RelationshipProperty , declared_attr , registry , relationship
38
39
from sqlalchemy .orm .attributes import set_attribute
39
40
from sqlalchemy .orm .decl_api import DeclarativeMeta
@@ -207,6 +208,7 @@ def Relationship(
207
208
@__dataclass_transform__ (kw_only_default = True , field_descriptors = (Field , FieldInfo ))
208
209
class SQLModelMetaclass (ModelMetaclass , DeclarativeMeta ):
209
210
__sqlmodel_relationships__ : Dict [str , RelationshipInfo ]
211
+ __sqlalchemy_constructs__ : Dict [str , Any ]
210
212
__config__ : Type [BaseConfig ]
211
213
__fields__ : Dict [str , ModelField ]
212
214
@@ -232,6 +234,7 @@ def __new__(
232
234
** kwargs : Any ,
233
235
) -> Any :
234
236
relationships : Dict [str , RelationshipInfo ] = {}
237
+ sqlalchemy_constructs = {}
235
238
dict_for_pydantic = {}
236
239
original_annotations = resolve_annotations (
237
240
class_dict .get ("__annotations__" , {}), class_dict .get ("__module__" , None )
@@ -241,6 +244,8 @@ def __new__(
241
244
for k , v in class_dict .items ():
242
245
if isinstance (v , RelationshipInfo ):
243
246
relationships [k ] = v
247
+ elif isinstance (v , hybrid_property ):
248
+ sqlalchemy_constructs [k ] = v
244
249
else :
245
250
dict_for_pydantic [k ] = v
246
251
for k , v in original_annotations .items ():
@@ -253,6 +258,7 @@ def __new__(
253
258
"__weakref__" : None ,
254
259
"__sqlmodel_relationships__" : relationships ,
255
260
"__annotations__" : pydantic_annotations ,
261
+ "__sqlalchemy_constructs__" : sqlalchemy_constructs ,
256
262
}
257
263
# Duplicate logic from Pydantic to filter config kwargs because if they are
258
264
# passed directly including the registry Pydantic will pass them over to the
@@ -276,6 +282,11 @@ def __new__(
276
282
** new_cls .__annotations__ ,
277
283
}
278
284
285
+ # We did not provide the sqlalchemy constructs to Pydantic's new function above
286
+ # so that they wouldn't be modified. Instead we set them directly to the class below:
287
+ for k , v in sqlalchemy_constructs .items ():
288
+ setattr (new_cls , k , v )
289
+
279
290
def get_config (name : str ) -> Any :
280
291
config_class_value = getattr (new_cls .__config__ , name , Undefined )
281
292
if config_class_value is not Undefined :
@@ -290,8 +301,9 @@ def get_config(name: str) -> Any:
290
301
# If it was passed by kwargs, ensure it's also set in config
291
302
new_cls .__config__ .table = config_table
292
303
for k , v in new_cls .__fields__ .items ():
293
- col = get_column_from_field (v )
294
- setattr (new_cls , k , col )
304
+ if k in sqlalchemy_constructs :
305
+ continue
306
+ setattr (new_cls , k , get_column_from_field (v ))
295
307
# Set a config flag to tell FastAPI that this should be read with a field
296
308
# in orm_mode instead of preemptively converting it to a dict.
297
309
# This could be done by reading new_cls.__config__.table in FastAPI, but
@@ -326,6 +338,8 @@ def __init__(
326
338
if getattr (cls .__config__ , "table" , False ) and not base_is_table :
327
339
dict_used = dict_ .copy ()
328
340
for field_name , field_value in cls .__fields__ .items ():
341
+ if field_name in cls .__sqlalchemy_constructs__ :
342
+ continue
329
343
dict_used [field_name ] = get_column_from_field (field_value )
330
344
for rel_name , rel_info in cls .__sqlmodel_relationships__ .items ():
331
345
if rel_info .sa_relationship :
0 commit comments