Skip to content

Commit

Permalink
S3 message spillover (#45)
Browse files Browse the repository at this point in the history
* Use middleware to spill large responses over to S3

* Fix tests

* Up requirements

* Upgrade again

* Bump middleware version
  • Loading branch information
alukach authored Sep 5, 2024
1 parent 710ea5e commit 021f960
Show file tree
Hide file tree
Showing 8 changed files with 650 additions and 82 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ jobs:
DB_NAME: mydatabase
DB_USER: myuser
DB_PASSWORD: mypassword
DB_TABLE_NAME: space2stats
DB_TABLE_NAME: space2stats
S3_BUCKET_NAME: test-bucket
14 changes: 13 additions & 1 deletion space2stats_api/cdk/aws_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from aws_cdk import aws_apigatewayv2_integrations as integrations
from aws_cdk import aws_certificatemanager as acm
from aws_cdk import aws_lambda as _lambda
from aws_cdk import aws_s3 as s3
from aws_cdk.aws_lambda_python_alpha import PythonFunction
from constructs import Construct
from settings import AppSettings, DeploymentSettings
Expand All @@ -15,6 +16,12 @@ def __init__(self, scope: Construct, id: str, **kwargs) -> None:
app_settings = AppSettings(_env_file="./aws_app.env")
deployment_settings = DeploymentSettings(_env_file="./aws_deployment.env")

bucket = s3.Bucket(
self,
"LargeResponseBucket",
lifecycle_rules=[s3.LifecycleRule(expiration=Duration.days(1))],
)

lambda_function = PythonFunction(
self,
"Space2StatsFunction",
Expand All @@ -23,10 +30,15 @@ def __init__(self, scope: Construct, id: str, **kwargs) -> None:
index="space2stats/handler.py",
timeout=Duration.seconds(120),
handler="handler",
environment=app_settings.model_dump(),
environment={
"S3_BUCKET_NAME": bucket.bucket_name,
**app_settings.model_dump(),
},
memory_size=1024,
)

bucket.grant_read_write(lambda_function)

certificate = acm.Certificate.from_certificate_arn(
self, "Certificate", deployment_settings.CDK_CERTIFICATE_ARN
)
Expand Down
654 changes: 576 additions & 78 deletions space2stats_api/src/poetry.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions space2stats_api/src/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pydantic-settings = ">=2.0.0"
typing_extensions = "*"
starlette-cramjam = ">=0.3,<0.4"
mangum = "*"
asgi-s3-response-middleware = "^0.0.4"
boto3 = "^1.35.11"

[tool.poetry.group.lambda.dependencies]
mangum = "*"
Expand All @@ -31,6 +33,7 @@ pre-commit = "*"
pytest = "*"
pytest-cov = "*"
pytest-postgresql = "*"
moto = "^5.0.13"

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
16 changes: 15 additions & 1 deletion space2stats_api/src/space2stats/app.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
from contextlib import asynccontextmanager
from typing import Any, Dict, List

import boto3
from asgi_s3_response_middleware import S3ResponseMiddleware
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import ORJSONResponse
from starlette.requests import Request
from starlette_cramjam.middleware import CompressionMiddleware

from .db import close_db_connection, connect_to_db
from .main import SummaryRequest, get_available_fields, get_summaries_from_geom
from .main import (
SummaryRequest,
get_available_fields,
get_summaries_from_geom,
settings,
)

s3_client = boto3.client("s3")


@asynccontextmanager
Expand All @@ -34,6 +43,11 @@ async def lifespan(app: FastAPI):
allow_headers=["*"],
)
app.add_middleware(CompressionMiddleware)
app.add_middleware(
S3ResponseMiddleware,
s3_bucket_name=settings.S3_BUCKET_NAME,
s3_client=s3_client,
)


@app.post("/summary", response_model=List[Dict[str, Any]])
Expand Down
3 changes: 3 additions & 0 deletions space2stats_api/src/space2stats/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ class Settings(BaseSettings):
DB_PASSWORD: str
DB_TABLE_NAME: str

# Bucket for large responses
S3_BUCKET_NAME: str

# see https://www.psycopg.org/psycopg3/docs/api/pool.html#the-connectionpool-class for options
DB_MIN_CONN_SIZE: int = 1
DB_MAX_CONN_SIZE: int = 10
Expand Down
37 changes: 37 additions & 0 deletions space2stats_api/src/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import os

import boto3
import pytest
from moto import mock_aws


@pytest.fixture()
def aws_credentials():
"""Mocked AWS Credentials for moto."""
os.environ["AWS_ACCESS_KEY_ID"] = "testing"
os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
os.environ["AWS_SECURITY_TOKEN"] = "testing"
os.environ["AWS_SESSION_TOKEN"] = "testing"
os.environ["AWS_DEFAULT_REGION"] = "us-east-1"


@pytest.fixture()
def s3_client(aws_credentials):
"""
Return a mocked S3 client
"""
with mock_aws():
yield boto3.client("s3", region_name="us-east-1")


@pytest.fixture
def test_bucket(s3_client) -> str:
bucket_name = "test-bucket"
s3_client.create_bucket(Bucket=bucket_name)

return bucket_name


@pytest.fixture(autouse=True)
def set_bucket_name(monkeypatch, test_bucket):
monkeypatch.setenv("S3_BUCKET_NAME", test_bucket)
2 changes: 1 addition & 1 deletion space2stats_api/src/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def database(postgresql_proc):


@pytest.fixture(autouse=True)
def client(monkeypatch, database):
def client(monkeypatch, database, test_bucket):
monkeypatch.setenv("DB_HOST", database.host)
monkeypatch.setenv("DB_PORT", str(database.port))
monkeypatch.setenv("DB_NAME", database.dbname)
Expand Down

0 comments on commit 021f960

Please sign in to comment.