Plain is headed towards 1.0! Subscribe for development updates →

Runtime

Leverage user-settings at runtime.

Settings

Single-file

All of your settings go in app/settings.py. That's how you do it!

The file itself is not much different than how Django does it, but the location, and a strong recommendation to only use the one file makes a big difference.

Environment variables

It seems pretty well-accepted these days that storing settings in env vars is a good idea (12factor.net).

Your settings file should be looking at the environment for secrets or other values that might change between environments. For example:

# app/settings.py
STRIPE_SECRET_KEY = environ["STRIPE_SECRET_KEY"]

Local development

In local development, you should use .env files to set these values. The .env should then be in your .gitignore!

It would seem like .env.dev would be a good idea, but there's a chicken-and-egg problem with that. You would then have to prefix most (or all) of your local commands with PLAIN_ENV=dev or otherwise configure your environment to do that for you. Generally speaking, a production .env shouldn't be committed in your repo anyway, so using .env for local development is ok. The downside to this is that it's harder to share your local settings with others, but these also often contain real secrets which shouldn't be committed to your repo either! More advanced .env sharing patterns are currently beyond the scope of Plain...

Production

TODO

Minimum required settings

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = environ["SECRET_KEY"]

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = environ.get("DEBUG", "false").lower() in ("true", "1", "yes")

MIDDLEWARE = [
    "plain.middleware.security.SecurityMiddleware",
    "plain.sessions.middleware.SessionMiddleware",
    "plain.middleware.common.CommonMiddleware",
    "plain.csrf.middleware.CsrfViewMiddleware",
    "plain.auth.middleware.AuthenticationMiddleware",
]

if DEBUG:
    INSTALLED_PACKAGES += [
        "plain.dev",
    ]
    MIDDLEWARE += [
        "plain.dev.RequestsMiddleware",
    ]

TIME_ZONE = "America/Chicago"
 1import importlib.metadata
 2import sys
 3from os import environ
 4from pathlib import Path
 5
 6from dotenv import load_dotenv
 7
 8from .user_settings import Settings
 9
10try:
11    __version__ = importlib.metadata.version("plain")
12except importlib.metadata.PackageNotFoundError:
13    __version__ = "dev"
14
15
16# Made available without setup or settings
17APP_PATH = Path.cwd() / "app"
18
19
20# from plain.runtime import settings
21settings = Settings()
22
23
24class AppPathNotFound(RuntimeError):
25    pass
26
27
28def setup():
29    """
30    Configure the settings (this happens as a side effect of accessing the
31    first setting), configure logging and populate the app registry.
32    """
33    from plain.logs import configure_logging
34    from plain.packages import packages
35
36    if not APP_PATH.exists():
37        raise AppPathNotFound(
38            "No app directory found. Are you sure you're in a Plain project?"
39        )
40
41    # Automatically put the app dir on the Python path for convenience
42    if APP_PATH not in sys.path:
43        sys.path.insert(0, APP_PATH.as_posix())
44
45    # Load .env files automatically before settings
46    if app_env := environ.get("PLAIN_ENV", ""):
47        load_dotenv(f".env.{app_env}")
48    else:
49        load_dotenv(".env")
50
51    configure_logging(settings.LOGGING)
52
53    packages.populate(settings.INSTALLED_PACKAGES)
54
55
56__all__ = [
57    "setup",
58    "settings",
59    "APP_PATH",
60    "__version__",
61]