diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2011-04-27 22:33:37 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2011-04-27 22:33:37 -0400 |
| commit | d03226426c24eef5c9e19822ff07a5f72dd5379f (patch) | |
| tree | 4d4520085485e397f11c23248733bab1e30dfd7e /examples/generic_associations | |
| parent | 7df21e5a1f252ff768f684132db95bd1b5e78efa (diff) | |
| download | sqlalchemy-d03226426c24eef5c9e19822ff07a5f72dd5379f.tar.gz | |
- removed the ancient "polymorphic association"
examples and replaced with an updated set of
examples that use declarative mixins,
"generic_associations". Each presents an alternative
table layout.
Diffstat (limited to 'examples/generic_associations')
| -rw-r--r-- | examples/generic_associations/__init__.py | 17 | ||||
| -rw-r--r-- | examples/generic_associations/discriminator_on_association.py | 148 | ||||
| -rw-r--r-- | examples/generic_associations/table_per_association.py | 106 | ||||
| -rw-r--r-- | examples/generic_associations/table_per_related.py | 107 |
4 files changed, 378 insertions, 0 deletions
diff --git a/examples/generic_associations/__init__.py b/examples/generic_associations/__init__.py new file mode 100644 index 000000000..7e94694fe --- /dev/null +++ b/examples/generic_associations/__init__.py @@ -0,0 +1,17 @@ +""" +Illustrates various methods of associating multiple types of +parents with a particular child object. + +The examples all use the declarative extension along with +declarative mixins. Each one presents the identical use +case at the end - two clases, ``Customer`` and ``Supplier``, both +subclassing the ``HasAddresses`` mixin, which ensures that the +parent class is provided with an ``addresses`` collection +which contains ``Address`` objects. + +The ``discriminator_on_association.py`` script in particular is a modernized +version of the "polymorphic associations" example present in older versions of +SQLAlchemy. + + +"""
\ No newline at end of file diff --git a/examples/generic_associations/discriminator_on_association.py b/examples/generic_associations/discriminator_on_association.py new file mode 100644 index 000000000..a73b4df1d --- /dev/null +++ b/examples/generic_associations/discriminator_on_association.py @@ -0,0 +1,148 @@ +"""discriminator_on_related.py + +The HasAddresses mixin will provide a relationship +to the fixed Address table based on a fixed association table. + +The association table will also contain a "discriminator" +which determines what type of parent object associates to the +Address row. + +This is a "polymorphic association". Even though a "discriminator" +that refers to a particular table is present, the extra association +table is used so that traditional foreign key constraints may be used. + +This configuration has the advantage that a fixed set of tables +are used, with no extra-table-per-parent needed. The individual +Address record can also locate its parent with no need to scan +amongst many tables. + +""" +from sqlalchemy.ext.declarative import declarative_base, declared_attr +from sqlalchemy import create_engine, Integer, Column, \ + String, ForeignKey, Table +from sqlalchemy.orm import Session, relationship, backref +from sqlalchemy.ext.associationproxy import association_proxy + +class Base(object): + """Base class which provides automated table name + and surrogate primary key column. + + """ + @declared_attr + def __tablename__(cls): + return cls.__name__.lower() + id = Column(Integer, primary_key=True) +Base = declarative_base(cls=Base) + +class AddressAssociation(Base): + """Associates a collection of Address objects + with a particular parent. + + """ + __tablename__ = "address_association" + + @classmethod + def creator(cls, discriminator): + """Provide a 'creator' function to use with + the association proxy.""" + + return lambda addresses:AddressAssociation( + addresses=addresses, + discriminator=discriminator) + + discriminator = Column(String) + """Refers to the type of parent.""" + + @property + def parent(self): + """Return the parent object.""" + return getattr(self, "%s_parent" % self.discriminator) + +class Address(Base): + """The Address class. + + This represents all address records in a + single table. + + """ + association_id = Column(Integer, + ForeignKey("address_association.id") + ) + street = Column(String) + city = Column(String) + zip = Column(String) + association = relationship( + "AddressAssociation", + backref="addresses") + + parent = association_proxy("association", "parent") + + def __repr__(self): + return "%s(street=%r, city=%r, zip=%r)" % \ + (self.__class__.__name__, self.street, + self.city, self.zip) + +class HasAddresses(object): + """HasAddresses mixin, creates a relationship to + the address_association table for each parent. + + """ + @declared_attr + def address_association_id(cls): + return Column(Integer, + ForeignKey("address_association.id")) + + @declared_attr + def address_association(cls): + discriminator = cls.__name__.lower() + cls.addresses= association_proxy( + "address_association", "addresses", + creator=AddressAssociation.creator(discriminator) + ) + return relationship("AddressAssociation", + backref=backref("%s_parent" % discriminator, + uselist=False)) + + +class Customer(HasAddresses, Base): + name = Column(String) + +class Supplier(HasAddresses, Base): + company_name = Column(String) + +engine = create_engine('sqlite://', echo=True) +Base.metadata.create_all(engine) + +session = Session(engine) + +session.add_all([ + Customer( + name='customer 1', + addresses=[ + Address( + street='123 anywhere street', + city="New York", + zip="10110"), + Address( + street='40 main street', + city="San Francisco", + zip="95732") + ] + ), + Supplier( + company_name="Ace Hammers", + addresses=[ + Address( + street='2569 west elm', + city="Detroit", + zip="56785") + ] + ), +]) + +session.commit() + +for customer in session.query(Customer): + for address in customer.addresses: + print address + print address.parent
\ No newline at end of file diff --git a/examples/generic_associations/table_per_association.py b/examples/generic_associations/table_per_association.py new file mode 100644 index 000000000..86ee212dc --- /dev/null +++ b/examples/generic_associations/table_per_association.py @@ -0,0 +1,106 @@ +"""table_per_association.py + +The HasAddresses mixin will provide a new "address_association" table for +each parent class. The "address" table will be shared +for all parents. + +This configuration has the advantage that all Address +rows are in one table, so that the definition of "Address" +can be maintained in one place. The association table +contains the foreign key to Address so that Address +has no dependency on the system. + + +""" +from sqlalchemy.ext.declarative import declarative_base, declared_attr +from sqlalchemy import create_engine, Integer, Column, \ + String, ForeignKey, Table +from sqlalchemy.orm import Session, relationship + +class Base(object): + """Base class which provides automated table name + and surrogate primary key column. + + """ + @declared_attr + def __tablename__(cls): + return cls.__name__.lower() + id = Column(Integer, primary_key=True) +Base = declarative_base(cls=Base) + +class Address(Base): + """The Address class. + + This represents all address records in a + single table. + + """ + street = Column(String) + city = Column(String) + zip = Column(String) + + def __repr__(self): + return "%s(street=%r, city=%r, zip=%r)" % \ + (self.__class__.__name__, self.street, + self.city, self.zip) + +class HasAddresses(object): + """HasAddresses mixin, creates a new address_association + table for each parent. + + """ + @declared_attr + def addresses(cls): + address_association = Table( + "%s_addresses" % cls.__tablename__, + cls.metadata, + Column("address_id", ForeignKey("address.id"), + primary_key=True), + Column("%s_id" % cls.__tablename__, + ForeignKey("%s.id" % cls.__tablename__), + primary_key=True), + ) + return relationship(Address, secondary=address_association) + +class Customer(HasAddresses, Base): + name = Column(String) + +class Supplier(HasAddresses, Base): + company_name = Column(String) + +engine = create_engine('sqlite://', echo=True) +Base.metadata.create_all(engine) + +session = Session(engine) + +session.add_all([ + Customer( + name='customer 1', + addresses=[ + Address( + street='123 anywhere street', + city="New York", + zip="10110"), + Address( + street='40 main street', + city="San Francisco", + zip="95732") + ] + ), + Supplier( + company_name="Ace Hammers", + addresses=[ + Address( + street='2569 west elm', + city="Detroit", + zip="56785") + ] + ), +]) + +session.commit() + +for customer in session.query(Customer): + for address in customer.addresses: + print address + # no parent here
\ No newline at end of file diff --git a/examples/generic_associations/table_per_related.py b/examples/generic_associations/table_per_related.py new file mode 100644 index 000000000..3130960b0 --- /dev/null +++ b/examples/generic_associations/table_per_related.py @@ -0,0 +1,107 @@ +"""table_per_related.py + +The HasAddresses mixin will provide a new "address" table for +each parent class, as well as a distinct "Address" subclass. + +This configuration has the advantage that each type of parent +maintains its "Address" rows separately, so that collection +size for one type of parent will have no impact on other types +of parent. Navigation between parent and "Address" is simple, +direct, and bidirectional. + +""" +from sqlalchemy.ext.declarative import declarative_base, declared_attr +from sqlalchemy import create_engine, Integer, Column, String, ForeignKey +from sqlalchemy.orm import Session, relationship + +class Base(object): + """Base class which provides automated table name + and surrogate primary key column. + + """ + @declared_attr + def __tablename__(cls): + return cls.__name__.lower() + id = Column(Integer, primary_key=True) +Base = declarative_base(cls=Base) + +class Address(object): + """Define columns that will be present in each + 'Address' table. + + This is a declarative mixin, so additional mapped + attributes beyond simple columns specified here + should be set up using @declared_attr. + + """ + street = Column(String) + city = Column(String) + zip = Column(String) + + def __repr__(self): + return "%s(street=%r, city=%r, zip=%r)" % \ + (self.__class__.__name__, self.street, + self.city, self.zip) + +class HasAddresses(object): + """HasAddresses mixin, creates a new Address class + for each parent. + + """ + @declared_attr + def addresses(cls): + cls.Address = type( + "%sAddress" % cls.__name__, + (Address, Base,), + dict( + __tablename__ = "%s_address" % + cls.__tablename__, + parent_id = Column(Integer, + ForeignKey("%s.id" % cls.__tablename__)), + parent = relationship(cls) + ) + ) + return relationship(cls.Address) + +class Customer(HasAddresses, Base): + name = Column(String) + +class Supplier(HasAddresses, Base): + company_name = Column(String) + +engine = create_engine('sqlite://', echo=True) +Base.metadata.create_all(engine) + +session = Session(engine) + +session.add_all([ + Customer( + name='customer 1', + addresses=[ + Customer.Address( + street='123 anywhere street', + city="New York", + zip="10110"), + Customer.Address( + street='40 main street', + city="San Francisco", + zip="95732") + ] + ), + Supplier( + company_name="Ace Hammers", + addresses=[ + Supplier.Address( + street='2569 west elm', + city="Detroit", + zip="56785") + ] + ), +]) + +session.commit() + +for customer in session.query(Customer): + for address in customer.addresses: + print address + print address.parent
\ No newline at end of file |
