Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
A
advanced_orm_relations
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
SOFT Core
SOFT 161 and 162
advanced_orm_relations
Commits
6b2f2a30
Commit
6b2f2a30
authored
Mar 22, 2019
by
Brady James Garvin
Browse files
Options
Downloads
Patches
Plain Diff
Updated example code to demo a many-to-many self-join (and use Python 3).
parent
df844fdc
Branches
Branches containing commit
No related tags found
No related merge requests found
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
.gitignore
+0
-1
0 additions, 1 deletion
.gitignore
database.py
+58
-10
58 additions, 10 deletions
database.py
installer.py
+5
-4
5 additions, 4 deletions
installer.py
with
63 additions
and
15 deletions
.gitignore
+
0
−
1
View file @
6b2f2a30
...
@@ -2,5 +2,4 @@
...
@@ -2,5 +2,4 @@
*.pyo
*.pyo
.idea
.idea
.buildozer
bin
bin
This diff is collapsed.
Click to expand it.
database.py
+
58
−
10
View file @
6b2f2a30
...
@@ -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
():
...
...
This diff is collapsed.
Click to expand it.
installer.py
+
5
−
4
View file @
6b2f2a30
# -*- 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
)
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment