お仕事で Django を使ってて便利だなーと思ったのが fixtures。
初期データを突っ込むのに便利。
Providing initial data for models | Django documentation | Django
yaml で初期データの定義を書いて、コマンド経由で DB にデータを入れられる。
Flask で似たようなことできないだろうかと探したら、Flask-Fixtures があったが、これはテストデータを入れるのを想定している模様。
Add load_fixtures_from_file by RoPP · Pull Request #21 · croach/Flask-Fixtures · GitHub という PR もあるが放置されている。
また Flask-SQLAlchemy に依存しているので、素の SQLALchemy を使ってる場合は使えない。
SQLAlchemy-Fixtures というものがあるが、Factory_boy を使えとある。
もう一つの方法としてはマイグレーションツールの Alembic の中で bulk_insert を使って初期データを入れる。
が、autogenreate を使う場合を想定すると、マイグレーションファイルとは別にしたい。
というわけで SQLAlchemy と PyYaml*1だけに依存しているものを作った。
GitHub - heavenshell/py-sqlalchemy_seed: Simple data seeder using SQLAlchemy.
こんな感じのディレクトリ構成。
.app /fixtures/pictures.yaml models /models.py /db.py views.py
Seed ファイルを書く。
書き方は Django と同じ(多分)。
- model: app.models.picture.Picture id: 1 fields: name: spam.jpg title: Spam description: spam description - model: app.models.picture.Picture id: 2 fields: name: ham.jpg title: Ham description: ham description - model: app.models.picture.Picture id: 3 fields: name: beacon.jpg title: Beacon description: beacon description
こんな感じで、 yaml ファイルを書く。
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker engine = create_engine('sqlite:///./db.sqlite', convert_unicode=True) Base = declarative_base() Base.metadata.bind = engine Session = sessionmaker(autocommit=False, autoflush=False, bind=engine) session = scoped_session(Session)
モデルはこんな感じ。
from sqlalchemy import Column, Integer, String from .db import Base class Picture(Base): __tablename__ = 'pictures' id = Column(Integer, primary_key=True) name = Column(String(254), nullable=False) title = Column(String(254), nullable=False) description = Column(String(254), nullable=False) def __init__(self, name, title, description): self.name = name self.title = title self.description = description
呼び出し側はこんな感じ。Flask-Script を使ってみる。
import os from flask_script import Manager, prompt_bool from sqlalchemy_seed import load_fixtures, load_fixture_files from app import create_app from app.models.db import session @manager.command def loaddata(filename=''): """Load seed data.""" if filename == '': return from app.models.db import session path = os.path.join(app.root_path, 'fixtures') fixtures = load_fixture_files(path, [filename]) load_fixtures(session, fixtures)
動かしてみる。
$ python manage.py loaddata -f pictures.yaml $ sqlite3 db.sqlite SQLite version 3.16.2 2017-01-06 16:32:41 Enter ".help" for usage hints. sqlite> select * from pictures; 1|spam.jpg|Spam|spam description 2|ham.jpg|Ham|ham description 3|beacon.jpg|Beacon|beacon description sqlite>
便利になった。