s3rius / FastAPI-template

Feature rich robust FastAPI template.
MIT License
1.92k stars 166 forks source link

Question: where to put Mangum handler? #65

Closed am1ru1 closed 2 years ago

am1ru1 commented 2 years ago

How do I add the Mangum handler into this template produced code?

Intention: https://dwisulfahnur.medium.com/fastapi-deployment-to-aws-lambda-with-serverless-framework-b637b455142c

from fastapi import FastAPI

from app.api.api_v1.api import router as api_router
from mangum import Mangum

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello World!"}

app.include_router(api_router, prefix="api/v1")
handler = Mangum(app)

not sure how to wrap the handler to the app?

def main() -> None:
    """Entrypoint of the application."""
    uvicorn.run(
        "<project>.web.application:get_app",
        workers=settings.workers_count,
        host=settings.host,
        port=settings.port,
        reload=settings.reload,
        factory=True,
    )

def get_app() -> FastAPI:
    """
    Get FastAPI application.

    This is the main constructor of an application.

    :return: application.
    """
    app = FastAPI(
        title="<project>",
        description="<project>",
        version=metadata.version("<project>"),
        docs_url=None,
        redoc_url=None,
        openapi_url="/api/openapi.json",
        default_response_class=UJSONResponse,
    )

    app.on_event("startup")(startup(app))
    app.on_event("shutdown")(shutdown(app))

    app.include_router(router=api_router, prefix="/api")
    app.mount(
        "/static",
        StaticFiles(directory=APP_ROOT / "static"),
        name="static",
    )

    return app
s3rius commented 2 years ago

Hi. I guess the best option would be to add this at the end of the main file.

handler = Mangum(get_app())
s3rius commented 2 years ago

And after you do it, you can try to add this to your serverless.yaml.

    handler: <project>.web.application.handler
s3rius commented 2 years ago

I'm not really sure if it's going to work as soon as you do the following, since I've not done anything to build serverless applications.

But if it works, I suggest you to share sample project so I can add configuration for serverless deployment.

gugwad commented 9 months ago

@s3rius I tried something similar, when i execute serverless locally, I get error ModuleNotFoundError: No module named 'project_x' and my handle is handler: project_x.web.application.handler

s3rius commented 9 months ago

Can you provide more information on this?

gugwad commented 9 months ago

Certainly, I believe I've resolved the issue, though it took some time due to an incorrect Python version for serverless deployment. To assist others, I've documented the steps I took.

I created a handler.py file in the project folder (fastapi_template, in my case it is src folder), drawing inspiration mainly from the application.py file.

import logging
import sentry_sdk
from src.settings import settings
from mangum import Mangum
from importlib import metadata
from pathlib import Path
from fastapi import FastAPI
from fastapi.responses import UJSONResponse
from fastapi.staticfiles import StaticFiles
from sentry_sdk.integrations.fastapi import FastApiIntegration
from sentry_sdk.integrations.logging import LoggingIntegration

from src.logging import configure_logging
from src.settings import settings
from src.web.api.router import api_router

APP_ROOT = Path(__file__).parent.parent

def get_handler() -> FastAPI:
    """
    Get the FastAPI application.

    This is the main constructor of an application.

    :return: The application.
    """
    configure_logging()
    if settings.sentry_dsn:
        # Enables Sentry integration.
        sentry_sdk.init(
            dsn=settings.sentry_dsn,
            traces_sample_rate=settings.sentry_sample_rate,
            environment=settings.environment,
            integrations=[
                FastApiIntegration(transaction_style="endpoint"),
                LoggingIntegration(
                    level=logging.getLevelName(
                        settings.log_level.value,
                    ),
                    event_level=logging.ERROR,
                ),
            ],
        )
    app = FastAPI(
        title="src",
        docs_url=None,
        redoc_url=None,
        openapi_url="/api/openapi.json",
        default_response_class=UJSONResponse,
    )

    # Adds startup and shutdown events.
    # register_startup_event(app)
    # register_shutdown_event(app)

    # Main router for the API.
    app.include_router(router=api_router, prefix="/api")
    # Adds a static directory.
    # This directory is used to access Swagger files.
    app.mount(
        "/static",
        StaticFiles(directory=APP_ROOT / "src/static"),
        name="static",
    )
    return app

handler = Mangum(get_handler(), lifespan="off")

The serverless.yaml file is configured for AWS Lambda deployment. Essential details such as service name, AWS profile, region, stage, runtime, and timeout are specified.

service: project-x
provider:
  name: aws
  profile: ${opt:aws-profile, "project-x-dev"}
  region: "us-west-1"
  stage: ${opt:stage, "dev"}
  runtime: python3.9
  timeout: 120

plugins:
  - serverless-offline
  - serverless-python-requirements

custom:
  pythonRequirements:
    dockerizePip: true
    usePoetry: true
    pythonBin: <poetry-python-env>/bin/python

functions:
  app:
    handler: src.handler.handler
    environment:
      STAGE: ${self:provider.stage}
      PYTHONPATH: <poetry-python-env>/lib/python3.9/site-packages
    events:
      - http:
          method: any
          path: /{proxy+}

Remember to modify paths in the serverless.yaml file according to your local machine configuration. Run the following command will start the serverless server serverless offline --stage dev --noPrependStageInUrl