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```