Package settings
An installable app declares its configuration as one typed Go struct. A deployment
can override tagged fields with CICADEL_<APP>_<NAME> environment variables.
#How it works
- The project passes the values in Go.
- A deployment may override tagged fields under the app’s own prefix.
- The application resolves and validates the settings before
Start. manage.go checkreports an invalid key without printing its value.- Secret fields are never displayed.
Good to know
Unlike Django, there is no global settings object for apps. Each app reads its
own resolved struct directly.
#Declare settings
- Tag each field a deployment may change with
setting:"NAME". - Add
,secretfor credentials. - Leave handles and callbacks untagged. They are set only in Go.
Supported types are string, bool, integers, unsigned integers, floats,
time.Duration and []string (comma separated).
package newsletter
import (
"context"
"errors"
"net/http"
"strings"
"time"
"github.com/fauzaanu/cicadel"
"github.com/fauzaanu/cicadel/response"
"github.com/fauzaanu/cicadel/settings"
"github.com/fauzaanu/cicadel/urls"
)
// Options is configured by the project. A deployment may override tagged
// fields with CICADEL_NEWSLETTER_FROM, CICADEL_NEWSLETTER_BATCH,
// CICADEL_NEWSLETTER_INTERVAL and CICADEL_NEWSLETTER_API_KEY.
type Options struct {
From string `setting:"FROM"`
Batch int `setting:"BATCH"`
Interval time.Duration `setting:"INTERVAL"`
APIKey string `setting:"API_KEY,secret"`
}
// Validate runs after environment overrides. Name the field, never the value.
func (o Options) Validate() error {
if !strings.Contains(o.From, "@") {
return errors.New("From must be an email address")
}
if o.Batch < 1 || o.Batch > 1000 {
return errors.New("Batch must be 1–1000")
}
return nil
}
func New(options Options) cicadel.App {
if options.Batch == 0 {
options.Batch = 100
}
config := settings.NewPackage(options)
return cicadel.App{
Name: "newsletter",
Settings: config,
Start: func(context.Context) (cicadel.Cleanup, error) {
_ = config.Value().Interval // resolved and valid here
return nil, nil
},
Routes: []urls.Route{
urls.Path("GET", "/newsletter/", "newsletter:index",
func(w http.ResponseWriter, r *http.Request) {
if err := response.JSON(w, http.StatusOK, map[string]int{"batch": config.Value().Batch}); err != nil {
response.Error(w, http.StatusInternalServerError)
}
}),
},
}
}settings.NewPackage records the project’s values without reading the
environment. Construct a fresh one inside the app constructor, as above, so each
application gets its own.
If Options has a Validate() error method, it runs after overrides.
#Configure them in the project
Install the app in project/apps.go with explicit Go values:
newsletter.New(newsletter.Options{From: "news@example.com", Interval: time.Hour})cicadel.New resolves every app’s settings in dependency order. It reads the
environment that settings.Load read (Settings.Env).
Override one value per deployment:
CICADEL_NEWSLETTER_BATCH=500 go run manage.go runserver#In tests
Settings from settings.Defaults() or apptest.Settings() read no environment.
Tests see only the Go values. Inject overrides with
settings.LoadWith(apptest.Settings(), lookup).
#Inspect and check
go run manage.go apps lists each key, its current value and where it came from.
Secret values print as [redacted]:
newsletter (local)
CICADEL_NEWSLETTER_FROM="news@example.com" (default)
CICADEL_NEWSLETTER_BATCH="500" (environment)
CICADEL_NEWSLETTER_INTERVAL="1h0m0s" (default)
CICADEL_NEWSLETTER_API_KEY="[redacted]" (default)An invalid value does not stop construction. Instead:
go run manage.go check(andcheck --json) reports it as<app>:settings:invalid. It names the key but not the value.Startrefuses to run.
error newsletter:settings:invalid: CICADEL_NEWSLETTER_BATCH must be an integer in rangeValidation errors read app newsletter settings: Batch must be 1–1000.
Application.PackageSettings returns the same redacted description for tools.
#Rules
- Keys are always
CICADEL_<APP>_<NAME>, withNAMEin upper snake case. cicadel.Newrejects:- a key that collides with a project setting (
settings.CoreKeys); - a key that collides with another app’s key;
- a duplicate name;
- an unsupported field type.
- a key that collides with a project setting (
- A system check named
settingsis reserved on apps that declare settings. - Settings resolve once. Sharing one
settings.Packagebetween two apps is an error. - A package that needs its values during construction resolves them itself with
Package.Resolve(app, s.Env()). It still setsApp.Settingsto the same value, soappsandchecksee it. settings.LoadPackageresolves without keeping the result.
#Accounts settings
The standard accounts app uses this contract. Generated projects set
AccountOptions in project/accounts.go. A deployment may override:
CICADEL_ACCOUNTS_SESSION_LIFETIMECICADEL_ACCOUNTS_RECOVERY_LIFETIMECICADEL_ACCOUNTS_IDLE_TIMEOUTCICADEL_ACCOUNTS_BCRYPT_COST(10–16)
With an invalid value, the project builds without account services. check
reports the key and runserver refuses to start.