From: Mike Bayer Date: Sun, 13 Mar 2011 17:59:00 +0000 (-0400) Subject: - Updated the association, association proxy examples X-Git-Tag: rel_0_7b3~38 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8af45f93cbf9d7d074206616069ca81b256d2522;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - 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. --- diff --git a/CHANGES b/CHANGES index 093c5f8fe5..1a7212f874 100644 --- 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 diff --git a/examples/association/__init__.py b/examples/association/__init__.py index 46b596991f..12d2ea6975 100644 --- a/examples/association/__init__.py +++ b/examples/association/__init__.py @@ -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 diff --git a/examples/association/basic_association.py b/examples/association/basic_association.py index 19b304f9a5..cd86aa504f 100644 --- a/examples/association/basic_association.py +++ b/examples/association/basic_association.py @@ -1,106 +1,94 @@ """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 index 0000000000..0720fdab99 --- /dev/null +++ b/examples/association/dict_of_sets_with_default.py @@ -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"] + + diff --git a/examples/association/proxied_association.py b/examples/association/proxied_association.py index fa41f21c32..96e9e6b073 100644 --- a/examples/association/proxied_association.py +++ b/examples/association/proxied_association.py @@ -1,103 +1,101 @@ -"""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]