移転します。

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

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

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

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

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

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

環境

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

前提

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

  • AWS アカウント
  • Heroku アカウント
  • 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」とかいう証明書は、https://aws-portal.amazon.com/gp/aws/developer/account/index.html?action=access-key にアクセスして、「X.509証明書」から「新しい証明書を作成する」とかやれば入手できます。入手したら、~/.ec2 の下に置いてください。

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

注意事項

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

  • Herokuアプリを新規に作成・起動します。
  • AWS RDS に新規インスタンス(最小構成)を作成・起動します。
  • AWS RDS の 「us-east-1」 のデフォルトのセキュリティグループに、「ローカルマシンからのグローバルIPアクセス」と、「Heroku のEC2セキュリティグループからのアクセス」を許可するように設定を追加します。
  • AWS RDS にアプリ用のDB Parameter Groupを追加します。
  • Flask のシークレットキーは勝手にランダム値で決めてます。

スクリプト

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]でログイン可能です。

今回作ったヤツのファイルレイアウトはこんな感じに出来てると思います。カレントパスに引数として指定した[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はインスタンスが起動したままだとお金がかかるので、必要がなくなったら管理画面上で削除する事をおすすめします。

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を作った方が多分良い。

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

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とかでも大丈夫。

なぜAppleStoreは日本全国で7店舗しかないのか?(妄想)

Apple程の大企業が日本全国でなんで7店舗しかないのか、個人的に疑問でした。んでなんとなく最近思った事を書いてみようと思います。あ、妄想ですよ。妄想。

ストア一覧 - Apple Store - Apple(日本)

AppleStoreで体験した事

連休中にAppleCareSupportを登録しようとしたら、なんか巧くできなくて、購入証明書をアップロードしてくださいとか出てきて心が折れたから、電話するのも面倒くさいので、買物がてらAppleStore渋谷店に相談しにいきました。

ちょっと面倒くさそうな感じ話なので、リアル店舗にいっても、適当に追い返されるかなくらいな気持ちを持って。

そしたら、最初に対応してくれた店員さんは、実際に画面を見ながら、一緒に検討してくれて「ちょっと待ってください」と言ったと思ったら、すぐに先週購入した時の履歴を調べてくれて,なんか買った時の店員まで一緒に連れてきてくれたw(先週、この店舗でAppleCareSupportを買った)

で先週の店員さんは、「あ事情はわかりましたので、こちらの方で直接登録しておくから大丈夫ですよー」と言ってくれて、僕の予想とは反してあっけなく終了した。

なんか当たり前のように書いたけど、家電量販店やら携帯ショップとかにいくと「受付カウンター」で受付票片手に結構待たされて、また事情を一から説明して、んで結局駄目で、この書類で、この手順でやっぱりWebからみたいな事を過去に経験した事のある僕にとっては結構驚きでした。

店員がめっさ多い

帰りに店内を見渡してみて、ある事に気がついた。「店員がめっさ多い」AppleStore渋谷店に行くとわかると思いますが、GeniusBarという受付みたいなのがあるけど、それとは別に店内に店員が溢れてる。決して大型とはいえない店舗内の1フロアにざっと見回しただけで
20人近くいる。。。

当然休日だから、混雑する事を見込んでの布陣だと思うけど、それでも多い気がする。

その店員さん達は、場所問わず、色んな所でお客と話しこんでいる。相談したくて、待ってる客がほとんどいない。なんかしかも、外国人対応のためか外国人の店員さんもちらほら。

これは凄いなと普通に感心した。

僕の感覚だと、人件費はなるべく押さえて、商品を効率よく売りさばいた方が良いという感じだったんですが、むしろ、その逆で、1対1の接客にしっかり時間をかけて、そのために待ちが発生しないように、人員を多くしているような印象でした。

AppleStoreの役割

ネットや、家電量販店での販売がメインのAppleが、フラッグシップショップを出す意味合いとしては「製品を売る」事が目的というよりも、「知ってもらう、理解してもらう」という事に重点を置いてるような気がしました。

だからこそ、FaceToFaceでコミュニケーションが取れる店員を多くして、GeniusBarやら、WorkShopやらのイベントをひっきりなしに行っているんじゃないかと。

AppleStoreが7つしかない訳

結局の所、Apple自身が満足のいくAppleStoreを展開するためには、人件費も含めて、時間も労力も普通のお店より圧倒的に掛かるので、携帯ショップや、家電量販店みたいにポンポンだせないんだろうなー。

というのが僕の中での着地点です。

なんか、対比するような話しじゃないけど、ヨドバシカメラが「値下げ交渉にとられる店員の時間」を削減するために「ポイント制」を導入した話しとは、なんとなく逆だなーとも思いました。

まとめ

長々と書いたけど、言いたかった事は「AppleStore渋谷店は素晴らしかったよ。みんな行くといいよ。」という事でした。ヾ(●⌒∇⌒●)ノ

いつぞやは、零細企業とかほざいて本当にすいませんでした。


余談

あくまで妄想ですよ。妄想。今回対応が良かったのは、たまたまという可能性もありますし。おすし。

ベッ別に、ビックカメラとか家電量販店が嫌いじゃなくて, むっむしろ好きなんだからね!

MacOSX (Lion) にRicty3.1.1 をインストールする

MaxOSX(Lion) にしたので、Rictyをインストールしようと思ったら、なんかビルドしないといけないようになっていたので、そのやり方と簡易インストールスクリプトをメモっとく。

前提

  • MacのOSはLion
  • homebrewインストール済
  • sudo つかうよ

インストール

インストールスクリプトはGistにしておいたので、下記コマンドを叩けばおわり。fontforgeのインストールとか結構時間かかるので気長にまっててね(はーと)

curl -O https://raw.github.com/gist/1228270/377a9cd1dfed1c28549acecac918e865c6ad0e54/install_ricty.sh
chmod 0755 install_ricty.sh
sh install_ricty.sh

sudo使ってるから途中で、自分のローカルマシンのID/PASSが求められるので、いれてください。インストールが終わったら

ls -la /Library/Fonts | grep Ricty

Rictyフォントがインストールされてることを確認できます。

余談

fontforgeなのかRicty なのかわからないけど、python2.7系では駄目で2.6系が必要っぽい。なのでデフォルトのpythonのリンク先をpython2.6に変えてる

今回もFontBookガン無視すた。