Search code examples
pythonsqlalchemydogpile.cache

SqlAlchemy cache with dogpile


I have three models with inheritance and relationship and I want to cache query to this models.

class Person(Base):
    __tablename__ = 'person'
    id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)
    type = Column(String(50))
    __mapper_args__ = {
        'polymorphic_identity': 'object',
        'polymorphic_on': type
        }

class Man(Person):
    __tablename__ = 'man'
    id = Column(Integer, ForeignKey('person.id'), primary_key=True)
    age = Column(String(100), nullable=False)
    __mapper_args__ = {'polymorphic_identity': 'man'}

class Config(Base):
    __tablename__ = "config"
    id = Column(Integer, primary_key=True)
    person = Column(Integer, ForeignKey('person.id'))
    address = Column(String)
    person_ref = relationship(Person)

There are a lot of others models inherited from Personal. For example I need to get access to Man attributes through Config relationship. Normally I would do:

config = session.query(Config).join(Config.person_ref).filter(Person.type == 'man').first()
print config.person_ref.age

How can I cache query like this with dogpile? I can cache query to Config, but I can't cache query to attributes of Man, emits SQL every time. I tried to use with_polymorphic, but it's only works without joinedload. (don't undestand why)

config = session.query(Config).options(FromCache("default")).first()
people = session.query(Person).options(FromCache("default")).with_polymorphic('*').get(config.person)

but I need joinedload to filter for types.


Solution

  • in order to ensure that the "man" table is loaded, of_type() can be used for any pattern of subtypes. We can instead join to a full polymorphic selectable using with_polymorphic(). See the examples at http://docs.sqlalchemy.org/en/latest/orm/inheritance.html#creating-joins-to-specific-subtypes for details on this. As long as the data you want comes out in one SELECT query, then that data will be within what is cached via FromCache. It's true that the caching recipe does not currently include a system by which the deferred load of additional joined inheritance attributes can be cached after the fact.

    from sqlalchemy import *
    from sqlalchemy.orm import *
    from sqlalchemy.ext.declarative import declarative_base
    from examples.dogpile_caching.caching_query import query_callable, FromCache, RelationshipCache
    from hashlib import md5
    from dogpile.cache.region import make_region
    
    Base = declarative_base()
    
    class Person(Base):
        __tablename__ = 'person'
        id = Column(Integer, primary_key=True)
        name = Column(String(100), nullable=False)
        type = Column(String(50))
        __mapper_args__ = {
            'polymorphic_identity': 'object',
            'polymorphic_on': type
            }
    
    class Man(Person):
        __tablename__ = 'man'
        id = Column(Integer, ForeignKey('person.id'), primary_key=True)
        age = Column(String(100), nullable=False)
        __mapper_args__ = {'polymorphic_identity': 'man'}
    
    class SomethingElse(Person):
        __tablename__ = 'somethingelse'
    
        id = Column(Integer, ForeignKey('person.id'), primary_key=True)
        age = Column(String(100), nullable=False)
        __mapper_args__ = {'polymorphic_identity': 'somethingelse'}
    
    class Config(Base):
        __tablename__ = "config"
        id = Column(Integer, primary_key=True)
        person = Column(Integer, ForeignKey('person.id'))
        address = Column(String)
        person_ref = relationship(Person)
    
    e = create_engine("sqlite://", echo=True)
    Base.metadata.create_all(e)
    
    def md5_key_mangler(key):
        """Receive cache keys as long concatenated strings;
        distill them into an md5 hash.
    
        """
        return md5(key.encode('ascii')).hexdigest()
    
    regions = {}
    regions['default'] = make_region(
                key_mangler=md5_key_mangler
                ).configure(
                    'dogpile.cache.memory_pickle',
                )
    
    Session = scoped_session(
                    sessionmaker(
                        bind=e,
                        query_cls=query_callable(regions)
                    )
                )
    
    sess = Session()
    sess.add(Config(person_ref=SomethingElse(age='45', name='se1')))
    sess.add(Config(person_ref=Man(age='30', name='man1')))
    sess.commit()
    
    all_types = with_polymorphic(Person, "*", aliased=True)
    
    conf = sess.query(Config).options(joinedload(Config.person_ref.of_type(all_types)), FromCache("default")).first()
    sess.commit()
    sess.close()
    
    print "_____NO MORE SQL!___________"
    
    
    conf = sess.query(Config).options(joinedload(Config.person_ref.of_type(all_types)), FromCache("default")).first()
    print conf.person_ref.age