@@ -499,6 +499,7 @@ class SQLModelMetaclass(ModelMetaclass, DeclarativeMeta):
499499 __sqlmodel_relationships__ : Dict [str , RelationshipInfo ]
500500 __sqlalchemy_constructs__ : Dict [str , SQLAlchemyConstruct ]
501501 __sqlalchemy_association_proxies__ : Dict [str , AssociationProxy ]
502+ __sqlalchemy_hybrid_property_setters__ : Dict [str , hybrid_property ]
502503 model_config : SQLModelConfig
503504 model_fields : Dict [str , FieldInfo ]
504505 __config__ : Type [SQLModelConfig ]
@@ -529,14 +530,37 @@ def __new__(
529530 relationships : Dict [str , RelationshipInfo ] = {}
530531 sqlalchemy_constructs : Dict [str , SQLAlchemyConstruct ] = {}
531532 sqlalchemy_association_proxies : Dict [str , AssociationProxy ] = {}
533+ sqlalchemy_hybrid_property_setters : Dict [str , hybrid_property ] = {}
532534 dict_for_pydantic = {}
533535 original_annotations = get_annotations (class_dict )
534536 pydantic_annotations = {}
535537 relationship_annotations = {}
538+ # First pass - collect all hybrid properties
539+ hybrid_properties_with_setters = {}
540+ for k , v in class_dict .items ():
541+ if (
542+ isinstance (v , hybrid_property )
543+ and hasattr (v , "fset" )
544+ and v .fset is not None
545+ ):
546+ # Store by the hybrid_property object itself to avoid duplicates
547+ hybrid_properties_with_setters [v ] = k
548+
549+ # Find the main property name (shortest name for each hybrid_property object)
550+ for hybrid_prop_obj , _prop_name in hybrid_properties_with_setters .items ():
551+ # Find all names that point to this same hybrid_property object
552+ all_names_for_this_prop = [
553+ name for name , obj in class_dict .items () if obj is hybrid_prop_obj
554+ ]
555+ # Choose the shortest name (main property, not setter function)
556+ main_name = min (all_names_for_this_prop , key = len )
557+ sqlalchemy_hybrid_property_setters [main_name ] = hybrid_prop_obj
558+
559+ # Second pass - process all items
536560 for k , v in class_dict .items ():
537561 if isinstance (v , AssociationProxy ):
538562 sqlalchemy_association_proxies [k ] = v
539- if isinstance (v , RelationshipInfo ):
563+ elif isinstance (v , RelationshipInfo ):
540564 relationships [k ] = v
541565 elif isinstance (
542566 v ,
@@ -563,6 +587,7 @@ def __new__(
563587 "__annotations__" : pydantic_annotations ,
564588 "__sqlalchemy_constructs__" : sqlalchemy_constructs ,
565589 "__sqlalchemy_association_proxies__" : sqlalchemy_association_proxies ,
590+ "__sqlalchemy_hybrid_property_setters__" : sqlalchemy_hybrid_property_setters ,
566591 }
567592 # Duplicate logic from Pydantic to filter config kwargs because if they are
568593 # passed directly including the registry Pydantic will pass them over to the
@@ -872,6 +897,7 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry
872897 __tablename__ : ClassVar [Union [str , Callable [..., str ]]]
873898 __sqlmodel_relationships__ : ClassVar [Dict [str , RelationshipProperty [Any ]]]
874899 __sqlalchemy_association_proxies__ : ClassVar [Dict [str , AssociationProxy ]]
900+ __sqlalchemy_hybrid_property_setters__ : ClassVar [Dict [str , hybrid_property ]]
875901 __name__ : ClassVar [str ]
876902 metadata : ClassVar [MetaData ]
877903 __allow_unmapped__ = True # https://docs.sqlalchemy.org/en/20/changelog/migration_20.html#migration-20-step-six
@@ -941,23 +967,19 @@ def __setattr__(self, name: str, value: Any) -> None:
941967 # Set in SQLAlchemy hybrid properties with setters
942968 if (
943969 is_table_model_class (self .__class__ )
944- and name in self .__class__ . __dict__
970+ and name in self .__sqlalchemy_hybrid_property_setters__
945971 ):
946- class_attr = self .__class__ .__dict__ [name ]
947- # Check if this is a hybrid property with a setter
948- if hasattr (class_attr , '__set__' ):
949- try :
950- # Try to use the hybrid property setter
951- class_attr .__set__ (self , value )
952- return
953- except AttributeError :
954- # No setter available, continue with normal flow
955- pass
972+ hybrid_property_setter = self .__sqlalchemy_hybrid_property_setters__ [
973+ name
974+ ]
975+ hybrid_property_setter .__set__ (self , value )
976+ return
956977 # Set in Pydantic model to trigger possible validation changes, only for
957978 # non relationship values
958979 if (
959980 name not in self .__sqlmodel_relationships__
960981 and name not in self .__sqlalchemy_association_proxies__
982+ and name not in self .__sqlalchemy_hybrid_property_setters__
961983 ):
962984 super ().__setattr__ (name , value )
963985
0 commit comments