fix: str-only keys, symlink-safe atomic writes, strict JSON, stale-tmp sweep
_load() masked permission/IO errors as an empty store via os.path.exists();
now raises FileNotFoundError only, propagating real failures. Non-str keys
silently never round-tripped through get() since JSON object keys are always
strings; set()/get() now raise ValueError on a non-str key. _save() clobbered
a symlinked store file with os.replace(); now realpaths the target first.
json.dumps() allows NaN/Infinity by default, producing invalid JSON for
strict readers; now allow_nan=False. A whitespace-only file raised while a
zero-byte file returned {}; both now return {}. Orphaned .<pid>.tmp files
from a hard crash of a dead process are swept on save. JSON (de)serialization
now runs via asyncio.to_thread instead of on the event loop.
Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
@@ -12,18 +12,18 @@ you `delete` or `clear` them.
|
||||
`requirements.txt`:
|
||||
|
||||
```
|
||||
aiokv @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiokv.git@v0.1.1
|
||||
aiokv @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiokv.git@v0.2.0
|
||||
```
|
||||
|
||||
Direct:
|
||||
|
||||
```bash
|
||||
pip install "aiokv @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiokv.git@v0.1.1"
|
||||
pip install "aiokv @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiokv.git@v0.2.0"
|
||||
```
|
||||
|
||||
Requires `aiofiles` (pulled transitively).
|
||||
|
||||
Drop the `@v0.1.1` suffix from the line above to install the latest unpinned.
|
||||
Drop the `@v0.2.0` suffix from the line above to install the latest unpinned.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -62,14 +62,15 @@ Prefer `AioKV` in new code.
|
||||
## Durability
|
||||
|
||||
Writes are **atomic**: data is written to a temp file in the same directory and
|
||||
`os.replace()`d over the target (atomic on POSIX). A **process** crash mid-write leaves
|
||||
the previous good file intact, and a reader never observes a partial file. (This is
|
||||
process-crash safety, not power-loss durability — there's no `fsync`, so an OS/power
|
||||
failure could still lose the last write; fine for reconstructible single-process state.)
|
||||
A single
|
||||
`asyncio.Lock` guards every read and write, so concurrent operations on one instance
|
||||
are consistent and no update is lost. All blocking filesystem calls run via
|
||||
`asyncio.to_thread`, so nothing stalls the event loop.
|
||||
`os.replace()`d over the target's realpath — symlink-safe, so a symlinked store file is
|
||||
written through rather than clobbered. A **process** crash mid-write leaves the previous
|
||||
good file intact, and a reader never observes a partial file. (This is process-crash
|
||||
safety, not power-loss durability — there's no `fsync`, so an OS/power failure could
|
||||
still lose the last write; fine for reconstructible single-process state.) Orphaned
|
||||
`.<pid>.tmp` files left by a hard crash of a *different, dead* process are swept on the
|
||||
next save. A single `asyncio.Lock` guards every read and write, so concurrent operations
|
||||
on one instance are consistent and no update is lost. All blocking filesystem calls and
|
||||
JSON (de)serialization run via `asyncio.to_thread`, so nothing stalls the event loop.
|
||||
|
||||
## Scope — read this
|
||||
|
||||
@@ -82,10 +83,13 @@ are consistent and no update is lost. All blocking filesystem calls run via
|
||||
|
||||
## Error contract
|
||||
|
||||
- Keys must be `str` — `get` / `set` raise `ValueError` on a non-str key (JSON object
|
||||
keys are always strings, so a non-str key would silently never round-trip).
|
||||
- `get` / `set` / `get_all` raise on unexpected I/O. `_load` raises `JSONDecodeError`
|
||||
on a truncated/corrupt file, and `ValueError` when the file holds valid JSON that
|
||||
isn't an object (a bare list/number/string/null) — so corruption or a wrong-shaped
|
||||
file is visible rather than silently masked.
|
||||
file is visible rather than silently masked. Non-finite floats (`NaN`/`Infinity`)
|
||||
raise `ValueError` on `set` rather than persisting invalid JSON.
|
||||
- `delete` / `clear` log the exception and return `False` on error, `True` otherwise.
|
||||
|
||||
## Versioning
|
||||
|
||||
Reference in New Issue
Block a user