~ubuntu-branches/debian/jessie/sqlalchemy/jessie

« back to all changes in this revision

Viewing changes to examples/generic_associations/generic_fk.py

  • Committer: Package Import Robot
  • Author(s): Piotr Ożarowski
  • Date: 2013-10-28 22:29:40 UTC
  • mfrom: (1.4.24)
  • Revision ID: package-import@ubuntu.com-20131028222940-wvyqffl4g617caun
Tags: 0.8.3-1
New upstream release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""generic_fk.py
 
2
 
 
3
This example will emulate key aspects of the system used by popular
 
4
frameworks such as Django, ROR, etc.
 
5
 
 
6
It approaches the issue by bypassing standard referential integrity
 
7
practices, and producing a so-called "generic foreign key", which means
 
8
a database column that is not constrained to refer to any particular table.
 
9
In-application logic is used to determine which table is referenced.
 
10
 
 
11
This approach is not in line with SQLAlchemy's usual style, as foregoing
 
12
foreign key integrity means that the tables can easily contain invalid
 
13
references and also have no ability to use in-database cascade functionality.
 
14
 
 
15
However, due to the popularity of these systems, as well as that it uses
 
16
the fewest number of tables (which doesn't really offer any "advantage",
 
17
though seems to be comforting to many) this recipe remains in
 
18
high demand, so in the interests of having an easy StackOverflow answer
 
19
queued up, here it is.   The author recommends "table_per_related"
 
20
or "table_per_association" instead of this approach.
 
21
 
 
22
.. versionadded:: 0.8.3
 
23
 
 
24
"""
 
25
from sqlalchemy.ext.declarative import as_declarative, declared_attr
 
26
from sqlalchemy import create_engine, Integer, Column, \
 
27
                    String, and_
 
28
from sqlalchemy.orm import Session, relationship, foreign, remote, backref
 
29
from sqlalchemy import event
 
30
 
 
31
 
 
32
@as_declarative()
 
33
class Base(object):
 
34
    """Base class which provides automated table name
 
35
    and surrogate primary key column.
 
36
 
 
37
    """
 
38
    @declared_attr
 
39
    def __tablename__(cls):
 
40
        return cls.__name__.lower()
 
41
    id = Column(Integer, primary_key=True)
 
42
 
 
43
class Address(Base):
 
44
    """The Address class.
 
45
 
 
46
    This represents all address records in a
 
47
    single table.
 
48
 
 
49
    """
 
50
    street = Column(String)
 
51
    city = Column(String)
 
52
    zip = Column(String)
 
53
 
 
54
    discriminator = Column(String)
 
55
    """Refers to the type of parent."""
 
56
 
 
57
    parent_id = Column(Integer)
 
58
    """Refers to the primary key of the parent.
 
59
 
 
60
    This could refer to any table.
 
61
    """
 
62
 
 
63
    @property
 
64
    def parent(self):
 
65
        """Provides in-Python access to the "parent" by choosing
 
66
        the appropriate relationship.
 
67
 
 
68
        """
 
69
        return getattr(self, "parent_%s" % self.discriminator)
 
70
 
 
71
    def __repr__(self):
 
72
        return "%s(street=%r, city=%r, zip=%r)" % \
 
73
            (self.__class__.__name__, self.street,
 
74
            self.city, self.zip)
 
75
 
 
76
class HasAddresses(object):
 
77
    """HasAddresses mixin, creates a relationship to
 
78
    the address_association table for each parent.
 
79
 
 
80
    """
 
81
 
 
82
@event.listens_for(HasAddresses, "mapper_configured", propagate=True)
 
83
def setup_listener(mapper, class_):
 
84
    name = class_.__name__
 
85
    discriminator = name.lower()
 
86
    class_.addresses = relationship(Address,
 
87
                        primaryjoin=and_(
 
88
                                        class_.id == foreign(remote(Address.parent_id)),
 
89
                                        Address.discriminator == discriminator
 
90
                                    ),
 
91
                        backref=backref(
 
92
                                "parent_%s" % discriminator,
 
93
                                primaryjoin=remote(class_.id) == foreign(Address.parent_id)
 
94
                                )
 
95
                        )
 
96
    @event.listens_for(class_.addresses, "append")
 
97
    def append_address(target, value, initiator):
 
98
        value.discriminator = discriminator
 
99
 
 
100
class Customer(HasAddresses, Base):
 
101
    name = Column(String)
 
102
 
 
103
class Supplier(HasAddresses, Base):
 
104
    company_name = Column(String)
 
105
 
 
106
engine = create_engine('sqlite://', echo=True)
 
107
Base.metadata.create_all(engine)
 
108
 
 
109
session = Session(engine)
 
110
 
 
111
session.add_all([
 
112
    Customer(
 
113
        name='customer 1',
 
114
        addresses=[
 
115
            Address(
 
116
                    street='123 anywhere street',
 
117
                    city="New York",
 
118
                    zip="10110"),
 
119
            Address(
 
120
                    street='40 main street',
 
121
                    city="San Francisco",
 
122
                    zip="95732")
 
123
        ]
 
124
    ),
 
125
    Supplier(
 
126
        company_name="Ace Hammers",
 
127
        addresses=[
 
128
            Address(
 
129
                    street='2569 west elm',
 
130
                    city="Detroit",
 
131
                    zip="56785")
 
132
        ]
 
133
    ),
 
134
])
 
135
 
 
136
session.commit()
 
137
 
 
138
for customer in session.query(Customer):
 
139
    for address in customer.addresses:
 
140
        print(address)
 
141
        print(address.parent)
 
 
b'\\ No newline at end of file'