One interface, multiple file formats — async nested storage with dot-path access and atomic writes
Project description
nestio
Async local storage for Python. One interface, multiple file formats.
Stop writing the same boilerplate every time you need local storage.
nestio provides a consistent async API for JSON, TOML, YAML, TOON, and MessagePack — plus a typed .env loader — with:
- Dot-path access —
"user.settings.theme" - Atomic writes — never leave files half-written
- Automatic locking — safe concurrent writes out of the box
- One API across every supported format
Supported formats: JSON • TOML • YAML • TOON • MessagePack
Same API. Same code. Different format.
Why nestio?
Without nestio:
import json
with open("config.json", "r") as f:
data = json.load(f)
data["user"]["settings"]["theme"] = "dark"
with open("config.json", "w") as f:
json.dump(data, f, indent=4)
With nestio:
from nestio.files import JSON
db = JSON("config.json")
await db.set("user.settings.theme", "dark")
Same logic. Less boilerplate.
Installation
pip install nestio
Features
| Feature | Supported |
|---|---|
| Async API | ✅ |
| Dot-path access | ✅ |
| Atomic writes | ✅ |
| Automatic locking | ✅ |
| JSON | ✅ |
| TOML | ✅ |
| YAML | ✅ |
| MessagePack | ✅ |
| TOON | ✅ |
.env loader |
✅ |
Supported formats
| Format | Class | File ext | Best for |
|---|---|---|---|
| JSON | JSON |
.json |
General purpose, APIs, configs |
| TOML | TOML |
.toml |
Configuration files |
| YAML | YAML |
.yaml |
Human-friendly configs |
| TOON | TOON |
.toon |
LLM input — compact, token-efficient |
| MessagePack | MSGPACK |
.msgpack |
Binary, fast, compact serialization |
Quickstart
File storage
import asyncio
from nestio.files import JSON # swap for TOML, YAML, TOON, or MSGPACK — API is identical
async def main():
db = JSON("data/config.json")
await db.set("user.name", "Alice")
await db.set("user.settings.theme", "dark")
await db.set("user.scores", [])
name = await db.get("user.name") # "Alice"
theme = await db.get("user.settings.theme") # "dark"
await db.append("user.scores", 42)
await db.update("user.settings", {"language": "en"})
await db.delete("user.settings.theme")
asyncio.run(main())
TOML
from nestio.files import TOML
cfg = TOML("data/config.toml")
await cfg.set("server.host", "localhost")
await cfg.set("server.port", 8080)
await cfg.update("server", {"debug": True})
YAML
from nestio.files import YAML
cfg = YAML("data/config.yaml")
await cfg.set("server.host", "localhost")
await cfg.set("tags", ["web", "api"])
await cfg.append("tags", "async")
MessagePack
from nestio.files import MSGPACK
cache = MSGPACK("data/cache.msgpack")
await cache.set("session.user_id", 1234)
await cache.set("session.permissions", ["read", "write"])
await cache.append("session.permissions", "admin")
TOON
from nestio.files import TOON
store = TOON("data/context.toon")
await store.set("context.task", "Our favorite hikes")
await store.set("friends", ["ana", "luis", "sam"])
await store.update("context", {"season": "spring_2025"})
Environment variables
nestio.env gives you a typed wrapper around your .env file — no more scattered os.getenv() calls.
from nestio.env import Env
env = Env(".env") # raises FileNotFoundError if missing
# Basic access
debug = env.get("DEBUG", default="false")
host = env["HOST"] # same as os.getenv("HOST")
# Typed getters
port = env.get_int("PORT") # int
ratio = env.get_float("RATIO") # float
verbose = env.get_bool("VERBOSE") # True for "true", "1", "yes", "y", "t"
tags = env.get_list("TAGS") # splits on "," by default
tags = env.get_list("TAGS", sep=" ") # custom separator
# Required variables — raises KeyError if missing or empty
secret = env.require("SECRET_KEY")
Given a .env file like:
HOST=localhost
PORT=8080
DEBUG=true
TAGS=web,api,async
SECRET_KEY=supersecret
File storage API
All five methods work the same across every format. All are async and must be awaited.
Import from the submodule or the top level — both work:
from nestio.files import JSON # explicit
from nestio import JSON # shortcut
get(path, default=None)
Returns the value at the dot-path, or default if it doesn't exist.
value = await db.get("server.host", default="localhost")
set(path, value)
Sets the value at the dot-path. Creates intermediate dicts as needed.
await db.set("server.port", 8080)
delete(path)
Removes the key at the dot-path. Does nothing if the key doesn't exist.
await db.delete("server.port")
append(path, value)
Appends a value to a list at the dot-path. Creates the list if it doesn't exist yet.
await db.append("logs", {"level": "info", "msg": "started"})
update(path, new_data)
Deep-merges a dict into the value at the dot-path. Existing keys not in new_data are preserved.
await db.update("config", {"retries": 3, "timeout": 30})
How it works
- Dot-path access — keys like
"a.b.c"resolve through nested dicts automatically. - Atomic writes — every save writes to a temp file first, then uses
os.replace()to swap it in. Your file is never half-written. - Per-key locking — concurrent writes to the same path are serialized with
asyncio.Lock, while independent paths write in parallel. Locks clean up automatically after a TTL.
Format-specific notes
TOON
TOON (Token-Oriented Object Notation) is a compact, human-readable format designed for LLM input. It combines YAML-style indentation for nested objects with CSV-style rows for uniform arrays — achieving up to 40% fewer tokens than JSON while maintaining full round-trip fidelity.
A .toon file produced by nestio looks like this:
context:
task: Our favorite hikes
season: spring_2025
friends[3]: ana,luis,sam
hikes[3]{id,name,distanceKm,wasSunny}:
1,Blue Lake Trail,7.5,true
2,Ridge Overlook,9.2,false
3,Wildflower Loop,5.1,true
MessagePack
MessagePack is a binary format — files are not human-readable, but they're smaller and faster to parse than any text-based format. Best for local caches and high-frequency storage where you don't need to inspect files manually.
Roadmap
- JSON support
- TOML support
- YAML support
- MessagePack support
- TOON support
- Atomic writes
- Async locking
-
.envloader - Benchmarks
- Automatic format detection
- More examples
Why I built nestio
I found myself repeatedly writing the same file handling code in async projects:
- Load a file
- Navigate nested dictionaries
- Modify values
- Save safely
- Handle concurrent writes
nestio was created to remove that boilerplate and provide a consistent interface across multiple storage formats.
Requirements
- Python 3.9+
aiofilespyyaml(YAML)tomli(TOML, Python < 3.11 only)tomli-w(TOML)toons(TOON)msgpack(MessagePack)python-dotenv(.env loader)
License
MIT © MrBaconHat
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 nestio-0.2.2.tar.gz.
File metadata
- Download URL: nestio-0.2.2.tar.gz
- Upload date:
- Size: 12.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fecd58608949bd3b0a2f39bc6129e9e330686144572827a5ff98c50f5316c48c
|
|
| MD5 |
37b9ad48b50a61a0bfd9426c16f34f5d
|
|
| BLAKE2b-256 |
29788f5230e8f2bdfe39cdf35bff3229cb06410e9d5e93eaf799a55ea989d637
|
File details
Details for the file nestio-0.2.2-py3-none-any.whl.
File metadata
- Download URL: nestio-0.2.2-py3-none-any.whl
- Upload date:
- Size: 12.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9345e071914e7a0e090ac396e3458f9f4d5d9a1dd8e75b006c8b3ec2adf60d34
|
|
| MD5 |
d2ec185f8a8d677e981e7f0b2615f4b9
|
|
| BLAKE2b-256 |
0f61d57374a1cff548550095a5d2d207225480be4217fb9f0bec7fc8d48b5fcf
|