@@ -353,7 +353,33 @@ def copy_data_from_parent_model( # noqa: CCR001
353353 :rtype: tuple[dict, dict]
354354 """
355355 if attrs .get ("ormar_config" ):
356- if model_fields and not base_class .ormar_config .abstract : # type: ignore
356+ child_config = attrs ["ormar_config" ]
357+ child_proxy = getattr (child_config , "proxy" , False )
358+ base_abstract = base_class .ormar_config .abstract # type: ignore
359+
360+ if child_proxy :
361+ if base_abstract :
362+ raise ModelDefinitionError (
363+ f"Proxy model { curr_class .__name__ } cannot inherit from "
364+ f"abstract class { base_class .__name__ } ; "
365+ f"use concrete inheritance instead."
366+ )
367+ new_fields = set (model_fields ) - set (
368+ base_class .ormar_config .model_fields # type: ignore
369+ )
370+ if new_fields :
371+ raise ModelDefinitionError (
372+ f"Proxy model { curr_class .__name__ } cannot declare new ormar "
373+ f"fields: { sorted (new_fields )} ."
374+ )
375+ update_attrs_from_base_config (
376+ base_class = base_class , # type: ignore
377+ attrs = attrs ,
378+ model_fields = model_fields ,
379+ )
380+ return attrs , dict (base_class .ormar_config .model_fields )
381+
382+ if model_fields and not base_abstract :
357383 raise ModelDefinitionError (
358384 f"{ curr_class .__name__ } cannot inherit "
359385 f"from non abstract class { base_class .__name__ } "
@@ -521,6 +547,50 @@ def update_attrs_and_fields(
521547 return updated_model_fields
522548
523549
550+ def wire_proxy_from_parent (new_model : type ["Model" ]) -> None :
551+ """
552+ Resolve the concrete ormar parent of a proxy model and share its table,
553+ columns, primary key, model_fields, metadata and database with the proxy.
554+
555+ Proxy models do not own a SQLAlchemy table. They reuse the parent's table
556+ so that queries via the proxy class hit the same physical rows.
557+
558+ :raises ModelDefinitionError: if the proxy has no concrete ormar parent or
559+ if it inherits from multiple concrete ormar models with different tables.
560+ :param new_model: the proxy model class being constructed
561+ :type new_model: type["Model"]
562+ """
563+ concrete_bases = [
564+ base
565+ for base in new_model .__mro__ [1 :]
566+ if hasattr (base , "ormar_config" )
567+ and not base .ormar_config .abstract
568+ and base is not new_model
569+ ]
570+ if not concrete_bases :
571+ raise ModelDefinitionError (
572+ f"Proxy model { new_model .__name__ } has no concrete ormar parent."
573+ )
574+ primary_table = concrete_bases [0 ].ormar_config .table
575+ for other in concrete_bases [1 :]:
576+ if other .ormar_config .table is not primary_table :
577+ raise ModelDefinitionError (
578+ f"Proxy model { new_model .__name__ } cannot inherit from multiple "
579+ f"concrete ormar models with different tables."
580+ )
581+ parent_cfg = concrete_bases [0 ].ormar_config
582+ cfg = new_model .ormar_config
583+ cfg .table = parent_cfg .table
584+ cfg .tablename = parent_cfg .tablename
585+ cfg .pkname = parent_cfg .pkname
586+ cfg .columns = parent_cfg .columns
587+ cfg .model_fields = parent_cfg .model_fields
588+ cfg .metadata = parent_cfg .metadata
589+ cfg .database = parent_cfg .database
590+ cfg .alias_manager = parent_cfg .alias_manager
591+ new_model .pk = PkDescriptor (name = parent_cfg .pkname )
592+
593+
524594def add_field_descriptor (
525595 name : str , field : "BaseField" , new_model : type ["Model" ]
526596) -> None :
@@ -653,7 +723,9 @@ def __new__( # type: ignore # noqa: CCR001
653723 register_signals (new_model = new_model )
654724 modify_schema_example (model = new_model )
655725
656- if not new_model .ormar_config .abstract :
726+ if new_model .ormar_config .proxy :
727+ wire_proxy_from_parent (new_model )
728+ elif not new_model .ormar_config .abstract :
657729 new_model = populate_config_tablename_columns_and_pk (name , new_model )
658730 populate_config_sqlalchemy_table_if_required (new_model .ormar_config )
659731 expand_reverse_relationships (new_model )
0 commit comments