Asynchronous Applications with MongoDB and Beanie in PyNest
Introduction
This documentation introduces a feature in PyNest that enables the creation of asynchronous applications using MongoDB and Beanie. This feature allows for efficient and scalable database operations in Python's asynchronous programming environment and leveraging the great capabilities of mongodb and it's nosql nature.
Requirements
- Python 3.9+
- PyNest (latest version)
- beanie
- motor
Beanie
Beanie - is an asynchronous Python object-document mapper (ODM) for MongoDB. Data models are based on Pydantic.
When using Beanie each database collection has a corresponding Document that is used to interact with that collection. In addition to retrieving data, Beanie allows you to add, update, or delete documents from the collection as well.
read more about beanie here
Motor
Motor presents a coroutine-based API for non-blocking access to MongoDB from Tornado or asyncio. The coroutine-based API simplifies asynchronous code, making it readable and maintainable. Motor also makes it easy to use MongoDB features that are unavailable in the MongoDB Python driver, like Aggregation Framework builders.
read more about motor here
Setting Up
Installation and Setup
Ensure you have the latest version of PyNest and SQLAlchemy 2.0 installed. You can install them using pip:
Start with cli
Create a new project
this command will create a new project with the following structure:
├── app.py
├── main.py
|── requirements.txt
|── README.md
├── src
│ ├── __init__.py
│ ├── config.py
│ ├── app_module.py
├── |── app_controller.py
├── |── app_service.py
once you have created your app, you can create a new module:
This will create a new module called examples in your application with the following structure under the src folder:
├── examples
│ ├── __init__.py
│ ├── examples_controller.py
│ ├── examples_service.py
│ ├── examples_model.py
│ ├── examples_entity.py
│ ├── examples_module.py
Let's go over the boilerplate code that support the mongo integration:
config.py
from nest.core.database.odm_provider import OdmProvider
from src.examples.examples_entity import Examples
import os
from dotenv import load_dotenv
load_dotenv()
config = OdmProvider(
config_params={{
"db_name": os.getenv("DB_NAME", "default_nest_db"),
"host": os.getenv("DB_HOST", "localhost"),
"user": os.getenv("DB_USER", "root"),
"password": os.getenv("DB_PASSWORD", "root"),
"port": os.getenv("DB_PORT", 27017),
}},
document_models=[Examples]
)
Note: you can add any parameters that needed in order to configure the database connection.
app_service.py
from nest.core import Injectable
@Injectable
class AppService:
def __init__(self):
self.app_name = "MongoApp"
self.app_version = "1.0.0"
async def get_app_info(self):
return {"app_name": self.app_name, "app_version": self.app_version}
app_controller.py
from nest.core import Controller, Get
from .app_service import AppService
@Controller("/")
class AppController:
def __init__(self, service: AppService):
self.service = service
@Get("/")
async def get_app_info(self):
return await self.service.get_app_info()
Now we need to declare the App object and register the module in
app_module.py
from .config import config
from nest.core import Module, PyNestFactory
from src.examples.examples_module import ExamplesModule
from .app_controller import AppController
from .app_service import AppService
@Module(
imports=[ExamplesModule],
controllers=[AppController],
providers=[AppService],
)
class AppModule:
pass
app = PyNestFactory.create(
AppModule,
description="This is my FastAPI app drive by MongoDB Engine",
title="My App",
version="1.0.0",
debug=True,
)
http_server = app.get_server()
@http_server.on_event("startup")
async def startup():
await config.create_all()
@Module(...)
: This is a decorator that defines a module. In PyNest, a module is a class annotated with a @Module()
decorator.
The imports array includes the modules required by this module. In this case, ExampleModule is imported. The controllers
and providers arrays are empty here, indicating this module doesn't directly provide any controllers or services.
PyNestFactory.create()
is a command to create an instance of the application.
The AppModule is passed as an argument, which acts as the root module of the application.
Additional metadata like description, title, version, and debug flag are also provided
http_server: FastAPI = app.get_server()
: Retrieves the HTTP server instance from the application.
Creating Entity
Define your Object Document Mapping using Beanie Document object. For example, the Examples model:
from beanie import Document
class Example(Document):
name: str
class Config:
schema_extra = {
"example": {
"name": "Example Name",
}
}
Creating Model
Define your model using Pydantic. For example, the Examples model:
Creating Service
Implement services to handle business logic.
from .examples_model import Examples
from .examples_entity import Examples as ExamplesEntity
from nest.core.decorators.database import db_request_handler
from nest.core import Injectable
@Injectable
class ExamplesService:
@db_request_handler
async def add_examples(self, examples: Examples):
new_examples = ExamplesEntity(
**examples.dict()
)
await new_examples.save()
return new_examples.id
@db_request_handler
async def get_examples(self):
return await ExamplesEntity.find_all().to_list()
create a controller to handle the requests and responses. The controller should call the service to execute business logic.
from nest.core import Controller, Get, Post
from .examples_service import ExamplesService
from .examples_model import Examples
@Controller("examples")
class ExamplesController:
def __init__(self, service: ExamplesService):
self.service = service
@Get("/")
async def get_examples(self):
return await self.service.get_examples()
@Post("/")
async def add_examples(self, examples: Examples):
return await self.service.add_examples(examples)
Creating Module
Create a module to register the controller and service.
from nest.core import Module
from .examples_controller import ExamplesController
from .examples_service import ExamplesService
@Module(
controllers=[ExamplesController],
providers=[ExamplesService]
)
class ExamplesModule:
pass
Run the application
Now you can access the application at http://localhost:8000/docs and test the endpoints.