correspond.stores#
The state stores: listen cursors, the web inbox’s reports and blobs, the Telegram log.
Each is a MutableMapping over dol files under the data root, one folder per kind
(~/.local/share/correspond/<kind>/), never inside a repository. Anything with the same
interface replaces one: a dict in tests, an s3dol store on a server. dol is
imported when a store is first made, not when correspond is imported.
Keys are relative paths whose segments are plain names; nothing can address a file outside the store’s folder:
>>> safe = SafeKeys({})
>>> safe["site/20260911T120000Z-1.json"] = {"text": "hi"}
>>> list(safe)
['site/20260911T120000Z-1.json']
>>> safe["../elsewhere"] = {}
Traceback (most recent call last):
...
KeyError: "store key '../elsewhere' must be /-separated names of letters, digits, '.', '_', '-', '@', '+', '=' or '%' (no '.' or '..' segments)"
Functions
|
The arrival stamps of the |
|
Binary files under |
|
Listen cursors, keyed by encoded conversation reference, under |
|
JSON files under |
|
The folder for one kind of state under the data root. |
|
The folder for one kind of state, created owner-only, and tightened to owner-only if it was not. |
|
What each idempotency key of |
|
Text files under |
Classes
|
Strictly increasing arrival stamps (nanoseconds) shared by every process that opens one file. |
|
Any string key, kept under a percent-encoded file-safe name (plus |
|
A mapping whose keys must be relative paths of plain names (no |
|
Idempotency records keyed by any key, each in |
- class correspond.stores.ArrivalStamps(path)[source]#
Bases:
objectStrictly increasing arrival stamps (nanoseconds) shared by every process that opens one file.
Writers that put events in one store, keyed by arrival so readers can keep a cursor (the Slack Events API receiver and Socket Mode listens), take their stamps here.
issue()holds an exclusive lock on the file for the whole block, so the writer that took a stamp also stores its event before any other writer takes the next one, and a reader lists the store underhold(): what it lists is whole, it has seen every event stamped below the last it saw, and no event lands below a cursor already handed out. The lock is the operating system’s (flock, ormsvcrt.lockingon Windows): it orders the processes of one machine, not a store shared between machines.>>> import tempfile >>> stamps = ArrivalStamps(Path(tempfile.mkdtemp()) / "demo.stamp") >>> with stamps.issue(5) as first: # store the event here, under the lock ... pass >>> with stamps.issue(3) as second: # a clock behind the last stamp ... pass >>> with stamps.issue(4, floor=9) as third: ... pass >>> first, second, third (5, 6, 10)
- class correspond.stores.QuotedKeys(store, *, suffix='')[source]#
Bases:
MutableMappingAny string key, kept under a percent-encoded file-safe name (plus
suffix) in a flat mapping.>>> cursors = QuotedKeys({}, suffix=".txt") >>> cursors["github:octocat/hello-world#1"] = "2026-09-11T12:00:00Z" >>> list(cursors), list(cursors.store) (['github:octocat/hello-world#1'], ['github%3Aoctocat%2Fhello-world%231.txt'])
- class correspond.stores.SafeKeys(store)[source]#
Bases:
MutableMappingA mapping whose keys must be relative paths of plain names (no
.., no absolute paths).
- class correspond.stores.SendStore(folder)[source]#
Bases:
MutableMappingIdempotency records keyed by any key, each in
<folder>/<sha256 of the key>.json, with an exclusiveclaim().Hashing keeps every file name short and safe whatever the key (the record keeps the key itself). The record file is its own claim:
claim()creates it with an exclusive create and writes the record through the same handle, so of two processes claiming one key at once exactly one wins, and no crash can leave a claim without a record. A key whose record saysfailedmay be claimed again. A record that cannot be read (a crash while it was written) reads as an attempt of unknown outcome, dated by its file.This is the contract
correspond.send()relies on: asends=mapping with aclaim(key, record) -> boolis exclusive across processes; a plain mapping is checked, then set, which covers one process only.
- correspond.stores.arrival_stamps(kind, *, data_dir=None)[source]#
The arrival stamps of the
kindstore:<data root>/locks/<kind>.stamp.- Return type:
- correspond.stores.bytes_store(kind, *, data_dir=None)[source]#
Binary files under
<data root>/<kind>/.- Return type:
- correspond.stores.cursor_store(*, data_dir=None, kind='cursors')[source]#
Listen cursors, keyed by encoded conversation reference, under
<data root>/<kind>/.A consumer that must not take events from another (the Claude Code channel server, next to manual
correspond listencalls) keeps its ownkind.- Return type:
- correspond.stores.json_store(kind, *, data_dir=None)[source]#
JSON files under
<data root>/<kind>/(keys end in.json).- Return type:
- correspond.stores.kind_dir(kind, *, data_dir=None)[source]#
The folder for one kind of state under the data root.
- correspond.stores.private_dir(kind, *, data_dir=None)[source]#
The folder for one kind of state, created owner-only, and tightened to owner-only if it was not.
A folder made by an older correspond kept the mode the umask gave it; the first use since takes the group’s and others’ access away. A folder another user owns is left as it is, with a warning, and so is a symbolic link (and what it points to). Only the kind’s folder is tightened, not the data root above it. (Windows has no such modes: nothing changes there.)
- Return type:
- correspond.stores.send_store(*, data_dir=None)[source]#
What each idempotency key of
correspond.send()did, under<data root>/sends/.- Return type: