Skip to content
Snippets Groups Projects
Commit 6b2f2a30 authored by Brady James Garvin's avatar Brady James Garvin
Browse files

Updated example code to demo a many-to-many self-join (and use Python 3).

parent df844fdc
Branches
No related tags found
No related merge requests found
...@@ -2,5 +2,4 @@ ...@@ -2,5 +2,4 @@
*.pyo *.pyo
.idea .idea
.buildozer
bin bin
...@@ -3,7 +3,7 @@ from sqlalchemy.ext.declarative import declarative_base ...@@ -3,7 +3,7 @@ from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy.orm import sessionmaker, relationship
Persisted = declarative_base() Persisted = declarative_base() # pylint: disable=invalid-name
class Person(Persisted): class Person(Persisted):
...@@ -12,20 +12,68 @@ class Person(Persisted): ...@@ -12,20 +12,68 @@ class Person(Persisted):
name = Column(String(256), nullable=False) name = Column(String(256), nullable=False)
supervisor_id = Column(Integer, ForeignKey('people.person_id', ondelete='CASCADE')) supervisor_id = Column(Integer, ForeignKey('people.person_id', ondelete='CASCADE'))
mentor_id = Column(Integer, ForeignKey('people.person_id', ondelete='CASCADE')) mentor_id = Column(Integer, ForeignKey('people.person_id', ondelete='CASCADE'))
# Because there are multiple keys the relationship could use, we have to specify the foreign key to use and the key # There are multiple foreign keys this many-to-one relationship could use, so we have to specify extra information.
# to match it to (in this case a primary key). # When we have only one object to relate to (the uselist=False case), there are two things to specify:
supervisor = relationship('Person', foreign_keys=[supervisor_id], remote_side=[person_id], back_populates='supervisees') # (1) which of our foreign keys this relationship should use to identify the other record and
mentor = relationship('Person', foreign_keys=[mentor_id], remote_side=[person_id], back_populates='mentees') # (2) the key in the other record to match it to (usually a primary key).
# For the reverse relationships (the ones with uselist=True), the key to match to is implicitly the primary key. # Note that all of this extra information is written in terms of classes and OO fields, not tables or DB fields.
supervisees = relationship('Person', foreign_keys=[supervisor_id], uselist=True, back_populates='supervisor') supervisor = relationship('Person', foreign_keys='[Person.supervisor_id]', remote_side='[Person.person_id]',
mentees = relationship('Person', foreign_keys=[mentor_id], uselist=True, back_populates='mentor') back_populates='supervisees')
mentor = relationship('Person', foreign_keys='[Person.mentor_id]', remote_side='[Person.person_id]',
back_populates='mentees')
# There are multiple foreign keys this one-to-many relationship could use, so we have to specify extra information.
# When we have potentially many objects to relate to (the uselist=True case), SQLAlchemy defaults to using our own
# primary key, so we do not have to specify the local side, just:
# (1) which of the other record's foreign keys this relationship should match against our primary key.
# Note that all of this extra information is written in terms of classes and OO fields, not tables or DB fields.
supervisees = relationship('Person', uselist=True, foreign_keys='[Person.supervisor_id]',
back_populates='supervisor')
mentees = relationship('Person', uselist=True, foreign_keys='[Person.mentor_id]',
back_populates='mentor')
# There are multiple foreign keys this many-to-many relationship could use, so we have to specify extra information.
# Here, however, there are two joins, so we must specify that information separately for each.
# For the first join, which finds the matching pairs in the join table, we must specify:
# (1) which of the join table's foreign keys this relationship should match against our key,
# (2) that we want to match on key equality (==), and
# (3) the key in our record to match the foreign key to (usually our primary key).
# For the second join, which finds the related records indicated by the matching pairs, we must specify:
# (1) which of the join table's foreign keys this relationship should use to identify the other record,
# (2) that we want to match on key equality (==), and
# (3) the key in the other record to match the foreign key to (usually a primary key).
# Note that all of these conditions are written in terms of classes, OO fields, and Python operators, not tables,
# DB fields, or SQL operators.
reviewers = relationship('Person', uselist=True, secondary='reviewerships',
primaryjoin='Reviewership.reviewer_id == Person.person_id',
secondaryjoin='Reviewership.reviewee_id == Person.person_id',
back_populates='reviewees')
reviewees = relationship('Person', uselist=True, secondary='reviewerships',
primaryjoin='Reviewership.reviewee_id == Person.person_id',
secondaryjoin='Reviewership.reviewer_id == Person.person_id',
back_populates='reviewers')
# If we want to be able to access the join table's pairs themselves, we write a standard one-to-many relationship
# with extra information.
reviewerships = relationship('Reviewership', uselist=True, foreign_keys='[Reviewership.reviewer_id]',
back_populates='reviewer')
revieweeships = relationship('Reviewership', uselist=True, foreign_keys='[Reviewership.reviewee_id]',
back_populates='reviewee')
class Reviewership(Persisted):
__tablename__ = 'reviewerships'
reviewer_id = Column(Integer, ForeignKey('people.person_id', ondelete='CASCADE'), primary_key=True)
reviewee_id = Column(Integer, ForeignKey('people.person_id', ondelete='CASCADE'), primary_key=True)
# If we want to be able to access the related records from a join-table pairs, we write a standard many-to-one
# relationship with extra information.
reviewer = relationship('Person', foreign_keys='[Reviewership.reviewer_id]', remote_side='[Person.person_id]',
back_populates='reviewerships')
reviewee = relationship('Person', foreign_keys='[Reviewership.reviewee_id]', remote_side='[Person.person_id]',
back_populates='revieweeships')
class Database(object): class Database(object):
@staticmethod @staticmethod
def construct_mysql_url(authority, port, database, username, password): def construct_mysql_url(authority, port, database, username, password):
return 'mysql+mysqlconnector://{username}:{password}@{authority}:{port}/{database}' \ return f'mysql+mysqlconnector://{username}:{password}@{authority}:{port}/{database}'
.format(authority=authority, port=port, database=database, username=username, password=password)
@staticmethod @staticmethod
def construct_in_memory_url(): def construct_in_memory_url():
......
# -*- coding: utf-8; -*-
from sys import stderr from sys import stderr
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
...@@ -10,7 +8,10 @@ from database import Database, Person ...@@ -10,7 +8,10 @@ from database import Database, Person
def add_starter_data(session): def add_starter_data(session):
alice = Person(name='Alice') alice = Person(name='Alice')
bob = Person(name='Bob', mentor=alice) bob = Person(name='Bob', mentor=alice)
carol = Person(name='Carol', supervisor=alice, mentor=bob) carol = Person(name='Carol', supervisor=alice)
bob.mentees = [carol]
alice.reviewers = [bob, carol]
bob.reviewees += [bob]
session.add(alice) session.add(alice)
session.add(bob) session.add(bob)
session.add(carol) session.add(carol)
...@@ -28,7 +29,7 @@ def main(): ...@@ -28,7 +29,7 @@ def main():
print('Records created.') print('Records created.')
except SQLAlchemyError as exception: except SQLAlchemyError as exception:
print('Database setup failed!', file=stderr) print('Database setup failed!', file=stderr)
print('Cause: {exception}'.format(exception=exception), file=stderr) print(f'Cause: {exception}', file=stderr)
exit(1) exit(1)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment