Hatena::ブログ(Diary)

Study03.net 対シンバシ専用 このページをアンテナに追加 RSSフィード Twitter

2012-01-04

移転します。

まだ使い勝手が良くわかってないんですが、新年ということもあって、はてなブログに完全移行します。このダイアリーはしばらく記事を更新しません。

Study08.net 対シンバシ殲滅用人型機動兵器

おちこんだりもしたけれど、私はげんきです。新しい方でもよろしくお願いいたしますm( _ _ )m

2011-11-06

Heroku で Flask + SQLAlchemy + Amazon RDS をやってみる。

Heroku で Pythonが動かせるらしいという情報をいち早くキャッチ(大分遅い) したのでやってみる事にした。Flaskのチュートリアルで作るアプリ「Flaskr」を Heroku + Amazon RDS(MySQL5.5) で動かします。

Heroku標準のPostgresとかの連携手順とかは、既に良くまとまってるブログがあるので、一番最後の参考URL群を参照してください。ここではせっかちな人のために、一気にHerokuアプリと、RDSインスタンスを作るスクリプトを晒しておきます。手順をシェルスクリプト化しただけともいいます。

環境

MacOSX (Lion) での作業を前提としています。

前提

以下のものが使えるようになっている必要があります。

  • heroku(ruby gems)
  • bash
  • git
  • virtualenv
  • pip
  • rds-command-line-tools

「rds-command-line-tools」は、あんま情報がなかったので、自分がインストールした方法を書いておきます。簡単にいうと、CLIAmazon RDSを操作するツール群です。それ以外は、結構検索に引っかかると思います。

rds-command-line-toolsのインストール

SDKs and Programming Toolkits for AWS から自分でダウンロードして、手動でセットアップしてもよいですが、Macであればhomebrewインストールができます。

brew install rds-command-line-tools

インストールが終わると、環境変数を設定するように求められるので、.bashrcとか設定してください。

export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Home"
export EC2_PRIVATE_KEY="$(/bin/ls $HOME/.ec2/pk-*.pem)"
export EC2_CERT="$(/bin/ls $HOME/.ec2/cert-*.pem)"
export AWS_RDS_HOME="/usr/local/Cellar/rds-command-line-tools/1.3.003/jars"

「EC2_PRIVATE_KEY」と「EC2_CERT」に設定されている「*.pem」とかいう証明書は、no title にアクセスして、「X.509証明書」から「新しい証明書を作成する」とかやれば入手できます。入手したら、~/.ec2 の下に置いてください。

これで「rds-describe-db-instances」とかいうコマンドを打ってみて反応があればインストール完了です。

注意事項

スクリプトは下記のような操作をやるので、その辺を了解した上で実行してください。いかなる責任にも対処しかねますので、不安な人は、スクリプトの中身をのぞいてから実行するか否かを決めてください。

スクリプト

gistにしておいたので、実行は簡単です。[ ]で囲まれた部分は自分で決めて引数として渡してあげてください。

curl -O https://raw.github.com/gist/1342640/083822585c1321a47ad8f193ea37e8c9eb26fadb/setup_flaskr_on_heroku.sh
sh setup_flaskr_on_heroku.sh [app_name] [username] [password]

#app_name => アプリケーションの名前
#username => Flaskrのログインユーザー名 & RDSインスタンスのログインユーザー名
#password => Flaskrのパスワード & RDSインスタンスのパスワード

スクリプトを実行すれば、後はひたすら待つのみです。結構時間かかります。20〜30分位かかります。時間がかかってるのは、RDSのインスタンスを新規作成したときに、インスタンスが起動し終わるまで、スクリプトは待ってるからですw

インスタンスを作成してから使えるようになるまで結構時間かかるんですよ。

セットアップが完了すれば、ブラウザ上にFlaskrが表示されると思います。スクリプト実行時に引数として渡した[username] と [password]でログイン可能です。

f:id:tell-k:20111106210600p:image

今回作ったヤツのファイルレイアウトはこんな感じに出来てると思います。カレントパスに引数として指定した[app_name]というディレクトリが出来きていると思います。


[app_name]
│
├── Procfile
├── apps
│        ├── __init__.py
│        ├── flaskr.py
│        ├── manage.py
│        ├── static
│        │        └── style.css
│        └── templates
│                   ├── layout.html
│                   ├── login.html
│                   └── show_entries.html
└── requirements.txt

メモ

Amazon RDS周りは、いくつかはまった所があるのでメモしておきます。

Heroku が連携できる RDS のリージョンは 「us-east」 のみ

Heroku -> RDS のアクセスを許可するためにRDS側で「HerokuのEC2セキュリティグループ」を設定しているが、それが「us-east」だかららしい。なので、ap-north-eastとかでRDSを作ってもHerokuからアクセスさせる設定ができないので注意。

RDSの utf8 の設定

RDS のDBインスタンスを作るときに、DB Paramter Group で「character_set_database」を「utf8」に変更しても、実際にDBが新規作成された時に、「character_set_database」が「 latin1」になってしまって日本語が文字化けする。

ParamterGroupでは自分がやった限りでは、どうしようもなかったので、SQLAlchemyでDBを新規作成する時に、下記のようにして逃げた。

@manager.command
def syncdb():
    db.engine.execute('alter database default character set utf8;')
    db.create_all()

最後に

とりあえず、herokuはそのままでも大丈夫だと思いますが、RDSはインスタンスが起動したままだとお金がかかるので、必要がなくなったら管理画面上で削除する事をおすすめします。

参考

2011-10-20

SQLAlchemyで日付情報の国際化対応(I18N)対応

お久しぶりです。tell-kです。L10NI18N が Localization と Internationalization の略で、10とか18とかは間の文字数だと知って「イカス!!」と思ってから幾数年です。

実は、大分前に書いたgistを眺めてたら、なんだこれ?って全く思い出せなかったのでブログにメモっておきます。

やりたい事

お題としては、DBに入ってる日付情報の海外のTZ対応みたいな感じでしょうか。

やりたい事はしごく簡単で、DBに入ってる日付情報を表示するような、ブログみたいなWebアプリがあったとして日本からアクセスしたら日本時間で表示、海外からアクセスしたら海外の時間で表示したいなんてケースです。

簡単に書くとこんな感じ

仮に"Asia/Tokyo"のタイムゾーンでそもそもDBにデータが入っているとして。

DBの日付情報: 2011-10-20 23:00:00.073015+09:00

  ↓ 「Canda/Atlantic」 からアクセス
 
画面に表示:  2011-10-20 11:00:00.735415-03:00

というのをSQLAlchemy のモデルでやる時に、どうやるのが簡単かという事を考えたのが下記の内容だった(ハズ。。。)

こんな感じにしてみた

#!/usr/bin/env python
#-*- coding:utf8 -*-

from datetime import datetime
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, Date
from sqlalchemy.types import TypeDecorator
from sqlalchemy import DateTime as SdateTime
import time
import pytz

#setting client timezone
#tz = pytz.timezone('Asia/Tokyo')
tz = pytz.timezone('Canada/Atlantic')

class DateTime(TypeDecorator):
    impl = SdateTime
    BASE_TZ = 'Asia/Tokyo'
    def process_bind_param(self, value, engine):
        return value
    def process_result_value(self, value, engine):
        return value.replace(tzinfo=pytz.timezone(self.BASE_TZ)).astimezone(tz)

dsn = 'sqlite:////Users/tell_k/test.db'
engine = create_engine(dsn, convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
                                 autoflush=False,
                                 bind=engine))

Base = declarative_base()
Base.query = db_session.query_property()

class Entry(Base):
    __tablename__ = 'entries'
    id = Column(Integer, primary_key=True)
    cdatetime = Column(DateTime, default=datetime.now, nullable=False)

if __name__ == "__main__":
    Base.metadata.drop_all(bind=engine)
    Base.metadata.create_all(bind=engine)

    db_session.add(Entry())
    db_session.commit()

    e = db_session.query(Entry).first()
    print e.cdatetime

SQLAlchemy には TypeDecoratorという便利なものがあってtypeを拡張する事ができる。これを利用して、プロパティを参照した時のフックメソッド「process_result_value」の中で、タイムゾーンをごにょごにょすればおkという感じ。

余談

from sqlalchemy import DateTime as SdateTime

.
.

class DateTime(TypeDecorator):
    impl = SdateTime

.
.

class Entry(Base):
    __tablename__ = 'entries'
    id = Column(Integer, primary_key=True)
    cdatetime = Column(DateTime, default=datetime.now, nullable=False) #カラム定義を変えたくなかった。


なんで上記のようにわざわざSQLAlchemyのDateTimeをSdateTimeとしてインポートして, あらたに「class DateTime」というtypeを作ってるのかというと、カラム定義に利用している「DateTime」を書き換える事なく、日付表示の切り替えを対応したかったから。

いっぱいモデルがあると書き換えて確認するのが面倒だからそうしたような気がする。ただこれは明示的でなく、デフォルトのDateTimeと混同しそうなので、素直に別のtypeを作った方が多分良い。

2011-10-09

Mac で virtualenvした時に遭遇したエラー

macでvirtualenvした時に遭遇したエラー

distutils.errors.DistutilsPlatformError: $MACOSX_DEPLOYMENT_TARGET mismatch: now "10.4" but "10.7" during configure

Distutilsが「$MACOSX_DEPLOYMENT_TARGET」とかいう環境変数を参照してるらしい。

とりえあず、下記のように環境変数を設定して再度実行したら、virtualenvできた。

export MACOSX_DEPLOYMENT_TARGET=10.7

2011-10-05

FlaskでBasic認証

タイトル通りな感じで試したのでメモっておく。というかFlaskのドキュメントに載ってた。Flaskアプリ側でBasic認証を掛けたいという欲求

HTTP Basic Auth | Flask (A Python Microframework)

1. 準備

適当に2ページ分の出力をするFlaskアプリがあるとする。

from flask import Flask
app = Flask(__name__)

@app.route("/")
@requires_auth
def index():
    return "Hello Index!"

@app.route("/hello")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run()

これを main.py とかいうファイル名だとする。

2. Basic認証用のデコレータを用意する

HTTP Basic Auth | Flask (A Python Microframework)

from functools import wraps
from flask import request, Response

def check_auth(username, password):
    """This function is called to check if a username /
    password combination is valid.
    """
    return username == 'admin' and password == 'secret'

def authenticate():
    """Sends a 401 response that enables basic auth"""
    return Response(
    'Could not verify your access level for that URL.\n'
    'You have to login with proper credentials', 401,
    {'WWW-Authenticate': 'Basic realm="Login Required"'})

def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            return authenticate()
        return f(*args, **kwargs)
    return decorated

ここのコードをもってきて、「decorator.py」とかいうファイル名にする。

ここまででこんなファイル構成構成になってる

/decorator.py
/main.py

3. デコレータ使ってみる

作ったデコレータを使ってみる。

from flask import Flask
from decorator import requires_auth # <- 追加 
app = Flask(__name__)

@app.route("/")
@requires_auth # <- 追加
def index():
    return "Hello Index!"

@app.route("/hello")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run()

これで 「http://127.0.0.1/」にアクセスすると、ID/PASSが求められるので、「admin/secret」て入力すると認証が通る。

4. サイト全体でBasic認証をかける

上のまんまだと、「http://127.0.0.1/hello」にはデコレータが無いのでBasic認証かからない。hello のviewにもデコレータを設定すればよいが、viewがいっぱいあると、面倒、

Flaskは、全てのviewアクションに対して、前処理を入れられるフックポイントが用意してあるのでそれを利用してサイト全体にBasic認証をかける。

from flask import Flask
from decorator import requires_auth 
app = Flask(__name__)

@app.before_request # <- 全てのviewで前処理を行うためのdecoratorを使った関数を用意
@requires_auth          #<- ここでBasic認証のdecoratorを使う
def before_request():
    pass

@app.route("/") # <- 個別のviewからはデコレータを外した。
def index():
    return "Hello Index!"

@app.route("/hello")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run()

これで サイト全体でBasic認証がかかるようになりました。Flaskが動く環境が有れば、Google App Engineとかでも大丈夫。

参考

( 三鷹の力をみたかー )

Connection: close