apprc: Application Runtime Config
AppRC is for Python applications that need configuration to be explicit, inspectable, and pleasant to operate. Instead of spreading environment variables, dotenv files, setup commands, and diagnostics across unrelated code, you declare the runtime contract once and let AppRC build the surrounding workflows from that metadata.
The three strongest parts:
- Typed config contracts: declare application settings once with
rc.Config,rc.ConfigBase,rc.field(...), and@MyRC.config(...). - Deterministic runtime config: load layered dotenv files predictably while keeping normal runtime reads and diagnostics zero-write.
- Generated operator UX: mount ready-made Typer
configcommands and open the same contract in the Textual editor.
Advanced integrations can inspect the same declared contract through
intentional namespaces such as rc.cli, rc.files, rc.storage,
rc.provenance, and rc.schema; normal app code should still start with
import apprc as rc.
Fig. 1 - AppRC Graphical Abstract: AppRC lets developers ship one typed config contract with generated setup, diagnostics, editing, and runtime config workflows.
Note
For the full system model, see docs/Explanations.md. For exact public names and command references, see docs/References.md.
Table Of Contents
apprc: Application Runtime Config- Installation
- Quickstart
- How AppRC Works
- Generated Workflows
- More Documentation
Install AppRC, declare the runtime contract, and mount the generated config
commands in your Typer application.
Installation
python -m pip install apprc
Install the optional Textual editor when you want config edit:
python -m pip install "apprc[tui]"
AppRC supports Python 3.12 and newer.
Note
For installation and first-setup recipes, see docs/How-To-User-Guides.md.
Quickstart
Use one root import and declare the app contract from that handle:
Create this standard package layout by hand, or generate a starter with
apprc scaffold config:
myapp/config/
__init__.py
__init__.pyi
_facade.py
app.py
sections/
__init__.py
__init__.pyi
_facade.py
app.py
bundle.py
catalog.py
apprc scaffold config \
--package myapp \
--storage \
--app-id myapp \
--display-name "My App" \
--storage-selector-env-key MYAPP_STORAGE \
--target src
The full declaration can live in one file while learning, but the package layout above is the recommended project structure.
Keep every app-declared config area under config/sections/. Small areas can
be one module, for example sections/client.py. When an area grows, turn it
into a package such as sections/rag/ and keep its local bundle/resources next
to its leaf settings there. Leave config/bundle.py for the top-level app
bundle and config/catalog.py for metadata. Keep package __init__.py files
lightweight; import section classes in bundle.py from leaf modules such as
config.sections.client, not from the config.sections package facade.
from pathlib import Path
import typer
import apprc as rc
MyRC = rc.AppRC(
app_id="myapp",
display_name="My App",
config_package="myapp.config",
storage=rc.Storage(selector_env_key="MYAPP_STORAGE"),
)
@MyRC.config("app", prefix="MYAPP_", title="App")
class AppSettings(rc.Config):
storage_root: Path = rc.field(
"MYAPP_STORAGE",
editable=False,
required=True,
title="Storage root",
)
profile: str = rc.field(
"MYAPP_PROFILE",
default="default",
title="Profile",
description="Named runtime profile.",
)
access_token: str = rc.field(
"MYAPP_ACCESS_TOKEN",
required=True,
secret=True,
title="Access token",
)
@MyRC.config("resources", title="Resources")
class PackageResources(rc.ConfigBase):
package: str = "myapp.resources"
@MyRC.bundle
class MyAppConfig:
app: AppSettings
resources: PackageResources
Add packaged defaults in myapp/config/apprc.defaults.env:
MYAPP_PROFILE="default"
Mount AppRC on your Typer application before commands construct runtime config objects:
from myapp.config import MyAppConfig, MyRC
app = typer.Typer()
MyRC.mount_cli(app)
@app.command()
def run() -> None:
cfg = MyAppConfig()
typer.echo(f"profile={cfg.app.profile}")
MyRC.mount_cli(...) adds the standard AppRC CLI runtime options, performs
runtime setup for commands that need resolved config, and mounts the generated
config command group. Apps with custom runtime state can pass
advanced options through rc.cli.mount_config_cli(...) or rc.cli.CliRuntime.
Apps that own their Typer callback and extra options can use
rc.cli.CliRuntime as the composable middle layer: the app builds its runtime
state, while AppRC
owns config command mounting, skip policy, context storage, and state
validation. When runtime.prepare(...) skips runtime setup,
session.runtime_setup_skipped is true and session.state is None.
Runtimeful generated config commands require the app callback to leave the
declared state_type on ctx.obj; runtime-independent config commands use
AppRC's stored context instead.
For non-Typer usage, call bootstrap explicitly and then construct config:
MyRC.bootstrap()
cfg = MyAppConfig()
Config() reads the current process environment at construction time.
Bootstrap is needed when AppRC should first merge its managed dotenv files;
it is not a requirement for tests or callers that deliberately use only
constructor values, Python defaults, and the current os.environ.
High-level convenience boundaries that want AppRC defaults without taking
bootstrap options can call MyRC.ensure_bootstrapped(). It performs the
default bootstrap once per AppRC declaration and reuses the successful
result. Keep explicit policy at the application entrypoint: call
MyRC.bootstrap(...) there when storage selection, env files, or precedence
options vary. Libraries should normally accept a constructed config object
from their caller.
rc.field("ENV_KEY") is required when no default is provided.
rc.field("ENV_KEY", default="x") and default_factory=... are optional.
An explicit required=True cannot be combined with either Python fallback;
put the value in apprc.defaults.env and describe it with
packaged_default=..., or pass the value to the config constructor.
secret=True redacts display output; it does not encrypt values, store them
elsewhere, or imply that the field is required.
Run the integration examples from a checkout with:
python -m pip install -e examples/example_apps --no-build-isolation
set -a; source .env.example_apps; set +a
python -m apprc_dev.example_apps.bootstrap --output-root "$APPRC_EXAMPLE_APPS_ROOT"
apprc-config-with-storage config doctor
apprc-examples-run-all
They cover apps with and without storage, named storage, explicit env-file
selector precedence, and the CliRuntime app-callback integration. With
direnv, .envrc sources
.env.example_apps and bootstraps
examples/example_app_disk_files/ automatically. Without direnv, source
.env.example_apps and run the bootstrap command above once. The generated
directory contains .apprc-example*/ sandboxes plus a shared
apprc-directories/ tree with commented user dotenv, storage dotenv, and TOML
files showing where the same files would live for a real app.
Note
For the step-by-step integration guide, see docs/How-To-User-Guides.md#integrate-apprc. For the exact import surface, see docs/References.md#public-interfaces.
How AppRC Works
AppRC starts from one declared contract, then uses that contract to load runtime values, inspect configuration health, write explicit setup files, and generate user-facing configuration tools.
| Fig. 2 - One Contract, Many Workflows: AppRC reuses the same contract metadata for runtime loading, provenance, diagnostics, generated CLI commands, and the editor. |
Mental Model
AppRC has one contract and several workflows built from it.
| Concept | Meaning |
|---|---|
| Config field | One typed setting declared with rc.field("FULL_ENV_KEY", ...). |
| Registered config | A related group of fields declared by @MyRC.config(...). |
| AppRC facade | The app-level contract that selects supported persistence layers. |
| Bootstrap | An optional startup step that merges managed dotenv layers into this Python process. |
| Config construction | A read of Python values and the current os.environ into a mutable config object. |
| Generated CLI | A reusable Typer config command group for inspection and edits. |
| Editor | A Textual view over the same sections, fields, and dotenv layers. |
Note
For the deeper architecture behind registered sections, fields, config layers, provenance, and the zero-write policy, see docs/Explanations.md#runtime-config-model.
Config And Storage
There are two declarations, not four capability levels:
# Config only. No storage controls are generated.
MyRC = rc.AppRC(
app_id="myapp",
config_package="myapp.config",
)
# The same config model plus storage.
MyRC = rc.AppRC(
app_id="myapp",
config_package="myapp.config",
storage=rc.Storage(selector_env_key="MYAPP_STORAGE"),
)
rc.Storage() derives MYAPP_STORAGE when selector_env_key is omitted. The
first setup suggests ~/.local/share/myapp/storage/ on every operating system.
The user sees that path before AppRC creates it and can pass another path with
config setup --storage-root PATH. Interactive setup offers the default path,
a custom path with directory completion, or cancellation.
AppRC-managed persistence files are explicit:
| Layer | Default location | Created by |
|---|---|---|
| Packaged defaults | package apprc.defaults.env |
shipped with package |
| User dotenv | ~/.local/share/myapp/apprc.user.env |
config setup or first user-scope save |
| Storage registry | ~/.local/share/myapp/apprc.toml |
storage setup or a storage registry command |
| Storage dotenv | <storage-root>/apprc.storage.env |
storage setup, storage add, or first storage-scope save |
The directory containing apprc.user.env and apprc.toml is the AppRC
directory. Set MYAPP_APPRC_DIR to relocate the complete directory. AppRC
does not split default files between .config, .local, %APPDATA%, and
~/Library/Application Support.
The complete default layouts are:
# rc.AppRC(...) — no storage
~/.local/share/myapp/
└── apprc.user.env
# rc.AppRC(..., storage=rc.Storage()) — one default storage
~/.local/share/myapp/
├── apprc.user.env
├── apprc.toml
└── storage/
└── apprc.storage.env
Additional storage names are user-owned registry entries. Their roots may be
anywhere; they do not gain another storage/<name>/ directory automatically.
Important
Files on disk never enable application capabilities. Only
storage=rc.Storage() enables storage support. Without it, AppRC hides
--storage, config storage ..., the storage editor section, and
--scope storage; a stale apprc.toml produces only a doctor warning.
Note
For declaration arguments, see docs/References.md#application-declaration.
Runtime Precedence
When dotenv layers are loaded, AppRC merges values in this order:
- packaged
apprc.defaults.env - user
apprc.user.env - selected storage
apprc.storage.env, when storage is selected and present - explicit
--env-filevalues - existing
os.environ
With --env-file-overrides-os-environ, explicit env files move after
os.environ and win over shell exports.
Storage selector resolution accepts registered names and filesystem paths:
- CLI
--storage - process environment or explicit env files, in the order selected by
--env-file-overrides-os-environ selected_storageinapprc.toml
apprc.user.env, apprc.storage.env, and packaged defaults never select a
storage. A direct path must be an existing directory with a readable
apprc.storage.env. Relative selectors such as ./data resolve from the
directory containing apprc.toml, never from the current working directory.
When a path matches one registered root, AppRC reports its name. An initialized
unregistered path is usable for one run; an interactive CLI offers to register
it, while a non-interactive caller performs no registry writes.
Note
For the rationale behind layer order and storage selector resolution, see docs/Explanations.md#runtime-bootstrap and docs/Explanations.md#storage-selection. For exact precedence tables, see docs/References.md#runtime-precedence.
Generated Workflows
Mount the generated workflows when you want your application to expose the same contract to users, setup commands, diagnostics, and the Textual editor.
Config CLI
Mounting APP_CONFIG.typer_app(...) gives your app these commands:
myapp config paths
myapp config doctor
myapp config show
myapp config setup
myapp config migrate --dry-run
myapp config purge --dry-run
myapp config set KEY VALUE --scope user
myapp config set KEY VALUE --scope storage
myapp config edit
myapp config storage add NAME PATH
myapp config storage list
myapp config storage select NAME
myapp config storage rename NAME NEW_NAME
myapp config storage repoint NAME PATH
myapp config storage move NAME PATH
myapp config storage remove NAME
Storage commands appear when the declaration includes rc.Storage(). The app
config commands are always available.
config edit requires the optional TUI extra:
python -m pip install "apprc[tui]".
The editor always shows Setup. It runs the same declaration-aware setup as
config setup. Storage apps also show New, Register,
Rename, Location, Move, Archive, and Delete. New and Register
can create the first AppRC TOML registry; opening the editor itself still
writes nothing.
Note
For the generated command table, see docs/References.md#generated-cli-commands.
Setup And Diagnostics
Use config paths before setup to see candidate paths and the declaration
without writing anything. Use config setup or the editor's
Setup action for explicit first storage setup, then use config doctor when
a machine is not runnable.
myapp config paths
myapp config setup --yes --storage-root /absolute/path/to/storage
myapp config doctor
myapp config set access_token secret-value --scope storage
myapp run
Setup creates the empty apprc.user.env, registers the initial storage as
default, records it as selected_storage, and creates
apprc.storage.env. No selector is written to a dotenv file and no shell
export is required. On an interactive terminal, the first storage-dependent
runtime command can offer the same setup. Use --storage-root PATH for a
custom path so the shell can complete it.
config doctor reports a status such as env_not_set, storage_not_ready,
user_dotenv_not_ready, storage_registry_not_ready, or runnable.
AppRC migrates the released 0.19 layout only. Inspect and apply it explicitly:
myapp config migrate --dry-run
myapp config migrate --yes
Migration finds platform-specific 0.19 directories, custom
MYAPP_APPRC_TOML locations, .env.apprc-app, .env.apprc-storage, and
path-valued MYAPP_STORAGE. It converts a path selector into the named
default storage and removes structural selector keys from the migrated user
dotenv. The unreleased apprc.app.env name is intentionally ignored.
Package uninstallers do not remove these user-owned files. Before uninstalling
an AppRC application, run config purge --dry-run, review the exact targets,
then run config purge --yes if desired. Purge deletes fixed AppRC files and
registered storage roots strictly inside the AppRC directory. For external
storage roots it deletes only apprc.storage.env and keeps all other data.
It never follows symlinks and removes the AppRC directory only when empty.
Caution
A registered internal storage root is application-owned. config purge
recursively deletes that root, including files AppRC did not create. Always
inspect the dry run first.
Important
Runtime reads and diagnostics do not create files. bootstrap, config paths, config doctor, and opening config edit are zero-write. Editor
actions such as Setup, New, and Register write only after confirmation. For
storage-backed applications, bootstrap requires the selected root to exist
and be a directory. Run config setup before runtime startup.
Note
For doctor troubleshooting, see docs/How-To-User-Guides.md#troubleshoot-config-doctor. For exact status names, see docs/References.md#doctor-statuses.
More Documentation
The README stays short. The detailed manual and maintainer workflow live in the documentation directory.
Detailed Manual
The detailed manual starts at docs/README.md.
Note
Use docs/How-To-User-Guides.md for integration recipes, docs/Explanations.md for the AppRC system model, docs/References.md for exact commands, files, and APIs, and docs/Development.md for maintainer workflow and docs rules.
The repository also ships runnable example CLIs in
examples/example_apps. Each example is its own
package, with a config/ package,
cli.py, and packaged config/apprc.defaults.env defaults so the source tree mirrors
a real app integration. Generated example disk files live outside that source
tree under examples/example_app_disk_files/.
Development
.venv/bin/ruff format .
.venv/bin/ruff check .
.venv/bin/pyright
.venv/bin/pytest
Regenerate the PyPI README after editing this file:
python src/apprc_dev/packaging/pypi_readme.py
Note
For maintainer workflow, documentation rules, and verification commands, see docs/Development.md.
Release files for apprc 0.21.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| apprc-0.21.0.tar.gz | 242.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| apprc-0.21.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 475.7 kB
Release files / apprc-0.21.0.tar.gz
| Download URL | apprc-0.21.0.tar.gz |
|---|---|
| Size | 242.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
caf15c937970c210d0b102bfd8fa84343cb10e3c1d822d4146635c650bc82161
|
|
BLAKE2b-256 checksum How to use checksums |
408543be2b76ac959d2fab216f8c76e628a5c8fe4ccb6bda7cf9bc6de836380a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / apprc-0.21.0-py3-none-any.whl
| Download URL | apprc-0.21.0-py3-none-any.whl |
|---|---|
| Size | 233.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
54806b01065cd6947ac09429ef6746ba4cca6a1d31fd9c30bf4a60c68d616938
|
|
BLAKE2b-256 checksum How to use checksums |
fc352069ec0e314b2b968265f433536bcef2831ca432f7a9dbea4e09b60ab666
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|