bookmark_borderMeasuring Sentry Impact on AWS Lambda Cold Start and Time to Handler

At Local Logic, we rely heavily on lambda functions. We currently monitor our environment with Sentry via their sdk and know that we could use, as an alternative, the sentry layer. As a good developer, I wondered what were the pros and cons. Obviously, one of them is: which option is the fastest? So, I compared them and will share the results in this post. Without further introduction, let’s dig into the experiment.

Continue reading “Measuring Sentry Impact on AWS Lambda Cold Start and Time to Handler”

bookmark_borderIt’s a Trap! Postgis Geometry with SRID 4326 is not a Geography

Long story short, you create a table with a geometry column that stores points with srid 4326 (read geographic lat/lng, or should I write lng/lat, coordinates). That should be interpreted as geography right? Not at all!

Continue reading “It’s a Trap! Postgis Geometry with SRID 4326 is not a Geography”

bookmark_borderHybrid Pydantic/FastAPI Model

Short story, I have an API that doesn’t run on FastAPI, but I still use FastAPI to generate documentation in OpenAPI format.

So I have models for endpoints and I want them to work with and without FastAPI, because in their pure version FastAPI is not deployed. (To make a long story short, installing FastAPI makes the package a bit heavier to deploy, but especially the cold starts of the AWS lambdas because of the Sentry initialization delay. Sentry detects that FastAPI is present in the environment and automatically adds monitoring, which takes a good 500ms longer than when it’s missing. Measured in December 2023).

Anyway, drawing heavily on a StackOverflow answer, I produced this.

# models.py
from typing import Annotated, Any, cast

from pydantic import BaseModel, Field
from pydantic.fields import FieldInfo

try:
    from fastapi import Query  # type:ignore[import-not-found]

    def FieldQuery(*args: Any, **kwargs: Any) -> FieldInfo:  # noqa:N802
        return cast(FieldInfo, Field(Query(*args, **kwargs)))

except ImportError:

    def FieldQuery(*args: Any, **kwargs: Any) -> FieldInfo:  # noqa:N802
        return cast(FieldInfo, Field(*args, **kwargs))  # type: ignore[pydantic-field]


class QueryParams(BaseModel):
    limit: Annotated[
        int,
        FieldQuery(
            -1,
            description="The limit of values to fetch. -1 means no limit.",
        ),
    ]

And for the routes:

# routes.py
from fastapi import FastAPI

from models import QueryParams

app = FastAPI()

@app.get("/things")
def get_things(
    params: Annotated[QueryParams, Depends()],
) -> dict:
    ...

Is it perfect? With the “type: ignore”, “noqa”, “import try/except”, not at all. It looks like a big hack.

Does it work? Oh yeah!

Note: The “noqa” and “type: ignore” tags are respectively linked to ruff and mypy linters.

bookmark_borderFastAPI Stripe Webhook Template

A FastAPI equivalent for verify-events-came-from-stripe.

import os

from http import HTTPStatus
from typing import Annotated

import stripe
from fastapi import Depends, FastAPI, Header, HTTPException, Request


app = FastAPI()


async def get_body(request: Request) -> bytes:
    return await request.body()


@app.post("/webhook", status_code=HTTPStatus.NO_CONTENT)
def post_report(
    stripe_signature: Annotated[str, Header(alias="stripe-signature")],
    body: bytes = Depends(get_body),
) -> None:
    endpoint_secret = os.environ["ENDPOINT_SECRET"]

    try:
        # signature validation
        event = stripe.Webhook.construct_event(body, stripe_signature, endpoint_secret)
    except ValueError as e:
        # Invalid payload
        raise HTTPException(status_code=HTTPStatus.BAD_REQUEST) from e
    except stripe.error.SignatureVerificationError as e:
        # Invalid signature
        raise HTTPException(status_code=HTTPStatus.UNPROCESSABLE_ENTITY) from e

    print(event)

    return

A possible catch, one that I ran into, is that I had FastAPI convert the request.body to a dictionary in the function parameters (so body: dict). Then I serialized it to a string for the validation step… and it failed because it was no longer identical to what came in.

bookmark_borderPython Type Hinting: To hint or to cast?

During a code review, a colleague, Zachary Paden, asked me why I was calling the typing.cast function on my variables rather than creating temporary variables just to type hint. Well, just as he didn’t know about cast, I didn’t know that this approach worked. Being the nerdz that we are, he decided to measure the performance of each approach.

Continue reading “Python Type Hinting: To hint or to cast?”