Files

146 lines
5.8 KiB
Python

from sqlalchemy import *
import sys
# this example illustrates how to create a relationship to a list of objects,
# where each object in the list has a different type. The typed objects will
# extend from a common base class, although this same approach can be used
# with
db = create_engine('sqlite://', echo=True, echo_uow=False)
#db = create_engine('postgres://user=scott&password=tiger&host=127.0.0.1&database=test', echo=True, echo_uow=False)
# a table to store companies
companies = Table('companies', db,
Column('company_id', Integer, primary_key=True),
Column('name', String(50))).create()
# we will define an inheritance relationship between the table "people" and "engineers",
# and a second inheritance relationship between the table "people" and "managers"
people = Table('people', db,
Column('person_id', Integer, primary_key=True),
Column('company_id', Integer, ForeignKey('companies.company_id')),
Column('name', String(50))).create()
engineers = Table('engineers', db,
Column('person_id', Integer, ForeignKey('people.person_id'), primary_key=True),
Column('special_description', String(50))).create()
managers = Table('managers', db,
Column('person_id', Integer, ForeignKey('people.person_id'), primary_key=True),
Column('description', String(50))).create()
# create our classes. The Engineer and Manager classes extend from Person.
class Person(object):
def __repr__(self):
return "Ordinary person %s" % self.name
class Engineer(Person):
def __repr__(self):
return "Engineer %s, description %s" % (self.name, self.special_description)
class Manager(Person):
def __repr__(self):
return "Manager %s, description %s" % (self.name, self.description)
class Company(object):
def __repr__(self):
return "Company %s" % self.name
# next we assign Person mappers. Since these are the first mappers we are
# creating for these classes, they automatically become the "primary mappers", which
# define the dependency relationships between the classes, so we do a straight
# inheritance setup, i.e. no modifications to how objects are loaded or anything like that.
assign_mapper(Person, people)
assign_mapper(Engineer, engineers, inherits=Person.mapper)
assign_mapper(Manager, managers, inherits=Person.mapper)
# next, we define a query that is going to load Managers and Engineers in one shot.
# we will use a UNION ALL with an extra hardcoded column to indicate the type of object.
# this can also be done via several LEFT OUTER JOINS but a UNION is more appropriate
# since they are distinct result sets.
# The select() statement is also given an alias 'pjoin', since the mapper requires
# that all Selectables have a name.
#
# TECHNIQUE - when you want to load a certain set of objects from a in one query, all the
# columns in the Selectable must have unique names. Dont worry about mappers at this point,
# just worry about making a query where if you were to view the results, you could tell everything
# you need to know from each row how to construct an object instance from it. this is the
# essence of "resultset-based-mapping", which is the core ideology of SQLAlchemy.
#
person_join = select(
[people, managers.c.description,column("'manager'").label('type')],
people.c.person_id==managers.c.person_id).union_all(
select(
[people, engineers.c.special_description.label('description'), column("'engineer'").label('type')],
people.c.person_id==engineers.c.person_id)).alias('pjoin')
# lets print out what this Selectable looks like. The mapper is going to take the selectable and
# Select off of it, with the flag "use_labels" which indicates to prefix column names with the table
# name. So here is what our mapper will see:
print "Person selectable:", str(person_join.select(use_labels=True)), "\n"
# MapperExtension object.
class PersonLoader(MapperExtension):
def create_instance(self, mapper, row, imap, class_):
if row['pjoin_type'] =='engineer':
e = Engineer()
e.special_description = row['pjoin_description']
return e
elif row['pjoin_type'] =='manager':
return Manager()
else:
return Person()
ext = PersonLoader()
# set up the polymorphic mapper, which maps the person_join we set up to
# the Person class, using an instance of PersonLoader.
people_mapper = mapper(Person, person_join, extension=ext)
# create a mapper for Company. the 'employees' relationship points to
# our new people_mapper.
#
# the dependency relationships which take effect on commit (i.e. the order of
# inserts/deletes) will be established against the Person class's primary
# mapper, and when the Engineer and
# Manager objects are found in the 'employees' list, the primary mappers
# for those subclasses will register
# themselves as dependent on the Person mapper's save operations.
# (translation: it'll work)
# TODO: get the eager loading to work (the compound select alias doesnt like being aliased itself)
assign_mapper(Company, companies, properties={
'employees': relation(people_mapper, lazy=False, private=True)
})
c = Company(name='company1')
c.employees.append(Manager(name='pointy haired boss', description='manager1'))
c.employees.append(Engineer(name='dilbert', special_description='engineer1'))
c.employees.append(Engineer(name='wally', special_description='engineer2'))
c.employees.append(Manager(name='jsmith', description='manager2'))
objectstore.commit()
objectstore.clear()
c = Company.get(1)
for e in c.employees:
print e, e._instance_key
print "\n"
dilbert = Engineer.mapper.get_by(name='dilbert')
dilbert.special_description = 'hes dibert!'
objectstore.commit()
objectstore.clear()
c = Company.get(1)
for e in c.employees:
print e, e._instance_key
objectstore.delete(c)
objectstore.commit()
managers.drop()
engineers.drop()
people.drop()
companies.drop()