diff options
-rw-r--r-- | doc/build/orm/examples.rst | 2 | ||||
-rw-r--r-- | doc/build/orm/inheritance.rst | 5 | ||||
-rw-r--r-- | examples/inheritance/__init__.py | 4 | ||||
-rw-r--r-- | examples/inheritance/concrete.py | 193 | ||||
-rw-r--r-- | examples/inheritance/joined.py | 111 | ||||
-rw-r--r-- | examples/inheritance/single.py | 199 |
6 files changed, 341 insertions, 173 deletions
diff --git a/doc/build/orm/examples.rst b/doc/build/orm/examples.rst index 25d243022..1a0007ca7 100644 --- a/doc/build/orm/examples.rst +++ b/doc/build/orm/examples.rst @@ -106,6 +106,8 @@ Vertical Attribute Mapping .. automodule:: examples.vertical +.. _examples_inheritance: + Inheritance Mapping Recipes ============================ diff --git a/doc/build/orm/inheritance.rst b/doc/build/orm/inheritance.rst index b57b8e3e7..2d21e9e78 100644 --- a/doc/build/orm/inheritance.rst +++ b/doc/build/orm/inheritance.rst @@ -17,6 +17,11 @@ When mappers are configured in an inheritance relationship, SQLAlchemy has the ability to load elements :term:`polymorphically`, meaning that a single query can return objects of multiple types. +.. seealso:: + + :ref:`examples_inheritance` - complete exampes of joined, single and + concrete inheritance + .. _joined_inheritance: Joined Table Inheritance diff --git a/examples/inheritance/__init__.py b/examples/inheritance/__init__.py index eb3e843ca..c97404376 100644 --- a/examples/inheritance/__init__.py +++ b/examples/inheritance/__init__.py @@ -1,6 +1,6 @@ """Working examples of single-table, joined-table, and concrete-table -inheritance as described in :ref:`datamapping_inheritance`. +inheritance as described in :ref:`inheritance_toplevel`. .. autosource:: -"""
\ No newline at end of file +""" diff --git a/examples/inheritance/concrete.py b/examples/inheritance/concrete.py index f9bdc81b4..258f41025 100644 --- a/examples/inheritance/concrete.py +++ b/examples/inheritance/concrete.py @@ -1,74 +1,161 @@ -"""Concrete (table-per-class) inheritance example.""" +"""Concrete-table (table-per-class) inheritance example.""" -from sqlalchemy import create_engine, MetaData, Table, Column, Integer, \ - String -from sqlalchemy.orm import mapper, sessionmaker, polymorphic_union +from sqlalchemy import Column, Integer, String, \ + ForeignKey, create_engine, inspect, or_ +from sqlalchemy.orm import relationship, Session, with_polymorphic +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.ext.declarative import ConcreteBase -metadata = MetaData() -managers_table = Table('managers', metadata, - Column('employee_id', Integer, primary_key=True), - Column('name', String(50)), - Column('manager_data', String(40)) -) +Base = declarative_base() -engineers_table = Table('engineers', metadata, - Column('employee_id', Integer, primary_key=True), - Column('name', String(50)), - Column('engineer_info', String(40)) -) -engine = create_engine('sqlite:///', echo=True) -metadata.create_all(engine) +class Company(Base): + __tablename__ = 'company' + id = Column(Integer, primary_key=True) + name = Column(String(50)) + employees = relationship( + "Person", + back_populates='company', + cascade='all, delete-orphan') -class Employee(object): - def __init__(self, name): - self.name = name def __repr__(self): - return self.__class__.__name__ + " " + self.name + return "Company %s" % self.name -class Manager(Employee): - def __init__(self, name, manager_data): - self.name = name - self.manager_data = manager_data - def __repr__(self): - return self.__class__.__name__ + " " + \ - self.name + " " + self.manager_data -class Engineer(Employee): - def __init__(self, name, engineer_info): - self.name = name - self.engineer_info = engineer_info +class Person(ConcreteBase, Base): + __tablename__ = 'person' + id = Column(Integer, primary_key=True) + company_id = Column(ForeignKey('company.id')) + name = Column(String(50)) + + company = relationship("Company", back_populates="employees") + + __mapper_args__ = { + 'polymorphic_identity': 'person', + } + def __repr__(self): - return self.__class__.__name__ + " " + \ - self.name + " " + self.engineer_info + return "Ordinary person %s" % self.name + + +class Engineer(Person): + __tablename__ = 'engineer' + id = Column(Integer, primary_key=True) + name = Column(String(50)) + company_id = Column(ForeignKey('company.id')) + status = Column(String(30)) + engineer_name = Column(String(30)) + primary_language = Column(String(30)) + company = relationship("Company", back_populates="employees") -pjoin = polymorphic_union({ - 'manager':managers_table, - 'engineer':engineers_table -}, 'type', 'pjoin') + __mapper_args__ = { + 'polymorphic_identity': 'engineer', + 'concrete': True + } -employee_mapper = mapper(Employee, pjoin, polymorphic_on=pjoin.c.type) -manager_mapper = mapper(Manager, managers_table, - inherits=employee_mapper, concrete=True, - polymorphic_identity='manager') -engineer_mapper = mapper(Engineer, engineers_table, - inherits=employee_mapper, concrete=True, - polymorphic_identity='engineer') + def __repr__(self): + return ( + "Engineer %s, status %s, engineer_name %s, " + "primary_language %s" % + ( + self.name, self.status, + self.engineer_name, self.primary_language) + ) + + +class Manager(Person): + __tablename__ = 'manager' + id = Column(Integer, primary_key=True) + name = Column(String(50)) + company_id = Column(ForeignKey('company.id')) + status = Column(String(30)) + manager_name = Column(String(30)) + + company = relationship("Company", back_populates="employees") + + __mapper_args__ = { + 'polymorphic_identity': 'manager', + 'concrete': True + } + + def __repr__(self): + return "Manager %s, status %s, manager_name %s" % ( + self.name, self.status, self.manager_name) + + +engine = create_engine('sqlite://', echo=True) +Base.metadata.create_all(engine) + +session = Session(engine) + +c = Company(name='company1', employees=[ + Manager( + name='pointy haired boss', + status='AAB', + manager_name='manager1'), + Engineer( + name='dilbert', + status='BBA', + engineer_name='engineer1', + primary_language='java'), + Person(name='joesmith'), + Engineer( + name='wally', + status='CGG', + engineer_name='engineer2', + primary_language='python'), + Manager( + name='jsmith', + status='ABA', + manager_name='manager2') +]) +session.add(c) + +session.commit() +c = session.query(Company).get(1) +for e in c.employees: + print(e, inspect(e).key, e.company) +assert set([e.name for e in c.employees]) == set( + ['pointy haired boss', 'dilbert', 'joesmith', 'wally', 'jsmith']) +print("\n") -session = sessionmaker(engine)() +dilbert = session.query(Person).filter_by(name='dilbert').one() +dilbert2 = session.query(Engineer).filter_by(name='dilbert').one() +assert dilbert is dilbert2 -m1 = Manager("pointy haired boss", "manager1") -e1 = Engineer("wally", "engineer1") -e2 = Engineer("dilbert", "engineer2") +dilbert.engineer_name = 'hes dilbert!' -session.add(m1) -session.add(e1) -session.add(e2) session.commit() -print(session.query(Employee).all()) +c = session.query(Company).get(1) +for e in c.employees: + print(e) + +# query using with_polymorphic. +eng_manager = with_polymorphic(Person, [Engineer, Manager]) +print( + session.query(eng_manager). + filter( + or_( + eng_manager.Engineer.engineer_name == 'engineer1', + eng_manager.Manager.manager_name == 'manager2' + ) + ).all() +) + +# illustrate join from Company +eng_manager = with_polymorphic(Person, [Engineer, Manager]) +print( + session.query(Company). + join( + Company.employees.of_type(eng_manager) + ).filter( + or_(eng_manager.Engineer.engineer_name == 'engineer1', + eng_manager.Manager.manager_name == 'manager2') + ).all()) +session.commit() diff --git a/examples/inheritance/joined.py b/examples/inheritance/joined.py index 6e0205e04..f9322158e 100644 --- a/examples/inheritance/joined.py +++ b/examples/inheritance/joined.py @@ -1,66 +1,79 @@ """Joined-table (table-per-subclass) inheritance example.""" -from sqlalchemy import Table, Column, Integer, String, \ +from sqlalchemy import Column, Integer, String, \ ForeignKey, create_engine, inspect, or_ from sqlalchemy.orm import relationship, Session, with_polymorphic from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() + class Company(Base): __tablename__ = 'company' id = Column(Integer, primary_key=True) name = Column(String(50)) - employees = relationship("Person", - backref='company', - cascade='all, delete-orphan') + employees = relationship( + "Person", + back_populates='company', + cascade='all, delete-orphan') def __repr__(self): return "Company %s" % self.name + class Person(Base): __tablename__ = 'person' id = Column(Integer, primary_key=True) - company_id = Column(Integer, ForeignKey('company.id')) + company_id = Column(ForeignKey('company.id')) name = Column(String(50)) type = Column(String(50)) + company = relationship("Company", back_populates="employees") + __mapper_args__ = { - 'polymorphic_identity':'person', - 'polymorphic_on':type + 'polymorphic_identity': 'person', + 'polymorphic_on': type } + def __repr__(self): return "Ordinary person %s" % self.name + class Engineer(Person): __tablename__ = 'engineer' - id = Column(Integer, ForeignKey('person.id'), primary_key=True) + id = Column(ForeignKey('person.id'), primary_key=True) status = Column(String(30)) engineer_name = Column(String(30)) primary_language = Column(String(30)) __mapper_args__ = { - 'polymorphic_identity':'engineer', + 'polymorphic_identity': 'engineer', } + def __repr__(self): - return "Engineer %s, status %s, engineer_name %s, "\ - "primary_language %s" % \ - (self.name, self.status, - self.engineer_name, self.primary_language) + return ( + "Engineer %s, status %s, engineer_name %s, " + "primary_language %s" % + ( + self.name, self.status, + self.engineer_name, self.primary_language) + ) + class Manager(Person): __tablename__ = 'manager' - id = Column(Integer, ForeignKey('person.id'), primary_key=True) + id = Column(ForeignKey('person.id'), primary_key=True) status = Column(String(30)) manager_name = Column(String(30)) __mapper_args__ = { - 'polymorphic_identity':'manager', + 'polymorphic_identity': 'manager', } + def __repr__(self): - return "Manager %s, status %s, manager_name %s" % \ - (self.name, self.status, self.manager_name) + return "Manager %s, status %s, manager_name %s" % ( + self.name, self.status, self.manager_name) engine = create_engine('sqlite://', echo=True) @@ -73,18 +86,21 @@ c = Company(name='company1', employees=[ name='pointy haired boss', status='AAB', manager_name='manager1'), - Engineer(name='dilbert', + Engineer( + name='dilbert', status='BBA', engineer_name='engineer1', primary_language='java'), Person(name='joesmith'), - Engineer(name='wally', - status='CGG', - engineer_name='engineer2', - primary_language='python'), - Manager(name='jsmith', - status='ABA', - manager_name='manager2') + Engineer( + name='wally', + status='CGG', + engineer_name='engineer2', + primary_language='python'), + Manager( + name='jsmith', + status='ABA', + manager_name='manager2') ]) session.add(c) @@ -93,8 +109,8 @@ session.commit() c = session.query(Company).get(1) for e in c.employees: print(e, inspect(e).key, e.company) -assert set([e.name for e in c.employees]) == set(['pointy haired boss', - 'dilbert', 'joesmith', 'wally', 'jsmith']) +assert set([e.name for e in c.employees]) == set( + ['pointy haired boss', 'dilbert', 'joesmith', 'wally', 'jsmith']) print("\n") dilbert = session.query(Person).filter_by(name='dilbert').one() @@ -110,27 +126,30 @@ for e in c.employees: print(e) # query using with_polymorphic. -eng_manager = with_polymorphic(Person, [Engineer, Manager], aliased=True) -print(session.query(eng_manager).\ - filter( - or_(eng_manager.Engineer.engineer_name=='engineer1', - eng_manager.Manager.manager_name=='manager2' - ) - ).all()) - -# illustrate join from Company, -# We use aliased=True -# to help when the selectable is used as the target of a join. -eng_manager = with_polymorphic(Person, [Engineer, Manager], aliased=True) -print(session.query(Company).\ +eng_manager = with_polymorphic(Person, [Engineer, Manager]) +print( + session.query(eng_manager). + filter( + or_( + eng_manager.Engineer.engineer_name == 'engineer1', + eng_manager.Manager.manager_name == 'manager2' + ) + ).all() +) + +# illustrate join from Company. +# flat=True means the tables inside the "polymorphic join" will be aliased. +# not strictly necessary in this example but helpful for the more general +# case of joins involving inheritance hierarchies as well as joined eager +# loading. +eng_manager = with_polymorphic(Person, [Engineer, Manager], flat=True) +print( + session.query(Company). join( - eng_manager, - Company.employees + Company.employees.of_type(eng_manager) ).filter( - or_(eng_manager.Engineer.engineer_name=='engineer1', - eng_manager.Manager.manager_name=='manager2') + or_(eng_manager.Engineer.engineer_name == 'engineer1', + eng_manager.Manager.manager_name == 'manager2') ).all()) session.commit() - - diff --git a/examples/inheritance/single.py b/examples/inheritance/single.py index 22a6fe027..56397f540 100644 --- a/examples/inheritance/single.py +++ b/examples/inheritance/single.py @@ -1,102 +1,157 @@ -"""Single-table inheritance example.""" - -from sqlalchemy import MetaData, Table, Column, Integer, String, \ - ForeignKey, create_engine -from sqlalchemy.orm import mapper, relationship, sessionmaker - -metadata = MetaData() - -# a table to store companies -companies = Table('companies', metadata, - Column('company_id', Integer, primary_key=True), - Column('name', String(50))) - -employees_table = Table('employees', metadata, - Column('employee_id', Integer, primary_key=True), - Column('company_id', Integer, ForeignKey('companies.company_id')), - Column('name', String(50)), - Column('type', String(20)), - Column('status', String(20)), - Column('engineer_name', String(50)), - Column('primary_language', String(50)), - Column('manager_name', String(50)) -) +"""Single-table (table-per-hierarchy) inheritance example.""" + +from sqlalchemy import Column, Integer, String, \ + ForeignKey, create_engine, inspect, or_ +from sqlalchemy.orm import relationship, Session, with_polymorphic +from sqlalchemy.ext.declarative import declarative_base, declared_attr + +Base = declarative_base() + +class Company(Base): + __tablename__ = 'company' + id = Column(Integer, primary_key=True) + name = Column(String(50)) + + employees = relationship( + "Person", + back_populates='company', + cascade='all, delete-orphan') + + def __repr__(self): + return "Company %s" % self.name + + +class Person(Base): + __tablename__ = 'person' + id = Column(Integer, primary_key=True) + company_id = Column(ForeignKey('company.id')) + name = Column(String(50)) + type = Column(String(50)) + + company = relationship("Company", back_populates="employees") + + __mapper_args__ = { + 'polymorphic_identity': 'person', + 'polymorphic_on': type + } -class Person(object): - def __init__(self, **kwargs): - for key, value in kwargs.items(): - setattr(self, key, value) def __repr__(self): return "Ordinary person %s" % self.name + + class Engineer(Person): - def __repr__(self): - return "Engineer %s, status %s, engineer_name %s, "\ - "primary_language %s" % \ - (self.name, self.status, - self.engineer_name, self.primary_language) -class Manager(Person): - def __repr__(self): - return "Manager %s, status %s, manager_name %s" % \ - (self.name, self.status, self.manager_name) -class Company(object): - def __init__(self, **kwargs): - for key, value in kwargs.items(): - setattr(self, key, value) - def __repr__(self): - return "Company %s" % self.name -person_mapper = mapper(Person, employees_table, - polymorphic_on=employees_table.c.type, - polymorphic_identity='person') -manager_mapper = mapper(Manager, inherits=person_mapper, - polymorphic_identity='manager') -engineer_mapper = mapper(Engineer, inherits=person_mapper, - polymorphic_identity='engineer') + engineer_name = Column(String(30)) + primary_language = Column(String(30)) -mapper(Company, companies, properties={ - 'employees': relationship(Person, lazy=True, backref='company') -}) + # illustrate a single-inh "conflicting" column declaration; + # see http://docs.sqlalchemy.org/en/latest/orm/extensions/ + # declarative/inheritance.html#resolving-column-conflicts + @declared_attr + def status(cls): + return Person.__table__.c.get('status', Column(String(30))) + __mapper_args__ = { + 'polymorphic_identity': 'engineer', + } -engine = create_engine('sqlite:///', echo=True) + def __repr__(self): + return ( + "Engineer %s, status %s, engineer_name %s, " + "primary_language %s" % + ( + self.name, self.status, + self.engineer_name, self.primary_language) + ) + + +class Manager(Person): + manager_name = Column(String(30)) -metadata.create_all(engine) + @declared_attr + def status(cls): + return Person.__table__.c.get('status', Column(String(30))) -session = sessionmaker(engine)() + __mapper_args__ = { + 'polymorphic_identity': 'manager', + } -c = Company(name='company1') -c.employees.append(Manager(name='pointy haired boss', status='AAB', - manager_name='manager1')) -c.employees.append(Engineer(name='dilbert', status='BBA', - engineer_name='engineer1', primary_language='java')) -c.employees.append(Person(name='joesmith', status='HHH')) -c.employees.append(Engineer(name='wally', status='CGG', - engineer_name='engineer2', primary_language='python' - )) -c.employees.append(Manager(name='jsmith', status='ABA', - manager_name='manager2')) + def __repr__(self): + return "Manager %s, status %s, manager_name %s" % ( + self.name, self.status, self.manager_name) + + +engine = create_engine('sqlite://', echo=True) +Base.metadata.create_all(engine) + +session = Session(engine) + +c = Company(name='company1', employees=[ + Manager( + name='pointy haired boss', + status='AAB', + manager_name='manager1'), + Engineer( + name='dilbert', + status='BBA', + engineer_name='engineer1', + primary_language='java'), + Person(name='joesmith'), + Engineer( + name='wally', + status='CGG', + engineer_name='engineer2', + primary_language='python'), + Manager( + name='jsmith', + status='ABA', + manager_name='manager2') +]) session.add(c) + session.commit() c = session.query(Company).get(1) for e in c.employees: - print(e, e.company) - + print(e, inspect(e).key, e.company) +assert set([e.name for e in c.employees]) == set( + ['pointy haired boss', 'dilbert', 'joesmith', 'wally', 'jsmith']) print("\n") dilbert = session.query(Person).filter_by(name='dilbert').one() dilbert2 = session.query(Engineer).filter_by(name='dilbert').one() assert dilbert is dilbert2 -dilbert.engineer_name = 'hes dibert!' +dilbert.engineer_name = 'hes dilbert!' -session.flush() -session.expunge_all() +session.commit() c = session.query(Company).get(1) for e in c.employees: print(e) -session.delete(c) +# query using with_polymorphic. +eng_manager = with_polymorphic(Person, [Engineer, Manager]) +print( + session.query(eng_manager). + filter( + or_( + eng_manager.Engineer.engineer_name == 'engineer1', + eng_manager.Manager.manager_name == 'manager2' + ) + ).all() +) + +# illustrate join from Company, +eng_manager = with_polymorphic(Person, [Engineer, Manager]) +print( + session.query(Company). + join( + Company.employees.of_type(eng_manager) + ).filter( + or_(eng_manager.Engineer.engineer_name == 'engineer1', + eng_manager.Manager.manager_name == 'manager2') + ).all()) + session.commit() |