Runtime
Access app and package settings at runtime.
Plain is configured by "settings", which are ultimately just Python variables. Most settings have default values which can be overidden either by your app/settings.py
file or by environment variables.
# app/settings.py
URLS_ROUTER = "app.urls.AppRouter"
TIME_ZONE = "America/Chicago"
INSTALLED_PACKAGES = [
"plain.models",
"plain.tailwind",
"plain.auth",
"plain.passwords",
"plain.sessions",
"plain.htmx",
"plain.admin",
"plain.elements",
# Local packages
"app.users",
]
AUTH_USER_MODEL = "users.User"
AUTH_LOGIN_URL = "login"
MIDDLEWARE = [
"plain.sessions.middleware.SessionMiddleware",
"plain.auth.middleware.AuthenticationMiddleware",
"plain.admin.AdminMiddleware",
]
While working inside a Plain application or package, you can access settings at runtime via plain.runtime.settings
.
from plain.runtime import settings
print(settings.AN_EXAMPLE_SETTING)
The Plain core settings are defined in plain/runtime/global_settings.py
and you should look at that for reference. Each installed package can also define its own settings in a default_settings.py
file.
Environment variables
It's common in both development and production to use environment variables to manage settings. To handle this, any type-annotated setting can be loaded from the env with a PLAIN_
prefix.
For example, to set the SECRET_KEY
setting is defined with a type annotation.
SECRET_KEY: str
And can be set by an environment variable.
PLAIN_SECRET_KEY=supersecret
For more complex types like lists or dictionaries, just use the list
or dict
type annotation and JSON-compatible types.
LIST_EXAMPLE: list[str]
And set the environment variable with a JSON-encoded string.
PLAIN_LIST_EXAMPLE='["one", "two", "three"]'
Custom behavior can always be supported by checking the environment directly.
# plain/models/default_settings.py
from os import environ
from . import database_url
# Make DATABASES a required setting
DATABASES: dict
# Automatically configure DATABASES if a DATABASE_URL was given in the environment
if "DATABASE_URL" in environ:
DATABASES = {
"default": database_url.parse(
environ["DATABASE_URL"],
# Enable persistent connections by default
conn_max_age=int(environ.get("DATABASE_CONN_MAX_AGE", 600)),
conn_health_checks=environ.get(
"DATABASE_CONN_HEALTH_CHECKS", "true"
).lower()
in [
"true",
"1",
],
)
}
# Classes used to implement DB routing behavior.
DATABASE_ROUTERS = []
.env files
Plain itself does not load .env
files automatically, except in development if you use plain.dev
. If you use .env
files in production then you will need to load them yourself.
Package settings
An installed package can provide a default_settings.py
file. It is strongly recommended to prefix any defined settings with the package name to avoid conflicts.
# app/users/default_settings.py
USERS_DEFAULT_ROLE = "user"
The way you define these settings can impact the runtime behavior. For example, a required setting should be defined with a type annotation but no default value.
# app/users/default_settings.py
USERS_DEFAULT_ROLE: str
Type annotations are only required for settings that don't provide a default value (to enable the environment variable loading). But generally type annotations are recommended as they also provide basic validation at runtime — if a setting is defined as a str
but the user sets it to an int
, an error will be raised.
# app/users/default_settings.py
USERS_DEFAULT_ROLE: str = "user"
Custom app-wide settings
At times it can be useful to create your own settings that are used across your application. When you define these in app/settings.py
, you simply prefix them with APP_
which marks them as a custom setting.
# app/settings.py
# A required env setting
APP_STRIPE_SECRET_KEY = os.environ["STRIPE_SECRET_KEY"]
# An optional env setting
APP_GIT_SHA = os.environ.get("HEROKU_SLUG_COMMIT", "dev")[:7]
# A setting populated by Python code
with open("app/secret_key.txt") as f:
APP_EXAMPLE_KEY = f.read().strip()
Using Plain in other environments
There may be some situations where you want to manually invoke Plain, like in a Python script. To get everything set up, you can call the plain.runtime.setup()
function.
import plain.runtime
plain.runtime.setup()