As your FastAPI applications for serving machine learning models mature, you'll find that certain values shouldn't be hardcoded directly into your source files. Model paths, API keys for external services, database connection strings, logging levels, and other deployment-specific parameters often need to change between development, testing, and production environments without requiring code modifications. Handling these configurations and sensitive pieces of information, often called secrets, correctly is fundamental to building maintainable, flexible, and secure applications.
Hardcoding configuration values directly in your code makes it brittle. Imagine needing to update the path to a retrained model or change a database password. You would have to modify the code, re-test, and redeploy the entire application. Furthermore, embedding sensitive information like API keys or passwords directly in your source code and committing it to version control systems like Git is a significant security risk. This section explores common and effective methods for managing application configuration and secrets within the context of FastAPI.
A widely adopted standard for providing configuration to applications is through environment variables. These are key-value pairs set in the operating system environment where your application runs. Most deployment platforms, including Docker containers, PaaS providers, and traditional servers, offer mechanisms to set environment variables for your application process.
Python's standard library provides the os
module to access environment variables:
import os
# Access an environment variable, providing a default if not set
model_path = os.environ.get("MODEL_PATH", "./models/default_model.joblib")
api_key = os.environ.get("EXTERNAL_API_KEY") # Returns None if not set
if api_key is None:
print("Warning: EXTERNAL_API_KEY environment variable not set.")
# Handle the missing key appropriately, maybe raise an error or disable features
print(f"Using model path: {model_path}")
While straightforward, relying solely on os.environ.get
has drawbacks. Environment variables are always strings, so you'll need to manually perform type conversions (e.g., for port numbers or boolean flags) and validation. As the number of configuration parameters grows, managing them individually can become cumbersome.
FastAPI integrates seamlessly with Pydantic, and Pydantic offers a powerful feature for managing application settings through its BaseSettings
class. This approach combines the convenience of environment variables with the type safety and validation capabilities of Pydantic models.
First, ensure you have pydantic-settings
installed, which might be needed depending on your Pydantic version (Pydantic v2 separated this functionality):
pip install pydantic-settings
Now, you can define a settings class:
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import Optional
class AppSettings(BaseSettings):
# Define your configuration variables with type hints
app_name: str = "ML Prediction Service"
log_level: str = "INFO"
model_path: str
database_url: Optional[str] = None # Example optional setting
external_api_key: str # Example required secret
# Pydantic v2 configuration (if using pydantic-settings)
model_config = SettingsConfigDict(
# Load environment variables from a .env file if present
# requires python-dotenv to be installed: pip install python-dotenv
env_file='.env',
env_file_encoding='utf-8',
# Make environment variable matching case-insensitive
case_sensitive=False
)
# Instantiate settings - Pydantic automatically reads from environment variables
# and the specified .env file
settings = AppSettings()
# You can now access settings with type safety
print(f"App Name: {settings.app_name}")
print(f"Model Path: {settings.model_path}")
print(f"API Key first few chars: {settings.external_api_key[:4]}...") # Be careful logging secrets!
Pydantic's BaseSettings
will automatically attempt to read values for the defined fields from environment variables. For example, it will look for an environment variable named MODEL_PATH
(case-insensitivity can be configured) to populate the model_path
attribute. If an .env
file is specified and exists (useful for local development), variables defined there will also be loaded. Pydantic handles the type conversion and validation automatically. If a required variable (like model_path
or external_api_key
in the example) is not found either in the environment or the .env
file, Pydantic will raise a validation error upon instantiation, preventing the application from starting with missing configuration.
You can easily integrate these settings into your FastAPI application using dependency injection:
from fastapi import FastAPI, Depends
# Assuming AppSettings is defined as above
settings = AppSettings()
app = FastAPI()
# Dependency function to provide settings
def get_settings() -> AppSettings:
return settings
@app.get("/info")
async def info(current_settings: AppSettings = Depends(get_settings)):
return {
"app_name": current_settings.app_name,
"model_path": current_settings.model_path,
"log_level": current_settings.log_level
}
# Example usage in another part of your application
def load_model(settings: AppSettings = Depends(get_settings)):
# Load model using settings.model_path
print(f"Loading model from: {settings.model_path}")
# ... loading logic ...
pass
This approach keeps your configuration centralized, validated, and easily accessible throughout your application.
Secrets are sensitive pieces of configuration like API keys, database passwords, or cryptographic keys. Never commit secrets directly into your version control system (like Git). Even if the repository is private, it's poor practice and increases the risk of accidental exposure.
Using environment variables (managed via Pydantic BaseSettings
or directly with os.environ
) is a good first step for handling secrets. The actual values are injected into the application's environment at runtime, keeping them out of the codebase.
For local development, you can use a .env
file to store secrets, but ensure this .env
file is listed in your .gitignore
file to prevent accidental commits.
# .gitignore
.env
*.pyc
__pycache__/
In production environments, managing secrets via environment variables is common, but more robust solutions often involve dedicated secret management systems. These systems provide centralized storage, access control, auditing, and rotation capabilities for secrets. Examples include:
These tools typically inject secrets into the application environment (often as environment variables or mounted files) just before the application starts. While implementing these systems is beyond the scope of this specific section, it's important to be aware of them as your deployment needs become more complex. The principle remains the same: separate secrets from your code and inject them securely at runtime.
Configuration and secrets flow into the FastAPI application at runtime.
By thoughtfully managing configuration and secrets, you create applications that are easier to deploy across different environments, simpler to maintain, and significantly more secure. Using Pydantic's BaseSettings
provides a convenient and robust way to handle typed configuration loaded primarily from environment variables, aligning well with modern deployment practices.
© 2025 ApX Machine Learning