Muffin-Mongo -- MongoDB support for Muffin framework.
Contents
- python >= 3.11
- motor (MongoDB driver)
Note
The plugin supports only the asyncio event loop (not trio or curio).
The plugin is a thin wrapper around motor, and motor's asyncio driver is
built directly on asyncio: it looks up the running loop with
asyncio.get_event_loop() and returns asyncio.Future objects. Any
request made from a trio or curio task therefore fails with
RuntimeError: There is no current event loop in thread 'MainThread'.
There is nothing to configure here -- the limitation comes from the
database driver, not from Muffin. The plugin's own tests pin asyncio with
an aiolib fixture for the same reason.
Note
Motor 3.7 declares support up to Python 3.13 and is in maintenance mode
(its author recommends moving to the pymongo async API). The plugin's
test suite passes on Python 3.14 and 3.15 as well, but those versions are
outside motor's own support range.
Muffin-Mongo should be installed using pip:
pip install muffin-mongo
Setup the plugin and connect it into your app:
from muffin import Application
from muffin_mongo import Plugin as Mongo
# Create Muffin Application
app = Application('example')
# Initialize the plugin
# As alternative: db = DB(app, **options)
mongo = Mongo(db_url='mongodb://localhost:27017', database='db_name')
mongo.setup(app)That's it now you are able to use the plugin inside your views:
@app.route('/items', methods=['GET'])
async def get_items(request):
"""Return a JSON with items from the database."""
documents = await mongo.items.find().sort('key').to_list(100)
return [dict(dd.items(), _id=str(dd['_id'])) for dd in documents]
@app.route('/items', methods=['POST'])
async def insert_item(request):
"""Store items from JSON into database. Return ids."""
data = await request.data() # parse formdata/json from the request
res = await mongo.items.insert_many(data)
return [str(key) for key in res.inserted_ids]The plugin exposes three things:
- client (property)
- The underlying
motor.motor_asyncio.AsyncIOMotorClientinstance. It is created insetup(), so reading the property before the plugin is bound to an application fails withAssertionError: Please setup plugin with an application. - __getattr__ (proxying)
Every other attribute is proxied to motor: to the client itself, or to the database named by the
databaseoption when it is set. This is what makes the plugin feel transparent:# database='db_name' is set: mongo.items # AsyncIOMotorCollection('items' in 'db_name') mongo.items.find_one({}) # a collection method # database is not set (None): mongo.items # AsyncIOMotorDatabase('items') mongo.list_database_names() # a client method
With
databaseset the proxy points at the database, so client level methods (list_database_names(),drop_database(),start_session(), ...) are not reachable through the plugin -- usemongo.clientfor them. The same assertion as forclientapplies beforesetup().- startup / shutdown are not overridden.
Motor connects lazily: creating the client opens nothing, the first operation does. The plugin does not override the two hooks (muffin runs
BasePlugin's no-op versions) and it never closes the client when the application stops. Closing it is your job, typically on application shutdown:@app.on_shutdown async def shutdown(): mongo.client.close() # note: close() is not a coroutine
The client is still usable after the application's lifespan ends -- until
close()is called, after which motor raisespymongo.errors.InvalidOperation: Cannot use MongoClient after close.
| Name | Default value | Description |
| db_url | "mongodb://localhost:27017" |
A mongo connection URL |
| database | None |
A database name (optional) |
db_url is passed to AsyncIOMotorClient as is, so any MongoDB
connection string (replica set, credentials, options) works.
database selects what the plugin proxies to: with a name, attributes are
resolved on that database (mongo.items is a collection); with the default
None they are resolved on the client (mongo.items is a database,
mongo.list_database_names() is available).
Unknown options are ignored: the plugin only knows the keys above, and an option that doesn't match one of them is dropped instead of raising.
You are able to provide the options when you are initializing the plugin:
mongo.setup(app, db_url='mongodb://localhost:27017')Or setup it from Muffin.Application configuration using the MONGO_ prefix:
MONGO_DB_URL = 'mongodb://localhost:27017'Muffin.Application configuration options are case insensitive
If you have any suggestions, bug reports or annoyances please report them to the issue tracker at https://github.com/klen/muffin-mongo/issues
Development of Muffin-Mongo happens at: https://github.com/klen/muffin-mongo
- klen (Kirill Klenov)
Licensed under a MIT license.