On Fri, Oct 19, 2018 at 2:30 PM Derek Lambert <dlamb...@dereklambert.com> wrote: > > I'm replying to my original post since it's related. > > I'm still seeing missing synonyms on child classes when they are defined in a > mixin imported on a parent. > > > import sqlalchemy as sa > from sqlalchemy import orm > from sqlalchemy.ext.declarative import declarative_base, declared_attr > > > Base = declarative_base() > > > class DirectoryEntry(Base): > guid = sa.Column(sa.Integer, primary_key=True) > _type = sa.Column(sa.String, nullable=False) > distinguished_name = sa.Column(sa.String) > name = sa.Column(sa.String) > > __tablename__ = 'directory_entry' > __mapper_args__ = { > 'polymorphic_on': _type, > 'polymorphic_identity': 'directory_entry', > } > > > class DirectoryGroup(DirectoryEntry): > __mapper_args__ = { > 'polymorphic_identity': 'directory_group', > } > > > class ActiveDirectoryEntry: > @declared_attr > def distinguishedName(self): > return orm.synonym('distinguished_name') > > > class ActiveDirectoryGroup(ActiveDirectoryEntry, DirectoryGroup): > __mapper_args__ = { > 'polymorphic_identity': 'active_directory_group' > } > > > class AnotherChild(ActiveDirectoryGroup): > __mapper_args__ = { > 'polymorphic_identity': 'another_child' > } > > > engine = > sa.create_engine('postgresql+psycopg2://postgres@localhost/postgres', > echo=True, isolation_level='AUTOCOMMIT') > > engine.execute('DROP DATABASE IF EXISTS inherit_test') > engine.execute('CREATE DATABASE inherit_test') > > engine = > sa.create_engine('postgresql+psycopg2://postgres@localhost/inherit_test', > echo=True) > > Base.metadata.create_all(engine) > > session = orm.sessionmaker(bind=engine)() > group = ActiveDirectoryGroup( > name='Users', > distinguishedName='cn=Users,ou=domain', > ) > child = AnotherChild( > name='Admins', > distinguishedName='cn=Admins,ou=domain', > ) > > session.add(group) > session.add(child) > session.flush() > session.commit() > > group = > session.query(ActiveDirectoryGroup).filter(ActiveDirectoryGroup.name == > 'Users').one() > group_mapper = sa.inspect(group.__class__) > group_synonyms = group_mapper.synonyms.keys() > > child = session.query(AnotherChild).filter(AnotherChild.name == > 'Admins').one() > child_mapper = sa.inspect(child.__class__) > child_synonyms = child_mapper.synonyms.keys() > > assert child_synonyms == group_synonyms > > > Maybe I'm off in unsupported land again? I could define AnotherChild > identical to ActiveDirectoryGroup, but there are additional synonyms on > ActiveDirectoryGroup they both should have.
the synonym() is only applied to ActiveDirectoryGroup because it is naturally inherited by AnotherChild: child_synonyms = child_mapper.inherits.synonyms.keys() assert child_synonyms == group_synonyms but it's not getting mapped anyway, which seems like a bug. works with @cascading though: class ActiveDirectoryEntry: @declared_attr.cascading def distinguishedName(cls): return orm.synonym('distinguished_name') c1 = session.query(AnotherChild.distinguishedName).filter( AnotherChild.distinguishedName == "cn=Admins,ou=domain").scalar() g1 = session.query(ActiveDirectoryGroup.distinguishedName).filter( ActiveDirectoryGroup.distinguishedName == "cn=Users,ou=domain").scalar() print(g1) print(c1) > > Thanks, > Derek > > On Wednesday, May 9, 2018 at 2:16:22 PM UTC-5, Derek Lambert wrote: >> >> That was my conclusion too after consulting the googles. >> >> I've done as you suggested and things are working as expected. Thanks! >> >> On Monday, April 30, 2018 at 4:26:02 PM UTC-5, Mike Bayer wrote: >>> >>> On Mon, Apr 30, 2018 at 4:18 PM, Derek Lambert >>> <dlam...@dereklambert.com> wrote: >>> >> >>> >> mmm what do you mean by "mixin" here, it looks like every class you >>> >> have is mapped. >>> >> >>> > >>> > They are mapped in the code, but that's only so I can query them. I >>> > attempted to make LdapEntry and ActiveDirectoryEntry true mixin's by >>> > setting >>> > __abstract__ = True. >>> > >>> >> >>> >> >>> >> this a heavy set of inheritance and I might also use composition >>> >> instead, though that would change your DB design. >>> >> >>> > >>> > The design isn't in production yet so now would be the time to change it. >>> > Are you aware of any SQLAlchemy projects using composition I could review? >>> >>> mmm not specifically, it means you might do something like store >>> "Entry" concepts in one table and "User" concepts in another. >>> looking more closely this seems like it would be akward also. >>> >>> looking more closely at your mappings it looks like only >>> DirectoryEntry and DirectoryUser actually have any columns. The rest >>> is all synonyms. I'd likely use mixins for all those synonym sets. >>> >>> >>> > >>> > Thanks, >>> > Derek >>> > >>> > On Monday, April 30, 2018 at 1:30:51 PM UTC-5, Mike Bayer wrote: >>> >> >>> >> On Mon, Apr 30, 2018 at 1:33 PM, Derek Lambert >>> >> <dlam...@dereklambert.com> wrote: >>> >> > I'm running into an issue in a hierarchy of single-table inheritance >>> >> > objects >>> >> > with multiple inheritance. The objects represent users/groups/etc. from >>> >> > various directories and applications. >>> >> > >>> >> > Retrieving the list of synonyms from an object at the bottom of the >>> >> > inheritance tree doesn't return the entire list of synonyms. >>> >> > >>> >> > When I make some of the "mixin" type objects abstract the synonyms >>> >> > returned >>> >> > are as expected, but I lose the ability to query those objects. >>> >> >>> >> mmm what do you mean by "mixin" here, it looks like every class you >>> >> have is mapped. >>> >> >>> >> I will say that what you are doing here: >>> >> >>> >> class LdapUser(DirectoryUser, LdapEntry): >>> >> givenName = orm.synonym('first_name') >>> >> sn = orm.synonym('last_name') >>> >> __mapper_args__ = { >>> >> 'polymorphic_identity': 'ldap_user', >>> >> } >>> >> >>> >> where DirectoryUser and LdapEntry are also both mapped, I'm amazed >>> >> that even works. That's not at all anything that has ever been >>> >> supported or attempted, as each mapper only "inherits" from at most >>> >> one mapped class - while declarative supports actual "mixin" classes, >>> >> where by "mixin" we mean "non-mapped class", nothing in SQLAlchemy ORM >>> >> is expecting multiple inheritance at the mapper level. Above, I >>> >> guess it's picking one superclass mapper at random to be "inherits", >>> >> an ignoring the other, and that is likely the source of your issue. >>> >> Unfortunately I think you have to work out this hierarchy in terms of >>> >> single-inhertanace for classes that are actually mapped, which means >>> >> adding some non-mapped "mixin" classes that just accommodate for the >>> >> extra synonyms, something like: >>> >> >>> >> class DirectoryEntry(Base): >>> >> >>> >> class AbstractDirectoryUser(object): >>> >> # synonyms >>> >> >>> >> class DirectoryUser(AbstractDirectoryUser, DirectoryEntry): >>> >> >>> >> class LdapEntry(DirectoryEntry): >>> >> >>> >> class LdapUser(AbstractDirectoryUser, LdapEntry): >>> >> >>> >> this a heavy set of inheritance and I might also use composition >>> >> instead, though that would change your DB design. >>> >> >>> >> >>> >> > >>> >> > Maybe I'm overlooking a simpler implementation, or simply using >>> >> > SQLAlchemy >>> >> > in a way that wasn't intended? >>> >> > >>> >> > Here's a simplified subset of the code. In practice any object ending >>> >> > with >>> >> > Entry and the base DirectoryUser and DirectoryGroup wouldn't be >>> >> > created. >>> >> > >>> >> > import sqlalchemy as sa >>> >> > import sqlalchemy.orm as orm >>> >> > from sqlalchemy.ext.declarative import declarative_base >>> >> > >>> >> > >>> >> > Base = declarative_base() >>> >> > >>> >> > >>> >> > class DirectoryEntry(Base): >>> >> > guid = sa.Column(sa.Integer, primary_key=True) >>> >> > _type = sa.Column(sa.String, nullable=False, >>> >> > index=True) >>> >> > distinguished_name = sa.Column(sa.String, index=True) >>> >> > name = sa.Column(sa.String, index=True) >>> >> > >>> >> > __tablename__ = 'directory_entry' >>> >> > __mapper_args__ = { >>> >> > 'polymorphic_on': _type, >>> >> > 'polymorphic_identity': 'directory_entry', >>> >> > } >>> >> > >>> >> > >>> >> > class DirectoryUser(DirectoryEntry): >>> >> > first_name = sa.Column(sa.String) >>> >> > last_name = sa.Column(sa.String) >>> >> > email = sa.Column(sa.String) >>> >> > username = sa.Column(sa.String) >>> >> > >>> >> > __mapper_args__ = { >>> >> > 'polymorphic_identity': 'directory_user', >>> >> > } >>> >> > >>> >> > >>> >> > class LdapEntry(DirectoryEntry): >>> >> > cn = orm.synonym('name') >>> >> > >>> >> > __mapper_args__ = { >>> >> > 'polymorphic_identity': 'ldap_entry', >>> >> > } >>> >> > >>> >> > >>> >> > class LdapUser(DirectoryUser, LdapEntry): >>> >> > givenName = orm.synonym('first_name') >>> >> > sn = orm.synonym('last_name') >>> >> > >>> >> > __mapper_args__ = { >>> >> > 'polymorphic_identity': 'ldap_user', >>> >> > } >>> >> > >>> >> > >>> >> > class ActiveDirectoryEntry(LdapEntry): >>> >> > distinguishedName = orm.synonym('distinguished_name') >>> >> > >>> >> > __mapper_args__ = { >>> >> > 'polymorphic_identity': 'active_directory_entry', >>> >> > } >>> >> > >>> >> > >>> >> > class ActiveDirectoryUser(LdapUser, ActiveDirectoryEntry): >>> >> > mail = orm.synonym('email') >>> >> > sAMAccountName = orm.synonym('username') >>> >> > >>> >> > __mapper_args__ = { >>> >> > 'polymorphic_identity': 'active_directory_user' >>> >> > } >>> >> > >>> >> > >>> >> > engine_url = 'postgresql+psycopg2://postgres@localhost/inherit_test' >>> >> > engine = sa.create_engine(engine_url, echo=True) >>> >> > >>> >> > Base.metadata.create_all(engine) >>> >> > >>> >> > session = orm.sessionmaker(bind=engine)() >>> >> > ad_user = ActiveDirectoryUser( >>> >> > cn='John Doe', >>> >> > sAMAccountName='jdoe', >>> >> > distinguishedName='ou=domain', >>> >> > givenName='John' >>> >> > ) >>> >> > >>> >> > session.add(ad_user) >>> >> > session.commit() >>> >> > >>> >> > user1 = session.query(DirectoryUser).filter(DirectoryUser.username == >>> >> > 'jdoe').one() >>> >> > user3 = session.query(LdapUser).filter(LdapUser.username == >>> >> > 'jdoe').one() >>> >> > user2 = >>> >> > session.query(ActiveDirectoryUser).filter(ActiveDirectoryUser.username >>> >> > == >>> >> > 'jdoe').one() >>> >> > user4 = session.query(DirectoryEntry).filter(DirectoryEntry.name == >>> >> > 'John >>> >> > Doe').one() >>> >> > >>> >> > assert(user1 == user2 == user3 == user4) >>> >> > >>> >> > mapper = sa.inspect(ad_user.__class__) >>> >> > synonyms = mapper.synonyms.keys() >>> >> > >>> >> > assert(synonyms == ['mail', 'sAMAccountName', 'givenName', 'sn', 'cn', >>> >> > 'distinguishedName']) >>> >> > >>> >> > >>> >> > Any help is appreciated! >>> >> > >>> >> > -- >>> >> > SQLAlchemy - >>> >> > The Python SQL Toolkit and Object Relational Mapper >>> >> > >>> >> > http://www.sqlalchemy.org/ >>> >> > >>> >> > To post example code, please provide an MCVE: Minimal, Complete, and >>> >> > Verifiable Example. See http://stackoverflow.com/help/mcve for a full >>> >> > description. >>> >> > --- >>> >> > 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 sqlalchemy+...@googlegroups.com. >>> >> > To post to this group, send email to sqlal...@googlegroups.com. >>> >> > Visit this group at https://groups.google.com/group/sqlalchemy. >>> >> > For more options, visit https://groups.google.com/d/optout. >>> > >>> > -- >>> > SQLAlchemy - >>> > The Python SQL Toolkit and Object Relational Mapper >>> > >>> > http://www.sqlalchemy.org/ >>> > >>> > To post example code, please provide an MCVE: Minimal, Complete, and >>> > Verifiable Example. See http://stackoverflow.com/help/mcve for a full >>> > description. >>> > --- >>> > 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 sqlalchemy+...@googlegroups.com. >>> > To post to this group, send email to sqlal...@googlegroups.com. >>> > Visit this group at https://groups.google.com/group/sqlalchemy. >>> > For more options, visit https://groups.google.com/d/optout. > > -- > SQLAlchemy - > The Python SQL Toolkit and Object Relational Mapper > > http://www.sqlalchemy.org/ > > To post example code, please provide an MCVE: Minimal, Complete, and > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > description. > --- > 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 sqlalchemy+unsubscr...@googlegroups.com. > To post to this group, send email to sqlalchemy@googlegroups.com. > Visit this group at https://groups.google.com/group/sqlalchemy. > For more options, visit https://groups.google.com/d/optout. -- SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper http://www.sqlalchemy.org/ To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description. --- 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 sqlalchemy+unsubscr...@googlegroups.com. To post to this group, send email to sqlalchemy@googlegroups.com. Visit this group at https://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout.