]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Updated the association, association proxy examples
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 13 Mar 2011 17:59:00 +0000 (13:59 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 13 Mar 2011 17:59:00 +0000 (13:59 -0400)
to use declarative, added a new example
dict_of_sets_with_default.py, a "pushing the envelope"
example of association proxy.

CHANGES
examples/association/__init__.py
examples/association/basic_association.py
examples/association/dict_of_sets_with_default.py [new file with mode: 0644]
examples/association/proxied_association.py

diff --git a/CHANGES b/CHANGES
index 093c5f8fe549e8f8fbbd181bdb2408191ebc356c..1a7212f874a2c99ff1b39f7d5e52956235670358 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -54,6 +54,12 @@ CHANGES
     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
index 46b596991f096ba998e8dbd8e76cb8e4ab941851..12d2ea6975c29d71f79b26d7616ee5415e1f70b1 100644 (file)
@@ -1,25 +1,20 @@
 """
 
 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
index 19b304f9a5e905483524134ec39f3ec8ef63e731..cd86aa504fafdb3651b7fed625d4286fac616068 100644 (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]
diff --git a/examples/association/dict_of_sets_with_default.py b/examples/association/dict_of_sets_with_default.py
new file mode 100644 (file)
index 0000000..0720fda
--- /dev/null
@@ -0,0 +1,87 @@
+"""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"]
+
+
index fa41f21c32c1639627075110f8773f8393a0919b..96e9e6b073f83edec4549e89cec196065cdd55f7 100644 (file)
-"""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]