]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
modernize and repair inheritance examples
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 16 May 2017 13:51:06 +0000 (09:51 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 16 May 2017 13:51:06 +0000 (09:51 -0400)
remarkably, the examples for concrete and single were still
using classical mappings.    Ensure all three examples use
modern declarative patterns, each illustrate the identical set
of query operations.  Use back_populates, flat=True for joins,
etc. ensure flake8 linting, correct links and add a link back
from newly reworked inheritance documentation.

Change-Id: I8465a9badbb0eda804f457ccac599f051ee3c27c

doc/build/orm/examples.rst
doc/build/orm/inheritance.rst
examples/inheritance/__init__.py
examples/inheritance/concrete.py
examples/inheritance/joined.py
examples/inheritance/single.py

index 25d2430229334eff3f7b745338781aeb53a094e0..1a0007ca7eb73b6eb890b28028d1114ea44f62c9 100644 (file)
@@ -106,6 +106,8 @@ Vertical Attribute Mapping
 .. automodule:: examples.vertical
 
 
+.. _examples_inheritance:
+
 Inheritance Mapping Recipes
 ============================
 
index b57b8e3e713f719fe131c4857c21e79923705272..2d21e9e7886eee34c74190003a657a0f9861e21e 100644 (file)
@@ -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
index eb3e843ca3dbc3c68d6ee2a8014db49ff90c1d5b..c974043763b4a76c6d3c3d6c0e43d0fb584d94f5 100644 (file)
@@ -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
+"""
index f9bdc81b4f3714b643a9aa345aa6023d2d8eab23..258f41025076dfc2bab8ae417cca8be86413aefc 100644 (file)
-"""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()
index 6e0205e04e5e3dc306cf02e6ede58206d5e0fc63..f9322158ea6603e47f7bb46d8d0a632a10d2b3e8 100644 (file)
@@ -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()
-
-
index 22a6fe027d350f4ff22938eff2c460e0d60d9710..56397f540e345303e04e0b04b778433f0a97d296 100644 (file)
-"""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()