API reference

The objects on this page form the supported Upref v2 API. Start with ConfigStore for persistence and use collect() only when the application needs interactive input.

Public API summary

ConfigStore(app_name, *[, filename, ...])

Read and persist one application's YAML configuration.

Field(label[, description, required, ...])

Describe the presentation and conversion rules for one value.

Prompter(*args, **kwargs)

Specify the minimal interface for an interactive front end.

collect(schema[, initial, interface, mode])

Collect configuration values without mutating or persisting inputs.

UprefError

Base class for Upref's domain-specific exceptions.

ConfigPathError

Indicate that a configuration name or path is invalid.

ConfigFormatError

Indicate data that cannot be represented by Upref's data model.

ConfigReadError

Indicate that a configuration file could not be read.

ConfigWriteError

Indicate that a filesystem mutation could not be completed safely.

MigrationError

Indicate that a legacy configuration could not be migrated.

PromptCancelled

Indicate that the user cancelled interactive value collection.

PromptUnavailableError

Indicate that the requested interactive interface is unavailable.

Configuration storage

class upref.ConfigStore(app_name, *, filename='config.yaml', app_author=None, directory=None, roaming=False)

Read and persist one application’s YAML configuration.

A store is a lightweight, uncached handle to one configuration file. Its constructor resolves the path, including existing symbolic links, but does not read or write the configuration and creates no directory. Every load() reads the file again, and every save() validates and atomically replaces it.

The optional directory override is intended for tests and portable applications. Without it, Upref selects the platform-specific user configuration directory.

Every persisted value is readable plain-text YAML. A store provides file integrity, not encryption or credential-vault semantics.

Example

>>> store = ConfigStore("my-application")
>>> store.save({"theme": "dark", "retries": 3})
>>> store.load()["theme"]
'dark'
Parameters:
  • app_name (str)

  • filename (str)

  • app_author (str | None)

  • directory (str | PathLike[str] | None)

  • roaming (bool)

__init__(app_name, *, filename='config.yaml', app_author=None, directory=None, roaming=False)

Resolve a store without reading or writing its configuration.

Parameters:
  • app_name (str) – Portable application identifier used as the default configuration-directory name. It must be a safe, non-empty path component.

  • filename (str) – Configuration filename inside the selected directory. Directory separators, absolute paths, and unsafe Windows names are rejected.

  • app_author (str | None) – Optional publisher directory used by platformdirs. When omitted, the application directory is not nested below a duplicate author directory.

  • directory (str | PathLike[str] | None) – Absolute directory overriding the platform-specific location. The final file is directory / filename.

  • roaming (bool) – Whether Windows should prefer the roaming configuration directory. Other platforms pass this flag through to platformdirs.

Raises:

ConfigPathError – If an identifier, filename, author, or explicit directory is invalid or the resulting path escapes its base.

Return type:

None

property app_name: str

Return the validated application identifier used by the store.

Returns:

The original validated app_name constructor argument.

property filename: str

Return the configuration filename without directory components.

Returns:

The original validated filename constructor argument.

property path: Path

Return the resolved absolute configuration-file path.

Returns:

The confined path determined during construction.

exists()

Return whether the resolved path currently identifies a file.

Returns:

True for a regular file, including a symbolic link resolved at construction, otherwise False.

Raises:

ConfigReadError – If the operating system cannot inspect the path.

Return type:

bool

load(*, defaults=None)

Load a fresh configuration mapping.

Missing, whitespace-only, comment-only, and explicit YAML null documents behave like an empty mapping. When defaults are supplied, nested mappings are merged recursively and stored values override defaults. Lists and scalar values are replaced rather than combined.

Neither defaults nor any mutable value inside it is shared with the returned configuration. Calling this method never writes defaults to disk.

Parameters:

defaults (Mapping[str, bool | int | float | str | None | list[bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]] | dict[str, bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]]] | None) – Optional fallback configuration applied in memory below the stored values.

Returns:

A validated, detached dictionary containing the resolved values.

Raises:
  • ConfigReadError – If the file exists but cannot be read.

  • ConfigFormatError – If YAML is malformed, is not UTF-8, has a non-mapping root, or contains unsupported values or key types.

Return type:

dict[str, bool | int | float | str | None | list[bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]] | dict[str, bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]]]

save(data)

Validate and atomically replace the stored configuration.

The complete document is serialized as safe UTF-8 YAML before any filesystem change. A temporary file in the destination directory is flushed and then installed with an atomic replacement. YAML comments, anchors, and custom formatting are not preserved.

Values are written as readable plain text. This method provides no encryption or secret-store semantics.

Parameters:

data (Mapping[str, bool | int | float | str | None | list[bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]] | dict[str, bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]]]) – Complete mapping to persist. Only ConfigValue values and string mapping keys are supported.

Raises:
  • ConfigFormatError – If data cannot be represented by the Upref configuration model or serialized as safe YAML.

  • ConfigWriteError – If a directory cannot be created or the atomic write cannot be completed.

Return type:

None

update(changes)

Recursively merge changes into stored data and save the result.

Mappings merge recursively. Lists and scalars, including falsey values such as False, 0, and None, replace existing values.

This method is a convenience read/merge/write sequence, not a locked transaction. Atomic replacement prevents partial YAML files, but concurrent writers remain last-writer-wins. Changes are persisted as readable plain-text YAML.

Parameters:

changes (Mapping[str, bool | int | float | str | None | list[bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]] | dict[str, bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]]]) – Partial configuration whose values take precedence over the currently stored mapping.

Returns:

The detached merged configuration that was persisted.

Raises:
Return type:

dict[str, bool | int | float | str | None | list[bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]] | dict[str, bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]]]

delete()

Delete only this store’s configuration file.

Returns:

True when a file was removed, or False when it was already absent. Parent directories are never removed.

Raises:

ConfigWriteError – If the path exists but cannot be deleted.

Return type:

bool

import_legacy(name, *, overwrite=False, legacy_directory=None)

Import one historical v1 .conf file into this store.

Mappings recognized by v1’s structural descriptor heuristic are converted by extracting value entries; other mappings retain their shape. A raw mapping that resembles descriptors can be ambiguous. The source file is never modified or deleted, and using it as the target is rejected.

Migrated values, including historical password fields, are persisted as readable plain-text YAML.

Target existence is checked before saving but is not protected by a lock. Concurrent migration writers must coordinate externally.

Parameters:
  • name (str) – Legacy preference name without the .conf suffix.

  • overwrite (bool) – Permit replacement when the v2 target already exists.

  • legacy_directory (str | PathLike[str] | None) – Absolute override for the legacy source directory, primarily for tests and controlled migrations.

Returns:

A detached dictionary containing the imported raw values.

Raises:
  • ConfigPathError – If the legacy name or directory is unsafe.

  • ConfigReadError – If the legacy source cannot be read or the target path cannot be inspected.

  • MigrationError – If source and target are the same file, the source is missing, the target already exists without overwrite, or persistence fails.

  • ConfigFormatError – If the legacy YAML cannot be parsed or contains unsupported configuration data.

Return type:

dict[str, bool | int | float | str | None | list[bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]] | dict[str, bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]]]

Configuration types

upref.Config

A configuration mapping with string keys and ConfigValue values.

upref.ConfigValue

A recursive type alias accepting None, bool, int, float, str, lists of supported values, and dictionaries with string keys.

prompt.PromptMode

The collection mode literal: "missing" or "all".

Interactive schema and protocol

class upref.Field(label, description='', required=True, secret=False, parser=<class 'str'>, validator=None)

Describe the presentation and conversion rules for one value.

Fields are immutable and can therefore be reused safely across collection calls. The parser receives the raw string returned by the prompter. The validator then receives the parsed value and may either return False or raise ValueError to reject it. A return value of True or None accepts the value.

secret does not make a value safe to persist. It asks a compatible prompter to mask input and suppress the current value; custom prompters are responsible for honoring that hint. Parsed secret values remain plaintext.

Parameters:
  • label (str)

  • description (str)

  • required (bool)

  • secret (bool)

  • parser (Callable[[str], bool | int | float | str | None | list[bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]] | dict[str, bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]]])

  • validator (Callable[[bool | int | float | str | None | list[bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]] | dict[str, bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]]], bool | None] | None)

label

Human-readable field name. Bundled prompters fall back to the schema key when this string is empty.

Type:

str

description

Optional explanatory text displayed before entry.

Type:

str

required

Whether an empty raw answer is rejected. In "missing" mode, this also makes an existing empty string count as missing.

Type:

bool

secret

Whether compatible prompters should mask entry and hide the current value. This is a display-only safeguard, not encryption.

Type:

bool

parser

Callable converting raw input to a supported configuration value. Raising ValueError displays the error and retries; secret fields use a generic message instead of the exception text.

Type:

collections.abc.Callable[[str], bool | int | float | str | None | list[bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]] | dict[str, bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]]]

validator

Optional callable checking the parsed value. Returning False or raising ValueError rejects the value and retries the prompt.

Type:

collections.abc.Callable[[bool | int | float | str | None | list[bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]] | dict[str, bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]]], bool | None] | None

class upref.Prompter(*args, **kwargs)

Specify the minimal interface for an interactive front end.

Runtime-checkable implementations supply raw input and display recoverable parsing or validation errors. Returning None from ask() signals cancellation; an empty string is an entered value and is handled according to Field.required.

A custom implementation may expose a close() method, but collect() does not call it because the caller owns custom prompters.

ask(name, field, current)

Request one raw value from the user.

Parameters:
  • name (str) – Configuration key being collected.

  • field (Field) – Presentation and conversion metadata for the key.

  • current (bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]) – Existing value, or None when no value is available. Implementations should not reveal it when field.secret is true.

Returns:

The unparsed text entered by the user, including an empty string, or None to signal cancellation.

Return type:

str | None

show_error(message)

Display a recoverable conversion or validation error.

Parameters:

message (str) – Human-readable explanation of why the value was rejected.

Returns:

None.

Return type:

None

upref.collect(schema, initial=None, interface='tty', mode='missing')

Collect configuration values without mutating or persisting inputs.

In "missing" mode, a field is requested when its key is absent, its value is None, or a required string is empty. Values such as False and 0 are therefore already populated. "all" requests every field. Keys in initial that are not part of schema are preserved. Schema iteration order determines prompt order.

initial is normalized into a detached tree before collection, and parser results are normalized before insertion. Consequently, neither the input mapping nor nested mutable objects returned by a parser are shared with the result. Collection has no persistence side effects: callers must explicitly pass the returned mapping to a store.

String selectors create bundled prompters lazily. If a created prompter exposes close(), it is called in a finally block, including after cancellation or another error. A caller-provided prompter is never closed by this function. If no fields require input, a selected bundled interface is not instantiated.

Parameters:
  • schema (Mapping[str, Field]) – Mapping from configuration keys to immutable field definitions.

  • initial (Mapping[str, bool | int | float | str | None | list[bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]] | dict[str, bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]]] | None) – Existing configuration values. Unsupported values, non-string keys, and cyclic containers are rejected. Defaults to an empty mapping.

  • interface (str | Prompter) – "tty" for dependency-free terminal input, "gui" for optional wxPython dialogs, or a caller-owned Prompter.

  • mode (Literal['missing', 'all']) – "missing" to request only absent or incomplete fields, or "all" to request every schema field.

Returns:

A new, detached configuration dictionary containing preserved initial keys and all successfully collected values.

Raises:
  • TypeError – If schema keys are not strings, schema values are not Field instances, or a non-string interface does not implement Prompter.

  • ValueError – If mode or a string interface selector is invalid.

  • PromptCancelled – If the user cancels any requested field. Partial results are discarded and initial remains unchanged.

  • upref.errors.ConfigFormatError – If initial or a parser result lies outside the supported configuration data model.

  • upref.errors.PromptUnavailableError – If the GUI interface is requested but wxPython cannot be imported or initialized.

  • Exception – Unexpected exceptions from a prompter, parser, validator, or owned prompter’s close() method propagate. A close failure during exception handling can replace the original exception.

Return type:

dict[str, bool | int | float | str | None | list[bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]] | dict[str, bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]]]

Built-in interfaces

class upref.tty.TTYPrompter(input_func=None, getpass_func=None, print_func=None)

Ask for fields using standard terminal input.

The three functions are injectable so applications and tests can control input and presentation without replacing process-wide builtins. This prompter owns no external resource and requires no explicit close step.

Parameters:
  • input_func (ReadFunction | None)

  • getpass_func (ReadFunction | None)

  • print_func (PrintFunction | None)

__init__(input_func=None, getpass_func=None, print_func=None)

Initialize a terminal prompter.

Parameters:
  • input_func (Callable[[str], str] | None) – Reader for ordinary fields. It receives the prompt text and returns one raw line. Defaults to input().

  • getpass_func (Callable[[str], str] | None) – Reader for secret fields. It receives the prompt text and should avoid echoing entered characters. Defaults to getpass.getpass().

  • print_func (Callable[[str], object] | None) – Function used to display labels, descriptions, current values, and errors. Defaults to print().

Returns:

None.

Return type:

None

ask(name, field, current)

Display field context and read one raw line.

The field label and optional description are displayed first. A non-None current value is also displayed for ordinary fields. Secret fields instead use the injected password reader and never show the current value. The returned secret is nevertheless plaintext.

Parameters:
  • name (str) – Configuration key, used as the label when field.label is empty.

  • field (Field) – Presentation metadata, including whether entry is secret.

  • current (bool | int | float | str | None | list[ConfigValue] | dict[str, ConfigValue]) – Existing value to display for an ordinary field, or None when unavailable.

Returns:

The raw line returned by the selected reader, including an empty string, or None when reading raises EOFError or KeyboardInterrupt.

Raises:

Exception – Display-function errors and reader errors other than EOFError and KeyboardInterrupt propagate. The two cancellation exceptions are converted only when the selected reader raises them.

Return type:

str | None

show_error(message)

Display a conversion or validation error with a clear prefix.

Parameters:

message (str) – Human-readable error supplied by the collection layer.

Returns:

None.

Raises:

Exception – Any exception from the injected output function propagates unchanged.

Return type:

None

class upref.gui.GuiPrompter(title='Preferences', parent=None)

Collect one value at a time with wxPython modal dialogs.

Instances can be closed explicitly or used as context managers. Closing is idempotent and destroys a wx.App only when this instance created it. An application that already existed at construction remains owned by its original caller. Once closed, the prompter cannot display new dialogs.

Parameters:
  • title (str)

  • parent (Any)

__init__(title='Preferences', parent=None)

Initialize wxPython and prepare a modal-dialog prompter.

Parameters:
  • title (str) – Window title used by text-entry and error dialogs.

  • parent (Any) – Optional wx window that owns the dialogs. None creates top-level dialogs according to wxPython’s normal behavior.

Returns:

None.

Raises:

PromptUnavailableError – If wxPython is not installed or a wx application cannot be initialized.

Return type:

None

ask(name, field, current)

Show a modal text-entry dialog for one field.

Ordinary fields are prefilled with a non-None current value. Secret fields use wxPython’s password style and deliberately start empty, so the current secret is neither displayed nor copied into the widget. Any entered secret is still returned as plaintext. The dialog is destroyed after confirmation or cancellation and when displaying or reading the constructed dialog raises an error.

Parameters:
  • name (str) – Configuration key, used as the label when field.label is empty.

  • field (Field) – Presentation metadata, including description and secret handling.

  • current (ConfigValue) – Existing value used as the default for an ordinary field, or None when unavailable.

Returns:

The raw dialog text when the user confirms, including an empty string, or None when the dialog is cancelled or dismissed.

Raises:
  • RuntimeError – If the prompter has already been closed.

  • Exception – Errors raised by wxPython while constructing, showing, reading, or destroying the dialog propagate unchanged.

Return type:

str | None

show_error(message)

Display a modal conversion or validation error.

Parameters:

message (str) – Human-readable error supplied by the collection layer.

Returns:

None.

Raises:
  • RuntimeError – If the prompter has already been closed.

  • Exception – Errors raised by wx.MessageBox propagate unchanged.

Return type:

None

close()

Release GUI resources owned by this prompter.

Closing is idempotent. If construction created a private wx.App, its Destroy method is called when available. An application that predated this prompter is never destroyed.

Returns:

None.

Raises:

Exception – An error raised by the owned wx.App.Destroy method propagates. The prompter remains marked as closed.

Return type:

None

__enter__()

Enter a context manager without changing GUI ownership.

Entering does not initialize another application or reopen a prompter that was already closed.

Returns:

This prompter instance.

Return type:

GuiPrompter

__exit__(*exc_info)

Close the prompter when leaving a context manager.

Parameters:

*exc_info (object) – Exception details supplied by the context-management protocol. They are not inspected or suppressed.

Returns:

None. An exception from the managed block normally propagates.

Raises:

Exception – An exception from close() propagates and can replace an active exception from the managed block.

Return type:

None

Exceptions

exception upref.UprefError

Bases: Exception

Base class for Upref’s domain-specific exceptions.

Unexpected programming errors and third-party exceptions outside Upref’s documented boundaries are not necessarily wrapped in this hierarchy.

exception upref.ConfigPathError

Bases: UprefError, ValueError

Indicate that a configuration name or path is invalid.

This exception also subclasses ValueError, allowing callers that treat invalid path arguments as ordinary value errors to catch either API.

exception upref.ConfigFormatError

Bases: UprefError

Indicate data that cannot be represented by Upref’s data model.

Typical causes include malformed YAML, a non-mapping document root, non-string keys, unsupported Python values, and recursive container cycles.

exception upref.ConfigReadError

Bases: UprefError

Indicate that a configuration file could not be read.

Format and encoding failures use ConfigFormatError; this exception represents path conversion and filesystem read failures.

exception upref.ConfigWriteError

Bases: UprefError

Indicate that a filesystem mutation could not be completed safely.

The exception covers atomic saves and deletions. Invalid configuration data is reported separately through ConfigFormatError.

exception upref.MigrationError

Bases: UprefError

Indicate that a legacy configuration could not be migrated.

The original legacy file is intended to remain untouched when this error is raised.

exception upref.PromptCancelled

Bases: UprefError

Indicate that the user cancelled interactive value collection.

Cancellation is an expected control-flow outcome and does not persist a partially collected configuration.

exception upref.PromptUnavailableError

Bases: UprefError

Indicate that the requested interactive interface is unavailable.

This commonly means an optional GUI dependency is not installed or cannot be initialized in the current environment.

Package metadata

upref.__version__ contains the installed distribution version. upref.__author__, upref.__license__, and upref.__copyright__ describe the package’s authorship and license.