Package: fastapi-contrib
Opinionated set of utilities on top of FastAPIFree software: MIT licenseDocumentation:https://fastapi-contrib.readthedocs.io.FeaturesAuth Backend & Middleware (User or None in every request object)Permissions: reusable class permissions, specify multiple as FastAPI DependencyModelSerializers: serialize (pydantic) incoming request, connect data with DB model and saveUJSONResponse: correctly show slashes in fields with URLsLimit-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 appCustom Exceptions and Custom Exception HandlersOpentracing middleware & setup utility with Jaeger tracer + root span available in every Request’s stateStateRequestIDMiddleware: receives configurable header and saves it in request stateRoadmapSee GitHub ProjectRoadmap.InstallationTo install just Contrib (without mongodb, pytz, ujson):$pipinstallfastapi_contribTo install contrib with mongodb support:$pipinstallfastapi_contrib[mongo]To install contrib with ujson support:$pipinstallfastapi_contrib[ujson]To install contrib with pytz support:$pipinstallfastapi_contrib[pytz]To install contrib with opentracing & Jaeger tracer:$pipinstallfastapi_contrib[jaegertracing]To install everything:$pipinstallfastapi_contrib[all]UsageTo use Limit-Offset pagination:fromfastapiimportFastAPIfromfastapi_contrib.paginationimportPaginationfromfastapi_contrib.serializers.commonimportModelSerializerfromyourapp.modelsimportSomeModelapp=FastAPI()classSomeSerializer(ModelSerializer):classMeta:model=SomeModel@app.get("/")asyncdeflist(pagination:Pagination=Depends()):filter_kwargs={}returnawaitpagination.paginate(serializer_class=SomeSerializer,**filter_kwargs)Subclass this pagination to define custom default & maximum values for offset & limit:classCustomPagination(Pagination):default_offset=90default_limit=1max_offset=100max_limit=2000To use State Request ID Middleware:fromfastapiimportFastAPIfromfastapi_contrib.common.middlewaresimportStateRequestIDMiddlewareapp=FastAPI()@app.on_event('startup')asyncdefstartup():app.add_middleware(StateRequestIDMiddleware)To use Authentication Middleware:fromfastapiimportFastAPIfromfastapi_contrib.auth.backendsimportAuthBackendfromfastapi_contrib.auth.middlewaresimportAuthenticationMiddlewareapp=FastAPI()@app.on_event('startup')asyncdefstartup():app.add_middleware(AuthenticationMiddleware,backend=AuthBackend())Define & use custom permissions based on FastAPI Dependency framework:fromfastapiimportFastAPIfromfastapi_contrib.permissionsimportBasePermission,PermissionsDependencyclassTeapotUserAgentPermission(BasePermission):defhas_required_permissions(self,request:Request)->bool:returnrequest.headers.get('User-Agent')=="Teapot v1.0"app=FastAPI()@app.get("/teapot/",dependencies=[Depends(PermissionsDependency([TeapotUserAgentPermission]))])asyncdefteapot()->dict:return{"teapot":True}Setup uniform exception-handling:fromfastapiimportFastAPIfromfastapi_contrib.exception_handlersimportsetup_exception_handlersapp=FastAPI()@app.on_event('startup')asyncdefstartup():setup_exception_handlers(app)If you want to correctly handle scenario when request is an empty body (IMPORTANT: non-multipart):fromfastapiimportFastAPIfromfastapi_contrib.routesimportValidationErrorLoggingRouteapp=FastAPI()app.router.route_class=ValidationErrorLoggingRouteOr if you use multiple routes for handling different namespaces (IMPORTANT: non-multipart):fromfastapiimportAPIRouter,FastAPIfromfastapi_contrib.routesimportValidationErrorLoggingRouteapp=FastAPI()my_router=APIRouter(route_class=ValidationErrorLoggingRoute)To correctly show slashes in fields with URLs + ascii locking:fromfastapiimportFastAPIfromfastapi_contrib.common.responsesimportUJSONResponseapp=FastAPI()@app.get("/",response_class=UJSONResponse)asyncdefroot():return{"a":"b"}Or specify it as default response class for the whole app (FastAPI >= 0.39.0):fromfastapiimportFastAPIfromfastapi_contrib.common.responsesimportUJSONResponseapp=FastAPI(default_response_class=UJSONResponse)To setup Jaeger tracer and enable Middleware that captures every request in opentracing span:fromfastapiimportFastAPIfromfastapi_contrib.tracing.middlewaresimportOpentracingMiddlewarefromfastapi_contrib.tracing.utilsimportsetup_opentracingapp=FastAPI()@app.on_event('startup')asyncdefstartup():setup_opentracing(app)app.add_middleware(OpentracingMiddleware)To setup mongodb connection at startup and never worry about it again:fromfastapiimportFastAPIfromfastapi_contrib.db.utilsimportsetup_mongodbapp=FastAPI()@app.on_event('startup')asyncdefstartup():setup_mongodb(app)Use models to map data to MongoDB:fromfastapi_contrib.db.modelsimportMongoDBModelclassMyModel(MongoDBModel):additional_field1:stroptional_field2:int=42classMeta:collection="mymodel_collection"mymodel=MyModel(additional_field1="value")mymodel.save()assertmymodel.additional_field1=="value"assertmymodel.optional_field2==42assertisinstance(mymodel.id,int)Or use TimeStamped model with creation datetime:fromfastapi_contrib.db.modelsimportMongoDBTimeStampedModelclassMyTimeStampedModel(MongoDBTimeStampedModel):classMeta:collection="timestamped_collection"mymodel=MyTimeStampedModel()mymodel.save()assertisinstance(mymodel.id,int)assertisinstance(mymodel.created,datetime)Use serializers and their response models to correctly show Schemas and convert from JSON/dict to models and back:fromfastapiimportFastAPIfromfastapi_contrib.db.modelsimportMongoDBModelfromfastapi_contrib.serializersimportopenapifromfastapi_contrib.serializers.commonimportSerializerfromyourapp.modelsimportSomeModelapp=FastAPI()classSomeModel(MongoDBModel):field1:str@openapi.patchclassSomeSerializer(Serializer):read_only1:str="const"write_only2:intnot_visible:str="42"classMeta:model=SomeModelexclude={"not_visible"}write_only_fields={"write_only2"}read_only_fields={"read_only1"}@app.get("/",response_model=SomeSerializer.response_model)asyncdefroot(serializer:SomeSerializer):model_instance=awaitserializer.save()returnmodel_instance.dict()POST-ing to this route following JSON:{"read_only1":"a","write_only2":123,"field1":"b"}Should return following response:{"id":1,"field1":"b","read_only1":"const"}Auto-creation of MongoDB indexesSuppose we have this directory structure:-- 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 variableCONTRIB_APPS_FOLDER_NAME(by the way, all the setting of this package are overridable via ENV vars withCONTRIB_prefix before them).You also need to tell fastapi_contrib which apps to look into for your models. This is controlled byCONTRIB_APPSENV variable, which is list of str names of the apps with models. In the example above, this would beCONTRIB_APPS=[“app1”,”app2”].Just use create_indexes function after setting up mongodb:fromfastapiimportFastAPIfromfastapi_contrib.db.utilsimportsetup_mongodb,create_indexesapp=FastAPI()@app.on_event("startup")asyncdefstartup():setup_mongodb(app)awaitcreate_indexes()This will scan all the specifiedCONTRIB_APPSin theCONTRIB_APPS_FOLDER_NAMEfor 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:importpymongofromfastapi_contrib.db.modelsimportMongoDBTimeStampedModelclassMyModel(MongoDBTimeStampedModel):classMeta: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.CreditsThis package was created withCookiecutterand theaudreyr/cookiecutter-pypackageproject template.