3434from sqlalchemy import Boolean , Column , Date , DateTime
3535from sqlalchemy import Enum as sa_Enum
3636from sqlalchemy import Float , ForeignKey , Integer , Interval , Numeric , inspect
37+ from sqlalchemy .ext .hybrid import hybrid_property
3738from sqlalchemy .orm import RelationshipProperty , declared_attr , registry , relationship
3839from sqlalchemy .orm .attributes import set_attribute
3940from sqlalchemy .orm .decl_api import DeclarativeMeta
@@ -207,6 +208,7 @@ def Relationship(
207208@__dataclass_transform__ (kw_only_default = True , field_descriptors = (Field , FieldInfo ))
208209class SQLModelMetaclass (ModelMetaclass , DeclarativeMeta ):
209210 __sqlmodel_relationships__ : Dict [str , RelationshipInfo ]
211+ __sqlalchemy_constructs__ : Dict [str , Any ]
210212 __config__ : Type [BaseConfig ]
211213 __fields__ : Dict [str , ModelField ]
212214
@@ -232,6 +234,7 @@ def __new__(
232234 ** kwargs : Any ,
233235 ) -> Any :
234236 relationships : Dict [str , RelationshipInfo ] = {}
237+ sqlalchemy_constructs = {}
235238 dict_for_pydantic = {}
236239 original_annotations = resolve_annotations (
237240 class_dict .get ("__annotations__" , {}), class_dict .get ("__module__" , None )
@@ -241,6 +244,8 @@ def __new__(
241244 for k , v in class_dict .items ():
242245 if isinstance (v , RelationshipInfo ):
243246 relationships [k ] = v
247+ elif isinstance (v , hybrid_property ):
248+ sqlalchemy_constructs [k ] = v
244249 else :
245250 dict_for_pydantic [k ] = v
246251 for k , v in original_annotations .items ():
@@ -253,6 +258,7 @@ def __new__(
253258 "__weakref__" : None ,
254259 "__sqlmodel_relationships__" : relationships ,
255260 "__annotations__" : pydantic_annotations ,
261+ "__sqlalchemy_constructs__" : sqlalchemy_constructs ,
256262 }
257263 # Duplicate logic from Pydantic to filter config kwargs because if they are
258264 # passed directly including the registry Pydantic will pass them over to the
@@ -276,6 +282,11 @@ def __new__(
276282 ** new_cls .__annotations__ ,
277283 }
278284
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+
279290 def get_config (name : str ) -> Any :
280291 config_class_value = getattr (new_cls .__config__ , name , Undefined )
281292 if config_class_value is not Undefined :
@@ -290,8 +301,9 @@ def get_config(name: str) -> Any:
290301 # If it was passed by kwargs, ensure it's also set in config
291302 new_cls .__config__ .table = config_table
292303 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 ))
295307 # Set a config flag to tell FastAPI that this should be read with a field
296308 # in orm_mode instead of preemptively converting it to a dict.
297309 # This could be done by reading new_cls.__config__.table in FastAPI, but
@@ -326,6 +338,8 @@ def __init__(
326338 if getattr (cls .__config__ , "table" , False ) and not base_is_table :
327339 dict_used = dict_ .copy ()
328340 for field_name , field_value in cls .__fields__ .items ():
341+ if field_name in cls .__sqlalchemy_constructs__ :
342+ continue
329343 dict_used [field_name ] = get_column_from_field (field_value )
330344 for rel_name , rel_info in cls .__sqlmodel_relationships__ .items ():
331345 if rel_info .sa_relationship :
0 commit comments