apprc is a small Python library for application runtime configuration and
logging. It is useful when a project needs typed env-backed config, packaged
defaults, per-user storage registries, local override files, a reusable
config CLI, a terminal config editor, and structured logs without rebuilding
that plumbing in every application.
The key idea: the application declares its config contract once, and apprc
derives the boring workflows from that contract.
typed-settingsfor typed config fields and runtime dataclasses.typerfor CLI commands.python-dotenvfor.envfile parsing and writing.textualfor the terminal config editor.structlogfor structured logging.
python -m pip install apprcAny Python build system supporting pyproject.toml (PEP 621) works.
For published releases, depend on the PyPI package:
[project]
dependencies = [
"apprc",
]For the current repository revision, depend on the Git URL:
[project]
dependencies = [
"apprc @ git+https://github.com/HisQu/apprc.git",
]git clone https://github.com/HisQu/apprc.git
cd apprc
python -m venv .venv
source .venv/bin/activate
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip install -e "." --group devgit clone https://github.com/HisQu/apprc.git
cd apprc
uv sync --frozen --all-groupspython -c "import apprc; print(apprc.AppConfigKit)"
pytestAn application usually wires AppRC in three steps: declare fields, create the
kit, and mount the generated config CLI.
from dataclasses import dataclass
import typer
from apprc import AppConfigKit
from apprc.config import ConfigOwner, config_field
# 1) Declare the config fields your app owns.
CLIENT_OWNER = ConfigOwner(
key="client",
title="Client",
env_prefix="MYAPP_",
rc_path=("client",),
runtime_cls=None,
fields=(
config_field(
"model",
"MODEL",
str,
default="demo-model",
title="Model",
explanation="Model used by client calls.",
),
),
)
# 2) Create the reusable AppRC facade for your application.
MYAPP_CONFIG = AppConfigKit(
app_name="myapp",
display_name="MyApp",
config_package="myapp.config",
owners=(CLIENT_OWNER,),
storage_root_env_key="MYAPP_D_STORAGE",
registry_filename="myapp.toml",
shared_env_filename=".env.shared",
local_env_filename=".env.local",
)
# 3) Mount the generated `myapp config ...` command group.
@dataclass(slots=True)
class CliState:
env_bootstrap: object | None = None
storage: str | None = None
app = typer.Typer()
app.add_typer(
MYAPP_CONFIG.typer_app(state_type=CliState),
name="config",
)Runtime dataclasses can inherit BaseEnv when you want typed objects populated
from the bootstrapped environment. The important first step is the owner
inventory: AppRC reuses it for loading, validation, docs, CLI commands, and the
terminal editor.
AppRC does not install its own apprc command. The config CLI is meant to be
mounted as a subcommand of the application that depends on AppRC.
Users then get:
myapp config setup
myapp config init /absolute/path/to/storage --name default --default
myapp config doctor
myapp config show --json
myapp config set client.model other-model
myapp config editUse myapp config setup for normal first-time installation. It explains the
config file and storage root locations, asks for a default storage, and prints
next steps. config init remains available as the lower-level command for
scripts or manual storage registration.
apprc separates config into clear layers:
| Layer | Owner | Purpose |
|---|---|---|
ConfigField |
application | One typed env-backed setting. |
ConfigOwner |
application | A named section of related fields. |
.env.shared |
application package | Packaged defaults shipped with code. |
<storage>/.env.local |
user/project | Per-storage local overrides. |
| shell environment | user/process | Highest-priority process values by default. |
~/.config/<app>/<registry_filename> |
AppRC registry | Named storage roots and default storage. |
Runtime dataclasses inherit BaseEnv. The dataclass owns Python attributes;
ConfigOwner owns env names, docs labels, editor labels, choices, and
redaction metadata.
Applications call AppConfigKit.bootstrap(...) once at CLI startup. It merges:
- packaged
.env.shared - selected storage-local
.env.local - explicit
--env-file - current shell environment
By default, the shell wins over --env-file. Set
env_file_overrides_shell=True when an explicit file should win inside the
current process.
Globally installed commands need to find user data without hardcoding one path.
apprc stores named roots in ~/.config/<app>/<registry_filename>, unless
the app-specific <APP>_CONFIG_FILE environment variable points to a custom
TOML file. For an app named myapp, that override is MYAPP_CONFIG_FILE.
The interactive setup command refuses custom locations unless that variable is
already set, because future commands must be able to rediscover the same file.
For example:
default_storage = "default"
[storages.default]
root = "/absolute/path/to/storage"
[archived_storages.old-default]
archive = "/absolute/path/to/old-default.apprc.tar.xz"
source_root = "/absolute/path/to/old-default"Each storage root owns its own local override file, such as .env.local.
Archived storage records are only last-known restore shortcuts for the
terminal editor; runtime bootstrap still selects live directory entries from
[storages].
On POSIX/WSL hosts, Windows drive paths such as D:\Training\demo-project are
normalized to usable local paths before AppRC writes the registry or reads a
storage-root environment value.
Important
POSIX shells consume unquoted backslashes before Python sees the argument.
If you type C:\Projects\demo-storage without quotes, AppRC may receive
C:Projectsdemo-storage, which is not recoverable as a real Windows path.
Use one of these forms instead:
myapp config init 'C:\Projects\demo-storage' --name default --default
myapp config init C:/Projects/demo-storage --name default --default
myapp config init /mnt/c/Projects/demo-storage --name default --defaultapprc.logging wraps stdlib logging and structlog. setup_logging() installs
formatters and dependency logger levels. get_logger() returns an AppLogger
with semantic helper methods such as action_begin, success, retry,
fallback, telemetry, and traceback.
Use AppRC logging when application logs should stay structured and readable in CLI output, notebooks, and tests.
Put config declarations in your application package, usually
myapp/config/owners.py.
from pathlib import Path
from apprc.config import CONFIG_MISSING, ConfigOwner, config_field
STORAGE_OWNER = ConfigOwner(
key="storage",
title="Storage",
env_prefix="MYAPP_",
rc_path=("storage",),
runtime_cls=None,
fields=(
config_field(
"root",
"D_STORAGE",
Path,
default=CONFIG_MISSING,
editable=False,
required=True,
explanation_short="Active storage root.",
explanation_long="Selected through the AppRC storage registry.",
),
),
)Use editable=False for values owned by the registry instead of .env.local.
Call bootstrap before creating runtime config objects.
from apprc.cli import bootstrap_cli_env
from apprc.logging import setup_logging
state.env_bootstrap = bootstrap_cli_env(
MYAPP_CONFIG,
env_file=env_file,
env_file_overrides_shell=env_file_overrides_shell,
no_dotenv=no_dotenv,
storage_name=storage,
log_level=log_level,
setup_logging=setup_logging,
)config_app = MYAPP_CONFIG.typer_app(
state_type=CliState,
runtime_payload=lambda state: {
"storage": str(state.env_bootstrap.storage_root)
if state.env_bootstrap
else None,
},
)
app.add_typer(config_app, name="config")config edit opens a Textual editor. The editor shows:
- key number
- section
- env key
- shell status
- local value
- default value
- short explanation
Selecting a row opens a modal with type information, possible values, and the
long explanation. Secret values are redacted. Required missing values show
<required>.
The editor also manages storage lifecycle:
New storageregisters a directory or restores a*.apprc.tar.xzarchive.Set this as default storagechanges the registry default.Delete storagecan unregister only or delete the directory too.Archive storagewrites*.apprc.tar.xzand can optionally remove the source directory after compression.
If the last live default is removed, the editor prompts for a replacement path
prefilled with ~/.local/share/<app>/default or offers to leave AppRC in the
fresh-install state with no default storage.
from apprc.logging import get_logger, setup_logging
setup_logging(level="INFO", renderer="cli")
log = get_logger(__name__)
log.action_begin("Loading workspace")
log.success("Workspace ready", storage="default")| Module | Look Here For |
|---|---|
apprc.config.schema |
ConfigField, ConfigOwner, field lookup, typed loading. |
apprc.config.kit |
AppConfigKit, the high-level app integration facade. |
apprc.config.environment |
CLI startup dotenv/bootstrap precedence. |
apprc.config.paths |
Storage-root path normalization helpers. |
apprc.config.storage_registry |
~/.config/<app>/*.toml storage names. |
apprc.config.storage_archive |
*.apprc.tar.xz storage compression and restore. |
apprc.config.local_env |
<storage>/.env.local reads, writes, validation. |
apprc.config.tui |
Textual app and modal interactions. |
apprc.config.tui_rendering |
Pure table cell rendering and styles. |
apprc.cli.config_app |
Generated config Typer commands. |
apprc.cli.bootstrap |
Common root CLI bootstrap options. |
apprc.logging |
Logging facade: setup_logging, get_logger, AppLogger. |
| Type | Meaning |
|---|---|
AppConfigKit |
Convenient object applications keep around. |
AppConfigSpec |
Frozen declaration behind the kit. |
ConfigOwner |
One config section, env prefix, runtime path, and fields. |
ConfigField |
One editable or read-only env-backed setting. |
BaseEnv |
Runtime dataclass base that binds values from env. |
EnvBootstrapResult |
Files and storage selected during CLI startup. |
StorageRegistry |
Parsed TOML registry. |
ArchivedStorageRecord |
Last-known archive path for editor restore shortcuts. |
LocalEnvUpdate |
Result of writing one local dotenv override. |
| Command | Purpose |
|---|---|
config setup |
Interactively choose the registry file and default storage. |
config init STORAGE_ROOT --name NAME --default |
Register a storage root. |
config doctor |
Diagnose registry and selected storage state. |
config show --json |
Print resolved runtime config payload. |
config set-default NAME |
Change default storage. |
config set KEY VALUE |
Write one local override. |
config edit |
Open the Textual editor. |
| Function | Purpose |
|---|---|
setup_logging(...) |
Configure stdlib/structlog output. |
get_logger(__name__) |
Return an AppLogger. |
log.action_begin(...) |
Start a visible operation. |
log.success(...) |
Mark a completed operation. |
log.retry(...) |
Record retry attempts. |
log.fallback(...) |
Record fallback behavior. |
log.traceback(...) |
Emit exception information with redaction support. |
async_telemetry(...) |
Emit periodic async progress logs. |
git clone https://github.com/HisQu/apprc.git
cd apprc
python -m venv .venv
source .venv/bin/activate
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip install -e "." --group devor:
uv sync --frozen --all-groupsRun these before sending changes:
ruff format .
ruff check .
pyright
pytestHaiu is the main downstream integration test for AppRC. From the Haiu repo:
cd ../haiu
.venv/bin/python -m pip install --no-deps --no-build-isolation -e ../apprc
.venv/bin/pytest tests/haiu/core/test_config_tui.py -q
.venv/bin/pytestKeep refactors behavior-preserving. If a cleanup would remove a public module, change import-time side effects, or alter CLI output, treat that as a feature change and ask first.