Plain is headed towards 1.0! Subscribe for development updates →

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()