Skip to main content

Upref

Upref is a small Python library for storing an application's per-user configuration in a YAML file. Its v2 API keeps persistence explicit: loading configuration never opens a prompt, and collecting interactive values never writes them automatically.

Upref supports Python 3.10 and later. It is typed, uses platform-native user configuration directories, validates the complete value tree, and replaces files atomically.

Installation

Install the core package, including the dependency-free terminal interface:

pip install upref

The wxPython graphical interface is optional:

pip install "upref[gui]"

Importing upref does not import wxPython or open an interface.

Quickstart

Create one store for one application configuration:

from upref import ConfigStore

store = ConfigStore("my-application")

config = store.load(
    defaults={
        "theme": "dark",
        "network": {"host": "localhost", "port": 8080},
    }
)

store.save(config)
store.update({"network": {"port": 9000}})

print(store.load())
print(f"Configuration file: {store.path}")

load() returns an empty dictionary when the file is absent, empty, or an explicit YAML null. Saved values recursively override defaults, but loading defaults does not write them. save() validates and persists the entire mapping.

To merge and persist only selected changes, use update():

config = store.update({
    "network": {"port": 9001},
    "notifications": False,
})

Nested dictionaries are merged. Lists and scalar values are replaced. Duplicate explicit YAML keys are rejected with a file location instead of silently discarding earlier values. YAML merge directives may still provide defaults that explicit keys override. store.exists() checks for the file, while store.delete() removes only that file and reports whether it existed.

Configuration values

The document root must be a mapping with string keys. Values may be None, booleans, integers, floats, strings, lists, or nested mappings with string keys. False, 0, empty strings, and empty lists are valid values. Custom objects such as Path and datetime must be converted before saving.

Every operation that accepts or returns configuration data works with a detached tree: mutating the result of load() does not mutate defaults, earlier results, or caller-owned input.

Paths and portable mode

Without an explicit directory, Upref uses the current platform's per-user configuration location through platformdirs:

store = ConfigStore(
    "my-application",
    filename="settings.yaml",
    app_author="Example Corp",
)

For tests or portable applications, pass an absolute final directory. Upref does not append the application name to this override:

from pathlib import Path

portable_directory = Path.cwd().resolve() / "configuration"
store = ConfigStore(
    "my-application",
    directory=portable_directory,
)

Constructing or loading the store does not create the directory; save() creates it as needed. Application names, authors, and filenames must be safe single path components.

Interactive collection

Interactive collection is separate from storage and uses the terminal by default:

from upref import ConfigStore, Field, PromptCancelled, collect

store = ConfigStore("my-application")
schema = {
    "service_url": Field("Service URL"),
    "timeout": Field(
        "Timeout in seconds",
        parser=int,
        validator=lambda value: isinstance(value, int) and value > 0,
    ),
}

try:
    values = collect(
        schema,
        initial=store.load(),
        interface="tty",
        mode="missing",
    )
except PromptCancelled:
    print("Configuration unchanged")
else:
    store.save(values)

In missing mode, absent values, None, and required empty strings are requested. False and 0 already count as values. Use mode="all" to ask for every field, or interface="gui" after installing upref[gui].

For boolean input, use the public parse_bool parser; it accepts yes/no, true/false, on/off, y/n, and 1/0, ignoring case and whitespace. Python's bool("false") returns True and is unsuitable for this purpose.

from upref import Field, collect, parse_bool
from upref.tty import TTYPrompter

values = collect(
    {"enabled": Field("Enable notifications", parser=parse_bool)},
    initial={"enabled": False},
    interface=TTYPrompter(keep_current=True),
    mode="all",
)

With keep_current=True, Enter reuses a current non-secret value and runs it through the parser and validator again. By default, blank input remains blank. For structured input, pair a parser with a compatible keyword-only formatter: Field("Tags", parser=json.loads, formatter=json.dumps) (after import json). The GUI uses this formatter to prefill existing values correctly.

collect validates newly entered values. Existing values skipped in missing mode are not passed through field validators; validate application constraints after loading when stored files may have been edited manually.

Storage guarantees and limits

Upref writes UTF-8 YAML to a temporary file in the destination directory, flushes it, and replaces the destination with os.replace(). Invalid data is rejected before the existing file is touched. On POSIX systems, newly written files receive mode 0600.

Saving rewrites the YAML document, so comments, anchors, and custom formatting are not preserved. Concurrent writers cannot produce a partial YAML file, but there is no locking: the last successful atomic replacement wins, and simultaneous read-modify-write operations can lose updates.

Migrating from v1

The v1 API remains available during the v2 transition and emits DeprecationWarning. Import an existing v1 file explicitly:

from upref import ConfigStore

store = ConfigStore("my-application")
config = store.import_legacy("my_personnal_data")

The source file is left untouched, and using it as the v2 target is rejected. Migration checks for an existing v2 file unless overwrite=True is passed; because this preflight check is not locked, applications with concurrent writers must coordinate the migration externally.

Security

An Upref YAML file is not a secret vault. Field(secret=True) asks the built-in interfaces for protected input presentation, subject to terminal support; a custom interface must honor that hint itself. Any saved value remains plain text. Keep passwords, tokens, and private keys in the operating-system keyring or a dedicated secrets manager, and store only a reference in Upref.

Documentation and examples

The complete user guide and API reference are available on Read the Docs. The example catalog lists 16 runnable programs by difficulty, input method, and file effects. Start with these:

Level Example What it demonstrates
Simple portable_store.py Defaults, save/load, and updates in a temporary directory
Simple boolean_collection.py Boolean parsing, optional input, and cancellation
Intermediate edit_settings.py Enter to keep values and editable JSON lists
Intermediate gui_collection.py GUI ownership, formatted prefill, and cancellation
Intermediate handle_errors.py Reporting malformed YAML while retaining the file
Advanced nested_collection.py Editing a subsection before an explicit save
Advanced custom_interface.py A deterministic custom prompter with validation retries
Advanced typed_settings.py Application validation with a dataclass
Advanced schema_upgrade.py An idempotent application schema migration

The new demonstrations use memory or automatically cleaned temporary directories. Earlier examples that demonstrate persistent settings use named per-user directories; their effects are listed in the catalog. Run examples from an installed checkout (python -m pip install -e .):

python examples/portable_store.py
python examples/custom_interface.py

See also the troubleshooting guide and the code review and compatibility notes.

Development with .venv

On Windows, create or update the repository-local environment with:

.\make.bat setup

All project commands call .venv\Scripts\python.exe directly, so activation is optional:

.\make.bat test
.\make.bat check
.\make.bat docs
.\make.bat build

Activate it in an interactive PowerShell session when convenient:

.\.venv\Scripts\Activate.ps1

If automatic Python discovery fails while creating the environment, use .\make.bat setup -Python "C:\path\to\python.exe". An existing .venv always keeps its current interpreter; remove and recreate it deliberately to change Python. The .venv directory is machine-specific and ignored by Git. make.bat clean removes generated artifacts without deleting it.

License

Upref is distributed under the MIT license.

Release files for upref 2.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for upref 2.1.0
File Size Uploaded
upref-2.1.0.tar.gz 347.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for upref 2.1.0
File Interpreter ABI Platform
upref-2.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 530.6 kB

Release files / upref-2.1.0.tar.gz

Download URL upref-2.1.0.tar.gz
Size 347.9 kB
Tags Source
SHA-256 checksum
How to use checksums
65ca4b0ce7fe0296f019650196c80526d4b751c1ec479472bcd1beb97e22ef50
BLAKE2b-256 checksum
How to use checksums
bfc92bab09d6a3e85ff1930f5d10bd292c055ddac9786ec3b33d68016d008041
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

Release files / upref-2.1.0-py3-none-any.whl

Download URL upref-2.1.0-py3-none-any.whl
Size 182.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a6582ff3fe3730683586988924244e2dd96eb3a9f7c32d8c831057500be38eb8
BLAKE2b-256 checksum
How to use checksums
018b0fd692133ef3cfa6df9a26f10058550ad711ef665948652c97935dd7f1f6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

Release history Release notifications | RSS feed

2.2.0

2 release files

This release

2.1.0 This release

2 release files

2.0.1

2 release files

1.0.14

2 release files

1.0.13

2 release files

1.0.12

2 release files

1.0.9

2 release files

1.0.3

1 release file

1.0.2

1 release file

1.0.1

1 release file

1.0.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page