diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/sqlalchemy/connectors/pyodbc.py | 5 | ||||
| -rw-r--r-- | lib/sqlalchemy/dialects/sqlite/base.py | 12 | ||||
| -rw-r--r-- | lib/sqlalchemy/ext/associationproxy.py | 10 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/_orm_constructors.py | 57 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/decl_base.py | 84 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/descriptor_props.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 12 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/properties.py | 20 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/relationships.py | 2 |
9 files changed, 163 insertions, 41 deletions
diff --git a/lib/sqlalchemy/connectors/pyodbc.py b/lib/sqlalchemy/connectors/pyodbc.py index f920860cd..e8fbed18e 100644 --- a/lib/sqlalchemy/connectors/pyodbc.py +++ b/lib/sqlalchemy/connectors/pyodbc.py @@ -209,7 +209,10 @@ class PyODBCConnector(Connector): # omit the setinputsizes calls for .executemany() with # fast_executemany=True - if context.executemany and self.fast_executemany: + if ( + context.execute_style is interfaces.ExecuteStyle.EXECUTEMANY + and self.fast_executemany + ): return cursor.setinputsizes( diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py index 11554fcc0..fb9a19c89 100644 --- a/lib/sqlalchemy/dialects/sqlite/base.py +++ b/lib/sqlalchemy/dialects/sqlite/base.py @@ -2462,6 +2462,8 @@ class SQLiteDialect(default.DefaultDialect): r'REFERENCES +(?:(?:"(.+?)")|([a-z0-9_]+)) *\((.+?)\) *' r"((?:ON (?:DELETE|UPDATE) " r"(?:SET NULL|SET DEFAULT|CASCADE|RESTRICT|NO ACTION) *)*)" + r"((?:NOT +)?DEFERRABLE)?" + r"(?: +INITIALLY +(DEFERRED|IMMEDIATE))?" ) for match in re.finditer(FK_PATTERN, table_data, re.I): ( @@ -2471,7 +2473,9 @@ class SQLiteDialect(default.DefaultDialect): referred_name, referred_columns, onupdatedelete, - ) = match.group(1, 2, 3, 4, 5, 6) + deferrable, + initially, + ) = match.group(1, 2, 3, 4, 5, 6, 7, 8) constrained_columns = list( self._find_cols_in_sig(constrained_columns) ) @@ -2493,6 +2497,12 @@ class SQLiteDialect(default.DefaultDialect): onupdate = token[6:].strip() if onupdate and onupdate != "NO ACTION": options["onupdate"] = onupdate + + if deferrable: + options["deferrable"] = "NOT" not in deferrable.upper() + if initially: + options["initially"] = initially.upper() + yield ( constraint_name, constrained_columns, diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py index 15193e563..4d38ac536 100644 --- a/lib/sqlalchemy/ext/associationproxy.py +++ b/lib/sqlalchemy/ext/associationproxy.py @@ -94,6 +94,7 @@ def association_proxy( repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002 default: Optional[Any] = _NoArg.NO_ARG, default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG, + compare: Union[_NoArg, bool] = _NoArg.NO_ARG, kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG, ) -> AssociationProxy[Any]: r"""Return a Python property implementing a view of a target @@ -174,6 +175,13 @@ def association_proxy( .. versionadded:: 2.0.0b4 + :param compare: Specific to + :ref:`orm_declarative_native_dataclasses`, indicates if this field + should be included in comparison operations when generating the + ``__eq__()`` and ``__ne__()`` methods for the mapped class. + + .. versionadded:: 2.0.0b4 + :param kw_only: Specific to :ref:`orm_declarative_native_dataclasses`, indicates if this field should be marked as keyword-only when generating the ``__init__()`` method as generated by the dataclass process. @@ -218,7 +226,7 @@ def association_proxy( info=info, cascade_scalar_deletes=cascade_scalar_deletes, attribute_options=_AttributeOptions( - init, repr, default, default_factory, kw_only + init, repr, default, default_factory, compare, kw_only ), ) diff --git a/lib/sqlalchemy/orm/_orm_constructors.py b/lib/sqlalchemy/orm/_orm_constructors.py index 2450d1e83..cb28ac060 100644 --- a/lib/sqlalchemy/orm/_orm_constructors.py +++ b/lib/sqlalchemy/orm/_orm_constructors.py @@ -104,6 +104,7 @@ def mapped_column( repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002 default: Optional[Any] = _NoArg.NO_ARG, default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG, + compare: Union[_NoArg, bool] = _NoArg.NO_ARG, kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG, nullable: Optional[ Union[bool, Literal[SchemaConst.NULL_UNSPECIFIED]] @@ -112,6 +113,7 @@ def mapped_column( deferred: Union[_NoArg, bool] = _NoArg.NO_ARG, deferred_group: Optional[str] = None, deferred_raiseload: bool = False, + use_existing_column: bool = False, name: Optional[str] = None, type_: Optional[_TypeEngineArgument[Any]] = None, autoincrement: Union[bool, Literal["auto", "ignore_fk"]] = "auto", @@ -208,6 +210,18 @@ def mapped_column( :ref:`orm_queryguide_deferred_raiseload` + :param use_existing_column: if True, will attempt to locate the given + column name on an inherited superclass (typically single inheriting + superclass), and if present, will not produce a new column, mapping + to the superclass column as though it were omitted from this class. + This is used for mixins that add new columns to an inherited superclass. + + .. seealso:: + + :ref:`orm_inheritance_column_conflicts` + + .. versionadded:: 2.0.0b4 + :param default: Passed directly to the :paramref:`_schema.Column.default` parameter if the :paramref:`_orm.mapped_column.insert_default` parameter is not present. @@ -245,6 +259,13 @@ def mapped_column( specifies a default-value generation function that will take place as part of the ``__init__()`` method as generated by the dataclass process. + :param compare: Specific to + :ref:`orm_declarative_native_dataclasses`, indicates if this field + should be included in comparison operations when generating the + ``__eq__()`` and ``__ne__()`` methods for the mapped class. + + .. versionadded:: 2.0.0b4 + :param kw_only: Specific to :ref:`orm_declarative_native_dataclasses`, indicates if this field should be marked as keyword-only when generating the ``__init__()``. @@ -263,7 +284,7 @@ def mapped_column( autoincrement=autoincrement, insert_default=insert_default, attribute_options=_AttributeOptions( - init, repr, default, default_factory, kw_only + init, repr, default, default_factory, compare, kw_only ), doc=doc, key=key, @@ -275,6 +296,7 @@ def mapped_column( primary_key=primary_key, server_default=server_default, server_onupdate=server_onupdate, + use_existing_column=use_existing_column, quote=quote, comment=comment, system=system, @@ -296,6 +318,7 @@ def column_property( repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002 default: Optional[Any] = _NoArg.NO_ARG, default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG, + compare: Union[_NoArg, bool] = _NoArg.NO_ARG, kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG, active_history: bool = False, expire_on_flush: bool = True, @@ -389,7 +412,7 @@ def column_property( column, *additional_columns, attribute_options=_AttributeOptions( - init, repr, default, default_factory, kw_only + init, repr, default, default_factory, compare, kw_only ), group=group, deferred=deferred, @@ -415,6 +438,7 @@ def composite( repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002 default: Optional[Any] = _NoArg.NO_ARG, default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG, + compare: Union[_NoArg, bool] = _NoArg.NO_ARG, kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG, info: Optional[_InfoType] = None, doc: Optional[str] = None, @@ -436,6 +460,7 @@ def composite( repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002 default: Optional[Any] = _NoArg.NO_ARG, default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG, + compare: Union[_NoArg, bool] = _NoArg.NO_ARG, kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG, info: Optional[_InfoType] = None, doc: Optional[str] = None, @@ -458,6 +483,7 @@ def composite( repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002 default: Optional[Any] = _NoArg.NO_ARG, default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG, + compare: Union[_NoArg, bool] = _NoArg.NO_ARG, kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG, info: Optional[_InfoType] = None, doc: Optional[str] = None, @@ -521,6 +547,14 @@ def composite( specifies a default-value generation function that will take place as part of the ``__init__()`` method as generated by the dataclass process. + + :param compare: Specific to + :ref:`orm_declarative_native_dataclasses`, indicates if this field + should be included in comparison operations when generating the + ``__eq__()`` and ``__ne__()`` methods for the mapped class. + + .. versionadded:: 2.0.0b4 + :param kw_only: Specific to :ref:`orm_declarative_native_dataclasses`, indicates if this field should be marked as keyword-only when generating the ``__init__()``. @@ -533,7 +567,7 @@ def composite( _class_or_attr, *attrs, attribute_options=_AttributeOptions( - init, repr, default, default_factory, kw_only + init, repr, default, default_factory, compare, kw_only ), group=group, deferred=deferred, @@ -756,6 +790,7 @@ def relationship( repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002 default: Union[_NoArg, _T] = _NoArg.NO_ARG, default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG, + compare: Union[_NoArg, bool] = _NoArg.NO_ARG, kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG, lazy: _LazyLoadArgumentType = "select", passive_deletes: Union[Literal["all"], bool] = False, @@ -1593,6 +1628,13 @@ def relationship( specifies a default-value generation function that will take place as part of the ``__init__()`` method as generated by the dataclass process. + :param compare: Specific to + :ref:`orm_declarative_native_dataclasses`, indicates if this field + should be included in comparison operations when generating the + ``__eq__()`` and ``__ne__()`` methods for the mapped class. + + .. versionadded:: 2.0.0b4 + :param kw_only: Specific to :ref:`orm_declarative_native_dataclasses`, indicates if this field should be marked as keyword-only when generating the ``__init__()``. @@ -1615,7 +1657,7 @@ def relationship( cascade=cascade, viewonly=viewonly, attribute_options=_AttributeOptions( - init, repr, default, default_factory, kw_only + init, repr, default, default_factory, compare, kw_only ), lazy=lazy, passive_deletes=passive_deletes, @@ -1648,6 +1690,7 @@ def synonym( repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002 default: Union[_NoArg, _T] = _NoArg.NO_ARG, default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG, + compare: Union[_NoArg, bool] = _NoArg.NO_ARG, kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG, info: Optional[_InfoType] = None, doc: Optional[str] = None, @@ -1761,7 +1804,7 @@ def synonym( descriptor=descriptor, comparator_factory=comparator_factory, attribute_options=_AttributeOptions( - init, repr, default, default_factory, kw_only + init, repr, default, default_factory, compare, kw_only ), doc=doc, info=info, @@ -1890,6 +1933,7 @@ def deferred( repr: Union[_NoArg, bool] = _NoArg.NO_ARG, # noqa: A002 default: Optional[Any] = _NoArg.NO_ARG, default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG, + compare: Union[_NoArg, bool] = _NoArg.NO_ARG, kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG, active_history: bool = False, expire_on_flush: bool = True, @@ -1925,7 +1969,7 @@ def deferred( column, *additional_columns, attribute_options=_AttributeOptions( - init, repr, default, default_factory, kw_only + init, repr, default, default_factory, compare, kw_only ), group=group, deferred=True, @@ -1966,6 +2010,7 @@ def query_expression( _NoArg.NO_ARG, _NoArg.NO_ARG, _NoArg.NO_ARG, + _NoArg.NO_ARG, ), expire_on_flush=expire_on_flush, info=info, diff --git a/lib/sqlalchemy/orm/decl_base.py b/lib/sqlalchemy/orm/decl_base.py index 1e716e687..797828377 100644 --- a/lib/sqlalchemy/orm/decl_base.py +++ b/lib/sqlalchemy/orm/decl_base.py @@ -128,6 +128,28 @@ def _declared_mapping_info( return None +def _is_supercls_for_inherits(cls: Type[Any]) -> bool: + """return True if this class will be used as a superclass to set in + 'inherits'. + + This includes deferred mapper configs that aren't mapped yet, however does + not include classes with _sa_decl_prepare_nocascade (e.g. + ``AbstractConcreteBase``); these concrete-only classes are not set up as + "inherits" until after mappers are configured using + mapper._set_concrete_base() + + """ + if _DeferredMapperConfig.has_cls(cls): + return not _get_immediate_cls_attr( + cls, "_sa_decl_prepare_nocascade", strict=True + ) + # regular mapping + elif _is_mapped_class(cls): + return True + else: + return False + + def _resolve_for_abstract_or_classical(cls: Type[Any]) -> Optional[Type[Any]]: if cls is object: return None @@ -380,11 +402,8 @@ class _ImperativeMapperConfig(_MapperConfig): c = _resolve_for_abstract_or_classical(base_) if c is None: continue - if _declared_mapping_info( - c - ) is not None and not _get_immediate_cls_attr( - c, "_sa_decl_prepare_nocascade", strict=True - ): + + if _is_supercls_for_inherits(c) and c not in inherits_search: inherits_search.append(c) if inherits_search: @@ -430,6 +449,7 @@ class _ClassScanMapperConfig(_MapperConfig): "allow_unmapped_annotations", ) + is_deferred = False registry: _RegistryType clsdict_view: _ClassDict collected_annotations: Dict[str, _CollectedAnnotation] @@ -532,13 +552,15 @@ class _ClassScanMapperConfig(_MapperConfig): self.classname, self.cls, registry._class_registry ) + self._setup_inheriting_mapper(mapper_kw) + self._extract_mappable_attributes() self._extract_declared_columns() self._setup_table(table) - self._setup_inheritance(mapper_kw) + self._setup_inheriting_columns(mapper_kw) self._early_mapping(mapper_kw) @@ -739,13 +761,7 @@ class _ClassScanMapperConfig(_MapperConfig): # need to do this all the way up the hierarchy first # (see #8190) - class_mapped = ( - base is not cls - and _declared_mapping_info(base) is not None - and not _get_immediate_cls_attr( - base, "_sa_decl_prepare_nocascade", strict=True - ) - ) + class_mapped = base is not cls and _is_supercls_for_inherits(base) local_attributes_for_class = self._cls_attr_resolver(base) @@ -1358,6 +1374,7 @@ class _ClassScanMapperConfig(_MapperConfig): if mapped_container is not None or annotation is None: try: value.declarative_scan( + self, self.registry, cls, originating_module, @@ -1558,11 +1575,8 @@ class _ClassScanMapperConfig(_MapperConfig): else: return manager.registry.metadata - def _setup_inheritance(self, mapper_kw: _MapperKwArgs) -> None: - table = self.local_table + def _setup_inheriting_mapper(self, mapper_kw: _MapperKwArgs) -> None: cls = self.cls - table_args = self.table_args - declared_columns = self.declared_columns inherits = mapper_kw.get("inherits", None) @@ -1574,13 +1588,9 @@ class _ClassScanMapperConfig(_MapperConfig): c = _resolve_for_abstract_or_classical(base_) if c is None: continue - if _declared_mapping_info( - c - ) is not None and not _get_immediate_cls_attr( - c, "_sa_decl_prepare_nocascade", strict=True - ): - if c not in inherits_search: - inherits_search.append(c) + + if _is_supercls_for_inherits(c) and c not in inherits_search: + inherits_search.append(c) if inherits_search: if len(inherits_search) > 1: @@ -1594,6 +1604,12 @@ class _ClassScanMapperConfig(_MapperConfig): self.inherits = inherits + def _setup_inheriting_columns(self, mapper_kw: _MapperKwArgs) -> None: + table = self.local_table + cls = self.cls + table_args = self.table_args + declared_columns = self.declared_columns + if ( table is None and self.inherits is None @@ -1636,9 +1652,12 @@ class _ClassScanMapperConfig(_MapperConfig): if inherited_table.c[col.name] is col: continue raise exc.ArgumentError( - "Column '%s' on class %s conflicts with " - "existing column '%s'" - % (col, cls, inherited_table.c[col.name]) + f"Column '{col}' on class {cls.__name__} " + f"conflicts with existing column " + f"'{inherited_table.c[col.name]}'. If using " + f"Declarative, consider using the " + "use_existing_column parameter of mapped_column() " + "to resolve conflicts." ) if col.primary_key: raise exc.ArgumentError( @@ -1695,14 +1714,15 @@ class _ClassScanMapperConfig(_MapperConfig): mapper_args["inherits"] = self.inherits if self.inherits and not mapper_args.get("concrete", False): + # note the superclass is expected to have a Mapper assigned and + # not be a deferred config, as this is called within map() + inherited_mapper = class_mapper(self.inherits, False) + inherited_table = inherited_mapper.local_table + # single or joined inheritance # exclude any cols on the inherited table which are # not mapped on the parent class, to avoid # mapping columns specific to sibling/nephew classes - inherited_mapper = _declared_mapping_info(self.inherits) - assert isinstance(inherited_mapper, Mapper) - inherited_table = inherited_mapper.local_table - if "exclude_properties" not in mapper_args: mapper_args["exclude_properties"] = exclude_properties = { c.key @@ -1768,6 +1788,8 @@ def _as_dc_declaredattr( class _DeferredMapperConfig(_ClassScanMapperConfig): _cls: weakref.ref[Type[Any]] + is_deferred = True + _configs: util.OrderedDict[ weakref.ref[Type[Any]], _DeferredMapperConfig ] = util.OrderedDict() diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py index 55c7e9290..56d6b2f6f 100644 --- a/lib/sqlalchemy/orm/descriptor_props.py +++ b/lib/sqlalchemy/orm/descriptor_props.py @@ -64,6 +64,7 @@ if typing.TYPE_CHECKING: from .attributes import InstrumentedAttribute from .attributes import QueryableAttribute from .context import ORMCompileState + from .decl_base import _ClassScanMapperConfig from .mapper import Mapper from .properties import ColumnProperty from .properties import MappedColumn @@ -332,6 +333,7 @@ class CompositeProperty( @util.preload_module("sqlalchemy.orm.properties") def declarative_scan( self, + decl_scan: _ClassScanMapperConfig, registry: _RegistryType, cls: Type[Any], originating_module: Optional[str], diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 3d2f9708f..939426495 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -84,6 +84,7 @@ if typing.TYPE_CHECKING: from .context import ORMCompileState from .context import QueryContext from .decl_api import RegistryType + from .decl_base import _ClassScanMapperConfig from .loading import _PopulatorDict from .mapper import Mapper from .path_registry import AbstractEntityRegistry @@ -157,6 +158,7 @@ class _IntrospectsAnnotations: def declarative_scan( self, + decl_scan: _ClassScanMapperConfig, registry: RegistryType, cls: Type[Any], originating_module: Optional[str], @@ -195,6 +197,7 @@ class _AttributeOptions(NamedTuple): dataclasses_repr: Union[_NoArg, bool] dataclasses_default: Union[_NoArg, Any] dataclasses_default_factory: Union[_NoArg, Callable[[], Any]] + dataclasses_compare: Union[_NoArg, bool] dataclasses_kw_only: Union[_NoArg, bool] def _as_dataclass_field(self) -> Any: @@ -209,6 +212,8 @@ class _AttributeOptions(NamedTuple): kw["init"] = self.dataclasses_init if self.dataclasses_repr is not _NoArg.NO_ARG: kw["repr"] = self.dataclasses_repr + if self.dataclasses_compare is not _NoArg.NO_ARG: + kw["compare"] = self.dataclasses_compare if self.dataclasses_kw_only is not _NoArg.NO_ARG: kw["kw_only"] = self.dataclasses_kw_only @@ -256,7 +261,12 @@ class _AttributeOptions(NamedTuple): _DEFAULT_ATTRIBUTE_OPTIONS = _AttributeOptions( - _NoArg.NO_ARG, _NoArg.NO_ARG, _NoArg.NO_ARG, _NoArg.NO_ARG, _NoArg.NO_ARG + _NoArg.NO_ARG, + _NoArg.NO_ARG, + _NoArg.NO_ARG, + _NoArg.NO_ARG, + _NoArg.NO_ARG, + _NoArg.NO_ARG, ) diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 0b26cb872..21df672ec 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -28,6 +28,7 @@ from typing import TypeVar from . import attributes from . import strategy_options from .base import _DeclarativeMapped +from .base import class_mapper from .descriptor_props import CompositeProperty from .descriptor_props import ConcreteInheritedProperty from .descriptor_props import SynonymProperty @@ -65,6 +66,7 @@ if TYPE_CHECKING: from ._typing import _ORMColumnExprArgument from ._typing import _RegistryType from .base import Mapped + from .decl_base import _ClassScanMapperConfig from .mapper import Mapper from .session import Session from .state import _InstallLoaderCallableProto @@ -191,6 +193,7 @@ class ColumnProperty( def declarative_scan( self, + decl_scan: _ClassScanMapperConfig, registry: _RegistryType, cls: Type[Any], originating_module: Optional[str], @@ -530,6 +533,7 @@ class MappedColumn( "deferred_raiseload", "_attribute_options", "_has_dataclass_arguments", + "_use_existing_column", ) deferred: bool @@ -545,6 +549,8 @@ class MappedColumn( "attribute_options", _DEFAULT_ATTRIBUTE_OPTIONS ) + self._use_existing_column = kw.pop("use_existing_column", False) + self._has_dataclass_arguments = False if attr_opts is not None and attr_opts != _DEFAULT_ATTRIBUTE_OPTIONS: @@ -591,6 +597,7 @@ class MappedColumn( new._attribute_options = self._attribute_options new._has_insert_default = self._has_insert_default new._has_dataclass_arguments = self._has_dataclass_arguments + new._use_existing_column = self._use_existing_column util.set_creation_order(new) return new @@ -634,6 +641,7 @@ class MappedColumn( def declarative_scan( self, + decl_scan: _ClassScanMapperConfig, registry: _RegistryType, cls: Type[Any], originating_module: Optional[str], @@ -644,6 +652,18 @@ class MappedColumn( is_dataclass_field: bool, ) -> None: column = self.column + + if self._use_existing_column and decl_scan.inherits: + if decl_scan.is_deferred: + raise sa_exc.ArgumentError( + "Can't use use_existing_column with deferred mappers" + ) + supercls_mapper = class_mapper(decl_scan.inherits, False) + + column = self.column = supercls_mapper.local_table.c.get( # type: ignore # noqa: E501 + key, column + ) + if column.key is None: column.key = key if column.name is None: diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index 73d11e880..4a9bcd711 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -101,6 +101,7 @@ if typing.TYPE_CHECKING: from .base import Mapped from .clsregistry import _class_resolver from .clsregistry import _ModNS + from .decl_base import _ClassScanMapperConfig from .dependency import DependencyProcessor from .mapper import Mapper from .query import Query @@ -1723,6 +1724,7 @@ class RelationshipProperty( def declarative_scan( self, + decl_scan: _ClassScanMapperConfig, registry: _RegistryType, cls: Type[Any], originating_module: Optional[str], |
