Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/identixone/fastapi_contrib
Opinionated set of utilities on top of FastAPI
https://github.com/identixone/fastapi_contrib
fastapi fastapi-template mongodb pydantic python starlette ujson
Last synced: 5 days ago
JSON representation
Opinionated set of utilities on top of FastAPI
- Host: GitHub
- URL: https://github.com/identixone/fastapi_contrib
- Owner: identixone
- License: mit
- Created: 2019-08-21T20:51:35.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2022-09-12T13:55:21.000Z (over 2 years ago)
- Last Synced: 2024-12-13T17:23:02.897Z (13 days ago)
- Topics: fastapi, fastapi-template, mongodb, pydantic, python, starlette, ujson
- Language: Python
- Homepage: https://fastapi-contrib.readthedocs.io
- Size: 215 KB
- Stars: 623
- Watchers: 8
- Forks: 30
- Open Issues: 15
-
Metadata Files:
- Readme: README.rst
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.rst
- License: LICENSE
Awesome Lists containing this project
- awesome-fastapi - FastAPI Contrib - Opinionated set of utilities: pagination, auth middleware, permissions, custom exception handlers, MongoDB support, and Opentracing middleware. (Third-Party Extensions / Utils)
- awesome-fastapi - FastAPI Contrib - Opinionated set of utilities: pagination, auth middleware, permissions, custom exception handlers, MongoDB support, and Opentracing middleware. (Third-Party Extensions / Utils)
README
===============
FastAPI Contrib
===============.. image:: https://img.shields.io/pypi/v/fastapi_contrib.svg
:target: https://pypi.python.org/pypi/fastapi_contrib.. image:: https://img.shields.io/travis/identixone/fastapi_contrib.svg
:target: https://travis-ci.org/identixone/fastapi_contrib.. image:: https://readthedocs.org/projects/fastapi-contrib/badge/?version=latest
:target: https://fastapi-contrib.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status.. image:: https://pyup.io/repos/github/identixone/fastapi_contrib/shield.svg
:target: https://pyup.io/repos/github/identixone/fastapi_contrib/
:alt: UpdatesOpinionated set of utilities on top of FastAPI
* Free software: MIT license
* Documentation: https://fastapi-contrib.readthedocs.io.Features
--------* Auth Backend & Middleware (User or None in every request object)
* Permissions: reusable class permissions, specify multiple as FastAPI Dependency
* ModelSerializers: serialize (pydantic) incoming request, connect data with DB model and save
* UJSONResponse: correctly show slashes in fields with URLs
* Limit-Offset Pagination: use it as FastAPI Dependency (works only with ModelSerializers for now)
* MongoDB integration: Use models as if it was Django (based on pydantic models)
* MongoDB indices verification on startup of the app
* Custom Exceptions and Custom Exception Handlers
* Opentracing middleware & setup utility with Jaeger tracer + root span available in every Request's state
* StateRequestIDMiddleware: receives configurable header and saves it in request stateRoadmap
--------See GitHub Project `Roadmap `_.
Installation
------------To install just Contrib (without mongodb, pytz, ujson):
.. code-block:: console
$ pip install fastapi_contrib
To install contrib with mongodb support:
.. code-block:: console
$ pip install fastapi_contrib[mongo]
To install contrib with ujson support:
.. code-block:: console
$ pip install fastapi_contrib[ujson]
To install contrib with pytz support:
.. code-block:: console
$ pip install fastapi_contrib[pytz]
To install contrib with opentracing & Jaeger tracer:
.. code-block:: console
$ pip install fastapi_contrib[jaegertracing]
To install everything:
.. code-block:: console
$ pip install fastapi_contrib[all]
Usage
-----To use Limit-Offset pagination:
.. code-block:: python
from fastapi import FastAPI
from fastapi_contrib.pagination import Pagination
from fastapi_contrib.serializers.common import ModelSerializer
from yourapp.models import SomeModelapp = FastAPI()
class SomeSerializer(ModelSerializer):
class Meta:
model = SomeModel@app.get("/")
async def list(pagination: Pagination = Depends()):
filter_kwargs = {}
return await pagination.paginate(
serializer_class=SomeSerializer, **filter_kwargs
)Subclass this pagination to define custom default & maximum values for offset & limit:
.. code-block:: python
class CustomPagination(Pagination):
default_offset = 90
default_limit = 1
max_offset = 100
max_limit = 2000To use State Request ID Middleware:
.. code-block:: python
from fastapi import FastAPI
from fastapi_contrib.common.middlewares import StateRequestIDMiddlewareapp = FastAPI()
@app.on_event('startup')
async def startup():
app.add_middleware(StateRequestIDMiddleware)To use Authentication Middleware:
.. code-block:: python
from fastapi import FastAPI
from fastapi_contrib.auth.backends import AuthBackend
from fastapi_contrib.auth.middlewares import AuthenticationMiddlewareapp = FastAPI()
@app.on_event('startup')
async def startup():
app.add_middleware(AuthenticationMiddleware, backend=AuthBackend())Define & use custom permissions based on FastAPI Dependency framework:
.. code-block:: python
from fastapi import FastAPI
from fastapi_contrib.permissions import BasePermission, PermissionsDependencyclass TeapotUserAgentPermission(BasePermission):
def has_required_permissions(self, request: Request) -> bool:
return request.headers.get('User-Agent') == "Teapot v1.0"app = FastAPI()
@app.get(
"/teapot/",
dependencies=[Depends(
PermissionsDependency([TeapotUserAgentPermission]))]
)
async def teapot() -> dict:
return {"teapot": True}Setup uniform exception-handling:
.. code-block:: python
from fastapi import FastAPI
from fastapi_contrib.exception_handlers import setup_exception_handlersapp = FastAPI()
@app.on_event('startup')
async def startup():
setup_exception_handlers(app)If you want to correctly handle scenario when request is an empty body (IMPORTANT: non-multipart):
.. code-block:: python
from fastapi import FastAPI
from fastapi_contrib.routes import ValidationErrorLoggingRouteapp = FastAPI()
app.router.route_class = ValidationErrorLoggingRouteOr if you use multiple routes for handling different namespaces (IMPORTANT: non-multipart):
.. code-block:: python
from fastapi import APIRouter, FastAPI
from fastapi_contrib.routes import ValidationErrorLoggingRouteapp = FastAPI()
my_router = APIRouter(route_class=ValidationErrorLoggingRoute)
To correctly show slashes in fields with URLs + ascii locking:
.. code-block:: python
from fastapi import FastAPI
from fastapi_contrib.common.responses import UJSONResponseapp = FastAPI()
@app.get("/", response_class=UJSONResponse)
async def root():
return {"a": "b"}Or specify it as default response class for the whole app (FastAPI >= 0.39.0):
.. code-block:: python
from fastapi import FastAPI
from fastapi_contrib.common.responses import UJSONResponseapp = FastAPI(default_response_class=UJSONResponse)
To setup Jaeger tracer and enable Middleware that captures every request in opentracing span:
.. code-block:: python
from fastapi import FastAPI
from fastapi_contrib.tracing.middlewares import OpentracingMiddleware
from fastapi_contrib.tracing.utils import setup_opentracingapp = FastAPI()
@app.on_event('startup')
async def startup():
setup_opentracing(app)
app.add_middleware(OpentracingMiddleware)To setup mongodb connection at startup and never worry about it again:
.. code-block:: python
from fastapi import FastAPI
from fastapi_contrib.db.utils import setup_mongodbapp = FastAPI()
@app.on_event('startup')
async def startup():
setup_mongodb(app)Use models to map data to MongoDB:
.. code-block:: python
from fastapi_contrib.db.models import MongoDBModel
class MyModel(MongoDBModel):
additional_field1: str
optional_field2: int = 42class Meta:
collection = "mymodel_collection"mymodel = MyModel(additional_field1="value")
mymodel.save()assert mymodel.additional_field1 == "value"
assert mymodel.optional_field2 == 42
assert isinstance(mymodel.id, int)Or use TimeStamped model with creation datetime:
.. code-block:: python
from fastapi_contrib.db.models import MongoDBTimeStampedModel
class MyTimeStampedModel(MongoDBTimeStampedModel):
class Meta:
collection = "timestamped_collection"mymodel = MyTimeStampedModel()
mymodel.save()assert isinstance(mymodel.id, int)
assert isinstance(mymodel.created, datetime)Use serializers and their response models to correctly show Schemas and convert from JSON/dict to models and back:
.. code-block:: python
from fastapi import FastAPI
from fastapi_contrib.db.models import MongoDBModel
from fastapi_contrib.serializers import openapi
from fastapi_contrib.serializers.common import Serializerfrom yourapp.models import SomeModel
app = FastAPI()
class SomeModel(MongoDBModel):
field1: str@openapi.patch
class SomeSerializer(Serializer):
read_only1: str = "const"
write_only2: int
not_visible: str = "42"class Meta:
model = SomeModel
exclude = {"not_visible"}
write_only_fields = {"write_only2"}
read_only_fields = {"read_only1"}@app.get("/", response_model=SomeSerializer.response_model)
async def root(serializer: SomeSerializer):
model_instance = await serializer.save()
return model_instance.dict()POST-ing to this route following JSON:
.. code-block:: json
{"read_only1": "a", "write_only2": 123, "field1": "b"}
Should return following response:
.. code-block:: json
{"id": 1, "field1": "b", "read_only1": "const"}
Auto-creation of MongoDB indexes
----------------------------------------------------------------Suppose we have this directory structure:
.. code-block:: console
-- project_root/
-- apps/
-- app1/
-- models.py (with MongoDBModel inside with indices declared)
-- app2/
-- models.py (with MongoDBModel inside with indices declared)Based on this, your name of the folder with all the apps would be "apps". This is the default name for fastapi_contrib package to pick up your structure automatically. You can change that by setting ENV variable `CONTRIB_APPS_FOLDER_NAME` (by the way, all the setting of this package are overridable via ENV vars with `CONTRIB_` prefix before them).
You also need to tell fastapi_contrib which apps to look into for your models. This is controlled by `CONTRIB_APPS` ENV variable, which is list of str names of the apps with models. In the example above, this would be `CONTRIB_APPS=["app1","app2"]`.
Just use create_indexes function after setting up mongodb:
.. code-block:: python
from fastapi import FastAPI
from fastapi_contrib.db.utils import setup_mongodb, create_indexesapp = FastAPI()
@app.on_event("startup")
async def startup():
setup_mongodb(app)
await create_indexes()This will scan all the specified `CONTRIB_APPS` in the `CONTRIB_APPS_FOLDER_NAME` for models, that are subclassed from either MongoDBModel or MongoDBTimeStampedModel and create indices for any of them that has Meta class with indexes attribute:
models.py:
.. code-block:: python
import pymongo
from fastapi_contrib.db.models import MongoDBTimeStampedModelclass MyModel(MongoDBTimeStampedModel):
class Meta:
collection = "mymodel"
indexes = [
pymongo.IndexModel(...),
pymongo.IndexModel(...),
]This would not create duplicate indices because it relies on pymongo and motor to do all the job.
Credits
-------This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template.
.. _Cookiecutter: https://github.com/audreyr/cookiecutter
.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage