-"""basic_association.py
-
-illustrate a many-to-many relationship between an
+"""Illustrate a many-to-many relationship between an
"Order" and a collection of "Item" objects, associating a purchase price
with each via an association object called "OrderItem"
-"""dict_of_sets_with_default.py
-
-an advanced association proxy example which
+"""An advanced association proxy example which
illustrates nesting of association proxies to produce multi-level Python
collections, in this case a dictionary with string keys and sets of integers
as values, which conceal the underlying mapped classes.
-"""proxied_association.py
-
-same example as basic_association, adding in
+"""Same example as basic_association, adding in
usage of :mod:`sqlalchemy.ext.associationproxy` to make explicit references
to ``OrderItem`` optional.
-"""
-Illustrates how to attach events to all instrumented attributes
+"""Illustrates how to attach events to all instrumented attributes
and listen for change events.
"""
-"""advanced.py
-
-Illustrate usage of Query combined with the FromCache option,
+"""Illustrate usage of Query combined with the FromCache option,
including front-end loading, cache invalidation and collection caching.
"""
-"""caching_query.py
-
-Represent functions and classes
+"""Represent functions and classes
which allow the usage of Dogpile caching with SQLAlchemy.
Introduces a query option called FromCache.
"""
from sqlalchemy.orm.interfaces import MapperOption
from sqlalchemy.orm.query import Query
-from sqlalchemy.sql import visitors
from dogpile.cache.api import NO_VALUE
+
class CachingQuery(Query):
"""A Query subclass which optionally loads full results from a dogpile
cache region.
- The CachingQuery optionally stores additional state that allows it to consult
- a dogpile.cache cache before accessing the database, in the form
- of a FromCache or RelationshipCache object. Each of these objects
- refer to the name of a :class:`dogpile.cache.Region` that's been configured
- and stored in a lookup dictionary. When such an object has associated
- itself with the CachingQuery, the corresponding :class:`dogpile.cache.Region`
- is used to locate a cached result. If none is present, then the
- Query is invoked normally, the results being cached.
+ The CachingQuery optionally stores additional state that allows it to
+ consult a dogpile.cache cache before accessing the database, in the form of
+ a FromCache or RelationshipCache object. Each of these objects refer to
+ the name of a :class:`dogpile.cache.Region` that's been configured and
+ stored in a lookup dictionary. When such an object has associated itself
+ with the CachingQuery, the corresponding :class:`dogpile.cache.Region` is
+ used to locate a cached result. If none is present, then the Query is
+ invoked normally, the results being cached.
The FromCache and RelationshipCache mapper options below represent
the "public" method of configuring this state upon the CachingQuery.
-"""environment.py
-
-Establish data / cache file paths, and configurations,
+"""Establish data / cache file paths, and configurations,
bootstrap fixture data if necessary.
"""
# using a callable that will associate the dictionary
# of regions with the Query.
Session = scoped_session(
- sessionmaker(
- query_cls=caching_query.query_callable(regions)
- )
- )
+ sessionmaker(
+ query_cls=caching_query.query_callable(regions)
+ )
+)
# global declarative base class.
Base = declarative_base()
root = "./dogpile_data/"
if not os.path.exists(root):
- input("Will create datafiles in %r.\n"
- "To reset the cache + database, delete this directory.\n"
- "Press enter to continue.\n" % root
- )
+ input(
+ "Will create datafiles in %r.\n"
+ "To reset the cache + database, delete this directory.\n"
+ "Press enter to continue.\n" % root
+ )
os.makedirs(root)
dbfile = os.path.join(root, "dogpile_demo.db")
# configure the "default" cache region.
regions['default'] = make_region(
- # the "dbm" backend needs
- # string-encoded keys
- key_mangler=md5_key_mangler
- ).configure(
- # using type 'file' to illustrate
- # serialized persistence. Normally
- # memcached or similar is a better choice
- # for caching.
- 'dogpile.cache.dbm',
- expiration_time=3600,
- arguments={
- "filename": os.path.join(root, "cache.dbm")
- }
- )
+ # the "dbm" backend needs
+ # string-encoded keys
+ key_mangler=md5_key_mangler
+).configure(
+ # using type 'file' to illustrate
+ # serialized persistence. Normally
+ # memcached or similar is a better choice
+ # for caching.
+ 'dogpile.cache.dbm',
+ expiration_time=3600,
+ arguments={
+ "filename": os.path.join(root, "cache.dbm")
+ }
+)
# optional; call invalidate() on the region
# once created so that all data is fresh when
installed = False
+
def bootstrap():
global installed
from . import fixture_data
-"""fixture_data.py
-
-Installs some sample data. Here we have a handful of postal codes for a few US/
+"""Installs some sample data. Here we have a handful of postal codes for a few US/
Canadian cities. Then, 100 Person records are installed, each with a
randomly selected postal code.
from .model import City, Country, PostalCode, Person, Address
import random
+
def install():
Base.metadata.create_all(Session().bind)
('Montreal', 'Canada', ('H2S 3K9', 'H2B 1V4', 'H7G 2T8')),
('Edmonton', 'Canada', ('T5J 1R9', 'T5J 1Z4', 'T5H 1P6')),
('New York', 'United States',
- ('10001', '10002', '10003', '10004', '10005', '10006')),
+ ('10001', '10002', '10003', '10004', '10005', '10006')),
('San Francisco', 'United States',
- ('94102', '94103', '94104', '94105', '94107', '94108'))
+ ('94102', '94103', '94104', '94105', '94107', '94108'))
]
countries = {}
for i in range(1, 51):
person = Person(
- "person %.2d" % i,
- Address(
- street="street %.2d" % i,
- postal_code=all_post_codes[
- random.randint(0, len(all_post_codes) - 1)]
- )
- )
+ "person %.2d" % i,
+ Address(
+ street="street %.2d" % i,
+ postal_code=all_post_codes[
+ random.randint(0, len(all_post_codes) - 1)]
+ )
+ )
Session.add(person)
Session.commit()
# start the demo fresh
- Session.remove()
\ No newline at end of file
+ Session.remove()
-"""helloworld.py
-
-Illustrate how to load some data, and cache the results.
+"""Illustrate how to load some data, and cache the results.
"""
# these results are independently cached.
print("loading people two through twelve")
people_two_through_twelve = Session.query(Person).\
- options(FromCache("default")).\
- filter(Person.name.between("person 02", "person 12")).\
- all()
+ options(FromCache("default")).\
+ filter(Person.name.between("person 02", "person 12")).\
+ all()
# the data is cached under string structure of the SQL statement, *plus*
# the bind parameters of the query. So this query, having
# previous one, issues new SQL...
print("loading people five through fifteen")
people_five_through_fifteen = Session.query(Person).\
- options(FromCache("default")).\
- filter(Person.name.between("person 05", "person 15")).\
- all()
+ options(FromCache("default")).\
+ filter(Person.name.between("person 05", "person 15")).\
+ all()
# ... but using the same params as are already cached, no SQL
print("loading people two through twelve...again!")
people_two_through_twelve = Session.query(Person).\
- options(FromCache("default")).\
- filter(Person.name.between("person 02", "person 12")).\
- all()
+ options(FromCache("default")).\
+ filter(Person.name.between("person 02", "person 12")).\
+ all()
# invalidate the cache for the three queries we've done. Recreate
print("invalidating everything")
Session.query(Person).options(FromCache("default")).invalidate()
Session.query(Person).\
- options(FromCache("default")).\
- filter(Person.name.between("person 02", "person 12")).invalidate()
+ options(FromCache("default")).\
+ filter(Person.name.between("person 02", "person 12")).invalidate()
Session.query(Person).\
- options(FromCache("default", "people_on_range")).\
- filter(Person.name.between("person 05", "person 15")).invalidate()
+ options(FromCache("default", "people_on_range")).\
+ filter(Person.name.between("person 05", "person 15")).invalidate()
-"""local_session_caching.py
-
-Grok everything so far ? This example
-creates a new dogpile.cache backend that will persist data in a dictionary
-which is local to the current session. remove() the session
-and the cache is gone.
+"""This example creates a new dogpile.cache backend that will persist data in a
+dictionary which is local to the current session. remove() the session and
+the cache is gone.
Create a new Dogpile cache backend that will store
cached data local to the current Session.
from dogpile.cache.api import CacheBackend, NO_VALUE
from dogpile.cache.region import register_backend
+
class ScopedSessionBackend(CacheBackend):
"""A dogpile backend which will cache objects locally on
the current session.
# query to load Person by name, with criterion
# of "person 10"
q = Session.query(Person).\
- options(FromCache("local_session")).\
- filter(Person.name == "person 10")
+ options(FromCache("local_session")).\
+ filter(Person.name == "person 10")
# load from DB
person10 = q.one()
-"""model.py
-
-The datamodel, which represents Person that has multiple
+"""The datamodel, which represents Person that has multiple
Address objects, each with PostalCode, City, Country.
Person --(1..n)--> Address
"""
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
-from .caching_query import FromCache, RelationshipCache
+from .caching_query import RelationshipCache
from .environment import Base, bootstrap
+
class Country(Base):
__tablename__ = 'country'
def __init__(self, name):
self.name = name
+
class City(Base):
__tablename__ = 'city'
self.name = name
self.country = country
+
class PostalCode(Base):
__tablename__ = 'postal_code'
self.code = code
self.city = city
+
class Address(Base):
__tablename__ = 'address'
return self.postal_code.country
def __str__(self):
- return "%s\t"\
- "%s, %s\t"\
- "%s" % (self.street, self.city.name,
+ return (
+ "%s\t%s, %s\t%s" % (
+ self.street, self.city.name,
self.postal_code.code, self.country.name)
+ )
+
class Person(Base):
__tablename__ = 'person'
# which can be applied to Query(), causing the "lazy load"
# of these attributes to be loaded from cache.
cache_address_bits = RelationshipCache(PostalCode.city, "default").\
- and_(
- RelationshipCache(City.country, "default")
- ).and_(
- RelationshipCache(Address.postal_code, "default")
- )
+ and_(
+ RelationshipCache(City.country, "default")
+).and_(
+ RelationshipCache(Address.postal_code, "default")
+)
-bootstrap()
\ No newline at end of file
+bootstrap()
-"""relationship_caching.py
-
-Illustrates how to add cache options on
+"""Illustrates how to add cache options on
relationship endpoints, so that lazyloads load from cache.
Load a set of Person and Address objects, specifying that
from sqlalchemy.orm import joinedload
import os
-for p in Session.query(Person).options(joinedload(Person.addresses), cache_address_bits):
+for p in Session.query(Person).options(
+ joinedload(Person.addresses), cache_address_bits):
print(p.format_full())
-print("\n\nIf this was the first run of relationship_caching.py, SQL was likely emitted to "\
- "load postal codes, cities, countries.\n"\
- "If run a second time, assuming the cache is still valid, "\
- "only a single SQL statement will run - all "\
- "related data is pulled from cache.\n"\
- "To clear the cache, delete the file %r. \n"\
- "This will cause a re-load of cities, postal codes and countries on "\
- "the next run.\n"\
- % os.path.join(root, 'cache.dbm'))
+print(
+ "\n\nIf this was the first run of relationship_caching.py, "
+ "SQL was likely emitted to "
+ "load postal codes, cities, countries.\n"
+ "If run a second time, assuming the cache is still valid, "
+ "only a single SQL statement will run - all "
+ "related data is pulled from cache.\n"
+ "To clear the cache, delete the file %r. \n"
+ "This will cause a re-load of cities, postal codes and countries on "
+ "the next run.\n"
+ % os.path.join(root, 'cache.dbm'))
-"""discriminator_on_related.py
-
-Illustrates a mixin which provides a generic association
+"""Illustrates a mixin which provides a generic association
using a single target table and a single association table,
referred to by all parent tables. The association table
contains a "discriminator" column which determines what type of
-"""generic_fk.py
-
-Illustrates a so-called "generic foreign key", in a similar fashion
+"""Illustrates a so-called "generic foreign key", in a similar fashion
to that of popular frameworks such as Django, ROR, etc. This
approach bypasses standard referential integrity
practices, in that the "foreign key" column is not actually
-"""table_per_association.py
-
-Illustrates a mixin which provides a generic association
+"""Illustrates a mixin which provides a generic association
via a individually generated association tables for each parent class.
The associated objects themselves are persisted in a single table
shared among all parents.
-"""table_per_related.py
-
-Illustrates a generic association which persists association
+"""Illustrates a generic association which persists association
objects within individual tables, each one generated to persist
those objects on behalf of a particular parent class.