Begin to disallow back_populates with viewonly=True

A viewonly=True relationship should not be mutated and ideally
mutation itself would raise an error, but we're not there yet.
Warn when a viewonly is to be the target of a back_populates
as this only means that it should be locally mutated, which
by definition will not work as expected because post-flush
it will deliver doubled results, due to its state not being
reset.

Setting a relationship to viewonly=True which is also the target of a
back_populates or backref configuration will now emit a warning and
eventually be disallowed. back_populates refers specifically to mutation
of an attribute or collection, which is disallowed when the attribute is
subject to viewonly=True.   The viewonly attribute is not subject to
persistence behaviors which means it will not reflect correct results
when it is locally mutated.

Fixes: #5149
Change-Id: Ie51382a82e1a0ff5f3cf2cdbded780e77ace7f5f
This commit is contained in:
Mike Bayer
2020-02-11 12:51:46 -05:00
parent 532ddb0fa7
commit 4376c2e5b0
3 changed files with 50 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
.. change::
:tags: bug, orm
:tickets: 5149
Setting a relationship to viewonly=True which is also the target of a
back_populates or backref configuration will now emit a warning and
eventually be disallowed. back_populates refers specifically to mutation
of an attribute or collection, which is disallowed when the attribute is
subject to viewonly=True. The viewonly attribute is not subject to
persistence behaviors which means it will not reflect correct results
when it is locally mutated.
+21
View File
@@ -827,6 +827,19 @@ class RelationshipProperty(StrategizedProperty):
the full set of related objects, to prevent modifications of the
collection from resulting in persistence operations.
.. warning:: The viewonly=True relationship should not be mutated
in Python; that means, elements should not be added or removed
from collections nor should a many-to-one or one-to-one attribute
be altered in Python. The viewonly=True relationship should only
be accessed via read. Towards this behavior, it is also not
appropriate for the viewonly=True relationship to have any kind
of persistence cascade settings, nor should it be the target of
either :paramref:`.relationship.backref` or
:paramref:`.relationship.back_populates`, as backrefs imply
in-Python mutation of the attribute. SQLAlchemy may emit
warnings for some or all of these conditions as of the 1.3 and
1.4 series of SQLAlchemy and will eventually be disallowed.
:param omit_join:
Allows manual control over the "selectin" automatic join
optimization. Set to ``False`` to disable the "omit join" feature
@@ -1841,6 +1854,14 @@ class RelationshipProperty(StrategizedProperty):
def _add_reverse_property(self, key):
other = self.mapper.get_property(key, _configure_mappers=False)
if other.viewonly:
util.warn_limited(
"Setting backref / back_populates on relationship %s to refer "
"to viewonly relationship %s will be deprecated in SQLAlchemy "
"1.4, and will be disallowed in a future release. "
"viewonly relationships should not be mutated",
(self, other),
)
self._reverse_property.add(other)
other._reverse_property.add(self)
+18
View File
@@ -2703,6 +2703,12 @@ class ViewOnlyHistoryTest(fixtures.MappedTest):
)
mapper(B, self.tables.t2)
with testing.expect_warnings(
"Setting backref / back_populates on "
"relationship B.a to refer to viewonly relationship A.bs"
):
configure_mappers()
a1 = A()
b1 = B()
a1.bs.append(b1)
@@ -2734,6 +2740,12 @@ class ViewOnlyHistoryTest(fixtures.MappedTest):
)
mapper(B, self.tables.t2)
with testing.expect_warnings(
"Setting backref / back_populates on "
"relationship A.bs to refer to viewonly relationship B.a"
):
configure_mappers()
a1 = A()
b1 = B()
b1.a = a1
@@ -2834,6 +2846,12 @@ class ViewOnlyM2MBackrefTest(fixtures.MappedTest):
)
mapper(B, t2)
with testing.expect_warnings(
"Setting backref / back_populates on "
"relationship A.bs to refer to viewonly relationship B.a"
):
configure_mappers()
sess = create_session()
a1 = A()
b1 = B(as_=[a1])