Generic ledger and storage primitives for Python projects
Project description
ledgercore
Generic, typed storage and reference primitives for ledger-like Python applications.
ledgercore is a small Python library for projects that store structured records
as files. It provides reusable primitives for atomic writes, YAML front matter,
deterministic JSON/YAML storage, safe relative paths, config discovery, numeric
IDs, and cross-ledger references.
It has no CLI and no dependency on any downstream ledger application.
Why ledgercore exists
Ledger-like tools (task trackers, architecture logs, spec registries) share the
same low-level problems: safely writing files, formatting IDs, validating paths,
and linking records across namespaces. ledgercore extracts those shared
primitives into one typed, zero-surprise package so downstream projects do not
reinvent them.
What is included
- Atomic UTF-8 text writes and create-only writes.
- YAML front matter read/write helpers.
- Deterministic JSON and YAML file I/O.
- Safe relative POSIX path validation.
- Upward config discovery.
- Prefixed numeric ID formatting.
- Cross-ledger references such as
tl:task-0001. - A typed public API and shared exception hierarchy.
What is not included
- No command-line interface.
- No database layer.
- No sync protocol.
- No task, architecture, or project-specific schema.
- No dependency on
taskledger,archledger, or another product package.
Installation
pip install ledgercore
Requirements:
- Python 3.10+
- PyYAML
Quick start
from pathlib import Path
from ledgercore.frontmatter import write_front_matter_document
from ledgercore.ids import LedgerIdFormat
from ledgercore.refs import parse_resource_ref
task_ids = LedgerIdFormat(prefix="task")
task_id = task_ids.next(["task-0001", "task-0002"])
write_front_matter_document(
Path(f"records/{task_id}.md"),
{"id": task_id, "status": "open"},
"# New task\n",
)
ref = parse_resource_ref("tl:task-0003")
assert ref.local_id == "task-0003"
assert ref.global_ref == "tl:task-0003"
Cross-ledger references
Inside a single ledger, keep local IDs short:
task-0001
adr-0002
When linking records across ledgers, use canonical global refs:
<ledger>:<kind>-<number>
Examples:
tl:task-0001
al:adr-0002
sw:spec-0003
A cross-ledger link can then store both endpoints unambiguously:
source: tl:task-0001
target: al:adr-0002
relation: implements
For filenames or systems that cannot use :, use the file-safe alias:
tl-task-0001
al-adr-0002
from ledgercore.refs import parse_resource_ref
ref = parse_resource_ref("tl:task-0001")
assert ref.ledger == "tl"
assert ref.kind == "task"
assert ref.number == 1
assert ref.local_id == "task-0001"
assert ref.global_ref == "tl:task-0001"
assert ref.file_ref == "tl-task-0001"
ID formatting
Use LedgerIdFormat as the primary ID formatter:
from ledgercore.ids import LedgerIdFormat
ids = LedgerIdFormat(prefix="task")
assert ids.format(1) == "task-0001"
assert ids.parse("task-0007") == 7
assert ids.next(["task-0001", "task-0002"]) == "task-0003"
For segmented, legacy-compatible IDs:
from ledgercore.ids import LedgerIdFormat
adr_ids = LedgerIdFormat(prefix="adr", separator="-", segment_separator="-")
assert adr_ids.format(13, segment="content") == "adr-content-0013"
NumericIdFormat remains available as a simpler compatibility wrapper.
Front matter documents
from pathlib import Path
from ledgercore.frontmatter import read_front_matter_document, write_front_matter_document
path = Path("records/task-0001.md")
write_front_matter_document(
path,
{"id": "task-0001", "status": "open"},
"# Implement parser\n",
body_mode="ensure-single-final-newline",
)
metadata, body = read_front_matter_document(path)
Front matter documents must start with --- followed by a newline and contain a
YAML mapping. The body follows the closing --- delimiter.
JSON and YAML stores
from pathlib import Path
from ledgercore.jsonio import load_json_object, write_json
from ledgercore.yamlio import load_yaml_object, write_yaml
state_path = Path("state.json")
write_json(state_path, {"next": 4})
state = load_json_object(state_path, missing="empty")
JSON output uses indent 2, sorted keys, and a final newline. YAML uses block style and can sort keys when requested.
Safe paths and config discovery
from pathlib import Path
from ledgercore.paths import (
ConfigLocator, locate_config, resolve_config_relative_path,
)
locator = locate_config(Path.cwd(), ("ledger.toml", ".ledger.toml"))
if locator is not None:
records_dir = resolve_config_relative_path(
locator.config_path,
"records",
field_name="records_dir",
)
locate_config returns a ConfigLocator with workspace_root, config_path,
and source fields. Path helpers reject absolute paths, .., . segments,
backslashes, and paths escaping the base directory.
Atomic writes
from pathlib import Path
from ledgercore.atomic import atomic_create_text, atomic_write_text
atomic_create_text(Path("records/task-0001.md"), "---\nid: task-0001\n---\n")
atomic_write_text(Path("index.json"), "{}\n")
atomic_create_text: create only; fails if target exists.atomic_write_text: replace target atomically via temp file andos.replace.
Error model
All package-specific errors inherit from LedgerCoreError.
from ledgercore.errors import (
LedgerCoreError, StorageError, AtomicWriteError,
FrontMatterError, JsonStoreError, YamlStoreError,
PathValidationError, IdFormatError,
)
try:
...
except LedgerCoreError as exc:
print(exc.code, str(exc))
Each exception carries a stable code attribute for programmatic handling.
Type checking
ledgercore ships a py.typed marker. It is fully typed and passes strict
mypy with strict = true.
Development
python -m pip install -e ".[dev]"
python -m pytest -q
python -m ruff check .
python -m mypy ledgercore
Release checklist
- Update version in
pyproject.toml. - Run
python -m pytest -q. - Run
python -m ruff check .. - Run
python -m mypy ledgercore. - Run
python -m build. - Run
python -m twine check dist/*. - Smoke-test the built wheel in a clean virtualenv.
Stability
ledgercore is pre-1.0. Public APIs are intended to be stable within the
0.1.x series, but breaking changes may still happen before 1.0.0 when needed
to keep the core API small and consistent.
- No CLI is included.
- No global configuration format is imposed.
- No ledger schema is imposed.
- No product-specific IDs are baked in.
- All paths and refs are strings/paths chosen by downstream packages.
License
Apache-2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ledgercore-0.1.0.tar.gz.
File metadata
- Download URL: ledgercore-0.1.0.tar.gz
- Upload date:
- Size: 38.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3a4e7f67b99f89b65cdd7c2b06f50395eeb6fd54273f86c6ef62a9d12424438e
|
|
| MD5 |
48bd8b8cd455013bae7155360a6e021e
|
|
| BLAKE2b-256 |
8e3020671a7d456bfe51672cbfc7e07bdbe2e23e8788f7a581024e4b686f8311
|
File details
Details for the file ledgercore-0.1.0-py3-none-any.whl.
File metadata
- Download URL: ledgercore-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6d76e68e626bba18bdee3368629daec10718944d58515011404ba05465e244f9
|
|
| MD5 |
4a30c23eb1856884eb8af6efc31d2491
|
|
| BLAKE2b-256 |
ad4de002c7bdb93ba3dcdc86e165abbbe02152380e72a49361019cd2a0bddda1
|