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.sessions.middleware.SessionMiddleware",
"plain.auth.middleware.AuthenticationMiddleware",
]
if DEBUG:
INSTALLED_PACKAGES += [
"plain.dev",
]
MIDDLEWARE += [
"plain.dev.RequestsMiddleware",
]
TIME_ZONE = "America/Chicago"
1import importlib.metadata
2import sys
3from importlib.metadata import entry_points
4from pathlib import Path
5
6from .user_settings import Settings
7
8try:
9 __version__ = importlib.metadata.version("plain")
10except importlib.metadata.PackageNotFoundError:
11 __version__ = "dev"
12
13
14# Made available without setup or settings
15APP_PATH = Path.cwd() / "app"
16
17# from plain.runtime import settings
18settings = Settings()
19
20
21class AppPathNotFound(RuntimeError):
22 pass
23
24
25def setup():
26 """
27 Configure the settings (this happens as a side effect of accessing the
28 first setting), configure logging and populate the app registry.
29 """
30
31 # Packages can hook into the setup process through an entrypoint.
32 for entry_point in entry_points().select(group="plain.setup"):
33 entry_point.load()()
34
35 from plain.logs import configure_logging
36 from plain.packages import packages
37
38 if not APP_PATH.exists():
39 raise AppPathNotFound(
40 "No app directory found. Are you sure you're in a Plain project?"
41 )
42
43 # Automatically put the project dir on the Python path
44 # which doesn't otherwise happen when you run `plain` commands.
45 # This makes "app.<module>" imports and relative imports work.
46 if APP_PATH.parent not in sys.path:
47 sys.path.insert(0, APP_PATH.parent.as_posix())
48
49 configure_logging(settings.LOGGING)
50
51 packages.populate(settings.INSTALLED_PACKAGES)
52
53
54__all__ = [
55 "setup",
56 "settings",
57 "APP_PATH",
58 "__version__",
59]