Plain is headed towards 1.0! Subscribe for development updates →

  1# Runtime
  2
  3**Access app and package settings at runtime.**
  4
  5Plain 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.
  6
  7```python
  8# app/settings.py
  9URLS_ROUTER = "app.urls.AppRouter"
 10
 11TIME_ZONE = "America/Chicago"
 12
 13INSTALLED_PACKAGES = [
 14    "plain.models",
 15    "plain.tailwind",
 16    "plain.auth",
 17    "plain.passwords",
 18    "plain.sessions",
 19    "plain.htmx",
 20    "plain.admin",
 21    "plain.elements",
 22    # Local packages
 23    "app.users",
 24]
 25
 26AUTH_USER_MODEL = "users.User"
 27AUTH_LOGIN_URL = "login"
 28
 29MIDDLEWARE = [
 30    "plain.sessions.middleware.SessionMiddleware",
 31    "plain.auth.middleware.AuthenticationMiddleware",
 32    "plain.admin.AdminMiddleware",
 33]
 34```
 35
 36While working inside a Plain application or package, you can access settings at runtime via `plain.runtime.settings`.
 37
 38```python
 39from plain.runtime import settings
 40
 41print(settings.AN_EXAMPLE_SETTING)
 42```
 43
 44The Plain core settings are defined in [`plain/runtime/global_settings.py`](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.
 45
 46## Environment variables
 47
 48It'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.
 49
 50For example, to set the `SECRET_KEY` setting is defined with a type annotation.
 51
 52```python
 53SECRET_KEY: str
 54```
 55
 56And can be set by an environment variable.
 57
 58```bash
 59PLAIN_SECRET_KEY=supersecret
 60```
 61
 62For more complex types like lists or dictionaries, just use the `list` or `dict` type annotation and JSON-compatible types.
 63
 64```python
 65LIST_EXAMPLE: list[str]
 66```
 67
 68And set the environment variable with a JSON-encoded string.
 69
 70```bash
 71PLAIN_LIST_EXAMPLE='["one", "two", "three"]'
 72```
 73
 74Custom behavior can always be supported by checking the environment directly.
 75
 76```python
 77# plain/models/default_settings.py
 78from os import environ
 79
 80from . import database_url
 81
 82# Make DATABASE a required setting
 83DATABASE: dict
 84
 85# Automatically configure DATABASE if a DATABASE_URL was given in the environment
 86if "DATABASE_URL" in environ:
 87    DATABASE = database_url.parse_database_url(
 88        environ["DATABASE_URL"],
 89        # Enable persistent connections by default
 90        conn_max_age=int(environ.get("DATABASE_CONN_MAX_AGE", 600)),
 91        conn_health_checks=environ.get("DATABASE_CONN_HEALTH_CHECKS", "true").lower()
 92        in [
 93            "true",
 94            "1",
 95        ],
 96    )
 97```
 98
 99### .env files
100
101Plain itself does not load `.env` files automatically, except in development if you use [`plain.dev`](/plain-dev/README.md). If you use `.env` files in production then you will need to load them yourself.
102
103## Package settings
104
105An 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.
106
107```python
108# app/users/default_settings.py
109USERS_DEFAULT_ROLE = "user"
110```
111
112The 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.
113
114```python
115# app/users/default_settings.py
116USERS_DEFAULT_ROLE: str
117```
118
119Type 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.
120
121```python
122# app/users/default_settings.py
123USERS_DEFAULT_ROLE: str = "user"
124```
125
126## Custom app-wide settings
127
128At 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.
129
130```python
131# app/settings.py
132# A required env setting
133APP_STRIPE_SECRET_KEY = os.environ["STRIPE_SECRET_KEY"]
134
135# An optional env setting
136APP_GIT_SHA = os.environ.get("HEROKU_SLUG_COMMIT", "dev")[:7]
137
138# A setting populated by Python code
139with open("app/secret_key.txt") as f:
140    APP_EXAMPLE_KEY = f.read().strip()
141```
142
143## Using Plain in other environments
144
145There 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.
146
147```python
148import plain.runtime
149
150plain.runtime.setup()
151```