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]