読者です 読者をやめる 読者になる 読者になる

Python + peeweeを使って、Heroku Postgresに接続する

Python Heroku

最近、新旧のHerokuアプリ間でデータを移行する機会がありました。

当初は単純にリストアすればいいかと考えましたが、新旧間でテーブル構造などを変えたことに気づきました。

また、一部のテーブルのみのリストアができなくなったみたいで、ローカルのPostgreSQLへリストア・列変換後にHerokuにリストアという方法も取りにくくなりました。
Heroku PGBackups | Heroku Dev Center

 
そのため、直接Heroku Postgresに接続してデータ変換を行おうと考えました。良さそうなライブラリ(ORM)を探してみたところpeeweeがあったため、試してみました。
coleifer/peewee - GitHub

 

環境

  • Windows7 x64
  • Python 3.4.3 x86
  • virtualenvに以下をインストール
    • peewee 2.6.3
    • psygopg2 (win-psycopg) 2.6.1

 

流れ

pwizによるModelの自動生成

peeweeを使う際、Modelを作成する必要があることから、peeweeのpwizを使ってModelを自動生成しました。

  1. HerokuのWebサイトにログインし、新旧のアプリのHeroku Postgresの接続情報を確認
  2. virtualenvをactivate
  3. コマンドラインから以下の内容でpwizを実行し、新旧のModelファイルを生成
python -m pwiz -e postgresql -u "user_name" -P "password" -H "host_name" "databasename" > "modelファイル名"

 
できあがったModelファイルは以下のような感じになりました。

before.py
from peewee import *

database = PostgresqlDatabase(<your connection>)

# 略

class Apples(BaseModel):
    created_at = DateTimeField()
    name = CharField(null=True)
    tweet = CharField(null=True)
    tweet_id = BigIntegerField(null=True)
    tweeted_at = DateTimeField(null=True)
    updated_at = DateTimeField()

# 略

class Tweets(BaseModel):
    created_at = DateTimeField()
    last_searched = BigIntegerField(db_column='last_searched_id', null=True)
    updated_at = DateTimeField()

    class Meta:
        db_table = 'tweets'
after.py
from peewee import *

database = PostgresqlDatabase(<your connection>)

# 略

class TweetsTweets(BaseModel):
    name = CharField()
    tweet = CharField()
    tweet_id = BigIntegerField()
    tweeted_at = DateTimeField()

    class Meta:
        db_table = 'tweets_tweets'

class TweetsLastsearch(BaseModel):
    prev_since = BigIntegerField(db_column='prev_since_id')

    class Meta:
        db_table = 'tweets_lastsearch'

 

新旧データ変換のスクリプトの作成

以下のような感じのPythonスクリプトを作成しました。

import traceback
from before import database as before_db, Apples, Tweets
from after import database as after_db, TweetsLastsearch, TweetsTweets


def main():
    '''新旧のテーブル移行処理'''
    try:
        with before_db.transaction():
            tweets = Apples.select()
            last = Tweets.select()

            with after_db.transaction():
                tweet_source = [convert_tweet(x) for x in tweets ]
                TweetsTweets.insert_many(tweet_source).execute()

                last_source = [{ 'prev_since': last[0].last_searched }]
                TweetsLastsearch.insert_many(last_source).execute()

                print('commit')

    except Exception:
        traceback.print_exc()
        print('error')


def convert_tweet(tweets):
    '''新旧のテーブル列をマッピング'''
    return {
        'name': tweets.name,
        'tweet': tweets.tweet,
        'tweet_id': tweets.tweet_id,
        'tweeted_at': tweets.tweeted_at
    }


if __name__ == '__main__':
    main()

 
あとは、Pythonスクリプトを実行し、pgAdminIIIなどで確認すると、無事にHerokuの新アプリへデータが移行されていました。

 

ソースコード

GitHubに上げました。
thinkAmi-sandbox/ringo_tabetter_migration

 

参考

テーブル構造が一致するなど、単純にリストアすればいいだけなら、以下の方法で良さそうでした。
herokuのデータベースをローカルにリストアする | Workabroad.jp