Mike,
Thanks for the response! Here is the demonstration, please let me know if
there is something obvious that I am missing:
> cat ~/python/sq3.py
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64))
# association proxy of "user_keywords" collection
# to "keyword" attribute
keywords = association_proxy('user_keywords', 'keyword')
def __init__(self, name):
self.name = name
class UserKeyword(Base):
__tablename__ = 'user_keyword'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
keyword_id = Column(Integer, ForeignKey('keyword.id'), primary_key=True)
special_key = Column(String(50))
# bidirectional attribute/collection of "user"/"user_keywords"
user = relationship(User,
backref=backref("user_keywords",
cascade="all, delete-orphan")
)
keyword = relationship("Keyword",
backref=backref("user_keywords", cascade="all,
delete-orphan"))
def __init__(self, keyword=None, user=None, special_key=None):
self.user = user
self.keyword = keyword
self.special_key = special_key
class Keyword(Base):
__tablename__ = 'keyword'
id = Column(Integer, primary_key=True)
keyword = Column('keyword', String(64))
users = association_proxy('user_keywords', 'user')
def __init__(self, keyword):
self.keyword = keyword
def __repr__(self):
return 'Keyword(%s)' % repr(self.keyword)
> python -i ~/python/sq3.py
>>> from sqlalchemy import create_engine
>>> from sqlalchemy.orm import Session
>>> e = create_engine("sqlite:///:memory:")
>>> Base.metadata.drop_all(e)
>>> Base.metadata.create_all(e)
>>> session=Session(e)
>>> rory=User("rory")
>>> session.add(rory)
>>> chicken=Keyword("chicken")
>>> session.add(chicken)
>>> rory.keywords.append(chicken)
>>> rory.keywords
[Keyword('chicken')]
>>> chicken.users
[<__main__.User object at 0x7f82dff87390>]
>>> rory.keywords.remove(chicken)
>>> rory.keywords
[]
>>> chicken.users
[None]
>>> session.flush()
/hostname/multiacl2/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py:235:
SAWarning: Object of type <UserKeyword> not in session, add operation along
'Keyword.user_keywords' will not proceed
(orm_util.state_class_str(state), operation, prop))
>>> chicken.user_keywords
[<__main__.UserKeyword object at 0x7f82dfa6ded0>]
>>> chicken.user_keywords[0].user
>>> chicken.user_keywords[0].keyword
Keyword('chicken')
>>>
So it definitely seems that the association object is half-disassociated. I
have this currently worked around by setting a validator on
UserKeyword.user that removes self from UserKeyword.keyword.user_keywords
if self.user is None, but I am not confident in this workaround.
On Wednesday, August 24, 2016 at 1:57:58 PM UTC-4, Mike Bayer wrote:
>
>
>
> The issue mentioned in 2655 is a major behavior of the ORM, in that an
> object is considered orphan if any of its relationships are non-present
> in all cases, including pending and persistent, rather than if all of
> them are un-present. This is not at all subtle and is covered in a
> wide range of test cases. The test case attached to the bug continues
> to pass, as does the demonstration attached in the migration notes:
>
> http://docs.sqlalchemy.org/en/latest/changelog/migration_08.html#the-consideration-of-a-pending-object-as-an-orphan-has-been-made-more-aggressive.
>
>
>
>
> >
> >
> > Is anyone aware whether this bug was reintroduced on purpose (i.e.
> > figured out the rationale for the original behavior) or by accident?
>
> reintroduction of a bug here would need to be demonstrated.
>
--
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.