Skip to content

Commit 8bfc213

Browse files
committed
Add example code for SQLAlchemy
1 parent d1758a7 commit 8bfc213

23 files changed

+958
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test.db
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Orm
2+
Sample object-relational mapping (ORM) code implemented in Pythno's
3+
`sqlalchemy`. Note that the database schema is not the same as that of
4+
the SQL examples in the parent directory.
5+
6+
TODO: make schemas on slides, in SQL examples and ORM code consistent
7+
8+
## What is it?
9+
1. `experiments.py`: class definitions for experiments, researchers and
10+
samples, taking into account their associations.
11+
1. `create.py`: create database schema.
12+
1. `fill.py`: add some data to the database, illustrating back references.
13+
1. `view.py`: illustrate using back references when using objects.
14+
1. `orm_utils.py`: some helper functions for more convenient ORM usage.
15+
1. `shell.py`: a shell to work with the database using a domain specific
16+
language.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env python
2+
3+
from argparse import ArgumentParser
4+
from sqlalchemy import create_engine
5+
import experiments
6+
7+
arg_parser = ArgumentParser(description='create tables in database')
8+
arg_parser.add_argument('db_name', help='name of DB to create')
9+
options = arg_parser.parse_args()
10+
engine = create_engine('sqlite:///{0}'.format(options.db_name))
11+
experiments.Base.metadata.create_all(engine)
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
'''Module that defines classes to describe experiments, reseachers and
2+
samples. Experiments can have multple researchers associated to them,
3+
and vice versa. Samples are assigned to experiments, but a sample can
4+
be used in a single experiment only. Object Relational Mapping is
5+
provided via SQLAlchemy.'''
6+
7+
from sqlalchemy import (Column, ForeignKey, UniqueConstraint,
8+
Integer, String, DateTime, Table)
9+
from sqlalchemy.ext.declarative import declarative_base
10+
from sqlalchemy.orm import relationship, backref
11+
12+
# Base class for all classes associated with tables in the database
13+
Base = declarative_base()
14+
15+
# Table to hold the experiments-researchers many-to-many association
16+
staff_assignments = Table(
17+
'staff_assignments', Base.metadata, Column(
18+
'experiment_id',
19+
Integer,
20+
ForeignKey('experiments.experiment_id')
21+
),
22+
Column(
23+
'researcher_id',
24+
Integer,
25+
ForeignKey('researchers.researcher_id')
26+
),
27+
UniqueConstraint('experiment_id', 'researcher_id')
28+
)
29+
30+
31+
class Experiment(Base):
32+
'''An experiment have a description, a start date, and when finished,
33+
an end date'''
34+
__tablename__ = 'experiments'
35+
experiment_id = Column(Integer, primary_key=True)
36+
start_date = Column(DateTime, nullable=False)
37+
end_date = Column(DateTime)
38+
description = Column(String(2048), nullable=False)
39+
researchers = relationship('Researcher', secondary=staff_assignments,
40+
backref='experiments')
41+
42+
def __str__(self):
43+
'''string representation of an experiment'''
44+
fmt_str = 'id {id:d}: {desc:s},\n\tstarted on {start}'
45+
str_repr = fmt_str.format(id=self.experiment_id,
46+
desc=self.description,
47+
start=self.start_date)
48+
if self.end_date:
49+
str_repr = '{base:s}, ended on {end}'.format(base=str_repr,
50+
end=self.end_date)
51+
return str_repr
52+
53+
54+
class Researcher(Base):
55+
'''A researcher has a first name, a last name, and optionally, a
56+
u-number, and description'''
57+
__tablename__ = 'researchers'
58+
researcher_id = Column(Integer, primary_key=True)
59+
u_number = Column(String(20))
60+
first_name = Column(String(20), nullable=False)
61+
last_name = Column(String(20), nullable=False)
62+
description = Column(String(20))
63+
64+
def __str__(self):
65+
'''string representation of a researcher'''
66+
fmt_str = 'id {id:d}: {last:s}, {first:s}'
67+
str_repr = fmt_str.format(id=self.researcher_id,
68+
last=self.last_name,
69+
first=self.first_name)
70+
if self.u_number:
71+
str_repr = '{base} ({u_nr})'.format(base=str_repr,
72+
u_nr=self.u_number)
73+
if self.description:
74+
str_repr = '{base}: {descr}'.format(base=str_repr,
75+
descr=self.description)
76+
77+
return str_repr
78+
79+
80+
class Sample(Base):
81+
'''A sample is associated to an experiment through the latter's ID,
82+
and it has a description'''
83+
__tablename__ = 'samples'
84+
sample_id = Column(Integer, primary_key=True)
85+
experiment_id = Column(Integer, ForeignKey('experiments.experiment_id'))
86+
description = Column(String, nullable=False)
87+
experiment = relationship('Experiment', backref=backref('samples'))
88+
89+
def __str__(self):
90+
'''string representation of a sample'''
91+
fmt_str = 'id {id:d}: {descr}'
92+
str_repr = fmt_str.format(id=self.sample_id, descr=self.description)
93+
if self.experiment_id:
94+
str_repr = '{base} used in {e_id:d}'.format(base=str_repr,
95+
e_id=self.experiment_id)
96+
return str_repr
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env python
2+
3+
from argparse import ArgumentParser
4+
from datetime import datetime
5+
from experiments import Experiment, Researcher, Sample
6+
from orm_utils import create_session
7+
8+
arg_parser = ArgumentParser(description='insert values into the database')
9+
arg_parser.add_argument('db_name', help='name of DB to create')
10+
options = arg_parser.parse_args()
11+
db_session = create_session(options.db_name)
12+
13+
# create and add researchers
14+
nele = Researcher(first_name='Nele', last_name='Famaey')
15+
db_session.add(nele)
16+
heleen = Researcher(first_name='Heleen', last_name='Fehervary')
17+
db_session.add(heleen)
18+
db_session.commit()
19+
20+
# create experiments
21+
exp1 = Experiment(
22+
start_date=datetime(2015, 10, 23, 9, 11),
23+
end_date=datetime(2015, 10, 25, 13, 43),
24+
description='first experiment'
25+
)
26+
exp1.researchers.append(nele)
27+
exp1.samples.append(Sample(description='sample 1'))
28+
exp1.samples.append(Sample(description='sample 2'))
29+
db_session.add(exp1)
30+
exp2 = Experiment(
31+
start_date=datetime(2015, 10, 27, 9, 5),
32+
end_date=datetime(2015, 10, 28, 14, 53),
33+
description='second experiment'
34+
)
35+
exp2.researchers.append(nele)
36+
exp2.researchers.append(heleen)
37+
exp2.samples.append(Sample(description='sample 3'))
38+
db_session.add(exp2)
39+
db_session.commit()
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'''Module with some convenient function for ORM usage'''
2+
3+
from sqlalchemy import create_engine
4+
from sqlalchemy.orm import sessionmaker
5+
from experiments import Base
6+
7+
8+
def create_session(db_name):
9+
'''Create a SQLAlchemy database session based on the provided name'''
10+
engine = create_engine('sqlite:///{0}'.format(db_name))
11+
Base.metadata.bind = engine
12+
DBSession = sessionmaker(bind=engine)
13+
return DBSession()
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env python
2+
3+
from datetime import datetime
4+
5+
6+
def convert2date(date_str):
7+
'''convert a string representation 'yyyy-mm-dd' to a Python
8+
datetime object'''
9+
year, month, day = list(map(int, date_str.split('-')))
10+
return datetime(year, month, day)
11+
12+
if __name__ == '__main__':
13+
from argparse import ArgumentParser
14+
from experiments import Experiment, Researcher
15+
from orm_utils import create_session
16+
arg_parser = ArgumentParser(description='query the database')
17+
arg_parser.add_argument('db_name', help='name of DB to create')
18+
arg_parser.add_argument('--first_name',
19+
help='search for researcher by first name')
20+
arg_parser.add_argument('--started_after',
21+
help='search experiments with a start date '
22+
'later than the given one')
23+
options = arg_parser.parse_args()
24+
db_session = create_session(options.db_name)
25+
26+
if options.first_name:
27+
researchers = db_session.query(Researcher).\
28+
filter_by(first_name=options.first_name).\
29+
all()
30+
for researcher in researchers:
31+
print(researcher)
32+
elif options.started_after:
33+
date_after = convert2date(options.started_after)
34+
experiments = db_session.query(Experiment).\
35+
filter(Experiment.start_date > date_after).\
36+
all()
37+
for experiment in experiments:
38+
print(experiment)

0 commit comments

Comments
 (0)