correspond.idempotency#
Idempotent sends: an idempotency_key keeps a message from going out twice.
A channel can post a message and then fail: the platform accepted the post and the
connection dropped before its answer, or a call after the post raised. send()
then reports ok=False, and whoever sends it again (a retry, an operator releasing the
draft by hand) posts it a second time. With idempotency_key= send()
remembers, in a store of its own (sends=, by default files under the data root), what
each key did:
right before the real write the key is claimed (
ATTEMPTED), and after it the key isSENT, with the result; a failure the platform reported before accepting anything (NOTHING_POSTED_KINDS) marks itFAILED, which a new try may claim again;the same key after
SENTposts nothing and answers the stored result, marked a replay;the same key after an attempt whose outcome is unknown (still
ATTEMPTED: a network or platform failure, a crash) reads the conversation back, where the channel can be read, for a message by correspond’s own account with the same text since the attempt. Found, that message is the result, and nothing is posted. Not found, or not readable, the send is not made: it fails witherror_kind="unconfirmed", saying to check the conversation, since a reader may not show a message that did go out (an email’s inbox does not list what was sent, and Telegram’s posted text starts with the title). The caller who has checked sends with a new key;a second send with a key another one claimed meanwhile (a retry that overlapped the first) writes nothing: the default store claims a key with an exclusive create, so of two processes exactly one sends;
a key used again for another conversation or another draft is refused (
validation).
A dry run reads the store and writes nothing. Nothing is added to the text posted.
>>> record = claimed("k1", "fake:example/demo", fingerprint="f", at=parse_time("2026-09-22T12:00:00Z"))
>>> record["state"], record["attempted_at"]
('attempted', '2026-09-22T12:00:00+00:00')
Module Attributes
The key was claimed for a real write whose outcome is not known yet (or never became known). |
|
The write went out; the record keeps its result. |
|
The platform refused the write before accepting anything; the key may be claimed again. |
|
The |
|
How far before an attempt a message read back may be dated and still be that attempt's: the platform's clock and this machine's differ. |
|
The longest key accepted (it names a file in the default store). |
Functions
|
|
|
When |
|
The record of |
|
What a key is bound to: the conversation and everything the draft says, as SHA-256 hex. |
|
The latest of |
|
The moment a key is claimed (UTC). |
|
|
- correspond.idempotency.ATTEMPTED = 'attempted'#
The key was claimed for a real write whose outcome is not known yet (or never became known).
- correspond.idempotency.FAILED = 'failed'#
The platform refused the write before accepting anything; the key may be claimed again.
- correspond.idempotency.MAX_KEY_LENGTH = 128#
The longest key accepted (it names a file in the default store).
- correspond.idempotency.NOTHING_POSTED_KINDS = frozenset({'auth', 'not_found', 'permission', 'rate_limited'})#
The
error_kindof failures that mean the platform accepted nothing, so a new try is safe. A network failure and an unavailable platform are not among them: the post may have landed.validationis not among them either: a platform can report an error with no type after it committed the write (GitHub’s GraphQL mutations do on a timeout).
- correspond.idempotency.READ_BACK_SKEW = datetime.timedelta(seconds=300)#
How far before an attempt a message read back may be dated and still be that attempt’s: the platform’s clock and this machine’s differ.
- correspond.idempotency.SENT = 'sent'#
The write went out; the record keeps its result.
- correspond.idempotency.check_key(key)[source]#
keystripped, orValueErrorfor one that is not a non-blank string of at mostMAX_KEY_LENGTHcharacters.>>> check_key(" case-12/reply-3 ") 'case-12/reply-3'
- Return type:
- correspond.idempotency.claimed(key, conversation, *, fingerprint, at)[source]#
The record of
keyclaimed atatfor a real write toconversation.
- correspond.idempotency.fingerprint(conversation, draft)[source]#
What a key is bound to: the conversation and everything the draft says, as SHA-256 hex.
- Return type:
- correspond.idempotency.matching_message(messages, draft, *, since)[source]#
The latest of
messageswritten by correspond’s own account withdraft’s text, at or aftersincelessREAD_BACK_SKEW.With
draft.notify, the text as a mention writer posts it (the mentions it lacked added,correspond.notify.with_mentions()) matches too.