Of course! That's an excellent question. The phrase "Python is settings" is a bit informal, but it gets to the heart of how Python applications are configured.

In short, "Python is settings" refers to the practice of configuring a Python application's behavior through a dedicated configuration file or module, rather than hard-coding values directly into the source code.
This is a fundamental principle of writing clean, maintainable, and flexible software.
Let's break down why this is so important, the common patterns, and best practices.
The Problem with Hard-Coding
Imagine you have a simple web application:

# app.py
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
# This is BAD - the database URL is hard-coded!
db_connection_string = "postgresql://user:password@localhost:5432/mydb"
return {"message": f"Connecting to DB at {db_connection_string}"}
if __name__ == "__main__":
# This port is also hard-coded!
uvicorn.run(app, host="0.0.0.0", port=8000)
This code is difficult to manage for several reasons:
- Environment Specificity: The database URL and port might be different on your local machine (
localhost), your testing server, and your production server. - Security: You shouldn't commit database passwords or API keys to your version control (like Git).
- Reusability: You can't easily run the same code on different ports or for different purposes without changing the code itself.
- Collaboration: If another developer needs a different database, they have to modify the code, which can lead to conflicts.
The Solution: Externalizing Settings
The solution is to move all these "magic values" into a separate settings file or module. This is what "Python is settings" means in practice.
Here are the most common and effective ways to do this.
Method 1: The Classic settings.py File
This is the simplest approach. You create a dedicated Python file for your settings.
settings.py
# This file contains all the configuration for your application. # App Settings APP_NAME = "My Awesome App" DEBUG = True SECRET_KEY = "a-very-secret-key-that-you-should-change" # Database Settings DATABASE_URL = "postgresql://user:password@localhost:5432/mydb" # Server Settings HOST = "0.0.0.0" PORT = 8000
app.py (Updated)
# app.py
import uvicorn
from fastapi import FastAPI
# Import the settings file
from settings import DATABASE_URL, HOST, PORT
app = FastAPI()
@app.get("/")
def read_root():
# Now we use the variable from our settings file
return {"message": f"Connecting to DB at {DATABASE_URL}"}
if __name__ == "__main__":
# And use it here as well
uvicorn.run(app, host=HOST, port=PORT)
Pros:
- Simple and easy to understand.
- Provides centralization for all configuration.
Cons:
- Security Risk: You must add
settings.pyto your.gitignorefile to avoid committing secrets. This can be forgotten. - Not ideal for managing different environments (dev, staging, prod).
Method 2: Using Environment Variables (The Modern Standard)
This is the most recommended approach for production applications. You store configuration values as environment variables on the machine where the code is running. Python libraries like python-dotenv make this easy.
Create a .env file for local development.
.env
# Add this file to your .gitignore! APP_NAME="My Awesome App" DEBUG=True SECRET_KEY="a-very-secret-key-that-you-should-change" DATABASE_URL="postgresql://user:password@localhost:5432/mydb" HOST="0.0.0.0" PORT="8000"
Create a settings.py file that loads from the .env file.
settings.py
from pydantic import BaseSettings, Field
class Settings(BaseSettings):
"""Application settings loaded from environment variables."""
app_name: str = Field(default="My Awesome App", env="APP_NAME")
debug: bool = Field(default=False, env="DEBUG")
secret_key: str = Field(..., env="SECRET_KEY") # Ellipsis (...) means it's required
database_url: str = Field(..., env="DATABASE_URL")
host: str = Field(default="0.0.0.0", env="HOST")
port: int = Field(default=8000, env="PORT")
class Config:
env_file = ".env" # Tells pydantic to read from .env
# Create a single instance to be imported by other modules
settings = Settings()
Update your application code.
app.py (Updated with Pydantic)
# app.py
import uvicorn
from fastapi import FastAPI
# Import the settings instance
from settings import settings
app = FastAPI()
@app.get("/")
def read_root():
# The settings object gives us type-hinted access to our variables
return {"message": f"Connecting to DB at {settings.database_url}"}
if __name__ == "__main__":
uvicorn.run(app, host=settings.host, port=settings.port)
Pros:
- Secure: Secrets are never stored in your codebase.
- Environment-Specific: You can have different
.envfiles for different environments (e.g.,.env.staging,.env.prod). - Standard Practice: This is the de-facto standard for modern containerized applications (Docker, Kubernetes).
Method 3: Using a Dedicated Library (e.g., Dynaconf)
For more complex applications, libraries like Dynaconf are fantastic. They can read from multiple sources in a specific order (e.g., .env files, environment variables, configuration files like TOML/JSON, secrets managers like AWS Secrets Manager).
settings.py with Dynaconf
# settings.py
from dynaconf import Dynaconf
# Dynaconf will automatically look for .toml, .yaml, .env, and environment variables
settings = Dynaconf(
settings_files=['settings.toml', '.secrets.toml'], # Example of other config files
environments=True, # Allows for [default], [development], [production] sections
env_switcher="ENV_FOR_DYNACONF", # The environment variable to switch contexts
)
# You can access settings like this:
# settings.DEBUG
# settings.get("DATABASE_URL")
Pros:
- Extremely flexible and powerful.
- Supports multiple formats and sources.
- Handles different environments seamlessly.
Summary: Best Practices
- NEVER hard-code configuration values (especially secrets) in your source code.
- Use a
settings.pyfile to centralize your configuration logic. - Use a
BaseSettingsclass from Pydantic or a library like Dynaconf to load your settings. This gives you type-hinting and validation for free. - Store secrets in environment variables or a secrets manager. Use a
.envfile for local development. - Add
.envand any secret files to your.gitignore. - Document your settings so other developers (or your future self) know what they are and how to use them.
By following these principles, you make your Python applications more secure, robust, and easier to manage across different environments.
