summaryrefslogtreecommitdiff
path: root/examples/generic_associations
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2011-04-27 22:33:37 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2011-04-27 22:33:37 -0400
commitd03226426c24eef5c9e19822ff07a5f72dd5379f (patch)
tree4d4520085485e397f11c23248733bab1e30dfd7e /examples/generic_associations
parent7df21e5a1f252ff768f684132db95bd1b5e78efa (diff)
downloadsqlalchemy-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__.py17
-rw-r--r--examples/generic_associations/discriminator_on_association.py148
-rw-r--r--examples/generic_associations/table_per_association.py106
-rw-r--r--examples/generic_associations/table_per_related.py107
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