From: Mike Bayer Date: Tue, 16 May 2017 13:51:06 +0000 (-0400) Subject: modernize and repair inheritance examples X-Git-Tag: rel_1_2_0b1~68 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5329fc481ed1dcea0e922fd63def3094bda12c91;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git modernize and repair inheritance examples 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 --- diff --git a/doc/build/orm/examples.rst b/doc/build/orm/examples.rst index 25d2430229..1a0007ca7e 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 b57b8e3e71..2d21e9e788 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 eb3e843ca3..c974043763 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 f9bdc81b4f..258f410250 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 6e0205e04e..f9322158ea 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 22a6fe027d..56397f540e 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()