Flask-PyMongoを使ってMongoDBとFlaskを連携させる

Webアプリケーションを構築する場合、ほとんどの場合、データベースからのデータを扱うことになります。好みに応じて様々なデータベースから選択することができます。

この記事では、最も人気のあるNoSQLデータベースの1つであるMongoDBをFlaskマイクロフレームワークと統合する方法を見ていくことにします。

MongoDBを統合するためのFlask拡張はいくつかありますが、ここではFlask-PyMongo拡張を使用します。

また、MongoDBのCRUD機能を調べるために、シンプルなTodo-List APIに取り組む予定です。

セットアップとコンフィギュレーション

このチュートリアルに沿って進めるには、MongoDB インスタンスにアクセスする必要があります。MongoDB Atlas から取得することもできますし、ローカルのインスタンスを使うこともできます。ここでは、個人所有のマシンにローカルインスタンスをインストールすることにします。

MongoDB のローカルインスタンスをインストールするには、公式ドキュメントのサイトでダウンロードとインストール方法を確認しましょう。

また、Flaskがインストールされている必要がありますが、もしインストールされていない場合は以下のコマンドでインストールできます。

$ pip install flask


次にFlask-PyMongoをセットアップする必要があります。これはPyMongo Pythonパッケージのラッパーのようなものです。

PyMongoはMongoDBの低レベルラッパーで、MongoDBのCLIコマンドと似たようなコマンドを使って、以下のことを行います。

  1. データの作成
  2. データへのアクセス
  3. データの変更

定義済みのスキーマを使わないので、MongoDBのスキーマレスな性質をフルに活用できます。

Flask-PyMongoを使い始めるには、以下のコマンドでインストールする必要があります。

$ pip install Flask-PyMongo


これで準備は整ったので、さっそく MongoDB を Flask アプリに組み込んでみましょう。

FlaskでMongoDBデータベースインスタンスに接続する。

実際に作業をする前に、MongoDB インスタンスを Flask アプリケーションに接続したいと思います。まず最初に Flask と Flask-PyMongo をアプリにインポートします。

from flask_pymongo import PyMongo
import flask


次に Flask のアプリオブジェクトを作成します。

app = flask.Flask(__name__)


そしてそれを使って MongoDB クライアントを初期化します。PyMongo コンストラクタ (flask_pymongo からインポート) は Flsk アプリオブジェクトとデータベース URI 文字列を受け取ります。

これでアプリケーションと MongoDB インスタンスの紐付けができました。

mongodb_client = PyMongo(app, uri="mongodb://localhost:27017/todo_db")
db = mongodb_client.db


URI 文字列は app.configMONGO_URI というキーに代入することもできます。

app.config["MONGO_URI"] = "mongodb://localhost:27017/todo_db"
mongodb_client = PyMongo(app)
db = mongodb_client.db


アプリケーションがインスタンスに接続できたら、アプリケーションの CRUD 機能の実装を開始できます。

Create Documents – データベースに新しい項目を追加する

MongoDB はコレクションを扱います。これは通常の SQL テーブルと同じようなものです。今回は TODO リストアプリを作るので、 todos コレクションを用意します。これを参照するには、db オブジェクトを使用します。各エンティティはドキュメントであり、コレクションは実際にはドキュメントのコレクションである。

新しいエントリを todos コレクションに挿入するには、 db.colection.insert_one() メソッドを使用します。MongoDB は挿入、問い合わせ、削除の構文が Python と自然に連動しています。

MongoDB のコレクションにドキュメントを挿入するときは、 <field<value を含む辞書を指定することになります。Python を仲介して MongoDB のコレクションにドキュメントを挿入するには、Python に組み込まれている辞書を渡します。

したがって、新しいエンティティを挿入するには、次のようなことをします。

@app.route("/add_one")
def add_one():
    db.todos.insert_one({'title': "todo title", 'body': "todo body"})
    return flask.jsonify(message="success")


また、db.colection.insert_many() メソッドを使えば、一度に複数のエントリを追加することができます。intert_many()` メソッドは辞書のリストを受け取り、コレクションに追加します。

@app.route("/add_many")
def add_many():
    db.todos.insert_many([
        {'_id': 1, 'title': "todo title one ", 'body': "todo body one "},
        {'_id': 2, 'title': "todo title two", 'body': "todo body two"},
        {'_id': 3, 'title': "todo title three", 'body': "todo body three"},
        {'_id': 4, 'title': "todo title four", 'body': "todo body four"},
        {'_id': 5, 'title': "todo title five", 'body': "todo body five"},
        {'_id': 1, 'title': "todo title six", 'body': "todo body six"},
        ])
    return flask.jsonify(message="success")


重複したレコードを追加しようとすると、BulkWriteError がスローされます。つまり、重複したレコードまでしか挿入されず、重複したレコード以降はすべて失われるので、多くのドキュメントを挿入しようとする場合はこの点に注意してください。

もし、有効でユニークなレコードだけをリストに挿入したい場合は、 insert_many() メソッドの ordered パラメータを false に設定して、 BulkWriteError 例外をキャッチしなければなりません。

from pymongo.errors import BulkWriteError


@app.route("/add_many")
def add_many():
    try:
        todo_many = db.todos.insert_many([
            {'_id': 1, 'title': "todo title one ", 'body': "todo body one "},
            {'_id': 8, 'title': "todo title two", 'body': "todo body two"},
            {'_id': 2, 'title': "todo title three", 'body': "todo body three"},
            {'_id': 9, 'title': "todo title four", 'body': "todo body four"},
            {'_id': 10, 'title': "todo title five", 'body': "todo body five"},
            {'_id': 5, 'title': "todo title six", 'body': "todo body six"},
        ], ordered=False)
    except BulkWriteError as e:
        return flask.jsonify(message="duplicates encountered and ignored",
                             details=e.details,
                             inserted=e.details['nInserted'],
                             duplicates=[x['op'] for x in e.details['writeErrors']])


return flask.jsonify(message="success", insertedIds=todo_many.inserted_ids)


この方法で、すべての有効なドキュメントを MongoDB コレクションに挿入できます。さらに、追加に失敗したときの詳細をログに記録して、JSON メッセージとしてユーザーに返します。

これは Flasks の jsonify() メソッドで行います。このメソッドには、返したいメッセージと、ロギング用にカスタマイズできる追加のパラメータを指定します。

最後に、挿入に成功したものを同じように返します。

ドキュメントを読む – データベースからデータを取得する

Flask-PyMongoはデータベースからデータを取得するためのいくつかのメソッド(PyMongoから拡張された)といくつかのヘルパーメソッドを提供しています。

todosコレクションからすべてのドキュメントを取得するために、db.collection.find()` メソッドを使用します。

このメソッドは、データベース内のすべての todos のリストを返します。find()と同様に、find_one()` メソッドは ID を指定してひとつのドキュメントを返します。

まずは find() から始めてみましょう。

@app.route("/")
def home():
    todos = db.todos.find()
    return flask.jsonify([todo for todo in todos])


find()` メソッドはオプションで filter パラメータを受け取ることができます。このフィルターパラメーターは辞書で表現され、探しているプロパティを指定します。MongoDB を使ったことがある人なら、クエリやコンパレータがどのようなものか知っていることでしょう。

そうでない場合は、Python の辞書を使って MongoDB のクエリ形式に対応させる方法を説明します。

# Query document where the `id` field is `3`
{"id":3}


# Query document where both `id` is `3` and `title` is `Special todo`
{"id":3, "title":"Special todo"}


# Query using special operator - Greater than Or Equal To, denoted with
# the dollar sign and name ($gte)
{"id" : {$gte : 5}}


その他、特殊な演算子として $eq$ne$gt$lt$lte$nin 演算子などがあります。

もし、これらの演算子についてよく知らない場合は、公式のドキュメントで学ぶことができます。

さて、ここまでで find() メソッドでフィルタリングするための MongoDB クエリの指定について説明しましたが、次は _id で指定したひとつのドキュメントを取得する方法を見ていきましょう。

@app.route("/get_todo/<int:todoid")
def insert_one(todoId):
    todo = db.todos.find_one({"_id": todoId})
    return todo


http://localhost:5000/get_todo/5GET` リクエストを送ると、次のような結果になります。

{
    "_id": 5,
    "body": "todo body six",
    "title": "todo title six"
}


5000 はデフォルトの Flask サーバーポートですが、Flask アプリのオブジェクトを作成する際に簡単に変更することができますので注意してください。

ほとんどの場合、アイテムを取得するか、アイテムが見つからなかったら 404 エラーを返したいでしょう。

Flask-PyMongo はこのためのヘルパー関数として find_one_or_404() メソッドを提供しています。このメソッドはリクエストされたリソースが見つからなかった場合に 404 エラーを発生させます。

ドキュメントの更新と差し替え

データベースのエントリを更新するには、update_one() または replace_one() メソッドを使用して、既存のエンティティの値を変更することができます。

replace_one() は以下の引数を持ちます。

    1. filter – どのエントリを置き換えるかを定義するクエリ。
    1. replacement – 置換されたときに、その場所に配置されるエントリ。
    1. {} – いくつかのオプションがある設定オブジェクトで、ここでは upsert にフォーカスします。

upserttrueに設定すると、データベースにマッチするフィルタがない場合に、replacementを新しいドキュメントとして挿入します。そして、マッチするものがあれば、代わりにreplacementを挿入します。もしupsert` が false の場合、存在しないドキュメントを更新しようとすると、何も起こりません。

では、どのようにドキュメントを更新するのかを見てみましょう。

@app.route("/replace_todo/<int:todoid")
def replace_one(todoId):
    result = db.todos.replace_one({'_id': todoId}, {'title': "modified title"})
    return {'id': result.raw_result}


@app.route("/update_todo/<int:todoid")
def update_one(todoId):
    result = db.todos.update_one({'_id': todoId}, {"$set": {'title': "updated title"}})
    return result.raw_result


http://localhost:5000/update_todo/5` にリクエストを送ると、次のような結果になります。

{
    "id": {
        "n": 1,
        "nModified": 1,
        "ok": 1.0,
        "updatedExisting": true
    }
}


同様に、http://localhost:5000/replace_todo/5にリクエストを送ると、次のような結果が得られます。

{
    "id": {
        "n": 1,
        "nModified": 1,
        "ok": 1.0,
        "updatedExisting": true
    }
}


このコードブロックは UpdatedResult オブジェクトを返しますが、これを扱うのは少し面倒かもしれません。Flask-PyMongo では find_one_and_update()find_one_and_replace() などの、エントリーを更新してそのエントリーを返す便利なメソッドが用意されているのはそのためです。

@app.route("/replace_todo/<int:todoid")
def replace_one(todoId):
    todo = db.todos.find_one_and_replace({'_id': todoId}, {'title': "modified title"})
    return todo


@app.route("/update_todo/<int:todoid")
def update_one(todoId):
    result = db.todos.find_one_and_update({'_id': todoId}, {"$set": {'title': "updated title"}})
    return result


ここで、http://localhost:5000/update_todo/5 にリクエストを送ると、次のような結果になります。

{
    "_id": 5,
    "title": "updated title"
}


同様に、http://localhost:5000/replace_todo/5にリクエストを送ると、次のような結果になるでしょう。

{
    "_id": 5,
    "title": "modified title"
}


Flask-PyMongo では update_many() メソッドで一括更新も可能です。

@app.route('/update_many')
def update_many():
    todo = db.todos.update_many({'title' : 'todo title two'}, {"$set": {'body' : 'updated body'}})
    return todo.raw_result


上記のコードブロックは “todo title two” というタイトルのすべてのエントリーを探して更新し、次のような結果になります。

新しく作った enpoints にリクエストを送ると、次のような結果が返されます。

{
    "n": 1,
    "nModified": 1,
    "ok": 1.0,
    "updatedExisting": true
}


ドキュメントの削除

他のメソッドと同様に、Flask-PyMongo では delete_one()delete_many() メソッドを使って単一のエントリやエントリの集合を削除するメソッドを提供しています。

このメソッドの引数は他のメソッドと同じです。では、例を見てみましょう。

@app.route("/delete_todo/<int:todoid", methods=['DELETE'])
def delete_todo(todoId):
    todo = db.todos.delete_one({'_id': todoId})
    return todo.raw_result


これは指定された ID のエントリーを検索し、削除します。このエンドポイントに http://localhost:5000/delete_todo/5 のような DELETE リクエストを送ると、次のような結果が得られます。

{
    "n": 1,
    "ok": 1.0
}


代わりに、削除されたアイテムを返す find_one_and_delete() メソッドを使用すると、便利ではない結果オブジェクトを使用せずに済みます。

@app.route("/delete_todo/<int:todoid", methods=['DELETE'])
def delete_todo(todoId):
    todo = db.todos.find_one_and_delete({'_id': todoId})
    if todo is not None:
        return todo.raw_result
    return "ID does not exist"


http://localhost:5000/delete_todo/8` を私たちのサーバーに送ると、次のような結果になります。

{
    "_id": 8,
    "body": "todo body two",
    "title": "todo title two"
}


最後に、delete_many() メソッドを使って一括で削除することができます。

@app.route('/delete_many', methods=['DELETE'])
def delete_many():
    todo = db.todos.delete_many({'title': 'todo title two'})
    return todo.raw_result


http://localhost:5000/delete_many` を私たちのサーバーに送信すると、以下のような結果になります。

{
    "n": 1,
    "ok": 1.0
}


ファイルの保存と取り出し

MongoDB では GridFS という仕様でバイナリデータをデータベースに保存することができます。

Flask-PyMongo では、GridFS にファイルを保存するための save_file() メソッドと、GridFS からファイルを取得するための send_file() メソッドが提供されています。

まずはGridFSにファイルをアップロードするルートから説明します。

@app.route("/save_file", methods=['POST', 'GET'])
def save_file():
    upload_form = """<h1Save file</h1
<form enctype="multipart/form-data" method="POST"
<input id="file" name="file" type="file"/
<br/<br/
<input type="submit"/
</form"""

    if request.method=='POST':
        if 'file' in request.files:
            file = request.files['file']
            mongodb_client.save_file(file.filename, file)
            return {"file name": file.filename}
    return upload_form


上記コードブロックでは、アップロードを処理するフォームを作成し、アップロードされたドキュメントのファイル名を返します。

次に、アップロードしたファイルを取得する方法を見てみましょう。

@app.route("/get_file/<filename")
def get_file(filename):
    return mongodb_client.send_file(filename)


このコードブロックは、指定されたファイル名のファイルを返すか、ファイルが見つからなかった場合は404エラーを発生させます。

結論

Flask-PyMongo 拡張は、MongoDB のインスタンスと通信するための低レベル API (公式の MongoDB 言語に非常に近いもの) を提供します。

この拡張モジュールはいくつかのヘルパーメソッドも提供しているので、定型的なコードを大量に書く必要はありません。

この記事では、MongoDB を Flask アプリに統合する方法と、いくつかの CRUD 操作、そして GridFS を使って MongoDB でファイルを操作する方法について説明しました。

できる限り説明したつもりですが、もし質問や要望があれば、下にコメントを残してください。

</filename

タイトルとURLをコピーしました