Skip to content

Commit

Permalink
Document and test FastAPI integration
Browse files Browse the repository at this point in the history
  • Loading branch information
br3ndonland committed Apr 11, 2024
1 parent 2da45db commit 4670b5b
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 0 deletions.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,41 @@ anyio.run(fastenv.dump_dotenv, dotenv)
# Path('/path/to/this/dir/.env')
```

Use fastenv in your FastAPI app:

```py
from contextlib import asynccontextmanager
from typing import AsyncIterator, TypedDict

import fastenv
from fastapi import FastAPI, Request


class LifespanState(TypedDict):
settings: fastenv.DotEnv


@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncIterator[LifespanState]:
"""Configure app lifespan.
https://fastapi.tiangolo.com/advanced/events/
https://www.starlette.io/lifespan/
"""
settings = await fastenv.load_dotenv(".env")
lifespan_state: LifespanState = {"settings": settings}
yield lifespan_state


app = FastAPI(lifespan=lifespan)


@app.get("/settings")
async def get_settings(request: Request) -> dict[str, str]:
settings = request.state.settings
return dict(settings)
```

## Documentation

Documentation is built with [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/), deployed on [Vercel](https://vercel.com/), and available at [fastenv.bws.bio](https://fastenv.bws.bio) and [fastenv.vercel.app](https://fastenv.vercel.app).
Expand Down
35 changes: 35 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,38 @@ import anyio
anyio.run(fastenv.dump_dotenv, dotenv)
# Path('/path/to/this/dir/.env')
```

Use fastenv in your FastAPI app:

```py
from contextlib import asynccontextmanager
from typing import AsyncIterator, TypedDict

import fastenv
from fastapi import FastAPI, Request


class LifespanState(TypedDict):
settings: fastenv.DotEnv


@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncIterator[LifespanState]:
"""Configure app lifespan.
https://fastapi.tiangolo.com/advanced/events/
https://www.starlette.io/lifespan/
"""
settings = await fastenv.load_dotenv(".env")
lifespan_state: LifespanState = {"settings": settings}
yield lifespan_state


app = FastAPI(lifespan=lifespan)


@app.get("/settings")
async def get_settings(request: Request) -> dict[str, str]:
settings = request.state.settings
return dict(settings)
```
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ httpx = [
]
tests = [
"coverage[toml]>=7,<8",
"fastapi>=0.110.1,<0.111",
"freezegun>=1,<2",
"httpx>=0.23,<1",
"pytest>=8.1.1,<9",
Expand Down
60 changes: 60 additions & 0 deletions tests/test_fastapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from __future__ import annotations

import os
from contextlib import asynccontextmanager
from typing import AsyncGenerator, AsyncIterator, Dict, TypedDict

import pytest
from anyio import Path
from fastapi import FastAPI, Request
from fastapi.testclient import TestClient

import fastenv


class LifespanState(TypedDict):
settings: fastenv.DotEnv


@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncIterator[LifespanState]:
"""Configure app lifespan.
https://fastapi.tiangolo.com/advanced/events/
https://www.starlette.io/lifespan/
"""
env_file = os.environ["ENV_FILE"]
settings = await fastenv.load_dotenv(env_file)
lifespan_state: LifespanState = {"settings": settings}
yield lifespan_state


app = FastAPI(lifespan=lifespan)


@app.get("/settings")
async def get_settings(request: Request) -> Dict[str, str]:
settings = request.state.settings
return dict(settings)


@pytest.fixture
async def test_client(
env_file: Path, monkeypatch: pytest.MonkeyPatch
) -> AsyncGenerator[TestClient, None]:
"""Instantiate a FastAPI test client.
https://fastapi.tiangolo.com/tutorial/testing/
https://www.starlette.io/testclient/
"""
monkeypatch.setenv("ENV_FILE", str(env_file))
with TestClient(app) as test_client:
yield test_client


@pytest.mark.anyio
async def test_fastapi_with_fastenv(test_client: TestClient) -> None:
"""Test loading a dotenv file into a FastAPI app with fastenv."""
response = test_client.get("/settings")
response_json = response.json()
assert response_json["AWS_ACCESS_KEY_ID_EXAMPLE"] == "AKIAIOSFODNN7EXAMPLE"

0 comments on commit 4670b5b

Please sign in to comment.