モブプロだった人のブログ

モブプログラミングに魅了されたエンジニアのブログ

Flask(Python) 標準の session と Flask-Session の違い

本日、同僚のエンジニアとハッカソンみたいなことをやってきました。

なかなか盛り上がったのですが、その中で「Flask 標準の session と Flask-Session ってどんな場面で使い分けるの?」をしっかり理解できていないと感じ、いろいろ勉強したのでまとめておきます。

なお、英語だと良記事が多いのですが、英語アレルギーの同僚のために日本語でまとめる、という裏テーマもあります。

f:id:kaorr_mob:20180101080410p:plain

標準の session について

早速ハマったのですが、こちら名前だけ見るとセッション管理に関する何かに見えます。(見えますよね・・・?)

しかし、一般的な Web アプリケーションでいうところのセッション管理とは若干意味合いが異なっています。

実際のコードとデータを見てもらうと分かりやすいのですが、

@app.route("/")
def top():
    session['foo'] = 'foo'
    return render_template('top.html')

前後は省略しますが、session[key] に対して何か値を入れてみましょう。

その後、ページにアクセスし、開発者ツールなどでクッキーを見てみましょう。

f:id:kaorr_mob:20180505230447p:plain

はい、session という名前のクッキーが出来ましたね。

この文字列を取り出して見ると、eyJmb28iOiJmb28ifQ.Dc9K2Q.wa2YaXF3ahbq1YmVdDp82y6jhRA となっています。

これは、base64 でエンコードされた文字列になっています。. は区切り文字になっており、1つ目に上記で設定した foo に関するデータが入っています。2つ目以降は値のチェック用です。

では、1つ目の文字列を base64 でデコードしてみましょう。その際、文字列の最後に == を付与します。base64 は4文字ずつ変換する仕様で、文字列の長さを4の倍数にするために、足りない部分を = で埋める必要があります。

import base64
base64.urlsafe_b64decode('eyJmb28iOiJmb28ifQ==')
#=> b'{"foo":"foo"}'

はい、設定した foo に関するデータが、そのまま出てきましたね。

ちなみに、上記は b'{"foo":"foo"}' と短いためこのような仕様ですが、これがある一定の長さを上回ると仕様が変わり、zlib での圧縮が追加されます。↓の部分で分岐します。

https://github.com/pallets/itsdangerous/blob/master/itsdangerous.py#L878

実際にやってみましょう。

@app.route("/")
def top():
    session['foo'] = 'foo' * 20
    return render_template('top.html')

そして、ページにアクセスし、クッキーを取得します。今回は、.eJyrVkrLz1eyApFkI6VaANY3HE8.Dc9QcQ.6WhEXofYJc2H-uOUvMeTeXnLL5s になりました。先頭に . が付与されていれば OK です。

import base64
import zlib
zlib.decompress(base64.urlsafe_b64decode('eJyrVkrLz1eyApFkI6VaANY3HE8='))
#=> b'{"foo":"foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo"}'

はい、無事にデータを取得できましたね。

以上のことから、標準の session はクッキーに全ての値を入れているだけであることが分かります。

Flask-Session について

詳しい導入方法は公式サイトを見て頂くとして、今回は sqlalchemy で動作させたいと思います。PostgreSQL を使います。

設定を終え、ブラウザのクッキーをクリアした後にページにアクセスすると、以下の通り名前は session と同じですが、文字列の雰囲気が変わったことに気付きます。

f:id:kaorr_mob:20180505233824p:plain

では、セッションテーブルの様子も見てみましょう。デフォルトでは、sessions というテーブル名です。

まず、テーブル定義ですが、以下の通りです。

f:id:kaorr_mob:20180505234346p:plain

次に、レコードですが、以下の通りです。data がバイナリデータのため長すぎて切れていますが、雰囲気は感じ取って頂けるかと思います。

f:id:kaorr_mob:20180505234909p:plain

以上のことから、Flask-Session は一般的な Web アプリケーションでいうところのセッション管理の役割を果たすものであることが分かります。

おわりに

というわけで、かなりざっくりな感じになってしまいましたが、Flask 標準の session と Flask-Session の違いをまとめてみました。

私自身まだ理解し切れていない部分も多いため、「ここ違うよ!」といった箇所がありましたら、ご指摘頂けますと有り難いです。

「BPStudy#125〜テスト駆動開発(TDD)の真髄」に参加してみて

先日、以下の勉強会に参加してきました。

まとめもあります。

社外の勉強会というと、以前に主催(というほど何もしてないけど)側で参加して以来でした。

学生時代は、大規模なイベントに足を運んで、そこで開催されているセミナーとかに参加したことがありましたが、アプリケーション開発のエンジニアとして働き出してから参加するのは初めてだったので「はてさてどんなものかしら?」という感じでした。

f:id:kaorr_mob:20180101080410p:plain

結果、めちゃくちゃ面白かったです! また機会があれば参加したいと思いました。

ただ同時に、よく Twitter などで登壇者側の人が言う「登壇しない立場で勉強会に何度も参加しても意味ない」みたいな言葉の意味も理解できました。

参加して楽しかった!」で終わることがほとんどのパターンですよね。それを実際の仕事に結びつけて価値を出すのって、かなり大変。

なので、今回の勉強会で得たものをしっかりと仕事なりプライベートプロダクトに結びつけることが出来るまでは、他の勉強会は良いかなぁという感じです。もしくは、LT 枠で登壇するか。

当日のメモ

当日、勢いでメモったものがあるので、改めてまとめておきます。未来の自分のために。

テスト駆動開発は自習(写経)に向いている

テスト駆動開発

テスト駆動開発

t_wada 先生が翻訳された本です。

本の中にはコードが大量に出てくるので、写経するのに向いていると説明されていました。

今読んでる本が読み終わり次第、購入して写経したいと考えています。

設計をせずにテストから書き始める方法「ではない」

よくある勘違いのようです。

TDD というと「とにかくテストを書き始めることからスタート!」のイメージがありましたが、そんなことはもちろんありません。ちゃんと設計はしますよ、という話。

たくさんテストを書くことになるが、まずは小さいところからクリアにしよう

結果的には多くのテストコードが必要になりますが、その最終形ばかりを見てウッとならずに、小さいところから進めていきましょう。

ひょっとすると実は一番難しいかもしれないのは、大きい問題を分解して、順番を決めて、着手すること

うーん。これは TDD の文脈だけではなく、仕事など全般に言えることですね。

大きい問題は、どこから手をつけて良いか分からなくなり、モチベーションが低下しがちですね。

大きい問題を大きいまま解決できる人なんてそうそういないので、しっかりと自分が理解できる大きさにまで分解して、進める必要がありますね。

最初に文章として書いて、それをリスト化していく

これは面白いやり方だなぁと思いました。

自分がその機能に求めること、そのテストに求めることをまずは普通の(箇条書きではない)文章で書いていく。

その後、その文章を分解してリスト化していく。そして、そのリストにそってテストを書いていく。

ぜひ実践したいです。

コアロジックから優先的にテストを書いていく

MVC モデルの V などは、テストを書く手間がかかる割りに、賞味期限が短い(ことが多い)のです。

全部のテスト書かなきゃ!」と思うことは多いでしょう。そして、全部書かないとなんか見栄えが悪い、ということもあるでしょう。

テストに理解のない人が周りに多いと、「なんでちゃんと全部書かないの?」と詰められることもあるかもしれません。

しかし、時間などのリソースは有限です。コストパフォーマンスの悪いテストコードをたくさん書くより、コストパフォーマンスの良いテストコードに集中していきましょう!

テストの内容を具体的にする(考える負荷を減らす)

「数値を文字列に変換する」ではなく「数値 1 を文字列 '1' に変換する」といったイメージです。

曖昧なゴールを設定すると、コードを書いているときに思考がブレてきて、考える負荷が上がってきます。

なので、より具体的なゴールに変更できないか、考え直すのが大事です。

意味のないテストが意味ありげに見えるのは問題

これは反省ポイントですね・・・。

なんとなく追加したテストとか、不必要に重複したテストとか、書いた本人は経緯を知っていても、他人は知らないことがほとんどです。

また、書いた本人でさえ、しばらくしたら忘れるものです。そうすると、そういったテストコードが、今度は全体の生産性を下げだします。

「このテスト、なんで追加したんだろう・・・? でも、あるから保守しないとなぁ・・・」となるのです。

自分の記憶が鮮明なうちに、不要なテストコードは大胆に削除しましょう!

Python で get や set メソッドよりも素のままの属性を使う

「Effective Python」を久しぶりに読み返して、当時は特に何も思わなかった箇所に引っかかったのでメモを残しておきますシリーズ第7弾。

Effective Python ―Pythonプログラムを改良する59項目

Effective Python ―Pythonプログラムを改良する59項目

Python で get や set メソッドよりも素のままの属性を使う

他の言語から Python に移ってきたプログラマは、ごく自然にクラスの中でゲッターやセッターメソッドを明示的に実装しようとする

ふむふむ、なるほど。確かに昔 Java を勉強してた時、ゲッターとかセッターとかちょくちょく書いてました。

class User1:
    def __init__(self, age):
        self.age = age

    def get_age(self):
        return self._age

    def set_age(self, age):
        self._age = age


if __name__ == '__main__':
    user1 = User1(30)
    user1.set_age(user1.get_age() + 1)

↑こんな感じのやつです。

ただ、見てわかる通り、age に 1 を足したいだけなのに、やけにゴチャゴチャした感じになってますね。

こういうのは「Python 流ではない」みたいです。

では、どうするかというと、

class User2:
    def __init__(self, age):
        self.age = age


if __name__ == '__main__':
    user2 = User2(30)
    user2.age += 1

まあ簡単。

いや、ふざけてないです。本当に必要なのか考えずに、おまじない的にゲッターやセッターを書くのはやめましょうという話ですね。

さて、しばらくして特別な振る舞いが必要になった時どうすれば良いんでしょうか?

例えば、年齢は200歳まで許容するが、それ以上は例外処理にしたい場合、

class User3:
    def __init__(self, age):
        self.age = age

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        if age > 200:
            raise ValueError('age must be <= 200')
        self._age = age


if __name__ == '__main__':
    user3 = User3(30)
    user3.age += 1

    user3.age += 170
    # ValueError: age must be <= 200

デコレータを付けました。これで OK です。

Flask + Flask-SQLAlchemy のチュートリアルを日本語で (Relationships 編)

チュートリアル関連記事

はじめに

今回は、既存の Model(User) に Relationships を追加してみたいと思います。

本格的にやるには、もっと色々考慮するポイントがありますが、まずはざっくりと Relationships を追加してみます。

追加する Model ですが、ユーザごとの投稿記事(Post)のものです。

各ファイルの修正

Python ファイル修正

hello.py ファイルを以下のように修正します。

# 〜〜省略〜〜

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True)
    posts = db.relationship('Post', backref='user', lazy=True) # 複数の Post への紐付け

    def __init__(self, username, posts=[]):
        self.username = username
        self.posts = posts # ユーザ追加時は基本的に空だと思いますが、念のため追加できるようにしておく

# Post クラスを追加
class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(120), nullable=False)
    body = db.Column(db.Text, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) # 一人の User への紐付け

    def __init__(self, title, body, user_id):
        self.title = title
        self.body = body
        self.user_id = user_id

    def __str__(self):
        return self.title

# 〜〜省略〜〜

テンプレートファイル修正

templates/top.html ファイルを以下のように修正します。

<!-- 省略 -->

  <table class="table">
    <thead>
      <tr>
        <th>ID</th>
        <th>名前</th>
        <th>投稿</th> <!-- 項目追加 -->
        <th>詳細</th>
      </tr>
    </thead>
    <tbody>
      {% for user in user_list %}
      <tr>
        <td>{{ user.id }}</td>
        <td>{{ user.username }}</td>
        <td>{{ user.posts|join(', ') }}</td> <!-- 投稿のタイトルをカンマ区切りで表示 -->
        <td>
          <a class="btn btn-success" href="{{ url_for('show_user', user_id=user.id) }}">
            <span class="glyphicon glyphicon-search" aria-hidden="true"></span> 詳細
          </a>
      </td>
      </tr>
      {% endfor %}
    </tbody>
  </table>

<!-- 省略 -->

動作確認前のデータ準備

Python インタプリタを起動し、以下を実行します。

# 色々 import
from hello import db, User, Post

# 一旦データベースを作り直す
db.drop_all()
db.create_all()

# admin ユーザを再作成
user = User('admin')
db.session.add(user)
db.session.commit()

# admin ユーザの投稿を2件作成
post1 = Post('title1', 'body1', user.id)
post2 = Post('title2', 'body2', user.id)
db.session.add(post1)
db.session.add(post2)
db.session.commit()

動作確認

トップページにアクセスすると・・・、

f:id:kaorr_mob:20180121214022p:plain

投稿したタイトルがうまく表示されていますね!

おわりに

今回は、本当にざっくりと Relationships を追加してみました。

また、データベースをばっさりと再作成していますが、本当は Flask-Migrate とか使うと良いと思います。が、説明に時間が掛かりそうなので、今回はばっさりといきました。

次回は、投稿データをどんな感じに表示するのか、どんな感じに変更、削除するのかを考えながら進めていきたいと思います。

Python の Type Hints を今更ながら勉強してみる

最近、開発環境をより便利にするために色々調査していて、今更ながら Type Hints が気になってきたので勉強してみることにしました。

Type Hints とは Python 3.5 から導入されたものです。

業務で扱っている Python が 2 系のため、そもそも対象外と考えて今まであまり意識したことがなかったのですが、2 系でも対応できるような書き方があったんですね。

んで、PyCharm などの IDE を使っているとそれらを自動的に良い感じにやってくれることが分かり、

こりゃキャッチアップしとかにゃマズイ!

という感じです。

で、実際にコードを書いて試してみたのですが、

def get_full_name(first_name, last_name):
    # type: (str, str) -> str
    return first_name + ' ' + last_name


if __name__ == '__main__':
    full_name = get_full_name('Stevie', 'Wonder')

ここまで書いて、その後に full_name. と打つと、

f:id:kaorr_mob:20180119225515p:plain

すんばらしいですね。

ぜひ、今後は積極的に使っていきたいと思います。