Posted on

Python Bottleのソースを読む テンプレート編

前回に続きPythonの軽量WebフレームワークBottleのソースを読む テンプレート編
 

Class Hierarchy

ここの部分
20160206230621
 

Bottleのテンプレートについて

Bottleのテンプレートは、Simple、Cheetah、Jinja2、Makoの4種類があり、BaseTemplateを継承している
Pythonのテンプレートの種類(参考)
http://www.cmscom.jp/blog/af0ga8

テンプレートの使い方

テンプレートの拡張子には以下が利用できる

extensions = ['tpl', 'html', 'thtml', 'stpl']

例えばjinja2を使う場合は以下のように呼び出すことができる(jinja2をpip installする必要あり)

@route('/')
def jinja2():
    name = 'text'
    return jinja2_template('jinja2', name=name)

view

{{ name }}

テンプレート呼び出しの処理をみてみる

各テンプレートは以下のように設定されている。 functools.partialにfunc templateが渡されている

mako_template = functools.partial(template, template_adapter=MakoTemplate)
cheetah_template = functools.partial(template, template_adapter=CheetahTemplate)
jinja2_template = functools.partial(template, template_adapter=Jinja2Template)

jinja2_templateが呼ばれた時の処理

*argsにはjinja2が、**kwargsにはtemplate_adapter=Jinja2Templateが渡される

def template(*args, **kwargs):

引数からjinja2テンプレート名を取得

tpl = args[0] if args else None

Jinja2Templateを取得

adapter = kwargs.pop('template_adapter', SimpleTemplate)

テンプレートパスをlookup(パスは./views/か./)

lookup = kwargs.pop('template_lookup', TEMPLATE_PATH)

Jinja2Templateをインスタンス化

TEMPLATES[tplid] = adapter(name=tpl, lookup=lookup, **settings)

BaseTemplateのコンストラクタの最後のprepareでJinja2Templateのprepareが呼ばれる

self.prepare(**self.settings)

ここではじめてjinja2がimportされる

from jinja2 import Environment, FunctionLoader

テンプレートのrenderメソッドにてレンダリングしviewを返す

return TEMPLATES[tplid].render(kwargs)

BottleのテンプレートはBaseTemplateを継承しprepareとrenderを実装することで使える仕組みになっている

def prepare(self, **options):
""" Run preparations (parsing, caching, ...).
It should be possible to call this again to refresh a template or to
update settings.
"""
raise NotImplementedError
def render(self, *args, **kwargs):
""" Render the template with the specified local variables and return
a single byte or unicode string. If it is a byte string, the encoding
must match self.encoding. This method must be thread-safe!
Local variables may be provided in dictionaries (args)
or directly, as keywords (kwargs).
"""
raise NotImplementedError
Posted on

Python Bottleのソースを読む 起動編

Pythonの軽量WebフレームワークBottleのソースを読む 起動編

Bottleとは

Bottle is a fast, simple and lightweight WSGI micro web-framework for Python. It is distributed as a single file module and has no dependencies other than the Python Standard Library.

Pythonの軽量Webフレームワークで、特徴はシンプルで早く、Pythonの標準ライブラリにも依存していないWebフレームワークであることとフレームワーク本体が1ファイルで構成されていることである

Class Hierarchy

Doxygenを使って出力した図
20160204171644
20160204171658
Bottleは1ファイルながら中でそれぞれのクラス、主にServer、Templateが継承関係にあるのがわかる(コード量は4000行位)。ServerやTemplateクラスはたくさんあるが実際はその中のどれかを選択して利用する形となる

起動

Bottleの起動はrun()を呼び出す方法とコマンドラインインターフェースを使う方法が用意されている

run()を使う方法

以下のように記載し起動することでサーバが立ち上がる

from bottle import run, route
@route('/')
def index():
    return 'Hello World'
run(host='localhost', port=8000, debug=True)

コマンドラインインターフェースを使う場合

以下のコマンドで起動可能

# コントローラを指定
python -m bottle 'package.controller'
# 説明は省略するが明示的にアプリを指定することも可能
python -m bottle 'package.controller:app'

controller.py

# runは不要
from bottle import ,route
@route('/')
def index():
    return 'Hello World'

起動処理を確認する

コマンドラインインターフェースを使う場合

mainが2箇所あるが、これはサーバアダプダに必要なライブラリを必要としているからで、1つ目のmainでまずサーバアダプタに必要なライブラリを読み込み2つ目のmainでサーバが起動される仕組みになっている

run()でサーバを起動する

run()では、渡された引数の値をそれぞれ読み込んだあと最後にServerAdapterのrun()を呼び出している。Bottleでは多くのサーバをServerAdapterを継承することでサポートしており、指定されたサーバを起動するようになっている。指定しない場合はwsgirefがデフォルトで呼ばれる。また、appパラメータは特別指定しなければ、内部で自動的にdefaultが使われるので特別指定する必要はない。

サーバをロードするまで処理を追ってみる

まず、Bottleでサポートしているサーバは以下のように宣言してある。

server_names = {
    'cgi': CGIServer,
    'flup': FlupFCGIServer,
    'wsgiref': WSGIRefServer,
    'waitress': WaitressServer,
    'cherrypy': CherryPyServer,
    'paste': PasteServer,
    'fapws3': FapwsServer,
    'tornado': TornadoServer,
    'gae': AppEngineServer,
    'twisted': TwistedServer,
    'diesel': DieselServer,
    'meinheld': MeinheldServer,
    'gunicorn': GunicornServer,
    'eventlet': EventletServer,
    'gevent': GeventServer,
    'geventSocketIO': GeventSocketIOServer,
    'rocket': RocketServer,
    'bjoern': BjoernServer,
    'aiohttp': AiohttpServer,
    'auto': AutoServer,
}

サーバを指定するには、run()実行時にserverとして必要な値を文字列で指定して渡す。以下に記載の通りデフォルトでは’wsgiref’が指定されている。

def run(app=None,
        server='wsgiref',
        host='127.0.0.1',
        port=8080,
        interval=1,
        reloader=False,
        quiet=False,
        plugins=None,
        debug=None,
        config=None, **kargs):

このあたりでサーバを取得し設定しておりServerAdapterを継承していない値が渡された場合はサポート外としてExceptionを投げる

if server in server_names:
    server = server_names.get(server)
if isinstance(server, basestring):
    server = load(server)
if isinstance(server, type):
    server = server(host=host, port=port, **kargs)
if not isinstance(server, ServerAdapter):
    raise ValueError("Unknown or unsupported server: %r" % server)

WSGIRefServerなどは標準エラーを出力する設定になっている

server.quiet = server.quiet or quiet
if not server.quiet:
    _stderr("Bottle v%s server starting up (using %s)...\n" %
            (__version__, repr(server)))
    _stderr("Listening on http://%s:%d/\n" %
            (server.host, server.port))
    _stderr("Hit Ctrl-C to quit.\n\n")

reloaderをTrueにした場合はFileCheckerThread(ファイルの変更をチェックして変更があったら自動でリロードする)を立ち上げて起動する

if reloader:
    lockfile = os.environ.get('BOTTLE_LOCKFILE')
    bgcheck = FileCheckerThread(lockfile, interval)
    with bgcheck:
        server.run(app)
    if bgcheck.status == 'reload':
        sys.exit(3)
else:
    server.run(app)

簡単に起動までの流れをおってみたが、細かく見てみると他にも起動オプションがあるので必要に応じて設定するとよい
ざっと見てみたところBottleは1ファイルで構成されているので中身を確認しやすくPython初級者の私にとってはとっつきやすいフレームワークに思う。起動自体も1ファイルだけでできるので開発も簡単に始められる。ただその分逆にどのようにアプリケーションとしてファイルを構成していけばいいかが分かりにくいというデメリットもあるのではと感じた。最終的には自分なりに構成していけばいいのだがその辺はまた追って確認していこうと思う。

Posted on

Gitのソースコードをデバッグする

これまでGitの内側の仕組みなどをチェックしてきて、最近ようやくソースコードをデバッグしてみても処理が追えそうというところまできたので記事にまとめておきます。ただ色々試しながらやった結果なので、もっとよいやり方はあるかもしれません。

emacsとgdbをインストール

今回OSはCentOSを使っていきます。

sudo yum install emacs
sudo yum install gdb

gitのソースコードをクローンする

今回はGitHubにあるソースコードを利用します。

git clone https://github.com/git/git.git
cd git
# バージョンを指定してチェックアウトしておきます。
git chekcout -b v2.6.0-rc1

ソースコードをコンパイルする

まず必要なライブラリをインストールしてからコンパイルします。

yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-ExtUtils-MakeMaker
make

emacsを起動してデバッグをする

環境が整ったので、ここからgdbを起動しgitコマンドのデバッグをしていきます。gdbはそのまま起動しても使えますが、emacsからの方がデバッグしやすいのでemacsからgdbを起動します。

# emacsを起動する
emacs -nw
# gdbを起動する
M-x
gdb
gdb --annotate=3 git
# main関数で止まるようにブレークポイントを指定します
(gdb) b main
# 実行
(gdb) r

デバッガが起動しました。画面上側がコマンドラインで、下側がソースになっています。
20150915005627

# ステップ実行(next)
(gdb) n
# 変数の内容をチェック
(gdb) print argc
$1 = 1
# ステップイン(step)
(gdb) s
# 最後まで実行(continue)
(gdb) c

gitコマンドを実行した場合、676行目までいき usage と The most commonly used git commands が表示されて終了します。

    /* The user didn't specify a command; give them help */
    commit_pager_choice();
    printf("usage: %s\n\n", git_usage_string);
    list_common_cmds_help();
    printf("\n%s\n", _(git_more_info_string));
=>  exit(1);

まとめ

普段開発ではIntelliJを利用しているのでCLionでもできないかと色々試してみましたが、そもそもMacでコンパイルを通すこともできず断念しました。ただおかげでLinux環境でもなんとかやっていけそうなので、このままコードリーディングを進めてみようと思います。

参考

Posted on 1 Comment

Gitの参照 – HEADとheadsとtagsとremotes

Gitの参照についてまとめました。また別記事にてGitの内側について記載しています。

Gitの内側についておさらい

  • 「git init」すると「.git」ディレクトリができるがその中についてがテーマ
  • 普段は使わない配管コマンドと呼ばれる下位レベルのコマンドがある
  • 4つのオブジェクトがある
    • blobオブジェクト(ファイルに相当)
    • treeオブジェクト(ディレクトリに相当)
    • commitオブジェクト(commit情報を保持)
    • tagオブジェクト(Tagを保持)

Gitの参照って?

ここで取り上げるGitの参照は「HEAD」ファイルと「.git/refs」配下のことを示します。「git init」した直後「HEAD」ファイルは「refs/heads/master」を参照し(初期化直後はなにも存在しない)、「.git/refs」配下には、headsとtagsディレクトリがあります。ブランチにcommitするとheads配下にブランチ名のファイルができ、HEADファイルはheads配下のブランチ名を参照します。

# masterにcommit後
# HEADファイル
ref: refs/heads/master
# refs配下の構造
└── refs
    ├── heads
    │   └── master
    └── tags

Gitの参照の種類

Gitの参照の種類は次の通りです。

  • heads:作業中ブランチのcommitを参照
  • tags:commitをわかりやすくする為の名前付け用
  • remotes:リモートブランチのcommitを参照
  • HEAD:作業中のブランチに対するシンボリック参照。HEADは基本的にheadsを参照しているので、他の参照と区別する為シンボリック参照と呼ばれます

動作確認の為の事前準備

次のような3つのcommitがあるリポジトリを用意しremoteへpushしておきます。

% git log --pretty=oneline master
1f36229bc26825222b191dcd63929392d9d8e2fd third commit
543ee947f5a865e1a998f793120667c3bf89fc80 second commit
23391397b4316d334e9293c4f7d1d601c1f24c4c first commit
% git remote add origin git@github.com:taisa007/internal-git.git

次からはこのリポジトリを利用します。クローンする場合はこちらを利用してください。

構成

構成は次の通りです。「.git/refs」ディレクトリ配下にそれぞれheads, remotes, tagsというディレクトリが作られています。

% tree .git
└── refs
    ├── heads
    │   └── master
    ├── remotes
    │   └── origin
    │       └── master
    └── tags

Gitの参照の解説

「refs」配下にあるファイルはそれぞれ最後のcommitをファイルに記録しています。例えば現在masterは「third commit」にいますが「git log –pretty=oneline master」とすると次のようなcommit履歴が確認できます。

% git log --pretty=oneline master
1f36229bc26825222b191dcd63929392d9d8e2fd third commit
543ee947f5a865e1a998f793120667c3bf89fc80 second commit
23391397b4316d334e9293c4f7d1d601c1f24c4c first commit
# このコマンドでも上記と同じ結果となる
% git log 1f36229bc26825222b191dcd63929392d9d8e2fd
1f36229bc26825222b191dcd63929392d9d8e2fd third commit
543ee947f5a865e1a998f793120667c3bf89fc80 second commit
23391397b4316d334e9293c4f7d1d601c1f24c4c first commit
# masterファイルの中を確認
% cat .git/refs/heads/master
17fb14ac7c9493751cff838ba0c22897582406a5
# 現在の位置のハッシュ値が書かれている

これはハッシュを指定した次のコマンド「git log –pretty=oneline 17fb14ac7c9493751cff838ba0c22897582406a5」と同じ結果です。「.git/refs/heads/master」ファイルにcommit情報が記述されている為同じ結果となります。
上記のことから「.git/refs/heads/master」にcommitのハッシュ値を指定することでmasterのheadが変更可能です。ただし直接操作は非推奨(通常操作することはありませんが)なので参照を変更する時は次のコマンドを使います。

# 参照を変更
% git update-ref refs/heads/master 543ee947f5a865e1a998f793120667c3bf89fc80
# headが変わった
% git log --pretty=oneline master
543ee947f5a865e1a998f793120667c3bf89fc80 second commit
23391397b4316d334e9293c4f7d1d601c1f24c4c first commit
% cat .git/refs/heads/master
543ee947f5a865e1a998f793120667c3bf89fc80

「second commit」に変わりました。また次のコマンドはそこからブランチを切ることができます。

% git update-ref refs/heads/test 543ee947f5a865e1a998f793120667c3bf89fc80
# testブランチができた
% git branch
master
test

「.git/refs」ディレクトリの構成を見ていきましたので、次は「HEAD」ファイルと「.git/refs」を構成する各参照について解説します。

HEAD

HEADファイルは現在作業中のブランチを参照しています。「git branch (ブランチ名)」を実行したときは、HEADからブランチが切られます。HEADファイルは 「.git」直下に配置されます。

.git
├── COMMIT_EDITMSG
├── FETCH_HEAD
├── HEAD ← ココ
├── ORIG_HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.sample
│   ├── post-update.sample
│   ├── pre-applypatch.sample
│   ├── pre-commit.sample
│   ├── pre-push.sample
│   ├── pre-rebase.sample
│   ├── prepare-commit-msg.sample
│   └── update.sample
・
・
・
## HEADファイルの中を確認
% cat .git/HEAD
ref: refs/heads/master ← refs/heads配下を参照している

「git checkout test」を実行した内容は次の通りです。

% git checkout test
cat .git/HEAD
ref: refs/heads/master

このHEADファイルを確認したり変更するには次のコマンドを使います。

# 確認
% git symbolic-ref HEAD
refs/heads/test
# 変更
% git symbolic-ref HEAD refs/heads/master
% git symbolic-ref HEAD
refs/heads/master

tags

tagはGitのオブジェクトでもあります。tagオブジェクトはcommitオブジェクトに似ていますが、treeではなくcommitを指し示します。また、tagには軽量 (lightweight) 版と注釈付き (annotated) 版の2つのタイプがあります。

軽量 (lightweight) 版

  • tagをつけられる
  • commitオブジェクトにtagがつく
  • tagオブジェクトではない
# tagを付ける
% git update-ref refs/tags/v1.0 543ee947f5a865e1a998f793120667c3bf89fc80
# 確認
% git tag -n
v1.0            second commit

注釈付き (annotated)版

  • tagとコメントがつけられる
  • tagオブジェクトになる
# tagとコメント付き
% git tag -a v1.1 23391397b4316d334e9293c4f7d1d601c1f24c4c -m 'test tag'
% git tag -n
v1.0            second commit
v1.1            test tag ← コメントが付く
% cat .git/refs/tags/v1.0
543ee947f5a865e1a998f793120667c3bf89fc80 ← second commitのハッシュ
% cat .git/refs/tags/v1.1
0d69685716366ce02ea4452c8e47c64e779a40d8 ← 新しいハッシュ
% git cat-file -p d4f4075dfccb64a4c1b1966b6c7aa853f526c33a
object 23391397b4316d334e9293c4f7d1d601c1f24c4c ← first commitのハッシュ
type commit
tag v1.1
tagger sato.masaki <sato.masaki@aainc.co.jp> 1435421112 +0900
test tag

軽量版は「second commit」のハッシュ値になのに対し、注釈付きは別のハッシュの値となっています。これはcommitに対する直接的な参照ではなく、tagオブジェクトがつくられた為です。また、refsの中は下記の通りです。

└── refs
    ├── heads
    │   ├── master
    │   └── test
    ├── remotes
    │   └── origin
    │       └── master
    └── tags
        ├── v1.0 ← タグ追加
        └── v1.1 ← タグ追加

remotes

remoteもほぼ上記内容と同様の仕組みとなっています。では試しにtestブランチをremoteにプッシュしてみます。するとさっきまでなかった「.git/refs/remotes/origin/test」が追加されます。またその中身はtestブランチのHEADのハッシュです。これはつまりremoteを確認することで最後にoriginと通信したときの情報を取得できるということです。

└── refs
    ├── heads
    │   ├── master
    │   └── test
    ├── remotes
    │   └── origin
    │       ├── master
    │       └── test ← 追加
    └── tags
        ├── v1.0
        └── v1.1
% cat .git/refs/remotes/origin/test
543ee947f5a865e1a998f793120667c3bf89fc80
% cat .git/refs/heads/test
543ee947f5a865e1a998f793120667c3bf89fc80
# 同じハッシュとなる

まとめ

  • 参照にはheadとremoteとtagがある
  • HEADファイルは参照を参照し現在のブランチのシンボリックリンクを参照している
  • tagには軽量版と注釈付き版があり注釈付きの方がtagオブジェクトとなる
  • remoteは最後にサーバと通信したときのハッシュを保持している

Gitの参照と参照系の下位コマンドが確認できました。普段何気なく使っているコマンドの内側で何が行なわれてるかが分かっておもしろいですね。

参考