110110 hybrid_method ,
111111 ColumnProperty ,
112112 declared_attr ,
113- AssociationProxy ,
114113]
115114
116115
@@ -498,6 +497,7 @@ def Relationship(
498497class SQLModelMetaclass (ModelMetaclass , DeclarativeMeta ):
499498 __sqlmodel_relationships__ : Dict [str , RelationshipInfo ]
500499 __sqlalchemy_constructs__ : Dict [str , SQLAlchemyConstruct ]
500+ __sqlalchemy_association_proxies__ : Dict [str , AssociationProxy ]
501501 model_config : SQLModelConfig
502502 model_fields : Dict [str , FieldInfo ]
503503 __config__ : Type [SQLModelConfig ]
@@ -527,11 +527,14 @@ def __new__(
527527 ) -> Any :
528528 relationships : Dict [str , RelationshipInfo ] = {}
529529 sqlalchemy_constructs : Dict [str , SQLAlchemyConstruct ] = {}
530+ sqlalchemy_association_proxies : Dict [str , AssociationProxy ] = {}
530531 dict_for_pydantic = {}
531532 original_annotations = get_annotations (class_dict )
532533 pydantic_annotations = {}
533534 relationship_annotations = {}
534535 for k , v in class_dict .items ():
536+ if isinstance (v , AssociationProxy ):
537+ sqlalchemy_association_proxies [k ] = v
535538 if isinstance (v , RelationshipInfo ):
536539 relationships [k ] = v
537540 elif isinstance (
@@ -558,6 +561,7 @@ def __new__(
558561 "__sqlmodel_relationships__" : relationships ,
559562 "__annotations__" : pydantic_annotations ,
560563 "__sqlalchemy_constructs__" : sqlalchemy_constructs ,
564+ "__sqlalchemy_association_proxies__" : sqlalchemy_association_proxies ,
561565 }
562566 # Duplicate logic from Pydantic to filter config kwargs because if they are
563567 # passed directly including the registry Pydantic will pass them over to the
@@ -584,6 +588,9 @@ def __new__(
584588 for k , v in sqlalchemy_constructs .items ():
585589 setattr (new_cls , k , v )
586590
591+ for k , v in sqlalchemy_association_proxies .items ():
592+ setattr (new_cls , k , v )
593+
587594 def get_config (name : str ) -> Any :
588595 config_class_value = get_config_value (
589596 model = new_cls , parameter = name , default = Undefined
@@ -639,10 +646,13 @@ def __init__(
639646 # triggers an error
640647 base_is_table = any (is_table_model_class (base ) for base in bases )
641648 if is_table_model_class (cls ) and not base_is_table :
649+ for (
650+ association_proxy_name ,
651+ association_proxy ,
652+ ) in cls .__sqlalchemy_association_proxies__ .items ():
653+ setattr (cls , association_proxy_name , association_proxy )
654+
642655 for rel_name , rel_info in cls .__sqlmodel_relationships__ .items ():
643- if rel_name in cls .__sqlalchemy_constructs__ :
644- # Skip hybrid properties
645- continue
646656 if rel_info .sa_relationship :
647657 # There's a SQLAlchemy relationship declared, that takes precedence
648658 # over anything else, use that and continue with the next attribute
@@ -860,6 +870,7 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry
860870 __slots__ = ("__weakref__" ,)
861871 __tablename__ : ClassVar [Union [str , Callable [..., str ]]]
862872 __sqlmodel_relationships__ : ClassVar [Dict [str , RelationshipProperty [Any ]]]
873+ __sqlalchemy_association_proxies__ : ClassVar [Dict [str , AssociationProxy ]]
863874 __name__ : ClassVar [str ]
864875 metadata : ClassVar [MetaData ]
865876 __allow_unmapped__ = True # https://docs.sqlalchemy.org/en/20/changelog/migration_20.html#migration-20-step-six
@@ -909,9 +920,19 @@ def __setattr__(self, name: str, value: Any) -> None:
909920 # Set in SQLAlchemy, before Pydantic to trigger events and updates
910921 if is_table_model_class (self .__class__ ) and is_instrumented (self , name ): # type: ignore[no-untyped-call]
911922 set_attribute (self , name , value )
923+ # Set in SQLAlchemy association proxies
924+ if (
925+ is_table_model_class (self .__class__ )
926+ and name in self .__sqlalchemy_association_proxies__
927+ ):
928+ association_proxy = self .__sqlalchemy_association_proxies__ [name ]
929+ association_proxy .__set__ (self , value )
912930 # Set in Pydantic model to trigger possible validation changes, only for
913931 # non relationship values
914- if name not in self .__sqlmodel_relationships__ :
932+ if (
933+ name not in self .__sqlmodel_relationships__
934+ and name not in self .__sqlalchemy_association_proxies__
935+ ):
915936 super ().__setattr__ (name , value )
916937
917938 def __repr_args__ (self ) -> Sequence [Tuple [Optional [str ], Any ]]:
0 commit comments