-
Notifications
You must be signed in to change notification settings - Fork 0
API configuration using pydantic-settings
#4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| from functools import lru_cache | ||
|
|
||
| from pydantic import Field, SecretStr | ||
| from pydantic_settings import BaseSettings, SettingsConfigDict | ||
|
|
||
|
|
||
| class Settings(BaseSettings): | ||
| model_config = SettingsConfigDict(env_file=".env") | ||
| # no default value for database_url, it must be provided via environment variable or .env file | ||
| database_url: SecretStr = Field( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Future enhancement idea (not blocking): If you want to validate that from pydantic import PostgresDsn
class Settings(BaseSettings):
database_url: PostgresDsn = Field(...)
# ...This would catch typos like |
||
| description="Database URL in the format: postgresql://user:password@host:port/database", | ||
| ) | ||
| environment: str = "development" | ||
|
|
||
|
|
||
| @lru_cache | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice use of For context: FastAPI dependencies like this are called on every request, so without caching you'd be re-reading env vars constantly. The cache makes this essentially free after the first call. One small note: in Python 3.9+, you can use |
||
| def get_settings() -> Settings: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question about the In my experience, this shouldn't be necessary —
If you do need the ignore, consider making it more specific: |
||
| # database_url does not have a default value, so if it's missing from the environment variables or .env file, | ||
| # Pydantic will raise a validation error here, which is desirable to catch configuration issues | ||
| # The type ignore below is for static type checkers (e.g., mypy) because 'database_url' has no default value, | ||
| # but this is safe since Pydantic will enforce its presence at runtime. | ||
| return Settings() # type: ignore[call-arg] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,15 @@ | ||
| import os | ||
| from collections.abc import AsyncGenerator | ||
|
|
||
| from dotenv import load_dotenv | ||
| from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine | ||
| from sqlmodel import SQLModel | ||
|
|
||
| load_dotenv() | ||
| from .config import get_settings | ||
|
|
||
| DATABASE_URL = os.environ.get("DATABASE_URL") | ||
| if not DATABASE_URL: | ||
| raise ValueError( | ||
| "DATABASE_URL environment variable is required. " | ||
| "See .env.example for setup instructions." | ||
| ) | ||
| settings = get_settings() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good refactor here! Moving from manual
The old explicit check was good defensive coding, but pydantic's validation is more comprehensive (it also checks types, not just presence). |
||
|
|
||
| engine = create_async_engine( | ||
| DATABASE_URL, echo=os.environ.get("ENVIRONMENT") == "development" | ||
| ) | ||
| DATABASE_URL = settings.database_url.get_secret_value() | ||
|
|
||
| engine = create_async_engine(DATABASE_URL, echo=settings.environment == "development") | ||
|
|
||
| # keeps objects usable after commit (relevant for async sessions) | ||
| _session_factory = async_sessionmaker(engine, expire_on_commit=False) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great use of
SecretStr! This prevents the database URL (which contains credentials) from being accidentally logged or printed. When you call.get_secret_value()indatabase.py, you're explicitly acknowledging you need the raw value.One thing to know:
SecretStrdoesn't encrypt anything — it just hides the value in logs and repr() output. The actual security comes from keeping the.envfile out of version control (already done via.gitignore).