Configuration#
Configuration is read from four places. The later ones win:
Application defaults
Python configuration modules
Keyword arguments passed to
Application()Environment variables
Basic usage#
Pass module paths during initialization. The modules are tried in order and the first one that exists is used:
import muffin
app = muffin.Application("config.local", "config.production")
The module can be replaced with the MUFFIN_CONFIG environment variable,
which is what the CLI --config option sets:
$ MUFFIN_CONFIG=settings.local uvicorn your_project:app
Single values can be overridden in place:
app = muffin.Application(DEBUG=True, ANY_OPTION="value", ONE_MORE="value2")
assert app.cfg.DEBUG is True
assert app.cfg.ANY_OPTION == "value"
assert app.cfg.ONE_MORE == "value2"
All of it is available as app.cfg.
Default application options#
{
# Application name: env prefix, logger, plugin configs
"NAME": "muffin",
# Configuration module that was actually loaded
"CONFIG": None,
# Debug mode
"DEBUG": False,
# Ignore a trailing slash when matching routes
"TRIM_LAST_SLASH": True,
# Compare routes against the Host header
"USE_HOSTS": False,
# Static files
"STATIC_URL_PREFIX": "/static",
"STATIC_FOLDERS": [],
# Logging
"LOG_LEVEL": "WARNING",
"LOG_FORMAT": "%(asctime)s [%(process)d] [%(levelname)s] %(message)s",
"LOG_DATE_FORMAT": "[%Y-%m-%d %H:%M:%S]",
"LOG_CONFIG": None,
}
NAME matters more than it looks: it prefixes environment variables and
selects the plugin configuration keys, so a real project sets it early, usually
in the configuration module.
Environment variables#
Every option can be set as {NAME}_{OPTION}. With the default name that means
MUFFIN_*:
$ MUFFIN_DEBUG=true MUFFIN_DB_PARAMS='{"pool": 50}' uvicorn app:app
Values are parsed as JSON when possible, which covers booleans, numbers, lists and dicts. Plain strings are accepted as they are. A type annotation on the option is respected by the parser.
Given settings.py:
DEBUG = True
DB_PARAMS = {"pool": 10}
TOKEN: str = None
these variables
os.environ["MUFFIN_TOKEN"] = "value"
os.environ["MUFFIN_DEBUG"] = "false"
os.environ["MUFFIN_DB_PARAMS"] = '{"pool": 50}'
produce:
app = muffin.Application("settings")
assert app.cfg.DEBUG is False
assert app.cfg.DB_PARAMS == {"pool": 50}
assert app.cfg.TOKEN == "value"
Plugin configuration#
A plugin reads options from the application config with the {PLUGIN_NAME}_
prefix. Precedence, lowest to highest:
Plugin
defaultsApplication config keys with the
{PLUGIN_NAME}_prefixKeyword arguments passed to
Plugin(app, ...)Environment variables
{APP_NAME}_{PLUGIN_NAME}_{OPTION}
os.environ["MUFFIN_PLUGIN_OPTION"] = "33"
class Plugin(BasePlugin):
name = "plugin"
defaults = {"option": 11}
app = Application()
plugin = Plugin(app)
assert plugin.cfg.option == 33
A plugin is registered in app.plugins unless disabled is set in its
config. Disabled plugins are skipped with a warning in the log.
Logging#
LOG_LEVEL, LOG_FORMAT and LOG_DATE_FORMAT cover the usual case. For
anything else, put a full dictConfig mapping into LOG_CONFIG:
LOG_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {"format": "%(asctime)s %(levelname)s %(name)s %(message)s"},
},
"handlers": {
"logfile": {
"level": "DEBUG",
"class": "logging.handlers.RotatingFileHandler",
"filename": "my_log.log",
"maxBytes": 50 * 1024 * 1024,
"backupCount": 10,
},
},
"loggers": {
"": {"handlers": ["logfile"], "level": "ERROR"},
"project": {"level": "INFO", "propagate": True},
},
}
The same mapping is passed to uvicorn when the app is started with muffin
<module> run.