On Thu, Apr 26, 2018 at 11:52:48AM -0400, Mike Bayer wrote:
> Attached is your script worked into separate tests.   For test_five,
> we can do it like this:
> 
>         
> s.query(Document).filter(Document.translations.any(DocumentTranslation.body
> == 'doc1_body_en')).all()
> 
> For the tests which state "# XXX the current_translation isn't
> joinedloaded()", it looks like they are, I added assertions that the
> object is present without SQL being emitted; if I remove lazy="joined"
> then it fails, so that joinedload is working.
> 
> For the other general issue that a query with
> Document/DocumentTranslation pulls in other tables, that's because the
> relationship has no idea that Document only refers to
> DocumentTranslation objects and nothing else.   We can make these
> relationships explicit.   That's in the second attachment.   For
> test_three, the queries only involve the content/document tables and
> there are no LEFT OUTER joins.
> 
> For the content_type_id thing, I guess the trigger is so that you can
> use raw SQL?   SQLAlchemy will do those columns for you within the
> ORM.   But you can also set polymorphic_on to be a correlated
> subquery.   the third attachment shows that as well.
> 
> In all three cases no changes were needed at all to the persistence side.
> 
> The tests include a setup_class() which I don't know how to do in
> unittest so I run these with py.test:
> 
>     py.test test_thing_3.py -k test_three -s
>

Hi Mike,

It works almost like a charm. One minor issue I have is in the test_six 
I've added in the attached test_thing_3.py. 

When I'm querying through the base class I'm wondering why SQLAlchemy 
issues a query for a property which is already lazy='joined' loaded in 
the previously query, and I wonder if there is way to avoid that .. ?

Thanks !

Julien

> 
> 
> 
> On Thu, Apr 26, 2018 at 6:54 AM, Julien Cigar <julien.ci...@gmail.com> wrote:
> > Hello Mike,
> >
> > I finally had the time to make a little full POC, see attached file.
> >
> > As said previously I'm used to the old mapper() and never used the
> > declarative until now, so forgive me if I missed something in the
> > script..
> >
> > Some explanations on the context and on what I'd like to do:
> >
> > It's a $work-made CMS that is used in some projects here at $work.
> > It is developed with the Pyramid framework which allow us to split
> > in a very simple and clean way "subprojects" that use our core CMS
> > package.
> >
> > At the core joinedload inheritance is used. "Content" is the base class
> > from which other polymorphic entities (Event, Document, Folder, ...)
> > inherits.
> >
> > The discriminator (polymorphic_on) is on a "content_type_id" column in
> > the content table which is a foreign key to a content_type table. In my
> > real app I use a function to retrieve the content_type.id, which is
> > "faked" in my POC (_fake_ids).
> >
> > Every polymorphic entity has common properties/columns (like
> > "title", "description") and specific properties/columns (like "body" for
> > a Document, etc). Nothing special, common properties go in the Content
> > class and specific properties in their own subclass.
> >
> > Now I'd like to add a "multilingual layer" to the app. Common properties
> > will me moved to a dedicated content_translation table, and each
> > polymorphic entity will have their own dedicated table
> > (document_translation, event_translation, etc). See attached file
> > (sa.png) for the db model. (Note that some entities may not have a
> > dedicated _translation table, like Folder, which has no more than a
> > "title" and "description")
> >
> > I'm not sure how to map this in SQLAlchemy as the "translation" parts
> > (which are also mapped in a joinedload inheritance) are "part of" or
> > "closely linked to" the translatable entity (ex: DocumentTranslation ->
> > Document, EventTranslation -> Event, etc).
> >
> > In an ideal scenario I'd like to have a .translations property
> > (relationship()) available in each entity which is lazy='subquery'
> > loaded and for which only related _translation tables are JOIN.
> > I'm not sure if I should add/overwrite the "translations" relationship
> > in every entity (Event, Document, ...)..
> >
> > In advance, a big "thank you" for the time spent analyzing this :)
> >
> > Julien
> >
> > On Fri, Apr 13, 2018 at 10:34:46AM +0200, Julien Cigar wrote:
> >> On Thu, Apr 12, 2018 at 08:25:06PM -0400, Mike Bayer wrote:
> >> > try sending me a long a full POC and Ill try to play with it, any
> >>
> >> yep I'll do it ..! I miss some time for now.. but it's definitively on
> >> my todo list. I don't think I have an overly complicated use case, it's
> >> just that I pay a lot of attention on the generated SQL queries.
> >>
> >> > reason you arne't using declarative?  the classical mappings are hard
> >> > to work with.
> >>
> >> mostly for historical reasons, but also because I prefer the classical
> >> mapping approach (mostly for separation of concerns).
> >>
> >> My application (internal CMS) is based on the Pyramid framework and it's
> >> easier to extend the "core" package when the "mapping" part is separated
> >> (maybe it's also possible with the declarative approach, I must admit
> >> that I haven't looked at it in details).
> >>
> >> >
> >> > On Wed, Apr 11, 2018 at 8:32 AM, Mike Bayer <mike...@zzzcomputing.com> 
> >> > wrote:
> >> > > On Wed, Apr 11, 2018 at 6:15 AM, Julien Cigar <julien.ci...@gmail.com> 
> >> > > wrote:
> >> > >> On Tue, Apr 10, 2018 at 11:53:09AM -0400, Mike Bayer wrote:
> >> > >>> On Tue, Apr 10, 2018 at 9:28 AM, Julien Cigar 
> >> > >>> <julien.ci...@gmail.com> wrote:
> >> > >>> > Hello,
> >> > >>> >
> >> > >>> > I wondered if it is possible to use a class mapped against multiple
> >> > >>> > tables as a relationship() in another class?
> >> > >>>
> >> > >>> it is, there's examples at
> >> > >>> http://docs.sqlalchemy.org/en/latest/orm/join_conditions.html#relationship-to-non-primary-mapper
> >> > >>>
> >> > >>> that is map your related class to whatever you want in a non primary
> >> > >>> mapper.   If you are just selecting data you have a lot of options.
> >> > >>>
> >> > >>
> >> > >> Hi Mike,
> >> > >>
> >> > >> Thanks for the link :) I tried and it seems to work,  except in an
> >> > >> inheritance scenario (1). I guess that in a non primary mapper I can't
> >> > >> put my polymorphic_on condition on a Join clause?
> >> > >>
> >> > >> (1) https://gist.github.com/silenius/a237baf8c4bcd79550dc884f2eeb1998
> >> > >
> >> > > this is a lot of code to follow, but if the point of the mutli-table
> >> > > thing you're doing is to game the joined inheritance into doing
> >> > > something, then that's probably not the appropriate use for it.   It
> >> > > looks like you're trying to add all kinds of columns to
> >> > > "ContentTranslation" from the other mappers like Content.
> >> > >
> >> > >
> >> > >>
> >> > >> Thanks!
> >> > >> Julien
> >> > >>
> >> > >>>
> >> > >>> >
> >> > >>> > something like:
> >> > >>> >
> >> > >>> > ### Content
> >> > >>> >
> >> > >>> > content_translation_join = sql.join(
> >> > >>> >   t_content, t_content_translation
> >> > >>> > )
> >> > >>> >
> >> > >>> > orm.mapper(
> >> > >>> >   ContentTranslation, content_translation_join,
> >> > >>> >   polymorphic_on=t_content.c.content_type_id
> >> > >>> > )
> >> > >>> >
> >> > >>> > orm.mapper(
> >> > >>> >   Content, t_content,
> >> > >>> >   polymorphic_on=t_content.c.content_type_id,
> >> > >>> >   properties={
> >> > >>> >     'translations': orm.relationship(
> >> > >>> >       ContentTranslation,
> >> > >>> >       # more stuff here
> >> > >>> >     )
> >> > >>> >   }
> >> > >>> > )
> >> > >>> >
> >> > >>> > ### Document
> >> > >>> >
> >> > >>> > orm.mapper(
> >> > >>> >   DocumentTranslation, t_document_translation,
> >> > >>> >   inherits=ContentTranslation,
> >> > >>> >   polymorphic_identity=some_id
> >> > >>> > )
> >> > >>> >
> >> > >>> > orm.mapper(
> >> > >>> >   Document, t_document,
> >> > >>> >   inherits=Content,
> >> > >>> >   polymorphic_identity=some_id
> >> > >>> > )
> >> > >>> >
> >> > >>> > I tried (1) but it doesn't seems to work so I want to be sure that 
> >> > >>> > it's
> >> > >>> > not possible :)
> >> > >>> >
> >> > >>> > Thanks!
> >> > >>> > Julien
> >> > >>> >
> >> > >>> > (1) 
> >> > >>> > https://gist.github.com/silenius/561b13f4b987c36434cd81e2c08cab6e#file-foo-py
> >> > >>> >
> >> > >>> >
> >> > >>> >
> >> > >>> > --
> >> > >>> > Julien Cigar
> >> > >>> > Belgian Biodiversity Platform (http://www.biodiversity.be)
> >> > >>> > PGP fingerprint: EEF9 F697 4B68 D275 7B11  6A25 B2BB 3710 A204 23C0
> >> > >>> > No trees were killed in the creation of this message.
> >> > >>> > However, many electrons were terribly inconvenienced.
> >> > >>> >
> >> > >>> > --
> >> > >>> > 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.
> >> > >>
> >> > >> --
> >> > >> Julien Cigar
> >> > >> Belgian Biodiversity Platform (http://www.biodiversity.be)
> >> > >> PGP fingerprint: EEF9 F697 4B68 D275 7B11  6A25 B2BB 3710 A204 23C0
> >> > >> No trees were killed in the creation of this message.
> >> > >> However, many electrons were terribly inconvenienced.
> >> > >>
> >> > >> --
> >> > >> 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.
> >>
> >> --
> >> Julien Cigar
> >> Belgian Biodiversity Platform (http://www.biodiversity.be)
> >> PGP fingerprint: EEF9 F697 4B68 D275 7B11  6A25 B2BB 3710 A204 23C0
> >> No trees were killed in the creation of this message.
> >> However, many electrons were terribly inconvenienced.
> >>
> >> --
> >> 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.
> >
> >
> >
> > --
> > Julien Cigar
> > Belgian Biodiversity Platform (http://www.biodiversity.be)
> > PGP fingerprint: EEF9 F697 4B68 D275 7B11  6A25 B2BB 3710 A204 23C0
> > No trees were killed in the creation of this message.
> > However, many electrons were terribly inconvenienced.
> >
> > --
> > 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.

> import logging
> 
> from sqlalchemy import *
> from sqlalchemy.orm import *
> from sqlalchemy.orm.collections import attribute_mapped_collection
> from sqlalchemy.ext.declarative import declarative_base
> from sqlalchemy.ext.declarative import declared_attr
> from sqlalchemy.ext.hybrid import hybrid_property
> import unittest
> 
> Base = declarative_base()
> 
> logging.basicConfig()
> logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
> 
> _fake_ids = {
>     'document': 1,
>     'event': 2,
>     'folder': 3
> }
> 
> 
> def _get_locale():
>     return 'en'
> 
> 
> class ContentType(Base):
> 
>     __tablename__ = 'content_type'
> 
>     id = Column(Integer, primary_key=True)
>     name = Column(String(50), nullable=False)
> 
>     def __init__(self, id, name):
>         self.id = id
>         self.name = name
> 
> 
> class Language(Base):
> 
>     __tablename__ = 'language'
> 
>     id = Column(String(2), primary_key=True)
>     name = Column(String(50), nullable=False)
> 
>     def __init__(self, id, name):
>         self.id = id
>         self.name = name
> 
> 
> class Country(Base):
> 
>     __tablename__ = 'country'
> 
>     iso = Column(String(2), primary_key=True)
>     name = Column(String(50), nullable=False)
> 
>     def __init__(self, iso, name):
>         self.iso = iso
>         self.name = name
> 
> 
> class Content(Base):
> 
>     __tablename__ = 'content'
> 
>     # XXX: not supported by SQLite
>     #__table_args__ = (
>     #    Index(
>     #        'idx_container_id',
>     #        text('((1)) where container_id is NULL')
>     #    ),
>     #)
> 
>     id = Column(Integer, primary_key=True)
>     content_type_id = Column(Integer, ForeignKey('content_type.id'),
>                              nullable=False)
>     container_id = Column(Integer, ForeignKey('folder.content_id'))
> 
>     parent = relationship(
>         lambda: Folder,
>         foreign_keys=lambda: Content.container_id,
>         innerjoin=True,
>         uselist=False,
>         backref=backref('children', cascade='all, delete-orphan')
>     )
> 
>     current_translation = relationship(
>         lambda: ContentTranslation,
>         primaryjoin=lambda: and_(
>             ContentTranslation.content_id == Content.id,
>             ContentTranslation.language_id == bindparam(
>                 None,
>                 callable_=lambda: _get_locale(),
>                 type_=String()
>             )
>         ),
>         lazy='joined',
>         uselist=False,
>         innerjoin=True,
>         viewonly=True,
>         bake_queries=False,
>         back_populates='content'
>     )
> 
>     translations = relationship(
>         lambda: ContentTranslation,
>         cascade='all, delete-orphan',
>         lazy='subquery',
>         innerjoin=True,
>         back_populates='content',
>         collection_class=attribute_mapped_collection('language_id')
>     )
> 
>     content_type = relationship(
>         ContentType,
>         lazy='joined',
>         innerjoin=True,
>         uselist=False
>     )
> 
>     __mapper_args__ = {
>         'polymorphic_on': content_type_id
>     }
> 
>     @hybrid_property
>     def title(self):
>         return self.current_translation.title
> 
>     @title.setter
>     def title(self, value):
>         self.current_translation.title = value
> 
>     @hybrid_property
>     def description(self):
>         return self.current_translation.description
> 
>     @description.setter
>     def description(self, value):
>         self.current_translation.description = value
> 
> 
> class ContentTranslation(Base):
> 
>     __tablename__ = 'content_translation'
> 
>     language_id = Column(String(2), ForeignKey('language.id'),
>                          primary_key=True)
>     content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
> 
>     title = Column(String, nullable=False)
>     description = Column(String)
> 
>     # XXX: In an ideal scenario I'd like to put the discriminator on the
>     # "content.content_type_id" column, something like;
>     # polymorphic_on = sql.join(
>     #   content_translation, content,
>     #   content_translation.c.content_id == content.c.id
>     # ).c.content_content_type_id
> 
>     # But as this is not supported by SQLAlchemy (yet?) I "duplicated" the
>     # "content_type_id" column and created a trigger which simply select the
>     # "content_type_id" in the "content" table for the corresponding
>     # "content_translation.content_id". So in the "real" app there is 
> something
>     # like:
> 
>     # create or replace function content_translation_content_id()
>     # returns trigger as $content_id$
>     #    begin
>     #        NEW.content_type_id := (
>     #            select
>     #                content_type_id
>     #            from
>     #                content
>     #            where
>     #                content.id = NEW.content_id
>     #        );
>     #
>     #        return NEW;
>     #    end;
>     # $content_id$ language plpgsql;
> 
>     # create trigger content_translation_content_id
>     #     before insert or update on content_translation
>     #     for each row execute procedure content_translation_content_id();
> 
>     content_type_id = Column(Integer, ForeignKey('content_type.id'),
>                              nullable=False)
> 
>     content = relationship(
>         Content,
>         back_populates='translations',
>         innerjoin=True,
>         uselist=False
>     )
> 
>     content_type = relationship(
>         ContentType,
>         lazy='joined',
>         innerjoin=True,
>         uselist=False
>     )
> 
>     __mapper_args__ = {
>         'polymorphic_on': content_type_id
>     }
> 
>     def __init__(self, **kwargs):
>         if 'language_id' not in kwargs:
>             self.language_id = _get_locale()
> 
>         for k, v in kwargs.items():
>             setattr(self, k, v)
> 
> 
> class Folder(Content):
> 
>     __tablename__ = 'folder'
> 
>     content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
>     some_folder_col = Column(Integer, nullable=True)
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['folder'],
>         'inherit_condition': content_id == Content.id
>     }
> 
>     def __init__(self, title, description=None, some_folder_col=None):
>         self.some_folder_col = some_folder_col
>         locale = _get_locale()
>         self.translations[locale] = FolderTranslation(
>             title=title, content_type_id=_fake_ids['folder'],
>             description=description
>         )
> 
> 
> class FolderTranslation(ContentTranslation):
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['folder'],
>     }
> 
> 
> class Document(Content):
> 
>     __tablename__ = 'document'
> 
>     content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['document'],
>     }
> 
>     def __init__(self, title, body, description=None):
>         locale = _get_locale()
>         self.translations[locale] = DocumentTranslation(
>             body=body, title=title, content_type_id=_fake_ids['document'],
>             description=description
>         )
> 
>     @hybrid_property
>     def body(self):
>         return self.current_translation.body
> 
>     @body.setter
>     def body(self, value):
>         self.current_translation.body = value
> 
> 
> class DocumentTranslation(ContentTranslation):
> 
>     __tablename__ = 'document_translation'
> 
>     language_id = Column(String(2), primary_key=True)
>     content_id = Column(Integer, ForeignKey('document.content_id'),
>                         primary_key=True)
> 
>     body = Column(String, nullable=False)
> 
>     __table_args__ = (
>         ForeignKeyConstraint(
>             ['language_id', 'content_id'],
>             ['content_translation.language_id',
>              'content_translation.content_id']
>         ),
>     )
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['document'],
>         'polymorphic_load': 'inline'
>     }
> 
> 
> class Event(Content):
> 
>     __tablename__ = 'event'
> 
>     content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
>     country_iso = Column(String(2), ForeignKey('country.iso'), nullable=False)
> 
>     country = relationship(
>         Country,
>         innerjoin=True,
>         uselist=False,
>         backref='events',
>         lazy='joined'
>     )
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['event'],
>     }
> 
>     def __init__(self, title, country_iso, description=None,
>                  some_event_col1=None,
>                  some_event_col2=None):
>         locale = _get_locale()
>         self.country_iso = country_iso
>         self.translations[locale] = EventTranslation(
>             title=title, content_type_id=_fake_ids['event'],
>             some_event_col1=some_event_col1, some_event_col2=some_event_col2
>         )
> 
> 
> class EventTranslation(ContentTranslation):
> 
>     __tablename__ = 'event_translation'
> 
>     language_id = Column(String(2), primary_key=True)
>     content_id = Column(Integer, ForeignKey('event.content_id'),
>                         primary_key=True)
> 
>     some_event_col1 = Column(String())
>     some_event_col2 = Column(String())
> 
>     __table_args__ = (
>         ForeignKeyConstraint(
>             ['language_id', 'content_id'],
>             ['content_translation.language_id',
>              'content_translation.content_id']
>         ),
>     )
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['event'],
>         'polymorphic_load': 'inline'
>     }
> 
> 
> e = create_engine("sqlite://", echo=False)
> 
> Base.metadata.create_all(e)
> 
> 
> class FooTest(unittest.TestCase):
>     @classmethod
>     def setup_class(cls):
>         s = Session(e)
> 
>         for name, fake_id in _fake_ids.items():
>             s.add(ContentType(fake_id, name))
> 
>         s.add(Country('be', 'Belgium'))
>         s.add(Country('fr', 'France'))
> 
>         s.add(Language('fr', 'Français'))
>         s.add(Language('en', 'English'))
> 
>         root = Folder('root')
>         s.add(root)
> 
>         subroot = Folder('subroot')
>         root.children.append(subroot)
> 
>         doc1 = Document('doc1_title_en', 'doc1_body_en')
>         doc2 = Document('doc2_title_en', 'doc2_body_en')
>         subroot.children.append(doc1)
>         subroot.children.append(doc2)
> 
>         s.flush()
> 
>         cls.doc1_id = doc1.id
> 
>         trans_fr = DocumentTranslation(
>             language_id='fr',
>             title='doc1_title_fr',
>             description='doc1_description_fr',
>             body='doc1_body_fr'
>         )
>         doc1.translations['fr'] = trans_fr
> 
>         evt1 = Event('Event1', 'be', some_event_col1='sometext')
>         evt2 = Event('Event2', 'be', some_event_col1='sometext',
>                      some_event_col2="blah")
>         evt3 = Event('Event3', 'fr', some_event_col2='sometext')
>         subroot.children.append(evt1)
>         subroot.children.append(evt2)
>         subroot.children.append(evt3)
>         s.commit()
> 
>     def test_one(self):
>         s = Session(e)
>         foo = s.query(Document).get(self.doc1_id)  # XXX the 
> current_translation isn't joinedloaded()
> 
>         s.close()
>         assert foo.current_translation
> 
>     def test_two(self):
>         s = Session(e)
>         foo = s.query(Document).get(self.doc1_id)  # XXX the 
> current_translation isn't joinedloaded()
> 
>         s.close()
>         assert foo.current_translation
>         assert foo.translations
> 
>     def test_three(self):
>         s = Session(e)
>         foo = s.query(Document).get(self.doc1_id)  # XXX the 
> current_translation isn't joinedloaded()
> 
>         # .body is specific to document_translation
>         # without polymorphic_load='inline' a query is made .. in practice
>         # with the lazy='subquery' on the "translations" relationship it
>         # should be the case however, with polymorphic_load='inline' the
>         #table is always joined, even for non-Document entities
> 
>         s.close()
>         assert foo.current_translation
>         assert foo.translations
> 
>         foo.translations['en'].body
> 
>     def test_four(self):
>         s = Session(e)
>         
> s.query(Document).filter(Document.translations.any(language_id='fr')).all()
> 
>     def test_five(self):
>         s = Session(e)
>         # XXX should work, but fail, as .body is specific to 
> DocumentTranslation
>         
> s.query(Document).filter(Document.translations.any(DocumentTranslation.body 
> == 'doc1_body_en')).all()

> import logging
> 
> from sqlalchemy import *
> from sqlalchemy.orm import *
> from sqlalchemy.orm.collections import attribute_mapped_collection
> from sqlalchemy.ext.declarative import declarative_base
> from sqlalchemy.ext.declarative import declared_attr
> from sqlalchemy.ext.hybrid import hybrid_property
> import unittest
> 
> Base = declarative_base()
> 
> logging.basicConfig()
> logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
> 
> _fake_ids = {
>     'document': 1,
>     'event': 2,
>     'folder': 3
> }
> 
> 
> def _get_locale():
>     return 'en'
> 
> 
> class ContentType(Base):
> 
>     __tablename__ = 'content_type'
> 
>     id = Column(Integer, primary_key=True)
>     name = Column(String(50), nullable=False)
> 
>     def __init__(self, id, name):
>         self.id = id
>         self.name = name
> 
> 
> class Language(Base):
> 
>     __tablename__ = 'language'
> 
>     id = Column(String(2), primary_key=True)
>     name = Column(String(50), nullable=False)
> 
>     def __init__(self, id, name):
>         self.id = id
>         self.name = name
> 
> 
> class Country(Base):
> 
>     __tablename__ = 'country'
> 
>     iso = Column(String(2), primary_key=True)
>     name = Column(String(50), nullable=False)
> 
>     def __init__(self, iso, name):
>         self.iso = iso
>         self.name = name
> 
> 
> def _setup_translation(content_cls, translation_cls):
>     content_cls.current_translation = relationship(
>         lambda: translation_cls,
>         primaryjoin=lambda: and_(
>             foreign(translation_cls.content_id) == Content.id,
>             translation_cls.language_id == bindparam(
>                 None,
>                 callable_=lambda: _get_locale(),
>                 type_=String()
>             )
>         ),
>         lazy='joined',
>         uselist=False,
>         innerjoin=True,
>         viewonly=True,
>         bake_queries=False,
>         back_populates='content'
>     )
> 
>     content_cls.translations = relationship(
>         lambda: translation_cls,
>         cascade='all, delete-orphan',
>         lazy='subquery',
>         innerjoin=True,
>         back_populates='content',
>         collection_class=attribute_mapped_collection('language_id')
>     )
> 
>     translation_cls.content = relationship(
>         content_cls,
>         back_populates='translations',
>         innerjoin=True,
>         uselist=False
>     )
> 
> 
> class Content(Base):
> 
>     __tablename__ = 'content'
> 
>     id = Column(Integer, primary_key=True)
>     content_type_id = Column(Integer, ForeignKey('content_type.id'),
>                              nullable=False)
>     container_id = Column(Integer, ForeignKey('folder.content_id'))
> 
>     parent = relationship(
>         lambda: Folder,
>         foreign_keys=lambda: Content.container_id,
>         innerjoin=True,
>         uselist=False,
>         backref=backref('children', cascade='all, delete-orphan')
>     )
> 
>     content_type = relationship(
>         ContentType,
>         lazy='joined',
>         innerjoin=True,
>         uselist=False
>     )
> 
>     __mapper_args__ = {
>         'polymorphic_on': content_type_id
>     }
> 
>     @hybrid_property
>     def title(self):
>         return self.current_translation.title
> 
>     @title.setter
>     def title(self, value):
>         self.current_translation.title = value
> 
>     @hybrid_property
>     def description(self):
>         return self.current_translation.description
> 
>     @description.setter
>     def description(self, value):
>         self.current_translation.description = value
> 
> 
> class ContentTranslation(Base):
> 
>     __tablename__ = 'content_translation'
> 
>     language_id = Column(String(2), ForeignKey('language.id'),
>                          primary_key=True)
>     content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
> 
>     title = Column(String, nullable=False)
>     description = Column(String)
> 
>     # XXX: In an ideal scenario I'd like to put the discriminator on the
>     # "content.content_type_id" column, something like;
>     # polymorphic_on = sql.join(
>     #   content_translation, content,
>     #   content_translation.c.content_id == content.c.id
>     # ).c.content_content_type_id
> 
>     # But as this is not supported by SQLAlchemy (yet?) I "duplicated" the
>     # "content_type_id" column and created a trigger which simply select the
>     # "content_type_id" in the "content" table for the corresponding
>     # "content_translation.content_id". So in the "real" app there is 
> something
>     # like:
> 
>     # create or replace function content_translation_content_id()
>     # returns trigger as $content_id$
>     #    begin
>     #        NEW.content_type_id := (
>     #            select
>     #                content_type_id
>     #            from
>     #                content
>     #            where
>     #                content.id = NEW.content_id
>     #        );
>     #
>     #        return NEW;
>     #    end;
>     # $content_id$ language plpgsql;
> 
>     # create trigger content_translation_content_id
>     #     before insert or update on content_translation
>     #     for each row execute procedure content_translation_content_id();
> 
>     content_type_id = Column(Integer, ForeignKey('content_type.id'),
>                              nullable=False)
> 
>     content_type = relationship(
>         ContentType,
>         lazy='joined',
>         innerjoin=True,
>         uselist=False
>     )
> 
>     __mapper_args__ = {
>         'polymorphic_on': content_type_id
>     }
> 
>     def __init__(self, **kwargs):
>         if 'language_id' not in kwargs:
>             self.language_id = _get_locale()
> 
>         for k, v in kwargs.items():
>             setattr(self, k, v)
> 
> 
> class Folder(Content):
> 
>     __tablename__ = 'folder'
> 
>     content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
>     some_folder_col = Column(Integer, nullable=True)
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['folder'],
>         'inherit_condition': content_id == Content.id
>     }
> 
>     def __init__(self, title, description=None, some_folder_col=None):
>         self.some_folder_col = some_folder_col
>         locale = _get_locale()
>         self.translations[locale] = FolderTranslation(
>             title=title, content_type_id=_fake_ids['folder'],
>             description=description
>         )
> 
> 
> class FolderTranslation(ContentTranslation):
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['folder'],
>     }
> 
> 
> class Document(Content):
> 
>     __tablename__ = 'document'
> 
>     content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['document'],
>     }
> 
>     def __init__(self, title, body, description=None):
>         locale = _get_locale()
>         self.translations[locale] = DocumentTranslation(
>             body=body, title=title, content_type_id=_fake_ids['document'],
>             description=description
>         )
> 
>     @hybrid_property
>     def body(self):
>         return self.current_translation.body
> 
>     @body.setter
>     def body(self, value):
>         self.current_translation.body = value
> 
> 
> class DocumentTranslation(ContentTranslation):
> 
>     __tablename__ = 'document_translation'
> 
>     language_id = Column(String(2), primary_key=True)
>     content_id = Column(Integer, ForeignKey('document.content_id'),
>                         primary_key=True)
> 
>     body = Column(String, nullable=False)
> 
>     __table_args__ = (
>         ForeignKeyConstraint(
>             ['language_id', 'content_id'],
>             ['content_translation.language_id',
>              'content_translation.content_id']
>         ),
>     )
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['document'],
>         'polymorphic_load': 'inline'
>     }
> 
> 
> class Event(Content):
> 
>     __tablename__ = 'event'
> 
>     content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
>     country_iso = Column(String(2), ForeignKey('country.iso'), nullable=False)
> 
>     country = relationship(
>         Country,
>         innerjoin=True,
>         uselist=False,
>         backref='events',
>         lazy='joined'
>     )
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['event'],
>     }
> 
>     def __init__(self, title, country_iso, description=None,
>                  some_event_col1=None,
>                  some_event_col2=None):
>         locale = _get_locale()
>         self.country_iso = country_iso
>         self.translations[locale] = EventTranslation(
>             title=title, content_type_id=_fake_ids['event'],
>             some_event_col1=some_event_col1, some_event_col2=some_event_col2
>         )
> 
> 
> class EventTranslation(ContentTranslation):
> 
>     __tablename__ = 'event_translation'
> 
>     language_id = Column(String(2), primary_key=True)
>     content_id = Column(Integer, ForeignKey('event.content_id'),
>                         primary_key=True)
> 
>     some_event_col1 = Column(String())
>     some_event_col2 = Column(String())
> 
>     __table_args__ = (
>         ForeignKeyConstraint(
>             ['language_id', 'content_id'],
>             ['content_translation.language_id',
>              'content_translation.content_id']
>         ),
>     )
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['event'],
>         'polymorphic_load': 'inline'
>     }
> 
> _setup_translation(Content, ContentTranslation)
> _setup_translation(Folder, FolderTranslation)
> _setup_translation(Document, DocumentTranslation)
> _setup_translation(Event, EventTranslation)
> 
> 
> e = create_engine("sqlite://", echo=False)
> 
> Base.metadata.create_all(e)
> 
> 
> class FooTest(unittest.TestCase):
>     @classmethod
>     def setup_class(cls):
>         s = Session(e)
> 
>         for name, fake_id in _fake_ids.items():
>             s.add(ContentType(fake_id, name))
> 
>         s.add(Country('be', 'Belgium'))
>         s.add(Country('fr', 'France'))
>         s.flush()
> 
>         s.add(Language('fr', 'Français'))
>         s.add(Language('en', 'English'))
>         s.flush()
> 
>         root = Folder('root')
>         s.add(root)
>         s.flush()
> 
>         subroot = Folder('subroot')
>         root.children.append(subroot)
>         s.flush()
> 
>         doc1 = Document('doc1_title_en', 'doc1_body_en')
>         doc2 = Document('doc2_title_en', 'doc2_body_en')
>         subroot.children.append(doc1)
>         subroot.children.append(doc2)
>         s.flush()
> 
>         cls.doc1_id = doc1.id
> 
>         trans_fr = DocumentTranslation(
>             language_id='fr',
>             title='doc1_title_fr',
>             description='doc1_description_fr',
>             body='doc1_body_fr'
>         )
>         doc1.translations['fr'] = trans_fr
>         s.flush()
> 
>         evt1 = Event('Event1', 'be', some_event_col1='sometext')
>         evt2 = Event('Event2', 'be', some_event_col1='sometext',
>                      some_event_col2="blah")
>         evt3 = Event('Event3', 'fr', some_event_col2='sometext')
>         subroot.children.append(evt1)
>         subroot.children.append(evt2)
>         subroot.children.append(evt3)
>         s.commit()
> 
>     def test_one(self):
>         s = Session(e)
>         foo = s.query(Document).get(self.doc1_id)  # XXX the 
> current_translation isn't joinedloaded()
> 
>         s.close()
>         assert foo.current_translation
> 
>     def test_two(self):
>         s = Session(e)
>         foo = s.query(Document).get(self.doc1_id)  # XXX the 
> current_translation isn't joinedloaded()
>         foo.translations
> 
>     def test_three(self):
>         s = Session(e)
>         foo = s.query(Document).get(self.doc1_id)  # XXX the 
> current_translation isn't joinedloaded()
> 
>         # .body is specific to document_translation
>         # without polymorphic_load='inline' a query is made .. in practice
>         # with the lazy='subquery' on the "translations" relationship it
>         # should be the case however, with polymorphic_load='inline' the
>         #table is always joined, even for non-Document entities
> 
>         foo.translations['en'].body
> 
>     def test_four(self):
>         s = Session(e)
>         
> s.query(Document).filter(Document.translations.any(language_id='fr')).all()
> 
>     def test_five(self):
>         s = Session(e)
>         # XXX should work, but fail, as .body is specific to 
> DocumentTranslation
>         
> s.query(Document).filter(Document.translations.any(body='doc1_body_en')).all()

> import logging
> 
> from sqlalchemy import *
> from sqlalchemy.orm import *
> from sqlalchemy.orm.collections import attribute_mapped_collection
> from sqlalchemy.ext.declarative import declarative_base
> from sqlalchemy.ext.declarative import declared_attr
> from sqlalchemy.ext.associationproxy import association_proxy
> from sqlalchemy.ext.hybrid import hybrid_property
> import unittest
> 
> Base = declarative_base()
> 
> logging.basicConfig()
> logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
> 
> _fake_ids = {
>     'document': 1,
>     'event': 2,
>     'folder': 3
> }
> 
> 
> def _get_locale():
>     return 'en'
> 
> 
> class ContentType(Base):
> 
>     __tablename__ = 'content_type'
> 
>     id = Column(Integer, primary_key=True)
>     name = Column(String(50), nullable=False)
> 
>     def __init__(self, id, name):
>         self.id = id
>         self.name = name
> 
> 
> class Language(Base):
> 
>     __tablename__ = 'language'
> 
>     id = Column(String(2), primary_key=True)
>     name = Column(String(50), nullable=False)
> 
>     def __init__(self, id, name):
>         self.id = id
>         self.name = name
> 
> 
> class Country(Base):
> 
>     __tablename__ = 'country'
> 
>     iso = Column(String(2), primary_key=True)
>     name = Column(String(50), nullable=False)
> 
>     def __init__(self, iso, name):
>         self.iso = iso
>         self.name = name
> 
> 
> def _setup_translation(content_cls, translation_cls):
>     content_cls.current_translation = relationship(
>         lambda: translation_cls,
>         primaryjoin=lambda: and_(
>             foreign(translation_cls.content_id) == Content.id,
>             translation_cls.language_id == bindparam(
>                 None,
>                 callable_=lambda: _get_locale(),
>                 type_=String()
>             )
>         ),
>         lazy='joined',
>         uselist=False,
>         innerjoin=True,
>         viewonly=True,
>         bake_queries=False,
>         back_populates='content'
>     )
> 
>     content_cls.translations = relationship(
>         lambda: translation_cls,
>         cascade='all, delete-orphan',
>         lazy='subquery',
>         innerjoin=True,
>         back_populates='content',
>         collection_class=attribute_mapped_collection('language_id')
>     )
> 
>     translation_cls.content = relationship(
>         content_cls,
>         back_populates='translations',
>         innerjoin=True,
>         uselist=False
>     )
> 
> 
> class Content(Base):
> 
>     __tablename__ = 'content'
> 
>     id = Column(Integer, primary_key=True)
>     content_type_id = Column(Integer, ForeignKey('content_type.id'),
>                              nullable=False)
>     container_id = Column(Integer, ForeignKey('folder.content_id'))
> 
>     parent = relationship(
>         lambda: Folder,
>         foreign_keys=lambda: Content.container_id,
>         innerjoin=True,
>         uselist=False,
>         backref=backref('children', cascade='all, delete-orphan')
>     )
> 
>     content_type = relationship(
>         ContentType,
>         lazy='joined',
>         innerjoin=True,
>         uselist=False
>     )
> 
>     __mapper_args__ = {
>         'polymorphic_on': content_type_id
>     }
> 
>     @hybrid_property
>     def title(self):
>         return self.current_translation.title
> 
>     @title.setter
>     def title(self, value):
>         self.current_translation.title = value
> 
>     @hybrid_property
>     def description(self):
>         return self.current_translation.description
> 
>     @description.setter
>     def description(self, value):
>         self.current_translation.description = value
> 
> 
> class ContentTranslation(Base):
> 
>     __tablename__ = 'content_translation'
> 
>     language_id = Column(String(2), ForeignKey('language.id'),
>                          primary_key=True)
>     content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
> 
>     title = Column(String, nullable=False)
>     description = Column(String)
> 
>     content_type = association_proxy("content", "content_type")
> 
>     __mapper_args__ = {
>         'polymorphic_on':
>             select([Content.content_type_id]).where(content_id == 
> Content.id).\
>             correlate_except(Content).as_scalar()
>     }
> 
>     def __init__(self, **kwargs):
>         if 'language_id' not in kwargs:
>             self.language_id = _get_locale()
> 
>         for k, v in kwargs.items():
>             setattr(self, k, v)
> 
> 
> class Folder(Content):
> 
>     __tablename__ = 'folder'
> 
>     content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
>     some_folder_col = Column(Integer, nullable=True)
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['folder'],
>         'inherit_condition': content_id == Content.id
>     }
> 
>     def __init__(self, title, description=None, some_folder_col=None):
>         self.some_folder_col = some_folder_col
>         locale = _get_locale()
>         self.translations[locale] = FolderTranslation(
>             title=title, content_type_id=_fake_ids['folder'],
>             description=description
>         )
> 
> 
> class FolderTranslation(ContentTranslation):
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['folder'],
>     }
> 
> 
> class Document(Content):
> 
>     __tablename__ = 'document'
> 
>     content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['document'],
>     }
> 
>     def __init__(self, title, body, description=None):
>         locale = _get_locale()
>         self.translations[locale] = DocumentTranslation(
>             body=body, title=title, content_type_id=_fake_ids['document'],
>             description=description
>         )
> 
>     @hybrid_property
>     def body(self):
>         return self.current_translation.body
> 
>     @body.setter
>     def body(self, value):
>         self.current_translation.body = value
> 
> 
> class DocumentTranslation(ContentTranslation):
> 
>     __tablename__ = 'document_translation'
> 
>     language_id = Column(String(2), primary_key=True)
>     content_id = Column(Integer, ForeignKey('document.content_id'),
>                         primary_key=True)
> 
>     body = Column(String, nullable=False)
> 
>     __table_args__ = (
>         ForeignKeyConstraint(
>             ['language_id', 'content_id'],
>             ['content_translation.language_id',
>              'content_translation.content_id']
>         ),
>     )
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['document'],
>         'polymorphic_load': 'inline'
>     }
> 
> 
> class Event(Content):
> 
>     __tablename__ = 'event'
> 
>     content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
>     country_iso = Column(String(2), ForeignKey('country.iso'), nullable=False)
> 
>     country = relationship(
>         Country,
>         innerjoin=True,
>         uselist=False,
>         backref='events',
>         lazy='joined'
>     )
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['event'],
>     }
> 
>     def __init__(self, title, country_iso, description=None,
>                  some_event_col1=None,
>                  some_event_col2=None):
>         locale = _get_locale()
>         self.country_iso = country_iso
>         self.translations[locale] = EventTranslation(
>             title=title, content_type_id=_fake_ids['event'],
>             some_event_col1=some_event_col1, some_event_col2=some_event_col2
>         )
> 
> 
> class EventTranslation(ContentTranslation):
> 
>     __tablename__ = 'event_translation'
> 
>     language_id = Column(String(2), primary_key=True)
>     content_id = Column(Integer, ForeignKey('event.content_id'),
>                         primary_key=True)
> 
>     some_event_col1 = Column(String())
>     some_event_col2 = Column(String())
> 
>     __table_args__ = (
>         ForeignKeyConstraint(
>             ['language_id', 'content_id'],
>             ['content_translation.language_id',
>              'content_translation.content_id']
>         ),
>     )
> 
>     __mapper_args__ = {
>         'polymorphic_identity': _fake_ids['event'],
>         'polymorphic_load': 'inline'
>     }
> 
> _setup_translation(Content, ContentTranslation)
> _setup_translation(Folder, FolderTranslation)
> _setup_translation(Document, DocumentTranslation)
> _setup_translation(Event, EventTranslation)
> 
> 
> e = create_engine("sqlite://", echo=False)
> 
> Base.metadata.create_all(e)
> 
> 
> class FooTest(unittest.TestCase):
>     @classmethod
>     def setup_class(cls):
>         s = Session(e)
> 
>         for name, fake_id in _fake_ids.items():
>             s.add(ContentType(fake_id, name))
> 
>         s.add(Country('be', 'Belgium'))
>         s.add(Country('fr', 'France'))
>         s.flush()
> 
>         s.add(Language('fr', 'Français'))
>         s.add(Language('en', 'English'))
>         s.flush()
> 
>         root = Folder('root')
>         s.add(root)
>         s.flush()
> 
>         subroot = Folder('subroot')
>         root.children.append(subroot)
>         s.flush()
> 
>         doc1 = Document('doc1_title_en', 'doc1_body_en')
>         doc2 = Document('doc2_title_en', 'doc2_body_en')
>         subroot.children.append(doc1)
>         subroot.children.append(doc2)
>         s.flush()
> 
>         cls.doc1_id = doc1.id
> 
>         trans_fr = DocumentTranslation(
>             language_id='fr',
>             title='doc1_title_fr',
>             description='doc1_description_fr',
>             body='doc1_body_fr'
>         )
>         doc1.translations['fr'] = trans_fr
>         s.flush()
> 
>         evt1 = Event('Event1', 'be', some_event_col1='sometext')
>         evt2 = Event('Event2', 'be', some_event_col1='sometext',
>                      some_event_col2="blah")
>         evt3 = Event('Event3', 'fr', some_event_col2='sometext')
>         subroot.children.append(evt1)
>         subroot.children.append(evt2)
>         subroot.children.append(evt3)
>         s.commit()
> 
>     def test_one(self):
>         s = Session(e)
>         foo = s.query(Document).get(self.doc1_id)  # XXX the 
> current_translation isn't joinedloaded()
> 
>         s.close()
>         assert foo.current_translation
> 
>     def test_two(self):
>         s = Session(e)
>         foo = s.query(Document).get(self.doc1_id)  # XXX the 
> current_translation isn't joinedloaded()
>         foo.translations
> 
>     def test_three(self):
>         s = Session(e)
>         foo = s.query(Document).get(self.doc1_id)  # XXX the 
> current_translation isn't joinedloaded()
> 
>         # .body is specific to document_translation
>         # without polymorphic_load='inline' a query is made .. in practice
>         # with the lazy='subquery' on the "translations" relationship it
>         # should be the case however, with polymorphic_load='inline' the
>         #table is always joined, even for non-Document entities
> 
>         foo.translations['en'].body
> 
>     def test_four(self):
>         s = Session(e)
>         
> s.query(Document).filter(Document.translations.any(language_id='fr')).all()
> 
>     def test_five(self):
>         s = Session(e)
>         # XXX should work, but fail, as .body is specific to 
> DocumentTranslation
>         
> s.query(Document).filter(Document.translations.any(body='doc1_body_en')).all()


-- 
Julien Cigar
Belgian Biodiversity Platform (http://www.biodiversity.be)
PGP fingerprint: EEF9 F697 4B68 D275 7B11  6A25 B2BB 3710 A204 23C0
No trees were killed in the creation of this message.
However, many electrons were terribly inconvenienced.

-- 
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.
import logging

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.hybrid import hybrid_property
import unittest

Base = declarative_base()

logging.basicConfig()
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)

_fake_ids = {
    'document': 1,
    'event': 2,
    'folder': 3
}


def _get_locale():
    return 'en'


class ContentType(Base):

    __tablename__ = 'content_type'

    id = Column(Integer, primary_key=True)
    name = Column(String(50), nullable=False)

    def __init__(self, id, name):
        self.id = id
        self.name = name


class Language(Base):

    __tablename__ = 'language'

    id = Column(String(2), primary_key=True)
    name = Column(String(50), nullable=False)

    def __init__(self, id, name):
        self.id = id
        self.name = name


class Country(Base):

    __tablename__ = 'country'

    iso = Column(String(2), primary_key=True)
    name = Column(String(50), nullable=False)

    def __init__(self, iso, name):
        self.iso = iso
        self.name = name


def _setup_translation(content_cls, translation_cls):
    content_cls.current_translation = relationship(
        lambda: translation_cls,
        primaryjoin=lambda: and_(
            foreign(translation_cls.content_id) == Content.id,
            translation_cls.language_id == bindparam(
                None,
                callable_=lambda: _get_locale(),
                type_=String()
            )
        ),
        lazy='joined',
        uselist=False,
        innerjoin=True,
        viewonly=True,
        bake_queries=False,
        back_populates='content'
    )

    content_cls.translations = relationship(
        lambda: translation_cls,
        cascade='all, delete-orphan',
        lazy='subquery',
        innerjoin=True,
        back_populates='content',
        collection_class=attribute_mapped_collection('language_id')
    )

    translation_cls.content = relationship(
        content_cls,
        back_populates='translations',
        innerjoin=True,
        uselist=False
    )


class Content(Base):

    __tablename__ = 'content'

    id = Column(Integer, primary_key=True)
    content_type_id = Column(Integer, ForeignKey('content_type.id'),
                             nullable=False)
    container_id = Column(Integer, ForeignKey('folder.content_id'))

    parent = relationship(
        lambda: Folder,
        foreign_keys=lambda: Content.container_id,
        innerjoin=True,
        uselist=False,
        backref=backref('children', cascade='all, delete-orphan')
    )

    content_type = relationship(
        ContentType,
        lazy='joined',
        innerjoin=True,
        uselist=False
    )

    __mapper_args__ = {
        'polymorphic_on': content_type_id
    }

    @hybrid_property
    def title(self):
        return self.current_translation.title

    @title.setter
    def title(self, value):
        self.current_translation.title = value

    @hybrid_property
    def description(self):
        return self.current_translation.description

    @description.setter
    def description(self, value):
        self.current_translation.description = value


class ContentTranslation(Base):

    __tablename__ = 'content_translation'

    language_id = Column(String(2), ForeignKey('language.id'),
                         primary_key=True)
    content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)

    title = Column(String, nullable=False)
    description = Column(String)

    content_type = association_proxy("content", "content_type")

    __mapper_args__ = {
        'polymorphic_on':
            select([Content.content_type_id]).where(content_id == Content.id).\
            correlate_except(Content).as_scalar()
    }

    def __init__(self, **kwargs):
        if 'language_id' not in kwargs:
            self.language_id = _get_locale()

        for k, v in kwargs.items():
            setattr(self, k, v)


class Folder(Content):

    __tablename__ = 'folder'

    content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
    some_folder_col = Column(Integer, nullable=True)

    __mapper_args__ = {
        'polymorphic_identity': _fake_ids['folder'],
        'inherit_condition': content_id == Content.id
    }

    def __init__(self, title, description=None, some_folder_col=None):
        self.some_folder_col = some_folder_col
        locale = _get_locale()
        self.translations[locale] = FolderTranslation(
            title=title, content_type_id=_fake_ids['folder'],
            description=description
        )


class FolderTranslation(ContentTranslation):

    __mapper_args__ = {
        'polymorphic_identity': _fake_ids['folder'],
    }


class Document(Content):

    __tablename__ = 'document'

    content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)

    __mapper_args__ = {
        'polymorphic_identity': _fake_ids['document'],
    }

    def __init__(self, title, body, description=None):
        locale = _get_locale()
        self.translations[locale] = DocumentTranslation(
            body=body, title=title, content_type_id=_fake_ids['document'],
            description=description
        )

    @hybrid_property
    def body(self):
        return self.current_translation.body

    @body.setter
    def body(self, value):
        self.current_translation.body = value


class DocumentTranslation(ContentTranslation):

    __tablename__ = 'document_translation'

    language_id = Column(String(2), primary_key=True)
    content_id = Column(Integer, ForeignKey('document.content_id'),
                        primary_key=True)

    body = Column(String, nullable=False)

    __table_args__ = (
        ForeignKeyConstraint(
            ['language_id', 'content_id'],
            ['content_translation.language_id',
             'content_translation.content_id']
        ),
    )

    __mapper_args__ = {
        'polymorphic_identity': _fake_ids['document'],
        'polymorphic_load': 'inline'
    }


class Event(Content):

    __tablename__ = 'event'

    content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
    country_iso = Column(String(2), ForeignKey('country.iso'), nullable=False)

    country = relationship(
        Country,
        innerjoin=True,
        uselist=False,
        backref='events',
        lazy='joined'
    )

    __mapper_args__ = {
        'polymorphic_identity': _fake_ids['event'],
    }

    def __init__(self, title, country_iso, description=None,
                 some_event_col1=None,
                 some_event_col2=None):
        locale = _get_locale()
        self.country_iso = country_iso
        self.translations[locale] = EventTranslation(
            title=title, content_type_id=_fake_ids['event'],
            some_event_col1=some_event_col1, some_event_col2=some_event_col2
        )


class EventTranslation(ContentTranslation):

    __tablename__ = 'event_translation'

    language_id = Column(String(2), primary_key=True)
    content_id = Column(Integer, ForeignKey('event.content_id'),
                        primary_key=True)

    some_event_col1 = Column(String())
    some_event_col2 = Column(String())

    __table_args__ = (
        ForeignKeyConstraint(
            ['language_id', 'content_id'],
            ['content_translation.language_id',
             'content_translation.content_id']
        ),
    )

    __mapper_args__ = {
        'polymorphic_identity': _fake_ids['event'],
        'polymorphic_load': 'inline'
    }

_setup_translation(Content, ContentTranslation)
_setup_translation(Folder, FolderTranslation)
_setup_translation(Document, DocumentTranslation)
_setup_translation(Event, EventTranslation)


e = create_engine("sqlite://", echo=False)

Base.metadata.create_all(e)


class FooTest(unittest.TestCase):
    @classmethod
    def setup_class(cls):
        s = Session(e)

        for name, fake_id in _fake_ids.items():
            s.add(ContentType(fake_id, name))

        s.add(Country('be', 'Belgium'))
        s.add(Country('fr', 'France'))
        s.flush()

        s.add(Language('fr', 'Français'))
        s.add(Language('en', 'English'))
        s.flush()

        root = Folder('root')
        s.add(root)
        s.flush()

        subroot = Folder('subroot')
        root.children.append(subroot)
        s.flush()

        doc1 = Document('doc1_title_en', 'doc1_body_en')
        doc2 = Document('doc2_title_en', 'doc2_body_en')
        subroot.children.append(doc1)
        subroot.children.append(doc2)
        s.flush()

        cls.doc1_id = doc1.id

        trans_fr = DocumentTranslation(
            language_id='fr',
            title='doc1_title_fr',
            description='doc1_description_fr',
            body='doc1_body_fr'
        )
        doc1.translations['fr'] = trans_fr
        s.flush()

        evt1 = Event('Event1', 'be', some_event_col1='sometext')
        evt2 = Event('Event2', 'be', some_event_col1='sometext',
                     some_event_col2="blah")
        evt3 = Event('Event3', 'fr', some_event_col2='sometext')
        subroot.children.append(evt1)
        subroot.children.append(evt2)
        subroot.children.append(evt3)
        s.commit()

    def test_one(self):
        s = Session(e)
        foo = s.query(Document).get(self.doc1_id)  # XXX the current_translation isn't joinedloaded()

        s.close()
        assert foo.current_translation

    def test_two(self):
        s = Session(e)
        foo = s.query(Document).get(self.doc1_id)  # XXX the current_translation isn't joinedloaded()
        foo.translations

    def test_three(self):
        s = Session(e)
        foo = s.query(Document).get(self.doc1_id)  # XXX the current_translation isn't joinedloaded()

        # .body is specific to document_translation
        # without polymorphic_load='inline' a query is made .. in practice
        # with the lazy='subquery' on the "translations" relationship it
        # should be the case however, with polymorphic_load='inline' the
        #table is always joined, even for non-Document entities

        foo.translations['en'].body

    def test_four(self):
        s = Session(e)
        s.query(Document).filter(Document.translations.any(language_id='fr')).all()

    def test_five(self):
        s = Session(e)
        # XXX should work, but fail, as .body is specific to DocumentTranslation
        s.query(Document).filter(Document.translations.any(body='doc1_body_en')).all()

    def test_six(self):
        s = Session(e)
        a = s.query(Content).all() # note: current_translation is joinedload
        [_.title for _ in a]

Attachment: signature.asc
Description: PGP signature

Reply via email to