SQLAlchemyでリレーション
以下のサンプルでは、
各クラスについてidやテーブル名を定義するのが面倒なのでベースクラスを用意する。
from sqlalchemy.ext.declarative import declarative_base, declared_attr
Base = declarative_base()
class BaseModel(Base):
# 継承してね
__abstract__ = True
# idはすべてのクラスで使うので
id = Column(Integer, primary_key=True)
# 例えば、__tablename__ = "user" と同じ
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()
多対一
ユーザーが複数の日記をもっているリレーションの例。
class User(BaseModel):
name = Column(String)
class Dialy(BaseModel):
# ForeignKeyはデータベースレベルでのみリレーションを定義する。
user_id = Column(Integer, ForeignKey("user.id"), nullable=False)
title = Column(String)
# オブジェクトレベルでのリレーションを張る
# order_by はクラス名から書かないと組み込み関数idとかいわれる。
user = relationship(User, backref=backref("dialies"), order_by='User.id')
def test_many2one():
engine = create_engine("sqlite:///:memory:", echo=True)
BaseModel.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
sess = Session()
user = User(name="hoge")
user2 = User(name="foo")
sess.add(user)
sess.add(user2)
sess.commit()
d = Dialy(user_id=user.id, title="first")
sess.add(d)
user.name = "fuga"
sess.commit()
for instance in sess.query(Dialy).all():
print(instance.title, instance.user.name)
一対一
ユーザーが専用のプロフィールをもっている例。
class Profile(BaseModel):
user_id = Column(Integer, ForeignKey("user.id"), nullable=False)
age = Column(Integer)
# ProfileからもUserからもuselist=Falseを使うことがポイント
user = relationship(User, uselist=False, backref=backref("profile", uselist=False))
def test_one2one():
engine = create_engine("sqlite:///:memory:", echo=True)
BaseModel.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
sess = Session()
user = User(name="hoge")
sess.add(user)
sess.commit() ## コミットするとidプロパティが暗黙的に定義される
p = Profile(age=50, user_id=user.id)
sess.add(p)
sess.commit()
for u, p in sess.query(User, Profile).join(Profile, User.id == Profile.user_id).all():
print(p.user.name, p.user.profile.age)
多対多
ユーザーと行ったことがある国の関係。
user_country_table = Table("user_country", Base.metadata,
Column("user_id", Integer, ForeignKey("user.id")),
Column("country_id", Integer, ForeignKey("country.id")),
)
class Country(BaseModel):
name = Column(String)
users = relationship("User", secondary=user_country_table, backref="countries")
def test_many2many():
engine = create_engine("sqlite:///:memory:", echo=True)
BaseModel.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
sess = Session()
user = User(name="hoge")
user2 = User(name="foo")
c1 = Country(name="Japan")
c2 = Country(name="Russia")
user.countries.extend([c1, c2])
user2.countries.extend([c1, c2])
sess.add(user)
sess.add(user2)
sess.commit()
for u, c in sess.query(User, Country).join("countries").all():
print(u.name, c.name)
その他Tips
relationは内部でrelationshipを呼んでいるので、
relationshipを直接呼ぶべし。
orm/init.py:112
def relation(*arg, **kw):
"""A synonym for :func:`relationship`."""
return relationship(*arg, **kw)