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,9 @@ def __new__(
276
282
** new_cls .__annotations__ ,
277
283
}
278
284
285
+ for k , v in sqlalchemy_constructs .items ():
286
+ setattr (new_cls , k , v )
287
+
279
288
def get_config (name : str ) -> Any :
280
289
config_class_value = getattr (new_cls .__config__ , name , Undefined )
281
290
if config_class_value is not Undefined :
@@ -290,8 +299,9 @@ def get_config(name: str) -> Any:
290
299
# If it was passed by kwargs, ensure it's also set in config
291
300
new_cls .__config__ .table = config_table
292
301
for k , v in new_cls .__fields__ .items ():
293
- col = get_column_from_field (v )
294
- setattr (new_cls , k , col )
302
+ if k in sqlalchemy_constructs :
303
+ continue
304
+ setattr (new_cls , k , get_column_from_field (v ))
295
305
# Set a config flag to tell FastAPI that this should be read with a field
296
306
# in orm_mode instead of preemptively converting it to a dict.
297
307
# This could be done by reading new_cls.__config__.table in FastAPI, but
@@ -326,6 +336,8 @@ def __init__(
326
336
if getattr (cls .__config__ , "table" , False ) and not base_is_table :
327
337
dict_used = dict_ .copy ()
328
338
for field_name , field_value in cls .__fields__ .items ():
339
+ if field_name in cls .__sqlalchemy_constructs__ :
340
+ continue
329
341
dict_used [field_name ] = get_column_from_field (field_value )
330
342
for rel_name , rel_info in cls .__sqlmodel_relationships__ .items ():
331
343
if rel_info .sa_relationship :
0 commit comments