)
continue
- self._configure_property(key, possible_col_prop, False)
+ self._configure_property(key, possible_col_prop, init=False)
# step 2: pull properties from the inherited mapper. reconcile
# columns with those which are explicit above. for properties that
# it now in the order in which it corresponds to the
# Table / selectable
key, prop = explicit_col_props_by_column[column]
- self._configure_property(key, prop, False)
+ self._configure_property(key, prop, init=False)
continue
elif column in self._columntoproperty:
self,
key: str,
prop_arg: Union[KeyedColumnElement[Any], MapperProperty[Any]],
+ *,
init: bool = True,
setparent: bool = True,
+ warn_for_existing: bool = False,
) -> MapperProperty[Any]:
descriptor_props = util.preloaded.orm_descriptor_props
self._log(
oldprop = self._props[key]
self._path_registry.pop(oldprop, None)
+ if (
+ warn_for_existing
+ and self.class_.__dict__.get(key, None) is not None
+ and not isinstance(
+ self._props.get(key, None),
+ (descriptor_props.ConcreteInheritedProperty,),
+ )
+ ):
+ util.warn(
+ "User-placed attribute %r on %s being replaced with "
+ 'new property "%s"; the old attribute will be discarded'
+ % (self.class_.__dict__[key], self, prop)
+ )
+
self._props[key] = prop
if not self.non_primary:
import logging
import logging.handlers
+import re
import sqlalchemy as sa
from sqlalchemy import column
from sqlalchemy import testing
from sqlalchemy import util
from sqlalchemy.engine import default
+from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import aliased
from sqlalchemy.orm import attributes
from sqlalchemy.orm import backref
from sqlalchemy.orm import column_property
from sqlalchemy.orm import composite
from sqlalchemy.orm import configure_mappers
+from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import deferred
from sqlalchemy.orm import dynamic_loader
from sqlalchemy.orm import Load
from sqlalchemy.testing import AssertsCompiledSQL
from sqlalchemy.testing import eq_
from sqlalchemy.testing import expect_raises_message
+from sqlalchemy.testing import expect_warnings
from sqlalchemy.testing import fixtures
from sqlalchemy.testing import is_
from sqlalchemy.testing import is_false
assert m._is_userland_descriptor("foo", MyClass.foo)
+ @testing.variation(
+ "attr_type",
+ ["method", "descriptor", "assocprox", "plain", "relationship"],
+ )
+ def test_backref_replacing_descriptor_warning(self, attr_type):
+ """test #4629"""
+ User, users = self.classes.User, self.tables.users
+ Address, addresses = self.classes.Address, self.tables.addresses
+
+ class MyClass(User):
+ if attr_type.method:
+
+ def addresses(self):
+ pass
+
+ elif attr_type.descriptor:
+
+ @property
+ def addresses(self):
+ pass
+
+ elif attr_type.assocprox:
+ addresses = association_proxy("addresses", "email")
+ elif attr_type.plain:
+ addresses = "addresses"
+ elif attr_type.relationship:
+ addresses = relationship(Address)
+ else:
+ attr_type.fail()
+
+ self.mapper(MyClass, users)
+
+ self.mapper(
+ Address,
+ addresses,
+ properties={"user": relationship(MyClass, backref="addresses")},
+ )
+
+ attr_repr = re.sub(r"[\(\)]", ".", repr(MyClass.__dict__["addresses"]))
+ with expect_warnings(
+ rf"User-placed attribute {attr_repr} on "
+ r"Mapper\[MyClass\(users\)\] being replaced with new property "
+ '"MyClass.addresses"; the old attribute will be discarded'
+ ):
+ configure_mappers()
+
+ @testing.variation(
+ "attr_type",
+ ["assocprox", "relationship"],
+ )
+ @testing.variation("as_mixin", [True, False])
+ def test_backref_replacing_descriptor_warning_declarative(
+ self, attr_type, as_mixin
+ ):
+ """test #4629"""
+ users = self.tables.users
+ Address, addresses = self.classes.Address, self.tables.addresses
+
+ Base = self.mapper_registry.generate_base()
+
+ if as_mixin:
+
+ class MyMixin:
+ if attr_type.assocprox:
+
+ @declared_attr
+ def addresses(cls):
+ return association_proxy("addresses", "email")
+
+ elif attr_type.relationship:
+
+ @declared_attr
+ def addresses(cls):
+ return relationship(Address)
+
+ else:
+ attr_type.fail()
+
+ class MyClass(MyMixin, Base):
+ __table__ = users
+
+ else:
+
+ class MyClass(Base):
+ __table__ = users
+
+ if attr_type.assocprox:
+ addresses = association_proxy("addresses", "email")
+ elif attr_type.relationship:
+ addresses = relationship(Address)
+ else:
+ attr_type.fail()
+
+ self.mapper(
+ Address,
+ addresses,
+ properties={"user": relationship(MyClass, backref="addresses")},
+ )
+
+ if attr_type.relationship:
+ with expect_raises_message(
+ sa.exc.ArgumentError, "Error creating backref"
+ ):
+ configure_mappers()
+ elif attr_type.assocprox:
+ with expect_warnings("User-placed attribute"):
+ configure_mappers()
+ else:
+ attr_type.fail()
+
def test_configure_on_get_props_1(self):
User, users = self.classes.User, self.tables.users