constructor. The method _constructor should
be implemented in this case.
+- examples
+ - Updated the association, association proxy examples
+ to use declarative, added a new example
+ dict_of_sets_with_default.py, a "pushing the envelope"
+ example of association proxy.
+
0.7.0b2
========
- orm
"""
Examples illustrating the usage of the "association object" pattern,
-where an intermediary object associates two endpoint objects together.
-
-The first example illustrates a basic association from a User object
-to a collection or Order objects, each which references a collection of Item objects.
-
-The second example builds upon the first to add the Association Proxy extension.
-
-E.g.::
-
- # create an order
- order = Order('john smith')
-
- # append an OrderItem association via the "itemassociations"
- # collection with a custom price.
- order.itemassociations.append(OrderItem(item('MySQL Crowbar'), 10.99))
-
- # append two more Items via the transparent "items" proxy, which
- # will create OrderItems automatically using the default price.
- order.items.append(item('SA Mug'))
- order.items.append(item('SA Hat'))
+where an intermediary class mediates the relationship between two
+classes that are associated in a many-to-many pattern.
+
+This directory includes the following examples:
+
+* basic_association.py - 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"
+* proxied_association.py - same example as basic_association, adding in
+ usage of :mod:`sqlalchemy.ext.associationproxy` to make explicit references
+ to "OrderItem" optional.
+* dict_of_sets_with_default.py - 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.
"""
\ No newline at end of file
"""A basic example of using the association object pattern.
-The association object pattern is a richer form of a many-to-many
-relationship.
-
-The model will be an ecommerce example. We will have an Order, which
-represents a set of Items purchased by a user. Each Item has a price.
-However, the Order must store its own price for each Item, representing
-the price paid by the user for that particular order, which is independent
-of the price on each Item (since those can change).
+The association object pattern is a form of many-to-many which
+associates additional data with each association between parent/child.
+
+The example illustrates an "order", referencing a collection
+of "items", with a particular price paid associated with each "item".
+
"""
from datetime import datetime
from sqlalchemy import (create_engine, MetaData, Table, Column, Integer,
- String, DateTime, Numeric, ForeignKey, and_)
+ String, DateTime, Float, ForeignKey, and_)
from sqlalchemy.orm import mapper, relationship, Session
+from sqlalchemy.ext.declarative import declarative_base
-# Uncomment these to watch database activity.
-#import logging
-#logging.basicConfig(format='%(message)s')
-#logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
-
-engine = create_engine('sqlite:///')
-metadata = MetaData(engine)
-
-orders = Table('orders', metadata,
- Column('order_id', Integer, primary_key=True),
- Column('customer_name', String(30), nullable=False),
- Column('order_date', DateTime, nullable=False, default=datetime.now()),
- )
+Base = declarative_base()
-items = Table('items', metadata,
- Column('item_id', Integer, primary_key=True),
- Column('description', String(30), nullable=False),
- Column('price', Numeric(8, 2), nullable=False)
- )
+class Order(Base):
+ __tablename__ = 'order'
-orderitems = Table('orderitems', metadata,
- Column('order_id', Integer, ForeignKey('orders.order_id'),
- primary_key=True),
- Column('item_id', Integer, ForeignKey('items.item_id'),
- primary_key=True),
- Column('price', Numeric(8, 2), nullable=False)
- )
-metadata.create_all()
+ order_id = Column(Integer, primary_key=True)
+ customer_name = Column(String(30), nullable=False)
+ order_date = Column(DateTime, nullable=False, default=datetime.now())
+ order_items = relationship("OrderItem", cascade="all, delete-orphan",
+ backref='order')
-class Order(object):
def __init__(self, customer_name):
self.customer_name = customer_name
-class Item(object):
+class Item(Base):
+ __tablename__ = 'item'
+ item_id = Column(Integer, primary_key=True)
+ description = Column(String(30), nullable=False)
+ price = Column(Float, nullable=False)
+
def __init__(self, description, price):
self.description = description
self.price = price
+
def __repr__(self):
- return 'Item(%s, %s)' % (repr(self.description), repr(self.price))
+ return 'Item(%r, %r)' % (
+ self.description, self.price
+ )
+
+class OrderItem(Base):
+ __tablename__ = 'orderitem'
+ order_id = Column(Integer, ForeignKey('order.order_id'), primary_key=True)
+ item_id = Column(Integer, ForeignKey('item.item_id'), primary_key=True)
+ price = Column(Float, nullable=False)
-class OrderItem(object):
def __init__(self, item, price=None):
self.item = item
self.price = price or item.price
+ item = relationship(Item, lazy='joined')
-mapper(Order, orders, properties={
- 'order_items': relationship(OrderItem, cascade="all, delete-orphan",
- backref='order')
-})
-mapper(Item, items)
-mapper(OrderItem, orderitems, properties={
- 'item': relationship(Item, lazy='joined')
-})
-
-session = Session()
-
-# create our catalog
-session.add(Item('SA T-Shirt', 10.99))
-session.add(Item('SA Mug', 6.50))
-session.add(Item('SA Hat', 8.99))
-session.add(Item('MySQL Crowbar', 16.99))
-session.commit()
-
-# function to return items from the DB
-def item(name):
- return session.query(Item).filter_by(description=name).one()
-
-# create an order
-order = Order('john smith')
-
-# add three OrderItem associations to the Order and save
-order.order_items.append(OrderItem(item('SA Mug')))
-order.order_items.append(OrderItem(item('MySQL Crowbar'), 10.99))
-order.order_items.append(OrderItem(item('SA Hat')))
-session.add(order)
-session.commit()
-
-# query the order, print items
-order = session.query(Order).filter_by(customer_name='john smith').one()
-print [(order_item.item.description, order_item.price)
- for order_item in order.order_items]
-
-# print customers who bought 'MySQL Crowbar' on sale
-q = session.query(Order).join('order_items', 'item')
-q = q.filter(and_(Item.description == 'MySQL Crowbar',
- Item.price > OrderItem.price))
-
-print [order.customer_name for order in q]
+if __name__ == '__main__':
+ engine = create_engine('sqlite://')
+ Base.metadata.create_all(engine)
+
+ session = Session(engine)
+
+ # create catalog
+ tshirt, mug, hat, crowbar = (
+ Item('SA T-Shirt', 10.99),
+ Item('SA Mug', 6.50),
+ Item('SA Hat', 8.99),
+ Item('MySQL Crowbar', 16.99)
+ )
+ session.add_all([tshirt, mug, hat, crowbar])
+ session.commit()
+
+ # create an order
+ order = Order('john smith')
+
+ # add three OrderItem associations to the Order and save
+ order.order_items.append(OrderItem(mug))
+ order.order_items.append(OrderItem(crowbar, 10.99))
+ order.order_items.append(OrderItem(hat))
+ session.add(order)
+ session.commit()
+
+ # query the order, print items
+ order = session.query(Order).filter_by(customer_name='john smith').one()
+ print [(order_item.item.description, order_item.price)
+ for order_item in order.order_items]
+
+ # print customers who bought 'MySQL Crowbar' on sale
+ q = session.query(Order).join('order_items', 'item')
+ q = q.filter(and_(Item.description == 'MySQL Crowbar',
+ Item.price > OrderItem.price))
+
+ print [order.customer_name for order in q]
--- /dev/null
+"""Illustrate a 'dict of sets of integers' model.
+
+This is a three table model which represents a parent table referencing a
+dictionary of string keys and sets as values, where each set stores a
+collection of integers. The association proxy extension is used to hide the
+details of this persistence. The dictionary also generates new collections
+upon access of a non-existent key, in the same manner as Python's
+"collections.defaultdict" object.
+
+"""
+
+from sqlalchemy import String, Integer, Column, create_engine, ForeignKey
+from sqlalchemy.orm import relationship, Session
+from sqlalchemy.orm.collections import MappedCollection
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.ext.associationproxy import association_proxy
+import operator
+
+class Base(object):
+ id = Column(Integer, primary_key=True)
+
+Base = declarative_base(cls=Base)
+
+class GenDefaultCollection(MappedCollection):
+ def __missing__(self, key):
+ self[key] = b = B(key)
+ return b
+
+class A(Base):
+ __tablename__ = "a"
+ associations = relationship("B",
+ collection_class=lambda: GenDefaultCollection(operator.attrgetter("key"))
+ )
+
+ collections = association_proxy("associations", "values")
+ """Bridge the association from 'associations' over to the 'values'
+ association proxy of B.
+ """
+
+class B(Base):
+ __tablename__ = "b"
+ a_id = Column(Integer, ForeignKey("a.id"), nullable=False)
+ elements = relationship("C", collection_class=set)
+ key = Column(String)
+
+ values = association_proxy("elements", "value")
+ """Bridge the association from 'elements' over to the
+ 'value' element of C."""
+
+ def __init__(self, key, values=None):
+ self.key = key
+ if values:
+ self.values = values
+
+class C(Base):
+ __tablename__ = "c"
+ b_id = Column(Integer, ForeignKey("b.id"), nullable=False)
+ value = Column(Integer)
+ def __init__(self, value):
+ self.value = value
+
+if __name__ == '__main__':
+ engine = create_engine('sqlite://', echo=True)
+ Base.metadata.create_all(engine)
+ session = Session(engine)
+
+ # only "A" is referenced explicitly. Using "collections",
+ # we deal with a dict of key/sets of integers directly.
+
+ session.add_all([
+ A(collections={
+ "1":set([1, 2, 3]),
+ })
+ ])
+ session.commit()
+
+ a1 = session.query(A).first()
+ print a1.collections["1"]
+ a1.collections["1"].add(4)
+ session.commit()
+
+ a1.collections["2"].update([7, 8, 9])
+ session.commit()
+
+ print a1.collections["2"]
+
+
-"""this is a modified version of the basic association example, which illustrates
-the usage of the associationproxy extension."""
+"""An extension to the basic_association.py example, which illustrates
+the usage of sqlalchemy.ext.associationproxy.
+
+"""
from datetime import datetime
+
from sqlalchemy import (create_engine, MetaData, Table, Column, Integer,
String, DateTime, Float, ForeignKey, and_)
from sqlalchemy.orm import mapper, relationship, Session
-from sqlalchemy.ext.associationproxy import AssociationProxy
-
-engine = create_engine('sqlite://')
-#engine = create_engine('sqlite://', echo=True)
-metadata = MetaData(engine)
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.ext.associationproxy import association_proxy
-orders = Table('orders', metadata,
- Column('order_id', Integer, primary_key=True),
- Column('customer_name', String(30), nullable=False),
- Column('order_date', DateTime, nullable=False, default=datetime.now))
+Base = declarative_base()
-items = Table('items', metadata,
- Column('item_id', Integer, primary_key=True),
- Column('description', String(30), nullable=False),
- Column('price', Float, nullable=False))
+class Order(Base):
+ __tablename__ = 'order'
-orderitems = Table('orderitems', metadata,
- Column('order_id', Integer, ForeignKey('orders.order_id'),
- primary_key=True),
- Column('item_id', Integer, ForeignKey('items.item_id'),
- primary_key=True),
- Column('price', Float, nullable=False))
+ order_id = Column(Integer, primary_key=True)
+ customer_name = Column(String(30), nullable=False)
+ order_date = Column(DateTime, nullable=False, default=datetime.now())
+ order_items = relationship("OrderItem", cascade="all, delete-orphan",
+ backref='order')
+ items = association_proxy("order_items", "item")
-metadata.create_all()
-
-class OrderItem(object):
- def __init__(self, item, price=None):
- self.item = item
- self.price = price is None and item.price or price
-
-class Order(object):
def __init__(self, customer_name):
self.customer_name = customer_name
- items = AssociationProxy('itemassociations', 'item',
- creator=OrderItem)
-class Item(object):
+class Item(Base):
+ __tablename__ = 'item'
+ item_id = Column(Integer, primary_key=True)
+ description = Column(String(30), nullable=False)
+ price = Column(Float, nullable=False)
+
def __init__(self, description, price):
self.description = description
self.price = price
+ def __repr__(self):
+ return 'Item(%r, %r)' % (
+ self.description, self.price
+ )
-mapper(Order, orders, properties={
- 'itemassociations':relationship(OrderItem, cascade="all, delete-orphan", lazy='joined')
-})
-mapper(Item, items)
-mapper(OrderItem, orderitems, properties={
- 'item':relationship(Item, lazy='joined')
-})
-
-session = Session()
-
-# create our catalog
-session.add_all([Item('SA T-Shirt', 10.99),
- Item('SA Mug', 6.50),
- Item('SA Hat', 8.99),
- Item('MySQL Crowbar', 16.99)])
-session.commit()
-
-# function to return items
-def item(name):
- return session.query(Item).filter_by(description=name).one()
+class OrderItem(Base):
+ __tablename__ = 'orderitem'
+ order_id = Column(Integer, ForeignKey('order.order_id'), primary_key=True)
+ item_id = Column(Integer, ForeignKey('item.item_id'), primary_key=True)
+ price = Column(Float, nullable=False)
-# create an order
-order = Order('john smith')
-
-# append an OrderItem association via the "itemassociations"
-# collection with a custom price.
-order.itemassociations.append(OrderItem(item('MySQL Crowbar'), 10.99))
-
-# append two more Items via the transparent "items" proxy, which
-# will create OrderItems automatically using the default price.
-order.items.append(item('SA Mug'))
-order.items.append(item('SA Hat'))
-
-session.add(order)
-session.commit()
-
-# query the order, print items
-order = session.query(Order).filter_by(customer_name='john smith').one()
-
-print "Order #%s:\n%s\n%s\n%s items.\n" % (
- order.order_id, order.customer_name, order.order_date, len(order.items))
-
-# print items based on the OrderItem collection directly
-print [(assoc.item.description, assoc.price, assoc.item.price)
- for assoc in order.itemassociations]
-
-# print items based on the "proxied" items collection
-print [(item.description, item.price)
- for item in order.items]
-
-# print customers who bought 'MySQL Crowbar' on sale
-orders = session.query(Order).join('itemassociations', 'item').filter(
- and_(Item.description=='MySQL Crowbar', Item.price > OrderItem.price))
-print [order.customer_name for order in orders]
+ def __init__(self, item, price=None):
+ self.item = item
+ self.price = price or item.price
+ item = relationship(Item, lazy='joined')
+
+if __name__ == '__main__':
+ engine = create_engine('sqlite://')
+ Base.metadata.create_all(engine)
+
+ session = Session(engine)
+
+ # create catalog
+ tshirt, mug, hat, crowbar = (
+ Item('SA T-Shirt', 10.99),
+ Item('SA Mug', 6.50),
+ Item('SA Hat', 8.99),
+ Item('MySQL Crowbar', 16.99)
+ )
+ session.add_all([tshirt, mug, hat, crowbar])
+ session.commit()
+
+ # create an order
+ order = Order('john smith')
+
+ # add items via the association proxy.
+ # the OrderItem is created automatically.
+ order.items.append(mug)
+ order.items.append(hat)
+
+ # add an OrderItem explicitly.
+ order.order_items.append(OrderItem(crowbar, 10.99))
+
+ session.add(order)
+ session.commit()
+
+ # query the order, print items
+ order = session.query(Order).filter_by(customer_name='john smith').one()
+
+ # print items based on the OrderItem collection directly
+ print [(assoc.item.description, assoc.price, assoc.item.price)
+ for assoc in order.order_items]
+
+ # print items based on the "proxied" items collection
+ print [(item.description, item.price)
+ for item in order.items]
+
+ # print customers who bought 'MySQL Crowbar' on sale
+ orders = session.query(Order).\
+ join('order_items', 'item').\
+ filter(Item.description=='MySQL Crowbar').\
+ filter(Item.price > OrderItem.price)
+ print [order.customer_name for order in orders]