SlideShare une entreprise Scribd logo
1  sur  91
Télécharger pour lire hors ligne
SQLAlchemy 入門
for Kobe Python Meetup #13
2017/09/15 Kobe Japan
Yasushi Masuda PhD
( @whosaysni )
Tech team, Core IT grp. IT Dept.
MonotaRO Co., LTD.
Pythonista since 2001 (2.0~)
• elaphe (barcode library)
• oikami.py (老神.py)
• PyCon JP founder
翻訳もろもろ
アジェンダ
よくある誤解
SQLAlchemyを3行で



エンジンの基礎 (+hands-on)
SQL式を使う (+hands-on)
ORMの基礎 (+hands-on)
参考文献
オンラインドキュメント:

http://docs.sqlalchemy.org/en/
rel_1_1/



(古いけど)和訳:

http://omake.accense.com/
static/doc-ja/sqlalchemy/
準備
sakila DB SQLite版
https://github.com/jOOQ/jOOQ/jOOQ-examples/Sakila/sqlite-sakila-db/
Sakila
• MySQL エンジニア
作のサンプルDB
• レンタルビデオ屋

(...通じます?) のモ
デル
• BSD ライセンス
スキーマのドキュメントは下記
https://dev.mysql.com/doc/sakila/en/sakila-structure-tables.html
(上記のサイトのSQLiteデータベースバイナリは壊れているので、以下から取得してください)
https://github.com/wallymathieu/sakila-sample-database-ports/blob/master/sqlite-sakila-db/sqlite-sakila.sq
よくある誤解
[WRONG!] ORMライブラリですよね?

むしろ「DB操作フレームワーク」と考えましょう
[WRONG!] でもORMベースだよね?

SAのキモはコネクション管理とSQL式のフレームワークです。ORMがその上に乗ってます



[WRONG!] テーブル定義めんどそう。あと生SQL書いたりできないでしょ?

テーブル定義をしなくても使えます。生SQLの実行も問題ありません。



[WRONG!] SA 使うと値をエスケープしてくれるから安全ですね

値をエスケープしているのはDBAPIです。



[WRONG!] SAは高度すぎて初心者に向かない

SQLAlchemy は難しくないです。むしろ必要なのはSQLとDBの知識です。
[AGREE] ドキュメントよくわからん

そうですね
DB
プログラム
SQL
の生成
クエリの

実行
データ

型変換
パラメタ付き
クエリ
DBドライバ
セットアップ
接続管理
(cache, pool)
結果データ
へのアクセス
値の

エスケープ
スキーマ名の
管理
データ型変換
SQLの方言
クエリ構築
クエリの実行
クエリ結果
スキーマ構造の

定義
O/R マッピング
DB操作の抽象化・高水準化
DB操作
セッション
DB操作にまつわる関心事
SQLAlchemyを
3行で
エンジン: DB接続を管理して、DB間のSQLの
違いや操作方法の違いを吸収する
SQL式: カラムやテーブルをPythonのオブジェ
クトで表現して、SQL文をPythonの式の組み
合わせで書ける
ORM: テーブルをPythonクラスに対応付け
て、インスタンスの操作でレコードをいじれる
SQLAlchemy のドキュメント
SQLAlchemy のドキュメント
SQLAlchemy Core
エンジン
スキーマ定義

SQL 式
SQLAlchemy ORM
O/Rマッパー
宣言的O/Rマッピング
セッション
Dialect
DBバックエンドごとの定義
エンジン Engine
DB接続を管理する仕組み
SQL式 SQL Expression

SQLをPythonの式で表現する仕組み
マッパー mapper

DBレコードをPythonオブジェクトに対応付ける仕組み
ダイアレクト(方言) Dialect
DB バックエンドごとの違いを表現する仕組み
エンジン
使ってみましょう
• エンジンを生成してみましょう
• SQLite版のSakila databaseに接続して、簡単なクエリを
実行してみましょう
準備
sakila DB SQLite版 http://bit.ly/2fdeeft
https://github.com/jOOQ/jOOQ/jOOQ-examples/Sakila/sqlite-sakila-db/sqlite-sakila.sq
スキーマのドキュメントは下記
https://dev.mysql.com/doc/sakila/en/sakila-structure-tables.html
(上記のサイトのSQLiteデータベースバイナリは壊れているので、以下から取得してください)
https://github.com/wallymathieu/sakila-sample-database-ports/blob/master/sqlite-sakila-db/sqlite-sakila.sq
エンジンを生成してみよう
from	sqlalchemy	import	create_engine

e	=	create_engine('sqlite:///sqlite-sakila.sq')

#	str(e)	してみましょう

#	dir(e)	してみましょう

#	help(e)	してみましょう

SQLite以外のDB
#	インメモリ

create_engine('sqlite://')

#	MySQL

create_engine(

			'mysql://scott:tiger@dbserv/dbname')

#	SQL	Server

create_engine(

			'mssql://bill:gates@dbserv/dbname')
ちなみにDB-APIでは
#	sqlite3

>>>	from	sqlite3	import	connect

>>>	con	=	connect('sqlite-sakila.sq')

#	mysql

>>>	from	MySQLdb	import	connect

>>>	con	=	connect(db='sakila',	user='...')

クエリを実行してみよう
>>>	e	=	create_engine('sqlite:///sqlite-sakila.sq')

>>>	q	=	'select	title	from	film	limit	5'

>>>	res	=	e.execute(q)

>>>	res

<sqlalchemy....ResultProxy	object	at	...>

>>>

#	dir(res)	してみましょう

#	list(res)	してみましょう
ちなみにDB-APIでは
#	DBAPIごとに挙動がまちまち

#	sqlite3

>>>	con.execute('select	*	from	film')

<sqlite3.Cursor	object	at	...>

#	mysql

>>>	#	con.execute	がない。カーソルが必要

>>>	cur	=	con.cursor()

>>>	cur.execute('select	*	from	film')

1000L
ResultProxyにアクセスしよう
>>>	q	=	'select	title	from	film	limit	5'

>>>	res	=	e.execute(q)

>>>	for	row	in	res:

...					print(row.title)		#	カラム名のアトリビュート

...

ACADEMY	DINOSAUR

ACE	GOLDFINGER

ADAPTATION	HOLES

AFFAIR	PREJUDICE

AFRICAN	EGG
ResultProxyにアクセスしよう
>>>	q	=	'select	title	from	film	limit	5'

>>>	res	=	e.execute(q)

>>>	for	row	in	res:

...					print(row[0])		#	n	番目のカラム

...

ACADEMY	DINOSAUR

ACE	GOLDFINGER

ADAPTATION	HOLES

AFFAIR	PREJUDICE

AFRICAN	EGG
ResultProxyにアクセスしよう
>>>	q	=	'select	title	from	film	limit	5'

>>>	res	=	e.execute(q)

>>>	for	row	in	res:

...					print(row['title'])		#	辞書アクセス

...

ACADEMY	DINOSAUR

ACE	GOLDFINGER

ADAPTATION	HOLES

AFFAIR	PREJUDICE

AFRICAN	EGG
ResultProxyにアクセスしよう
>>>	q	=	'select	title	from	film	limit	5'

>>>	res	=	e.execute(q)

>>>	list(res)		#	1レコード1タプルのリストになる

[(1,	'ACADEMY	DINOSAUR',	'A	Epic	Drama	of	a	Feminist	
And	a	Mad	Scientist	...]



#	res.fetchall(),	res.fetchone(),	res.fetchmany(),

#	res.keys()	を実行してみましょう

#	dir(res)	してみましょう
update/insertを試そう
#	操作の前に、sqlite-sakila.sq	をバックアップ

してください!



>>>	res	=	e.execute('insert	into	film	(film_id,	
title,	language_id,	last_update)	values	(9990,	
"HOGE",	1,	CURRENT_TIME)')

>>>	res.rowcount		#	更新した行を返す

1



#	"update	set	language_id=99	where

#					language_id=1"	も試してみましょう
パラメタクエリを使おう
#	非推奨(だけどよくある)書き方

param	=	1

e.execute('select	title	from	film	where	
film_id=%s'	%(param)).fetchall()

[('ACADEMY	DINOSAUR',)]

パラメタクエリを使おう
#	文字列挿入だとインジェクションできてしまう

>>>	param	=	'0	union	select	password	as	title	from	staff'

>>	e.execute('select	title	from	film	where	film_id=%s'	%
(param)).fetchall()

[('8cb2237d0679ca88db6464eac60da96345513964',),	('ACADEMY	
DINOSAUR',)]

∵	SQLiteは最終的に以下のクエリを実行する

select	title	from	film	where	film_id=0

union

select	password	as	title	from	staff
パラメタクエリを使おう
e.execute('select	title	from	film	where	
film_id=?',	'0	union	select	password	as	title	
from	staff').fetchall()

[]		#	何もヒットしない

∵	SQLiteは最終的に以下のクエリを実行する

select	title	from	film	where	film_id='0	union	
select	...	staff

#	パラメタクエリの値のエスケープは、

#	SQLAlchemyでなくSQLiteの機能
トランザクションを使おう
>>>	conn	=	e.connect()

>>>	trans	=	conn.begin()

>>>	trans

<sqlalchemy...RootTransaction	object	at	...>

#	dir(trans)	してみましょう

#	begin()	して	update,	insert	したあとで	
trans.rollback()	してみましょう
トランザクションを使おう
>>>	conn	=	e.connect()

>>>	trans	=	conn.begin()

>>>	conn.execute('select	*	from	film	where	
film_id>9000').fetchall()

[(9990,	'HOGE	HOGE',	...)]

>>>	conn.execute('delete	from	film	where	
film_id>9000').rowcount		#	レコードを削除。2行消えた

2

>>>	conn.execute('select	*	from	film	where	
film_id>9000').fetchall()		#	消えたので見当たらない

[]

>>>	trans.rollback()		#	rollback	でなかったことにする

>>>	#	ロールバックしたのでもとに戻っている

>>>	list(conn.execute('select	*	from	film	where	
film_id>9000'))

[(9990,	'HOGE	HOGE',	..)]
トランザクションを使おう
#	with	を使うと、成功なら	commit,	失敗なら	
rollback	をシンプルに書ける

>>>	with	e.begin()	as	conn:

...					conn.execute('update	...')

...					conn.execute
ハンズオン: エンジン基礎
sqlite-sakila.sq	に

エンジンで接続

"DINOSAUR	SECRETARY"

という作品の	actor	
を検索しましょう
ここまでのまとめ
• create_engine() でエンジンを作れる
• engine.execute(query, *parameters) でクエリを実行
• 実行結果はResultProxyオブジェクト

アトリビュート、配列、辞書でアクセスできる

result.fetchall() や list(result) で一括取得もできる
• engine.connect().begin() でトランザクションを作れる

begin() でトランザクション接続を開ける
SQL 式
エンジン: DB接続を管理して、DB間のSQLの
違いや操作方法の違いを吸収する
SQL式: カラムやテーブルをPythonのオブジェ
クトで表現して、SQL文をPythonの式の組み
合わせで書ける
ORM: テーブルをPythonクラスに対応付け
て、インスタンスの操作でレコードをいじれる
俺の考えたさいきょうの...
• SELECT foo, bar, baz ... が

select(foo, bar, baz ...)

みたいに書ければ便利じゃない?
• WHERE foo='blue' AND bar=42 が

where(foo='blue', bar=42) みたいに書けないかな?
• t = table('mytable', foo, bar, baz) みたいに書けば、
t.foo='blue', t.bar=42 みたいに表現できるよね。
• 作った式をクエリに変換して実行できると嬉しい
SQLAlchemyではこうなる
• select([col1, col2, ...], ...)
• column('foo')=='blue' & column('bar')==42
• t = table('mytable', column('foo'), column('bar'), ...)

select([t]).where(t.c.foo=='blue', t.c.bar==42)
• query = select([t]).where(t.c.bar==42)

engine.execute(query).fetchall()
書いてみよう
• SELECT 文を書いてみましょう
• SQLite版のSakila databaseに接続して、簡単なクエリを
実行してみましょう
こんなモデルで
film (作品)
-------------------------
film_id PK
title タイトル
description 説明
release_year 年
language_id 言語
original_language_id
rental_rate 貸出料金
length 視聴時間
replacement_cost 原価
rating 対象年齢
last_update 最終更新
こんなクエリを書いてみましょう
• 料金 (rental_rate) 3ドル以下で借りられる
• 作品 (film) のタイトル(title) と料金を知りたい
SQLで表すと...
film (作品)
-------------------------
film_id PK
title タイトル
description 説明
release_year 年
language_id 言語
original_language_id
rental_rate 貸出料金
length 視聴時間
replacement_cost 原価
rating 対象年齢
last_update 最終更新
SELECT	title,	rental_rate

FROM	film

WHERE	rental_rate	<	3.0

text()	を使ってみましょう
text()	は	SQL	式の一部を生SQLで書きたい時に使える

>>>	from	sqlalchemy.sql	import	select,	text

>>>	q	=	select([text('title')])

>>>	q

<sqlalchemy.....Select	at	...;	Select	object>

>>>	str(q)

'SELECT	title'
from	を指定して実行してみましょう
>>>	from	sqlalchemy.sql	import	select,	text

>>>	q	=	select([text('title')],

...												from_obj=text('film'))

>>>	str(q)

'SELECT	title	nFROM	film'

>>>	from	sqlalchemy	create_engine

>>>	e	=	create_engine('sqlite://sqlite-sakila.sq')

>>>	e.execute(q).fetchall()

[('ACADEMY	DINOSAUR',),	('ACE	GOLDFINGER',),	
('ADAPTATION	HOLES',)...]
テーブルやカラムを表す

オブジェクトを使いましょう
>>>	from	sqlalchemy.sql	import	column,	table

>>>	q	=	select([column('title')],

...												from_obj=table('film'))

>>>	str(q)

'SELECT	title	nFROM	film'
カラムとテーブルを結びつけましょう
>>>	film	=	table('film',

...														column('film_id'),

...														column('title'),

...														column('rental_rate'))

>>>	q	=	select([film.c.title,	film.c.rental_rate])

>>>	str(q)

'SELECT	film.title,	film.rental_rate	nFROM	film'
where	rental_rate	<	3.0	を表現しましょう
>>>	column('rental_rate')	<	3.0

<sqlalchemy...BinaryExpression	object	at	...>

>>>	cond	=	column('rental_rate')	<	3.0

>>>	str(cond)

'rental_rate	<	:rental_rate_1'

>>>	str(film.c.rental_rate	<	3.0)

'film.rental_rate	<	:rental_rate_1'

>>>	q	=	select([film.c.title],

...												whereclause=(film.c.rental_rate<3.0))

...

>>>	print(q)

SELECT	film.title	

FROM	film	

WHERE	film.rental_rate	<	:rental_rate_1
generativeインタフェースを使いましょう
>>>	q	=	film.select(film.c.rental_rate<3.0)

>>>	print(q)

SELECT	film.title,	film.rental_rate	

FROM	film	

WHERE	film.rental_rate	<	:rental_rate_1

>>>	q	=	film.select().where(film.c.rental_rate<3.0)

>>>	print(q)

#	どうなりましたか?

>>>	q	=	film.select(film.c.rental_rate<3.0)	

...									.order_by(film.c.title)

...

>>>	print(q)

#	どうなりましたか?
テーブル結合を表現しましょう
>>>	film_category	=	table('film_category',

...					column('film_id'),	column('category_id'))

...

>>>	on	=	(film.c.film_id==film_category.c.film_id)

>>>	j	=	film.join(film_category,	onclause=on)

>>>	str(j)

'film	JOIN	film_category	ON	film.film_id	=	
film_category.film_id'

>>>	query	=	select([film.c.title],	from_obj=j)

...									.where(film_category.c.category_id==1)

>>>	print(query)

#	どうなりましたか?
ここまでのまとめ
• table() や column() を使ってテーブルやカラムを表せる
• カラム定義つきで tbl=table(...) を作ると、tbl.c.colname でカラムオブ
ジェクトにアクセスできる
• colname と何かを比較すると条件式オブジェクトになる
• select() にカラム(やテーブル)の列を指定するとSELECT文を表すオブ
ジェクトになる
• select() や テーブルのメソッドを呼び出して generative に式を作れる
• engine.execute(sql式) でクエリを実行できる
ハンズオン:こんなクエリを書いてみましょう
• ジャンル名 (category.name) が Animation で
• 料金 (rental_rate) 3ドル以下で借りられる
• 作品 (film) のタイトル(title) と料金を知りたい
こんなモデルで
film (作品)
-------------------------
film_id PK
title タイトル
description 説明
release_year 年
language_id 言語
original_language_id
rental_rate 貸出料金
length 視聴時間
replacement_cost 原価
rating 対象年齢
last_update 最終更新
category (ジャンル)
-------------------------
category_id PK
name ジャンル名
last_update 最終更新
film_category 

-------------------------
film_id 作品ID
category_id カテゴリID
last_update 最終更新
実現例

(他にも書き方があります。試してみて!)
>>>	category	=	table('category',

...																		column('category_id'),

...																		column('name'))

>>>	f,	fc,	cat	=	film,	film_category,	category

>>>	j	=	f.join(fc,

...												onclause=(f.c.film_id==fc.c.film_id))	

...						.join(cat,

...												onclause=(cat.c.category_id==

...																						fc.c.category_id))

...

>>>	query	=	select([f.c.title,	f.c.rental_rate],	from_obj=j)

...									.where(category.c.name=='Animation'))

...

>>>	e..execute(query).fetchall()

[('ALTER	VICTORY',	0.99),	('ANACONDA	CONFESSIONS',	0.99),	...]
スキーマ定義
エンジンチョットデキル
スキーマ定義チョットデキル
ORMチョットデキル
現在の習熟状況
今ココ
SQL式チョットデキル
エンジンチョットデキル
スキーマ定義チョットデキル
ORMチョットデキル
SQL式チョットデキル
SA expertへの道
上級編
エンジンの
仕組み
上級編
上級編
上級編
SQL式の
仕組み
mapper/sessionの
仕組み
SQL式の
仕組み
エンジンチョットデキル
スキーマ定義チョットデキル
ORMチョットデキル
SQL式チョットデキル
SA expertへの道
上級編
エンジンの
仕組み
上級編
上級編
上級編
SQL式の
仕組み
mapper/sessionの
仕組み
SQL式の
仕組み
結構使える 誰得レベル
充分使えるちょっと便利!
←挫折する人のパターン
スキーマ定義
スキーマ定義って何
• table() や column() との違い:

テーブルやスキーマの属性を設定できる

カラムのデフォルト値とか、インデクスとか、制約とか

定義に基づいてCREATE TABLEできる
• クエリを書くだけならいらないですよね?

基本のSQL式だけより、書きやすくなります
• じゃ、知らなくてもいい?

本気のソフトウェア開発で使う時は必要です。

ORM使いたい人は覚えましょう
使ってみましょう
• テーブル定義を書いてみましょう
• テーブル定義からSQL式を作ってみましょう
• CREATE TABLEを生成してみましょう
location (商品棚)
---------------------------------
location_id INTEGER 棚番号
store_id INTEGER 店舗ID
last_update DATETIME 最終更新
こんなテーブルを作りましょう
from	sqlalchemy	import	Column,	MetaData,	Table

from	sqlalchemy	import	TIMESTAMP,	INTEGER

location	=	Table(

				'location',

				MetaData(),

				Column('location_id',	INTEGER,	primary_key=True),

				Column('store_id',	INTEGER,	nullable=False),

				Column('last_update',	TIMESTAMP,	nullable=False,

											server_default='CURRENT_TIMESTAMP',

											server_onupdate='CURRENT_TIMESTAMP'),

)
テーブルを定義する
location (商品棚)
---------------------------------
location_id INTEGER 棚番号
store_id INTEGER 店舗ID
last_update DATETIME 最終更新
#	基本は	table()	で作ったものと同じ

>>>	str(location.select())

'SELECT	location.location_id,	location.store_id,	
location.last_update	nFROM	location'

>>>	location

Table('location',	MetaData(bind=None),	Column...)



>>>	location.c.location_id

Column('location_id',	INTEGER(),	table=<location>,	
primary_key=True,	nullable=False)

>>>	str(location.c.location_id==1)

'location.location_id	=	:location_id_1'
スキーマ定義を操作してみる
なぜまぎらわしい同じインタフェースなのか
Visitable (base type)
ClauseElement
Selectable
FromClause
[ table() ]
ColumnElement
ColumnClause
[ column() ]
Column Table
SchemaItem
なぜまぎらわしい同じインタフェースなのか
Visitable (base type)
ClauseElement
Selectable
FromClause
[ table() ]
ColumnElement
ColumnClause
[ column() ]
Column Table
SchemaItem
同じAPIを

継承している
CREATE/DROP

まわりを実装
SQL式の基本機能
◯◯節を出力
する機能
カラム・テーブル

関連の機能
>>>	location.exists(bind=e)		#	テーブル存在チェック

False

>>>	location.create(bind=e)		#	CREATE	TABLE	実行

>>>	location.exists(bind=e)		#	できてる

True

>>>	location.drop(bind=e)		#	DROP	TABLE	実行

>>>	location.exists(bind=e)

False

【悲報】SQLAlchemy本体にはALTERのSQL式がありません
CREATE/DROP TABLEしてみる
>>>	from	sqlalchemy	import	Index

>>>	f	=	Table('film',	MetaData(),	Column...)	

>>>	Index('k_title',	f.c.title)

>>>	

>>>	from	sqlalchemy	import	ForeignKeyContraint

>>>	fc	=	Table('film_category',	MetaData(),

...												Column('film_id',	

...																			ForeignKey(f.c.film_id)),	...)

>>>	str(f.join(fc))		#	onclause	がなくても	JOIN	可

'film_category	JOIN	film	ON	film.film_id	=	film_category.film_id'



>>>	Index('k_title',	f.c.title)

Index('k_title',	Column('title',	VARCHAR(length=255),	table=<film>))

>>>	f.indexes

{Index('k_title',	Column('title',	VARCHAR(length=255),	table=<film>))}
インデクスと制約
location (商品棚)
---------------------------------
location_id INTEGER 棚番号
store_id INTEGER 店舗ID
last_update DATETIME 最終更新
ハンズオン: テーブル定義を書きましょう
• store_id にインデクスをはってみましょう
• store テーブルを定義して、location.store_id に
ForeignKey を追加し、テーブルを結合してみましょう
ここまでのまとめ
• Table() や Column() でテーブルを定義すると、DDL を
実行できる

カラムの制約やインデクスも貼れる
• Table() や Column() は、 table(), column() と同じよう
に扱える
さて、やっと

ORM basics
ORMって何だろう
• オブジェクトとリレーショナル(DB) のマップ (対応付け)

DBの内容をデータオブジェクトとして扱えるよう頑張る存在
• ActiveRecord パターンでの実現が華やか
• 1テーブルを1クラス、1レコードを1オブジェクトに対応付ける仕
組がよくある
• 外部キーを元に別テーブルのレコード(別クラスのインスタンス)
を参照できたりする
• カラムデータをプロパティやアトリビュートでアクセスできる

ことがある
SQLAlchemyのORMで覚えること
• ORMで頑張っているのはマッパー (mapper) とセッショ
ン (session)
• マッピングは古典的なのと近代的なのがある

裏側で使っている仕組みは同じ
• ORMでオブジェクトを操作するときはセッションを使う
マッピング
Tableオブジェクト
Column foo
Column bar
Column baz
...
エンティティクラス
魔改造されたエンティティ
仕掛けの付いた foo
仕掛けの付いた bar
仕掛けの付いた baz
...
method qux
method quux
method qux
method quux
...
その他有象無象
...
その他有象無象
得体の知れない

内部オブジェクト
とある

mapper
の魔法
classic mapping

古いやつ昔からあるやつ

新しいやつを支えている

古の魔法 = よくわからんから避けられる



declarative mapping

新しいやつ 書きやすい 流行の宣言的API

古の魔法x現代の魔法=よくわからないけど便利

SAでORMといえばだいたいこっちの話
mapping の種類
>>>	from	sqlalchemy.orm	import	mapper

>>>	actor_tbl	=	Table(...)

>>>	class	Actor(object):	pass

>>>	mapper(Actor,	actor_tbl)

>>>	dir(Actor)

['__class__',	...	'_sa_class_manager',	
'actor_id',	'first_name',	'last_name']

>>>	Actor.actor_id

<...InstrumentedAttribute	object	at	...>

classic mapping
またこんど
使ってみましょう
• 宣言的ORMの方法でテーブル定義を書いてみましょう
• セッションを作ってORMを操作しましょう
>>>	from	sqlalchemy.ext.declarative	import	
declarative_base

>>>	from	sqlalchmy	import	DateTime,	Integer,	
String

>>>	Base	=	declarative_base()

宣言的マッピングにはモデルベースクラスが必要
#	ベースクラスを拡張するとORMで扱えるモデルクラスになる



class	Actor(Base):

				__tablename__	=	'actor'

				actor_id	=	Column(Integer,primary_key=True)

				first_name	=	Column(String)

				last_name	=	Column(String)

				last_update	=	Column(DateTime)

				def	full_name(self):

								return	'{}	{}'.format(

								self.first_name,	self.last_name)

モデルクラスを定義しましょう
#	見かけは普通のクラス

>>>	Actor

<class	'__main__.Actor'>

#	定義したフィールドと得体の知れない属性が追加される

>>>	dir(Actor)

['__class__',	...	'_decl_class_registry',	
'_sa_class_manager',	'actor_id',	'first_name',	
'last_name',	'last_update',	'metadata']

#	actor_id	が魔法のアトリビュートになっている

>>>	Actor.actor_id

<sqlalchemy...InstrumentedAttribute	object	at	...>

モデルクラスを見てみましょう
>>>	actor	=	Actor()

>>>	actor

>>>	vars(actor_obj)

{'_sa_instance_state':	
<sqlalchemy...InstanceState	object	at	...>}

>>>	actor_obj.first_name	=	'foo'

>>>	actor_obj.last_name	=	'bar'

>>>	actor_obj.full_name()

'foo	bar'

>>>

#	興味がある人は、dir(_sa_instance_state)

#	を手がかりに研究してみましょう
モデルインスタンスを生成しよう
セッションを理解しよう
• セッション=トランザクションのようなもの

エンジンのDB接続一つが対応している

通常、セッションの持続中は、他のプログラムは

セッションが捕まえている接続にアクセスできない
• オブジェクトの読み出し:SELECT

新たなオブジェクトをセッションに追加:INSERT

オブジェクトのアトリビュートを更新:UPDATE
• 通常は、セッションを閉じたり明にcommit() しないと

DBを更新しない(「1000オブジェクト追加したのに何も起きない
これバグだろ」→セッション閉じてなかった は恥ずかしいFAQ)
session
Engine
Program
query A
select A
record Aobject A
start

tracking A...
(updates) flag A as
"dirty"
reflect new/dirty
changes
flush
start

tracking B
...
add
object B
update A
insert B
commit
begin
セッションを使ってみましょう
#	セッションを作るクラスを	sessionmaker	で作る

#	使うエンジンが一定ならこのとき指定する

>>>	from	sqlalchemy.orm	import	sessionmaker

>>>	SessionClass	=	sessionmaker(bind=e)

>>>	session	=	SessionClass()

>>>	session

<sqlalchemy.orm.session.Session	object	at	...>

#	dir(session)	してみましょう
クエリを生成してみましょう
>>>	query	=	session.query(Actor)

>>>	query

<sqlalchemy.orm.query.Query	object	at	...>

#	str(query)	してみましょう。どうなりますか?

#	dir(query)	してみましょう。
セッション中でオブジェクトを取り出そう
#	first(),	get(pk),	スライスなどの操作で

#	インスタンス	(の列)	を返す

>>>	my_actor	=	query.first()

>>>	my_actor

<...Actor	Object	at	...>

>>>	my_actor.first_name

'PENELOPE'

>>>	my_actor.full_name()		#	メソッドも呼べる

'PENELOPE	GUINESS'
セッション中でオブジェクトを取り出そう
>>>	q	=	session.query(Actor)

>>>	q.first()

<...Actor	object	at	...>

>>>	q.all()		#	WARNING:	SELECTs	all	records

<...Actor	object	at	...>,	<...Actor	object	at	...>,	...

>>>	q[:3]

<...Actor	object	at	...>,	<...Actor	object	at	...>,	...

>>>	q.count()

200
フィルタでクエリを絞込みしよう
>>>	q	=	session.query(Actor)

>>>	q1	=	q.filter(Actor.first_name=='PENELOPE')

>>>	str(q1)

'SELECT	...	nFROM	actornWHERE	actor.first_name	=	?'

>>>	q2	=	q1.filter_by(last_name='GUINESS')

>>>	str(q2)

'SELECT	...	nFROM	actornWHERE	actor.first_name	=	?	AND	
actor.last_name=	?'

>>>	[result.full_name()	for	result	in	q2.all()]

['PENELOPE	GUINESS']
オブジェクトを追加・更新しよう
#	新規オブジェクトを	session	に	add()	すると

#	INSERT	対象になる

>>>	session	=	Session()

>>>	actor_a	=	Actor(first_name='KEN',

...					last_name='WATANABE')

>>>	session.add(actor_a)

>>>	session.commit()

#	セッションから取り出したオブジェクトを

#	変更すると	UPDATE	対象になる

>>>	actor_b	=	session.query(Actor).get(1)

>>>	actor_b.last_name='GEORGE'

>>>	session.commit()
ここまでのまとめ
• SAのORMは、宣言的 (declarative) にモデルを定義する
ことで使える
• ORM 下のオブジェクトを操作するにはセッションを使う
• DBレコードからオブジェクトを生成して取り出すには、
セッションでクエリを作って、 all() や first() で取り出す
• SQLの実行はセッションにコントロールされている
ハンズオン: ORM
• category テーブルをORMで定義
してみましょう
• セッションを作って、Category
のモデルのクエリを実行しましょ
う
• Nature というカテゴリを追加し
ましょう。
• NatureをGeographyに変更しま
しょう
• Geographyを削除しましょう
以上です。
おつかれさまでした!
おまけ:今日お話していないこと
• エンジン: コネクション管理・コネクションプールの話

SQLiteでマルチスキーマDBぽく使う話
• スキーマ定義: マイグレーションの話

inspection/reflection でDBからスキーマ定義を自動構築する話
• ORM: いにしえのORM APIの話

外部キーを設定してたどる話
• MonotaROでの開発でSAを導入しようとしている話
• Django ORM他のORMとSAとの比較の話
また、機会があれば。

Contenu connexe

Tendances

DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話Koichiro Matsuoka
 
DXとかDevOpsとかのなんかいい感じのやつ 富士通TechLive
DXとかDevOpsとかのなんかいい感じのやつ 富士通TechLiveDXとかDevOpsとかのなんかいい感じのやつ 富士通TechLive
DXとかDevOpsとかのなんかいい感じのやつ 富士通TechLiveTokoroten Nakayama
 
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考えるGoのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考えるpospome
 
ネットストーカー御用達OSINTツールBlackBirdを触ってみた.pptx
ネットストーカー御用達OSINTツールBlackBirdを触ってみた.pptxネットストーカー御用達OSINTツールBlackBirdを触ってみた.pptx
ネットストーカー御用達OSINTツールBlackBirdを触ってみた.pptxShota Shinogi
 
君はyarn.lockをコミットしているか?
君はyarn.lockをコミットしているか?君はyarn.lockをコミットしているか?
君はyarn.lockをコミットしているか?Teppei Sato
 
分散システムについて語らせてくれ
分散システムについて語らせてくれ分散システムについて語らせてくれ
分散システムについて語らせてくれKumazaki Hiroki
 
テスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるなテスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるなKentaro Matsui
 
SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021Hiroshi Tokumaru
 
デキるプログラマだけが知っているコードレビュー7つの秘訣
デキるプログラマだけが知っているコードレビュー7つの秘訣デキるプログラマだけが知っているコードレビュー7つの秘訣
デキるプログラマだけが知っているコードレビュー7つの秘訣Masahiro Nishimi
 
「速」を落とさないコードレビュー
「速」を落とさないコードレビュー「速」を落とさないコードレビュー
「速」を落とさないコードレビューTakafumi ONAKA
 
Pythonによる黒魔術入門
Pythonによる黒魔術入門Pythonによる黒魔術入門
Pythonによる黒魔術入門大樹 小倉
 
Dockerからcontainerdへの移行
Dockerからcontainerdへの移行Dockerからcontainerdへの移行
Dockerからcontainerdへの移行Kohei Tokunaga
 
OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話
OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話
OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話Daichi Koike
 
それはYAGNIか? それとも思考停止か?
それはYAGNIか? それとも思考停止か?それはYAGNIか? それとも思考停止か?
それはYAGNIか? それとも思考停止か?Yoshitaka Kawashima
 
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)Takuto Wada
 
Dockerfile を書くためのベストプラクティス解説編
Dockerfile を書くためのベストプラクティス解説編Dockerfile を書くためのベストプラクティス解説編
Dockerfile を書くためのベストプラクティス解説編Masahito Zembutsu
 
DockerコンテナでGitを使う
DockerコンテナでGitを使うDockerコンテナでGitを使う
DockerコンテナでGitを使うKazuhiro Suga
 
メタプログラミングって何だろう
メタプログラミングって何だろうメタプログラミングって何だろう
メタプログラミングって何だろうKota Mizushima
 

Tendances (20)

DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
 
DXとかDevOpsとかのなんかいい感じのやつ 富士通TechLive
DXとかDevOpsとかのなんかいい感じのやつ 富士通TechLiveDXとかDevOpsとかのなんかいい感じのやつ 富士通TechLive
DXとかDevOpsとかのなんかいい感じのやつ 富士通TechLive
 
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考えるGoのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
 
ネットストーカー御用達OSINTツールBlackBirdを触ってみた.pptx
ネットストーカー御用達OSINTツールBlackBirdを触ってみた.pptxネットストーカー御用達OSINTツールBlackBirdを触ってみた.pptx
ネットストーカー御用達OSINTツールBlackBirdを触ってみた.pptx
 
君はyarn.lockをコミットしているか?
君はyarn.lockをコミットしているか?君はyarn.lockをコミットしているか?
君はyarn.lockをコミットしているか?
 
分散システムについて語らせてくれ
分散システムについて語らせてくれ分散システムについて語らせてくれ
分散システムについて語らせてくれ
 
テスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるなテスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるな
 
TLS, HTTP/2演習
TLS, HTTP/2演習TLS, HTTP/2演習
TLS, HTTP/2演習
 
SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021
 
デキるプログラマだけが知っているコードレビュー7つの秘訣
デキるプログラマだけが知っているコードレビュー7つの秘訣デキるプログラマだけが知っているコードレビュー7つの秘訣
デキるプログラマだけが知っているコードレビュー7つの秘訣
 
「速」を落とさないコードレビュー
「速」を落とさないコードレビュー「速」を落とさないコードレビュー
「速」を落とさないコードレビュー
 
Pythonによる黒魔術入門
Pythonによる黒魔術入門Pythonによる黒魔術入門
Pythonによる黒魔術入門
 
Dockerからcontainerdへの移行
Dockerからcontainerdへの移行Dockerからcontainerdへの移行
Dockerからcontainerdへの移行
 
OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話
OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話
OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話
 
それはYAGNIか? それとも思考停止か?
それはYAGNIか? それとも思考停止か?それはYAGNIか? それとも思考停止か?
それはYAGNIか? それとも思考停止か?
 
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
 
Dockerfile を書くためのベストプラクティス解説編
Dockerfile を書くためのベストプラクティス解説編Dockerfile を書くためのベストプラクティス解説編
Dockerfile を書くためのベストプラクティス解説編
 
DockerコンテナでGitを使う
DockerコンテナでGitを使うDockerコンテナでGitを使う
DockerコンテナでGitを使う
 
Docker Tokyo
Docker TokyoDocker Tokyo
Docker Tokyo
 
メタプログラミングって何だろう
メタプログラミングって何だろうメタプログラミングって何だろう
メタプログラミングって何だろう
 

Similaire à PlaySQLAlchemy: SQLAlchemy入門

PlaySQLAlchemyORM2017.key
PlaySQLAlchemyORM2017.keyPlaySQLAlchemyORM2017.key
PlaySQLAlchemyORM2017.key泰 増田
 
PySpark Intro Part.2 with SQL Graph
PySpark Intro Part.2 with SQL GraphPySpark Intro Part.2 with SQL Graph
PySpark Intro Part.2 with SQL GraphOshitari_kochi
 
Add PLEASE clause to Oracle Database
Add PLEASE clause to Oracle DatabaseAdd PLEASE clause to Oracle Database
Add PLEASE clause to Oracle DatabaseNoriyoshi Shinoda
 
SpringOne 2016 報告 Reactive APIの設計・実装・使用
SpringOne 2016 報告 Reactive APIの設計・実装・使用SpringOne 2016 報告 Reactive APIの設計・実装・使用
SpringOne 2016 報告 Reactive APIの設計・実装・使用Takuya Iwatsuka
 
Scalaでのプログラム開発
Scalaでのプログラム開発Scalaでのプログラム開発
Scalaでのプログラム開発Kota Mizushima
 
running-elixir-in-production
running-elixir-in-productionrunning-elixir-in-production
running-elixir-in-productionTsunenori Oohara
 
オフラインWebアプリの再到来で今、再び注目されるAPIの本命 ーJavaScript SQL-like database
オフラインWebアプリの再到来で今、再び注目されるAPIの本命 ーJavaScript SQL-like databaseオフラインWebアプリの再到来で今、再び注目されるAPIの本命 ーJavaScript SQL-like database
オフラインWebアプリの再到来で今、再び注目されるAPIの本命 ーJavaScript SQL-like databaseyoshikawa_t
 
Oracleがnode.jsをやり始めたというのだが!
Oracleがnode.jsをやり始めたというのだが!Oracleがnode.jsをやり始めたというのだが!
Oracleがnode.jsをやり始めたというのだが!Hiroshi Hayakawa
 
uroboroSQLの紹介 (OSC2017 Nagoya) #oscnagoya
uroboroSQLの紹介 (OSC2017 Nagoya) #oscnagoyauroboroSQLの紹介 (OSC2017 Nagoya) #oscnagoya
uroboroSQLの紹介 (OSC2017 Nagoya) #oscnagoyaKenichi Hoshi
 
Seas で語られたこととは?
Seas で語られたこととは?Seas で語られたこととは?
Seas で語られたこととは?Masayuki Ozawa
 
Scalaz-StreamによるFunctional Reactive Programming
Scalaz-StreamによるFunctional Reactive ProgrammingScalaz-StreamによるFunctional Reactive Programming
Scalaz-StreamによるFunctional Reactive ProgrammingTomoharu ASAMI
 
関数型言語ElixirのIoTシステム開発への展開
関数型言語ElixirのIoTシステム開発への展開関数型言語ElixirのIoTシステム開発への展開
関数型言語ElixirのIoTシステム開発への展開Hideki Takase
 
Elasticsearch workshop 23_sql
Elasticsearch workshop 23_sqlElasticsearch workshop 23_sql
Elasticsearch workshop 23_sqlshinhiguchi
 
Webアプリに低レイテンシ・高可用性を求めるのは間違っているのだろうか
Webアプリに低レイテンシ・高可用性を求めるのは間違っているのだろうかWebアプリに低レイテンシ・高可用性を求めるのは間違っているのだろうか
Webアプリに低レイテンシ・高可用性を求めるのは間違っているのだろうかChihiro Ito
 
Lambda: A Peek Under The Hood [Java Day Tokyo 2015 6-3]
Lambda: A Peek Under The Hood [Java Day Tokyo 2015 6-3]Lambda: A Peek Under The Hood [Java Day Tokyo 2015 6-3]
Lambda: A Peek Under The Hood [Java Day Tokyo 2015 6-3]David Buck
 
テスト駆動で行うネットワーク自動化のすすめ
テスト駆動で行うネットワーク自動化のすすめテスト駆動で行うネットワーク自動化のすすめ
テスト駆動で行うネットワーク自動化のすすめkinunori
 
Developers.IO 2019 Effective Datalake
Developers.IO 2019 Effective DatalakeDevelopers.IO 2019 Effective Datalake
Developers.IO 2019 Effective DatalakeSatoru Ishikawa
 

Similaire à PlaySQLAlchemy: SQLAlchemy入門 (20)

PlaySQLAlchemyORM2017.key
PlaySQLAlchemyORM2017.keyPlaySQLAlchemyORM2017.key
PlaySQLAlchemyORM2017.key
 
Apex Test Plusの紹介
Apex Test Plusの紹介Apex Test Plusの紹介
Apex Test Plusの紹介
 
PySpark Intro Part.2 with SQL Graph
PySpark Intro Part.2 with SQL GraphPySpark Intro Part.2 with SQL Graph
PySpark Intro Part.2 with SQL Graph
 
Add PLEASE clause to Oracle Database
Add PLEASE clause to Oracle DatabaseAdd PLEASE clause to Oracle Database
Add PLEASE clause to Oracle Database
 
SpringOne 2016 報告 Reactive APIの設計・実装・使用
SpringOne 2016 報告 Reactive APIの設計・実装・使用SpringOne 2016 報告 Reactive APIの設計・実装・使用
SpringOne 2016 報告 Reactive APIの設計・実装・使用
 
Scalaでのプログラム開発
Scalaでのプログラム開発Scalaでのプログラム開発
Scalaでのプログラム開発
 
running-elixir-in-production
running-elixir-in-productionrunning-elixir-in-production
running-elixir-in-production
 
オフラインWebアプリの再到来で今、再び注目されるAPIの本命 ーJavaScript SQL-like database
オフラインWebアプリの再到来で今、再び注目されるAPIの本命 ーJavaScript SQL-like databaseオフラインWebアプリの再到来で今、再び注目されるAPIの本命 ーJavaScript SQL-like database
オフラインWebアプリの再到来で今、再び注目されるAPIの本命 ーJavaScript SQL-like database
 
Oracleがnode.jsをやり始めたというのだが!
Oracleがnode.jsをやり始めたというのだが!Oracleがnode.jsをやり始めたというのだが!
Oracleがnode.jsをやり始めたというのだが!
 
uroboroSQLの紹介 (OSC2017 Nagoya) #oscnagoya
uroboroSQLの紹介 (OSC2017 Nagoya) #oscnagoyauroboroSQLの紹介 (OSC2017 Nagoya) #oscnagoya
uroboroSQLの紹介 (OSC2017 Nagoya) #oscnagoya
 
Seas で語られたこととは?
Seas で語られたこととは?Seas で語られたこととは?
Seas で語られたこととは?
 
Scalaz-StreamによるFunctional Reactive Programming
Scalaz-StreamによるFunctional Reactive ProgrammingScalaz-StreamによるFunctional Reactive Programming
Scalaz-StreamによるFunctional Reactive Programming
 
Trait in scala
Trait in scalaTrait in scala
Trait in scala
 
関数型言語ElixirのIoTシステム開発への展開
関数型言語ElixirのIoTシステム開発への展開関数型言語ElixirのIoTシステム開発への展開
関数型言語ElixirのIoTシステム開発への展開
 
Elasticsearch workshop 23_sql
Elasticsearch workshop 23_sqlElasticsearch workshop 23_sql
Elasticsearch workshop 23_sql
 
Webアプリに低レイテンシ・高可用性を求めるのは間違っているのだろうか
Webアプリに低レイテンシ・高可用性を求めるのは間違っているのだろうかWebアプリに低レイテンシ・高可用性を求めるのは間違っているのだろうか
Webアプリに低レイテンシ・高可用性を求めるのは間違っているのだろうか
 
Lambda: A Peek Under The Hood [Java Day Tokyo 2015 6-3]
Lambda: A Peek Under The Hood [Java Day Tokyo 2015 6-3]Lambda: A Peek Under The Hood [Java Day Tokyo 2015 6-3]
Lambda: A Peek Under The Hood [Java Day Tokyo 2015 6-3]
 
Pythonでexcel
PythonでexcelPythonでexcel
Pythonでexcel
 
テスト駆動で行うネットワーク自動化のすすめ
テスト駆動で行うネットワーク自動化のすすめテスト駆動で行うネットワーク自動化のすすめ
テスト駆動で行うネットワーク自動化のすすめ
 
Developers.IO 2019 Effective Datalake
Developers.IO 2019 Effective DatalakeDevelopers.IO 2019 Effective Datalake
Developers.IO 2019 Effective Datalake
 

Plus de 泰 増田

SQLAlchemy Primer
SQLAlchemy PrimerSQLAlchemy Primer
SQLAlchemy Primer泰 増田
 
Taming robotframework
Taming robotframeworkTaming robotframework
Taming robotframework泰 増田
 
Open bio2004 biopython
Open bio2004 biopythonOpen bio2004 biopython
Open bio2004 biopython泰 増田
 
Python languageupdate (2004)
Python languageupdate (2004)Python languageupdate (2004)
Python languageupdate (2004)泰 増田
 
Robot Framework (のSelenium2Libraryのお話)
Robot Framework (のSelenium2Libraryのお話)Robot Framework (のSelenium2Libraryのお話)
Robot Framework (のSelenium2Libraryのお話)泰 増田
 
wxPython入門(大阪Pythonユーザの集まり2014/03)
wxPython入門(大阪Pythonユーザの集まり2014/03)wxPython入門(大阪Pythonユーザの集まり2014/03)
wxPython入門(大阪Pythonユーザの集まり2014/03)泰 増田
 

Plus de 泰 増田 (7)

SQLAlchemy Primer
SQLAlchemy PrimerSQLAlchemy Primer
SQLAlchemy Primer
 
Taming robotframework
Taming robotframeworkTaming robotframework
Taming robotframework
 
Open bio2004 biopython
Open bio2004 biopythonOpen bio2004 biopython
Open bio2004 biopython
 
Python languageupdate (2004)
Python languageupdate (2004)Python languageupdate (2004)
Python languageupdate (2004)
 
Robot Framework (のSelenium2Libraryのお話)
Robot Framework (のSelenium2Libraryのお話)Robot Framework (のSelenium2Libraryのお話)
Robot Framework (のSelenium2Libraryのお話)
 
Django boodoo
Django boodooDjango boodoo
Django boodoo
 
wxPython入門(大阪Pythonユーザの集まり2014/03)
wxPython入門(大阪Pythonユーザの集まり2014/03)wxPython入門(大阪Pythonユーザの集まり2014/03)
wxPython入門(大阪Pythonユーザの集まり2014/03)
 

PlaySQLAlchemy: SQLAlchemy入門