これは、なにをしたくて書いたもの?
MongoDBの練習がてらに、PythonからMongoDBにアクセスするプログラムを書いてみようかなと。
MongoDBのPythonドライバー
MongoDBのPythonドライバーは、MongoDBのサイトを見るとPyMongoとMotorがあるようです。
MongoDB Python Drivers — MongoDB Ecosystem
Motor (Async Driver) — MongoDB Ecosystem
PyMongoが推奨のドライバーで、MotorはTornadeやasyncioと互換性のある非同期ドライバーのようです。
MongoDB本体のバージョンとの互換性を確認してみると、Motorは途中で止まっているようにも思います。
Motor(Async Driver) / Compatibility
この意味でも、PyMongoを選択したよさそうですね。
PyMongoの、ドキュメントおよびAPIリファレンスはこちらです。
PyMongo 3.9.0 Documentation — PyMongo 3.9.0 documentation
API Documentation — PyMongo 3.9.0 documentation
では、使っていってみましょう。
環境
今回の環境は、こちらです。
$ python3 -V Python 3.6.9 $ pip3 -V pip 9.0.1 from /path/to/venv/lib/python3.6/site-packages (python 3.6)
また、MongoDBのバージョンは4.2.3とし、MongoDBが動作しているサーバーのIPアドレスは172.17.0.2とします。
インストール
ドキュメントに沿って、PyMongoをインストールします。
Installing / Upgrading — PyMongo 3.9.0 documentation
Python 2.7、3.4以上に対応しているようですね。
PyMongo supports CPython 2.7, 3.4+, PyPy, and PyPy3.5+
pipでインストール。
$ pip3 install pymongo
今回は、3.10.1がインストールされました。
$ pip3 freeze | grep pymongo pymongo==3.10.1
が、この時点のドキュメントの「current」は、3.9.0なんですよね。これは、どういうことでしょう。まあ、今回は気にせずいきますか…。
このエントリが参照しているドキュメントへのリンクは、3.9.0で固定してあります。
あと、確認はテストコードで行おうと思うので、pytestもインストールしておきます。
$ pip3 install pytest $ pip3 freeze | grep pytest pytest==5.4.1
ディレクトリと__init__.pyを作成して、準備完了。
$ mkdir tests && touch tests/__init__.py
テストコードの雛形
テストコードの雛形は、こちら。
tests/test_pymongo.py
import re from pymongo import MongoClient import pymongo # ここに、テストを書く!
基本的には、PyMongoのチュートリアルを見つつ、必要に応じてAPIリファレンスを参照して進めていくのかな、と思います。
Tutorial — PyMongo 3.9.0 documentation
API Documentation — PyMongo 3.9.0 documentation
PyMongoからMongoDBに接続する
PyMongoからMongoDBに接続する方法は、こちら。
Making a Connection with MongoClient
接続先となるサーバーの記載方法は、2通りあるようですね。また、MongoClientはコンテキストマネージャープロトコルを実装して
いるので、with構文も使えます。
こんな感じですね。
def test_connect_mongo(): client = MongoClient("172.17.0.2", 27017) assert client is not None client.close() def test_connect_mongo_with(): with MongoClient("mongodb://172.17.0.2:27017") as client: assert client is not None
使わなくなった接続は、closeしておきましょう。
ちなみに、MongoClientはコネクションプールの役割も持っています。
How does connection pooling work in PyMongo?
スレッドセーフであり、プロセスセーフではありません、と。
続いて、データベースとコレクションの取得。
今回は書籍をお題に、データベースとコレクションを作成。
def test_get_database_and_collection(): with MongoClient("172.17.0.2", 27017) as client: test_db = client.test_db book_collection = test_db.book_collection assert book_collection is not None
ドキュメント1件の登録、1件の取得
では、ドキュメントを登録してみます。
まずは1件登録して、1件取得。
Getting a Single Document With find_one()
こんな感じで、コレクションに対してinsert_oneでドキュメントを1件登録、find_oneで指定のドキュメントを1件取得できます。
def test_document_insert_and_find_one(): with MongoClient("172.17.0.2", 27017) as client: test_db = client.test_db book_collection = test_db.book_collection book = {"isbn": "978-4873117386", "title": "入門 Python 3", "price": 4070} book_collection.insert_one(book) assert book_collection.find_one({"isbn": "978-4873117386"})["title"] == book["title"] assert book_collection.count_documents({}) == 1 book_collection.delete_many({}) assert book_collection.count_documents({}) == 0
テストコードの都合上、カウントと全件取得も行っていますが。
複数件のドキュメントの登録、取得
続いて、複数ドキュメントの登録と、複数ドキュメントの検索を行ってみましょう。
Querying for More Than One Document
こんな感じで。
def test_document_insert_and_find_many(): with MongoClient("172.17.0.2", 27017) as client: test_db = client.test_db book_collection = test_db.book_collection books = [ {"isbn": "978-4873117386", "title": "入門 Python 3", "price": 4070}, {"isbn": "978-4048930611", "title": "エキスパートPythonプログラミング改訂2版", "price": 3960}, {"isbn": "978-4297111113", "title": "Python実践入門", "price": 3278} ] book_collection.insert_many(books) assert book_collection.count_documents({}) == 3 found_all_books = [book for book in book_collection.find({}, sort = [("price", pymongo.DESCENDING)])] assert found_all_books[0]["title"] == "入門 Python 3" assert len(found_all_books) == 3 found_books = [book for book in book_collection.find({"title": re.compile("Python"), "price": {"$gt": 3500}}, sort = [("price", pymongo.ASCENDING)])] assert found_books[0]["title"] == "エキスパートPythonプログラミング改訂2版" assert len(found_books) == 2 book_collection.delete_many({}) assert book_collection.count_documents({}) == 0
ドキュメントを複数件登録するには、コレクションに対してinsert_manyを呼び出せばOKです。引数は、登録したいドキュメントの
リストです。
検索はfindで行うのですが、戻り値はCursorになるので今回は内包表記でリストに変換しています。
found_all_books = [book for book in book_collection.find({}, sort = [("price", pymongo.DESCENDING)])]
あと、価格の降順ソートにしておきました。
また、もうひとつ検索のパターンとしては、AND条件で正規表現や比較演算子を使うものも。
found_books = [book for book in book_collection.find({"title": re.compile("Python"), "price": {"$gt": 3500}}, sort = [("price", pymongo.ASCENDING)])]
正規表現で検索を行う場合は、Pythonの正規表現オブジェクトを渡せばOKです。こちらのソートは、価格の昇順にしておきました。
とりあえず、こんなところでしょうか。
まとめ
MongoDBのPythonドライバー、PyMongoからMongoDBにアクセスして、初歩的な操作をざっと試してみました。
ディクショナリーをベースにして使えるので、簡単に使えてよいのではないでしょうか。
ちょっとした時に利用していくんだろうなーと思います。