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.

Reply via email to