Handling Configuration (config.py)
Configuration is where many Flask apps go wrong.
Good config:
- separates dev vs prod
- uses environment variables for secrets
- keeps defaults sensible
A common pattern: config classes
Section titled “A common pattern: config classes”config.py:
import os
class BaseConfig:
SECRET_KEY = os.environ.get("SECRET_KEY", "dev-key")
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(BaseConfig):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", "sqlite:///app.db")
class TestingConfig(BaseConfig):
TESTING = True
SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
class ProductionConfig(BaseConfig):
DEBUG = False
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL")Loading config into app
Section titled “Loading config into app”app.config.from_object("config.DevelopmentConfig")Or:
app.config.from_object(config_object)Secrets
Section titled “Secrets”Never commit secrets (SECRET_KEY, DB passwords) to git.
Use:
.envfiles locally (with python-dotenv)- CI/hosting environment variables in production
Four ways to load configuration, and the order matters
Section titled “Four ways to load configuration, and the order matters” flowchart TD
A["from_object('config.Dev')
defaults in version control"] --> B["from_pyfile('config.py')
instance folder, not committed"]
B --> C["from_prefixed_env()
FLASK_* environment variables"]
C --> D["app.config['X'] = ...
explicit overrides, e.g. in tests"]
D --> R["the LAST write wins"]
Measured: after from_object("config.Dev") set DATABASE = "dev.db", a later
from_pyfile("config.py") changed it to instance.db, and from_prefixed_env() then
changed it to env.db. Load from least specific to most specific.
from_object only reads UPPERCASE names
Section titled “from_object only reads UPPERCASE names”class Base:
DEBUG = False
DATABASE = "base.db"
lowercase_ignored = "not loaded"
class Dev(Base):
DEBUG = True
DATABASE = "dev.db"DEBUG True
DATABASE 'dev.db'
lowercase_ignored None <- silently skipped
inherited DEBUG present <- class inheritance worksA lowercase name is not an error — it simply never arrives, which is a quiet way to
lose a setting. The convention exists so a config module can hold helpers and imports
without them polluting app.config.
from_pyfile and the instance folder
Section titled “from_pyfile and the instance folder”app = Flask(__name__, instance_relative_config=True)
app.config.from_pyfile("config.py") # reads instance/config.pySECRET_KEY 'from-instance-file'A missing file raises:
app.config.from_pyfile("nope.py") # FileNotFoundError
app.config.from_pyfile("nope.py", silent=True) # returns False, carries onUse silent=True for a genuinely optional local override, and let it raise for a file
that must exist.
from_prefixed_env parses values as JSON
Section titled “from_prefixed_env parses values as JSON”FLASK_DATABASE=env.db
FLASK_MAX_ITEMS=25
FLASK_FEATURE_ON=true
DATABASE=no-prefix-ignoredDATABASE 'env.db'
MAX_ITEMS 25 (int) <- parsed as JSON, not left as a string
FEATURE_ON True (bool)
unprefixed DATABASE ignoredThis is the detail people miss: values are run through a JSON parse, so 25 becomes an
int and true becomes a bool rather than the string 'true' — which would have been
truthy either way and hidden the bug. A value that is not valid JSON stays a string.
See it move
Section titled “See it move”Check yourself
Section titled “Check yourself”-
A config class defines DEBUG = True and lowercase_ignored = 'x'. What does from_object load?
Measured: lowercase_ignored came back None. The convention lets a config module hold imports and helpers without them landing in app.config — but it also means a mis-cased setting vanishes without an error.
pch.quizShowAnswer
B — only DEBUG; lowercase names are skipped silently — Measured: lowercase_ignored came back None. The convention lets a config module hold imports and helpers without them landing in app.config — but it also means a mis-cased setting vanishes without an error.
-
With FLASK_MAX_ITEMS=25 in the environment, what does from_prefixed_env put in app.config['MAX_ITEMS']?
Measured as int. FLASK_FEATURE_ON=true likewise became the bool True rather than the truthy string 'true'. Values that are not valid JSON remain strings.
pch.quizShowAnswer
B — the integer 25, because values are parsed as JSON — Measured as int. FLASK_FEATURE_ON=true likewise became the bool True rather than the truthy string 'true'. Values that are not valid JSON remain strings.
-
In what order should configuration sources be loaded?
Each load overwrites the previous, so the last write wins. Measured DATABASE moving dev.db to instance.db to env.db as each source was applied.
pch.quizShowAnswer
B — defaults first, then instance files, then environment, then explicit overrides — least specific to most — Each load overwrites the previous, so the last write wins. Measured DATABASE moving dev.db to instance.db to env.db as each source was applied.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading