Chapter 2: Application Configuration (Settings)
In the previous chapter, we learned about the Litestar Application Core, the central chassis that holds our application together. We saw that one of its jobs during startup is to load and apply Configuration. But what is configuration, and why do we need it? Let’s dive in!
Why Do We Need Settings?
Imagine you’re building a website that needs to talk to a database. When you’re working on your own computer (your “development” environment), you might use a simple test database with a password like password123. But when you put your website online for everyone to use (your “production” environment), you’ll want to use a real, powerful database with a very strong, secret password.
How does your application know which database and which password to use? You don’t want to write the password directly into your main code – that’s insecure and inflexible!
This is where Application Configuration (Settings) comes in. It’s a way to manage all the values your application needs that might change depending on where or how it’s running.
What is Application Configuration?
Think of Application Configuration as your app’s main control panel or settings menu. It’s a central place where you define details like:
- Database: How to connect to the database (address, username, password).
- Secret Keys: Random, secret values used for security features (like signing cookies).
- External Services: API keys or URLs for other websites or tools your app uses (like sending emails or processing payments).
- Behavior Switches: Simple flags to turn features on or off (e.g.,
DEBUG_MODE = truefor development,DEBUG_MODE = falsefor production). - Logging: How much detail should be recorded in the application’s logs.
By keeping all these settings together, we make it easy to:
- Change behavior: Switch from a test database to a production database without changing the core application code.
- Keep secrets safe: Store sensitive information like passwords outside of the main codebase.
- Manage different environments: Have one set of settings for development, another for testing, and another for the live production website.
How Configuration Works in litestar-fullstack
Our project uses a common and powerful approach for handling settings:
-
Environment Variables &
.envFiles: Settings often come from the system’s “environment variables” (system-level settings) or special files named.env. The.envfile in the main project folder is great for storing your local development settings, like your test database password. It’s usually not saved in version control (like Git) to keep secrets safe. For production, settings are typically set directly as environment variables on the server. -
Python Dataclasses: Instead of just having loose variables, the project organizes settings into structured Python classes called
dataclasses. This makes the code cleaner, helps catch errors (like typos in setting names), and makes it obvious what settings are available. -
Central Loading Function: A helper function reads the
.envfile and environment variables when the app starts and automatically fills in the values in our settings dataclasses.
Let’s look at the key pieces:
1. The .env file (Example - in your project root):
# File: .env (Example - DO NOT commit real secrets!)
# App Settings
SECRET_KEY=a_super_secret_key_for_development_only # Change this!
LITESTAR_DEBUG=True
# Database Settings
DATABASE_URL=sqlite+aiosqlite:///db.sqlite3 # Use a simple file database for dev
DATABASE_ECHO=False
# Add other development settings here...
- Explanation: This simple text file defines key-value pairs. When you run the app locally, it will read this file.
LITESTAR_DEBUG=Truetells the app to run in a mode helpful for development (like showing more detailed error messages).
2. Settings Classes (Simplified View):
Imagine simplified Python classes that represent these settings:
# Simplified concept from src/app/config/base.py
from dataclasses import dataclass
@dataclass
class AppSettings:
DEBUG: bool = False # Default value is False
SECRET_KEY: str = "default_secret" # A default
@dataclass
class DatabaseSettings:
URL: str = "default_db_url" # Default database location
ECHO: bool = False # Default: don't log all SQL
@dataclass
class Settings:
# This class holds all the other setting groups
app: AppSettings = AppSettings()
db: DatabaseSettings = DatabaseSettings()
# ... other groups like ServerSettings, LogSettings ...
- Explanation: We define classes like
AppSettingsandDatabaseSettingsto hold related settings. The mainSettingsclass brings them all together. Each setting has a type (likeboolfor true/false,strfor text) and often a default value.
3. The Loading Function:
There’s a function that does the magic of reading .env and environment variables to populate these classes.
# Simplified concept from src/app/config/base.py
import os # For reading environment variables
from functools import lru_cache # For efficiency
# (Code to load .env file happens here)
@lru_cache() # Optimization: only load settings once
def get_settings() -> Settings:
"""Loads settings from environment and .env file."""
print("Loading application settings...") # You might see this on startup
# Create instances of our settings classes
app_settings = AppSettings(
# Read 'LITESTAR_DEBUG' env var, default to False if not found
DEBUG=os.getenv("LITESTAR_DEBUG", "False").lower() == "true",
SECRET_KEY=os.getenv("SECRET_KEY", "default_secret"),
)
db_settings = DatabaseSettings(
URL=os.getenv("DATABASE_URL", "default_db_url"),
ECHO=os.getenv("DATABASE_ECHO", "False").lower() == "true",
)
# ... load other settings ...
# Return the main object containing all settings
return Settings(app=app_settings, db=db_settings)
- Explanation: The
get_settings()function is called when the application starts. It reads environment variables (usingos.getenv) corresponding to the fields in our dataclasses. If an environment variable isn’t found, it uses the default value defined in the class. It returns a singleSettingsobject containing all the loaded configuration values. The@lru_cache()part is a performance trick to ensure settings are only loaded once.
Using the Configuration
Once loaded, how do other parts of the application use these settings? Often, the Litestar Application Core uses the settings during its setup process.
For example, look at how the database configuration is prepared for Litestar:
# File: src/app/config/app.py (Simplified)
from .base import get_settings # Import our loading function
# 1. Load all application settings
settings = get_settings()
# 2. Use the loaded settings to configure specific parts
# Example: Configure the database plugin
alchemy_config = SQLAlchemyAsyncConfig( # Database plugin config object
# Get the DB URL from the loaded settings
engine_instance=settings.db.get_engine(),
# ... other database options using `settings.db` ...
)
# Example: Configure Cross-Origin Resource Sharing (CORS)
cors_config = CORSConfig(
# Get allowed origins from the loaded settings
allow_origins=settings.app.ALLOWED_CORS_ORIGINS
)
- Explanation:
- The code first calls
get_settings()to get the fully loadedSettingsobject. - It then accesses specific parts of the settings using dot notation, like
settings.db.URLorsettings.app.ALLOWED_CORS_ORIGINS. - These values are used to configure various Litestar plugins and features (like the database (
alchemy_config) or security settings (cors_config)).
- The code first calls
You generally don’t need to call get_settings() everywhere in your code. The configuration is usually accessed once during startup, and the results are used to set up components (like database connections or API clients) which are then made available to the rest of your application through Litestar’s dependency injection system (which we’ll see more of later).
How Loading Works: Under the Hood
Let’s trace the steps when the application needs its settings:
- First Request: Somewhere during application startup (often within the Application Core’s
on_app_initmethod), theget_settings()function is called for the first time. - Cache Check: The
@lru_cachedecorator checks if it has already loaded the settings. Since it’s the first time, it hasn’t. .envLoad: Code insideSettings.from_env()(called byget_settings()) looks for a.envfile in the project directory. If found, it loads the key-value pairs from it into the environment (using thepython-dotenvlibrary). This happens before reading individual variables, so.envvalues are available.- Environment Read & Dataclass Population: For each setting field defined in the
Settings,AppSettings,DatabaseSettings, etc., the code attempts to read a corresponding environment variable (e.g., readingDATABASE_URLforDatabaseSettings.URL). - Type Conversion & Defaults: If an environment variable is found, its string value is converted to the correct type (e.g.,
"True"becomesTrue,"8000"becomes8000). If an environment variable is not found, the default value specified in the dataclass definition is used. - Object Creation: Instances of
AppSettings,DatabaseSettings, etc., are created with these values. - Main Settings Object: The main
Settingsobject is created, holding all the sub-setting objects (likesettings.app,settings.db). - Caching: The
@lru_cachedecorator saves this fully populatedSettingsobject. - Return: The
Settingsobject is returned to the caller. - Subsequent Requests: If
get_settings()is called again later, the@lru_cachedecorator immediately returns the saved object from step 8 without reloading everything.
Here’s a simplified diagram of that flow:
sequenceDiagram
participant Caller as Code Needing Settings (e.g., App Core)
participant GetSettings as get_settings()
participant DotEnv as python-dotenv library
participant SettingsClass as Settings Dataclasses
participant OS as Operating System Environment
Caller->>GetSettings: Call get_settings()
GetSettings->>GetSettings: Check cache (miss)
GetSettings->>DotEnv: Load variables from .env file?
DotEnv->>OS: Set environment variables from .env
GetSettings->>SettingsClass: Instantiate Settings object
loop For each setting (e.g., DEBUG, DB_URL)
SettingsClass->>OS: Read env variable (e.g., os.getenv('LITESTAR_DEBUG'))
alt Env Var Found
OS-->>SettingsClass: Return variable value (string)
SettingsClass->>SettingsClass: Convert string to correct type (e.g., bool)
else Env Var Not Found
OS-->>SettingsClass: Return None
SettingsClass->>SettingsClass: Use default value from class definition
end
SettingsClass->>SettingsClass: Store final value in object field
end
SettingsClass-->>GetSettings: Return populated Settings object
GetSettings->>GetSettings: Store object in cache
GetSettings-->>Caller: Return Settings object
Conclusion
Application Configuration is like the control panel for your application. It allows you to separate how your application runs (database details, secret keys, debug modes) from the core logic itself. litestar-fullstack uses a standard approach with .env files, environment variables, and Python dataclasses, all accessed through the get_settings() function during startup. This makes it easy to manage different settings for development and production environments without changing your application code.
Now that we understand how the application gets its basic instructions and wiring, let’s look at how we define the actual web pages or API endpoints that users interact with.
Next up: Chapter 3: API Controllers
Generated by AI Codebase Knowledge Builder