diff --git a/README.md b/README.md index d7a5cb6..5797b03 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/docs/index.md b/docs/index.md index 9d5116d..5c29fc1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -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) +``` diff --git a/pyproject.toml b/pyproject.toml index ce8cf4b..d224946 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", diff --git a/tests/test_fastapi.py b/tests/test_fastapi.py new file mode 100644 index 0000000..0c07731 --- /dev/null +++ b/tests/test_fastapi.py @@ -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"