Examples
The programs below are complete and runnable from a checkout with the project
installed in .venv. Except for migration, they write to a distinct
platform-specific per-user configuration directory, not to the source tree.
Run an example on Windows with:
.\.venv\Scripts\python.exe examples\basic_store.py
Basic storage
This example saves a mapping, loads a fresh copy, and prints the exact path. Running it again replaces the same example configuration.
1"""Save and reload a small per-user configuration."""
2
3from upref import ConfigStore
4
5store = ConfigStore("upref-basic-example")
6
7store.save(
8 {
9 "theme": "dark",
10 "notifications": True,
11 }
12)
13
14config = store.load()
15print(config)
16print(f"Configuration saved to: {store.path}")
Defaults, update, and delete
Defaults are merged recursively in memory. The resolved mapping is then saved, selected changes are merged and persisted, and the example removes its file.
1"""Combine defaults, persist selected changes, then clean up the example."""
2
3from upref import Config, ConfigStore
4
5store = ConfigStore("upref-update-example")
6
7defaults: Config = {
8 "network": {
9 "host": "localhost",
10 "port": 8080,
11 },
12 "notifications": True,
13}
14
15config = store.load(defaults=defaults)
16print("With defaults:", config)
17
18# Loading defaults does not write them. Save the resolved configuration when
19# the application wants those values to become persistent.
20store.save(config)
21
22updated = store.update(
23 {
24 "network": {"port": 9000},
25 "notifications": False,
26 }
27)
28print("Saved changes:", updated)
29
30print("Configuration removed:", store.delete())
Required project variables
This terminal-based setup asks only for project variables that have not been
saved yet: directories, the expected Python version, and whether tests should
run before a build. Boolean input accepts common forms such as yes, no,
on, and off.
1"""Collect and persist the variables required to work on one project."""
2
3from upref import ConfigStore, ConfigValue, Field, PromptCancelled, collect
4
5
6def parse_boolean(raw: str) -> bool:
7 """Parse a human-friendly boolean entered in the terminal."""
8 normalized = raw.strip().lower()
9 if normalized in {"1", "true", "yes", "y", "on"}:
10 return True
11 if normalized in {"0", "false", "no", "n", "off"}:
12 return False
13 raise ValueError("Enter yes/no, true/false, on/off, or 1/0")
14
15
16def is_non_empty_string(value: ConfigValue) -> bool:
17 """Return whether a parsed configuration value is a non-empty string."""
18 return isinstance(value, str) and bool(value.strip())
19
20
21store = ConfigStore("upref-project-example", filename="variables.yaml")
22schema = {
23 "project_name": Field(
24 "Project name",
25 description="A human-readable name, for example customer-portal",
26 validator=is_non_empty_string,
27 ),
28 "source_directory": Field(
29 "Source directory",
30 description="Path relative to the project root, for example src",
31 validator=is_non_empty_string,
32 ),
33 "build_directory": Field(
34 "Build directory",
35 description="Path relative to the project root, for example dist",
36 validator=is_non_empty_string,
37 ),
38 "python_version": Field(
39 "Python version",
40 description="Version expected by the project, for example 3.12",
41 validator=is_non_empty_string,
42 ),
43 "run_tests_before_build": Field(
44 "Run tests before building",
45 parser=parse_boolean,
46 ),
47}
48
49try:
50 variables = collect(
51 schema,
52 initial=store.load(),
53 interface="tty",
54 mode="missing",
55 )
56except PromptCancelled:
57 print("Setup cancelled; the existing project variables were not changed.")
58else:
59 store.save(variables)
60 print("Project variables:")
61 for variable_name in schema:
62 print(f" {variable_name}: {variables[variable_name]}")
63 print(f"Saved to: {store.path}")
Environment profiles and runtime overrides
This example stores development, testing, and production profiles. The
UPREF_PROFILE, UPREF_SERVICE_URL, and UPREF_TIMEOUT environment
variables can temporarily override the selected values without modifying the
saved YAML file.
For example, in PowerShell:
$env:UPREF_PROFILE = "production"
$env:UPREF_TIMEOUT = "45"
.\.venv\Scripts\python.exe examples\environment_profiles.py
1"""Select a saved environment profile and apply temporary runtime overrides."""
2
3import os
4
5from upref import Config, ConfigStore, ConfigValue
6
7
8def require_mapping(value: ConfigValue, *, name: str) -> Config:
9 """Return a configuration mapping or report an invalid saved shape."""
10 if not isinstance(value, dict):
11 raise TypeError(f"{name} must be a mapping")
12 return value
13
14
15def require_string(value: ConfigValue, *, name: str) -> str:
16 """Return a string setting or report an invalid saved type."""
17 if not isinstance(value, str):
18 raise TypeError(f"{name} must be a string")
19 return value
20
21
22def require_integer(value: ConfigValue, *, name: str) -> int:
23 """Return an integer setting while rejecting booleans."""
24 if isinstance(value, bool) or not isinstance(value, int):
25 raise TypeError(f"{name} must be an integer")
26 return value
27
28
29def require_boolean(value: ConfigValue, *, name: str) -> bool:
30 """Return a boolean setting or report an invalid saved type."""
31 if not isinstance(value, bool):
32 raise TypeError(f"{name} must be a boolean")
33 return value
34
35
36DEFAULTS: Config = {
37 "active_profile": "development",
38 "profiles": {
39 "development": {
40 "service_url": "http://localhost:8000",
41 "timeout": 10,
42 "debug": True,
43 },
44 "testing": {
45 "service_url": "http://localhost:8001",
46 "timeout": 5,
47 "debug": False,
48 },
49 "production": {
50 "service_url": "https://api.example.org",
51 "timeout": 30,
52 "debug": False,
53 },
54 },
55}
56
57store = ConfigStore("upref-profiles-example")
58settings = store.load(defaults=DEFAULTS)
59if not store.exists():
60 store.save(settings)
61
62profiles = require_mapping(settings["profiles"], name="profiles")
63saved_profile = require_string(settings["active_profile"], name="active_profile")
64profile_name = os.environ.get("UPREF_PROFILE", saved_profile)
65if profile_name not in profiles:
66 choices = ", ".join(sorted(profiles))
67 raise ValueError(f"Unknown profile {profile_name!r}; choose one of: {choices}")
68
69profile = require_mapping(profiles[profile_name], name=f"profiles.{profile_name}")
70saved_service_url = require_string(profile["service_url"], name="service_url")
71service_url = os.environ.get("UPREF_SERVICE_URL", saved_service_url)
72
73saved_timeout = require_integer(profile["timeout"], name="timeout")
74timeout_text = os.environ.get("UPREF_TIMEOUT")
75timeout = saved_timeout if timeout_text is None else int(timeout_text)
76if timeout <= 0:
77 raise ValueError("UPREF_TIMEOUT must be greater than zero")
78debug = require_boolean(profile["debug"], name="debug")
79
80print(f"Profile: {profile_name}")
81print(f"Service URL: {service_url}")
82print(f"Timeout: {timeout} seconds")
83print(f"Debug logging: {debug}")
84print(f"Persistent configuration: {store.path}")
85print("Environment-variable overrides were not persisted.")
Multiple projects
Related projects can share one application configuration directory while using a distinct YAML filename for each project. This is useful for a workspace containing a frontend, backend, worker, or other independently configured component.
1"""Keep independent per-user settings for several related projects."""
2
3from upref import Config, ConfigStore
4
5PROJECT_DEFAULTS: dict[str, Config] = {
6 "frontend": {
7 "working_directory": "apps/frontend",
8 "test_command": "npm test",
9 "build_command": "npm run build",
10 "enabled": True,
11 },
12 "backend": {
13 "working_directory": "apps/backend",
14 "test_command": "python -m pytest",
15 "build_command": "python -m build",
16 "enabled": True,
17 },
18}
19
20for project_name, defaults in PROJECT_DEFAULTS.items():
21 # Each project receives a separate file below the same application-specific
22 # per-user configuration directory.
23 store = ConfigStore(
24 "upref-workspace-example",
25 filename=f"{project_name}.yaml",
26 )
27 settings = store.load(defaults=defaults)
28 if not store.exists():
29 store.save(settings)
30
31 print(f"{project_name}: {settings}")
32 print(f" file: {store.path}")
Terminal collection
On the first run, missing values are requested in the terminal. Later runs
reuse saved values. Cancelling before save leaves the existing file
unchanged.
1"""Ask only for missing values in a terminal, then save them explicitly."""
2
3from upref import ConfigStore, Field, PromptCancelled, collect
4
5store = ConfigStore("upref-tty-example")
6schema = {
7 "service_url": Field(
8 "Service URL",
9 description="For example: https://api.example.org",
10 ),
11 "timeout": Field(
12 "Timeout in seconds",
13 parser=int,
14 validator=lambda value: isinstance(value, int) and value > 0,
15 ),
16}
17
18try:
19 values = collect(
20 schema,
21 initial=store.load(),
22 interface="tty",
23 mode="missing",
24 )
25except PromptCancelled:
26 print("Input cancelled; the configuration was not changed.")
27else:
28 store.save(values)
29 print(f"Configuration saved to: {store.path}")
Migrating from v1
This program imports an existing historical file. Change LEGACY_NAME to
the old configuration name. Migration leaves its source untouched and checks
for an existing v2 target before saving. Concurrent migrations require an
external lock.
1"""Import values from an existing Upref v1 configuration."""
2
3from upref import ConfigStore, UprefError
4
5LEGACY_NAME = "my_personnal_data"
6store = ConfigStore("upref-migration-example")
7
8try:
9 migrated = store.import_legacy(LEGACY_NAME)
10except UprefError as error:
11 print(f"Migration was not performed: {error}")
12else:
13 print("Migrated keys:", ", ".join(sorted(migrated)))
14 print(f"New configuration: {store.path}")
Warning
Upref configuration files contain plain YAML. Masked interactive input is not encrypted storage; use a system keyring or secrets manager for passwords and tokens.