objects: Values the core takes and returns#
The values the core takes and returns.
A change set, whole or one page of it.
- class calendaring_sync.objects.change_set.ChangeSet(scope: str, since: str | None, token: str, changes: Mapping[str, Changed | Removed | Unchanged], full_listing: bool = False, truncated: bool = False)[source]#
Bases:
objectOne change set, or one truncated page of it, for one scope.
- since#
The sync token it was fetched from,
Nonefor a scope that has none. Every page of a full listing carries the scope’s token from before the listing.- Type:
str | None
- token#
The token the scope moves to. On a truncated page of an incremental change set that’s the page’s checkpoint. Every page of a full listing carries the same token, the one the listing read before it started, and the scope moves to it when the last page is ingested.
- Type:
- changes#
Each object id mapped to how the object changed. An object appears once. The change set keeps a read-only copy, so changing the mapping it was built from afterwards changes nothing.
- Raises:
TypeError – If a field has the wrong type.
LoneSurrogateError – If the scope id, a token or an object id holds a lone surrogate.
ValueError – If an incremental change set reports an object
Unchanged, which only a full listing can.
The three ways an object can appear in a change set.
- calendaring_sync.objects.change.Change: TypeAlias = calendaring_sync.objects.change.Changed | calendaring_sync.objects.change.Removed | calendaring_sync.objects.change.Unchanged#
Any one of the three.
- class calendaring_sync.objects.change.Changed(version: str | None, fingerprint: str, content: bytes, containers: frozenset[str] = frozenset({}), logical_key: str | None = None)[source]#
Bases:
objectThe object changed on the server, or is new there.
- fingerprint#
The object’s fingerprint, computed by the adapter.
- Type:
- containers#
The containers the object belongs to.
- logical_key#
The object’s logical key, if it has one.
- Type:
str | None
- Raises:
TypeError – If a field has the wrong type.
LoneSurrogateError – If a string holds a lone surrogate.
Examples
An adapter reports an object the server changed, and one it removed, in a change set from token
t1tot2:>>> from calendaring_sync import Changed, ChangeSet, Removed >>> changes = {"a": Changed("v2", "fp-a2", b"Team meeting, moved"), "b": Removed()} >>> change_set = ChangeSet("work", "t1", "t2", changes) >>> change_set.changes["a"].content b'Team meeting, moved'
- class calendaring_sync.objects.change.Removed[source]#
Bases:
objectThe object is gone from the server.
- class calendaring_sync.objects.change.Unchanged[source]#
Bases:
objectThe object hasn’t changed since the version the store holds.
Only a full listing reports this, so the object isn’t fetched again.
A record: everything the core stores about one object.
- class calendaring_sync.objects.record.Record(local_key: int, scope: str, object_id: str | None, state: RecordState, version: str | None, fingerprint: str | None, content: bytes | None, containers: frozenset[str], logical_key: str | None, synced_at: datetime | None, last_entry: int, edit: LocalEdit | None = None, conflict: Conflict | None = None, claims: int = 0)[source]#
Bases:
objectWhat the store keeps about one object on the server.
From
versiontological_key, the fields hold the synced copy: the object as your application and the server last agreed on it. Your pending edit is kept beside it inedit, and the server’s newer copy, when it conflicts with your edit, inconflict. A record in conflict therefore holds all three versions of the object: the synced copy, yours, and the server’s.- local_key#
The local key the core gave the record. It never changes, and is never given to another record.
- Type:
- object_id#
The object id, or
Noneuntil the server assigns one to an object you created.- Type:
str | None
- state#
The record’s state.
- version#
The version the server gave the synced copy, or
None. While the record is in conflict, the server holds a newer copy, whose version isconflict.remote_version;server_versiongives whichever is current.- Type:
str | None
- fingerprint#
The fingerprint of
content, orNoneafter a fingerprint scheme change until the object is listed again.- Type:
str | None
- content#
The content of the synced copy, the bytes the server sent, in the scope’s media type.
Nonefor an object you created that the server doesn’t have yet.- Type:
bytes | None
- containers#
The containers of the synced copy.
- logical_key#
The logical key of the synced copy, if it has one.
- Type:
str | None
- synced_at#
When an ingest last applied a change to the record, or
Noneif none has.- Type:
datetime.datetime | None
- last_entry#
The number of the last change feed entry about the record,
0if there’s none.- Type:
- edit#
The record’s pending edit, held while it’s pending, in flight or in conflict, and
Noneotherwise.- Type:
- conflict#
The server’s copy in an open conflict, held while the record is in conflict, and
Noneotherwise.- Type:
- claims#
How many times a push of the record was claimed. How a push ended only counts while no later claim has been made.
- Type:
- property is_create: bool#
Whether the record is an object you created that the server doesn’t have yet.
- property is_refused: bool#
Whether the server refused this record’s create or change for its logical key.
- property is_refused_change: bool#
Whether the server refused this record’s change for its logical key.
The server then still holds the synced copy, and the conflict holds no copy of its own.
- classmethod new(scope_id: str, object_id: str | None = None) Self[source]#
Return a record the store doesn’t hold yet, with nothing synced.
Its
local_keyis0until a store’sinsert_record()gives it one; fill in the rest withsynced_to(), or with an edit.Examples
>>> from calendaring_sync import Record >>> record = Record.new("work", "a") >>> record.local_key, record.object_id, record.content (0, 'a', None)
- removed_at(at: datetime) Self[source]#
Return the record once its object is gone from the server, with nothing left to push.
It keeps its synced copy, so a reader of the change feed can still see what was removed.
Examples
>>> from datetime import UTC, datetime >>> from calendaring_sync import Record >>> Record.new("work", "a").removed_at(datetime(2026, 11, 2, tzinfo=UTC)).state <RecordState.REMOVED: 'removed'>
- property server_logical_key: str | None#
The logical key of the server’s copy,
Noneif the server holds none.As with
server_version, that’s the conflict’s while the record is in conflict, and the synced one for a change the server refused. A create the server doesn’t have has none.
- property server_version: str | None#
The version of the server’s copy,
Noneif the store knows none.That’s the conflict’s while the record is in conflict, but the synced one for a change the server refused, and
Noneonce the server removed the object.
- synced_to(copy: Changed | LocalEdit | Conflict, at: datetime) Self[source]#
Return the record synced to
copy, with no edit or conflict left.copyis whatwith_copy()takes.Examples
>>> from datetime import UTC, datetime >>> from calendaring_sync import Changed, Record >>> change = Changed("v1", "fp-a", b"Team meeting") >>> Record.new("work", "a").synced_to(change, datetime(2026, 11, 2, tzinfo=UTC)).state <RecordState.SYNCED: 'synced'>
- with_copy(copy: Changed | LocalEdit | Conflict, at: datetime) Self[source]#
Return the record whose synced copy is
copy, synced atat.Its state, edit and conflict stay as they are.
copyis one of:a change from the server, at its version
your edit, once the server applied it, with no version: the server may store it rewritten, so the next ingest brings its version
a conflict’s server side, at its version
Examples
>>> from datetime import UTC, datetime >>> from calendaring_sync import Changed, Record >>> change = Changed("v2", "fp-a2", b"Team meeting, moved") >>> moved = Record.new("work", "a").with_copy(change, datetime(2026, 11, 2, tzinfo=UTC)) >>> moved.version, moved.content ('v2', b'Team meeting, moved')
- class calendaring_sync.objects.record.RecordState(*values)[source]#
Bases:
StrEnumThe five states a record can be in.
- PENDING = 'pending'#
Holds a pending edit.
- REMOVED = 'removed'#
Gone from the server, kept until it’s purged or the object comes back, on the server or as a create at its id.
- SYNCED = 'synced'#
Matches the server’s copy.
- calendaring_sync.objects.record.SyncedCopy: TypeAlias = calendaring_sync.objects.change.Changed | calendaring_sync.objects.edit.LocalEdit | calendaring_sync.objects.conflict.Conflict#
A copy of an object that a record can be synced to.
A local edit your application recorded and the server doesn’t have yet.
- class calendaring_sync.objects.edit.EditFields(fingerprint: str, containers: frozenset[str] = frozenset({}), logical_key: str | None = None)[source]#
Bases:
objectWhat an edit records about an object beside its content.
An adapter reads these from content the same way for the server’s copy and for an edit, so an edit back to the synced copy cancels, and the server’s copy of your own edit isn’t a conflict. To build them for content you made, such as a
Merge, call the adapter’sedit_fields().- fingerprint#
The content’s fingerprint.
- Type:
- containers#
The containers the object belongs to.
- logical_key#
The object’s logical key, if it has one.
- Type:
str | None
- Raises:
TypeError – If a field has the wrong type.
Examples
>>> from calendaring_sync import EditFields >>> fields = EditFields("fp-a2", frozenset({"work"}), "meeting-1") >>> fields.logical_key 'meeting-1'
- class calendaring_sync.objects.edit.EditKind(*values)[source]#
Bases:
StrEnumWhat a local edit does to its object.
- CHANGE = 'change'#
Changes an object the server has.
- CREATE = 'create'#
Creates an object the server doesn’t have yet.
- DELETE = 'delete'#
Deletes an object the server has.
- class calendaring_sync.objects.edit.LocalEdit(kind: EditKind, content: bytes | None, fingerprint: str | None, containers: frozenset[str], logical_key: str | None, recorded_at: datetime)[source]#
Bases:
objectOne pending edit of a record.
- kind#
What the edit does.
- fingerprint#
The fingerprint of
content,Nonefor a delete, or after a fingerprint scheme change until the object is edited again.- Type:
str | None
- containers#
The containers the object belongs to after the edit.
- logical_key#
The object’s logical key after the edit, if it has one.
- Type:
str | None
- recorded_at#
When the edit was last recorded.
- Type:
The server’s copy in a conflict, kept on its record until resolved.
- class calendaring_sync.objects.conflict.Conflict(kind: ConflictKind, remote_version: str | None, remote_fingerprint: str | None, remote_content: bytes | None, remote_containers: frozenset[str], remote_logical_key: str | None, opened_at: datetime, holder: str | None = None)[source]#
Bases:
objectThe server’s copy of an object, kept where it conflicts with your edit.
Together with its record’s synced copy and
Record.edit, it makes up the conflict record: the record’scontentis the object as both sides last agreed on it,edit.contentis yours, andremote_contentis the server’s. A resolution can compare all three, for example to keep both sides where they changed different parts of the object.- kind#
How the two sides disagree.
- remote_version#
The version of the server’s copy,
Noneif it has none, the server removed the object, or after a fingerprint scheme change until the server’s copy is fetched again.- Type:
str | None
- remote_fingerprint#
The fingerprint of the server’s copy,
Noneif the server removed the object, or after a fingerprint scheme change until the server’s copy is fetched again.- Type:
str | None
- remote_containers#
The containers of the server’s copy.
- remote_logical_key#
The logical key of the server’s copy, if it has one.
- Type:
str | None
- opened_at#
When the conflict was first stored.
- Type:
- holder#
For a create refused because another object holds its logical key, that object’s object id when the server named it;
Noneotherwise.- Type:
str | None
- class calendaring_sync.objects.conflict.ConflictKind(*values)[source]#
Bases:
StrEnumHow the server’s copy and the local edit disagree.
- BOTH_CHANGED = 'both_changed'#
Both sides changed the object.
- BOTH_CREATED = 'both_created'#
The server has a new object at the id your unsent create chose.
- DELETED_LOCALLY = 'deleted_locally'#
You deleted the object, and the server changed it.
- LOGICAL_KEY_TAKEN = 'logical_key_taken'#
The server refused your create, or your change, because another object holds its logical key. Once that object stops holding it, the edit is pending again.
- REMOVED_REMOTELY = 'removed_remotely'#
The server removed the object, and you changed it.
How a push ended, as the adapter reports it.
- class calendaring_sync.objects.push.Applied(object_id: str | None = None)[source]#
Bases:
The server applied the edit.
- object_id#
The object id the server gave the object, for a create whose id the server picks, or when the server moved the object to another id.
Nonekeeps the record’s id.- Type:
str | None
- Raises:
TypeError – If the id isn’t a string or
None.LoneSurrogateError – If the id holds a lone surrogate.
Examples
An adapter reports a create the server stored under an id it picked:
>>> from calendaring_sync import Applied >>> Applied("event-17").object_id 'event-17'
- class calendaring_sync.objects.push.Gone[source]#
Bases:
The object no longer exists on the server.
A delete is then done; a change becomes a conflict.
- class calendaring_sync.objects.push.LogicalKeyTaken(holder: str | None = None)[source]#
Bases:
The server refused a create or a change because another object holds its logical key.
- holder#
The object id of the object that holds the key, when the server names it;
Nonewhen it doesn’t.- Type:
str | None
- Raises:
TypeError – If the id isn’t a string or
None.LoneSurrogateError – If the id holds a lone surrogate.
- calendaring_sync.objects.push.PushOutcome: TypeAlias = calendaring_sync.objects.push.Applied | calendaring_sync.objects.push.Rejected | calendaring_sync.objects.push.Gone | calendaring_sync.objects.push.LogicalKeyTaken | calendaring_sync.objects.push.Unknown#
Any one of the five.
- class calendaring_sync.objects.push.Rejected[source]#
Bases:
The server refused the write because something changed since your last sync.
The edit stays pending. The next ingest makes it a conflict only if the object the edit is for changed, and otherwise it’s pushed again.
- class calendaring_sync.objects.push.Unknown[source]#
Bases:
The request may or may not have reached the server.
The push stays in flight, and the next complete ingest settles it from what the server then holds.
How your application resolves a conflict.
- class calendaring_sync.objects.resolution.Delete[source]#
Bases:
Delete the object, whatever the server’s copy holds.
Where the server has the object, the delete goes out over its current copy, and for a change the server refused for its logical key, that’s the object the change was for. Where the server doesn’t have it, the record is removed, or deleted if the server never had it.
- class calendaring_sync.objects.resolution.KeepLocal[source]#
Bases:
Push your local edit as it is, over the server’s current copy.
Where the server removed the object, a change goes out as a create. An edit the server refused because another object holds its logical key can’t go out unchanged, so this refuses it.
- class calendaring_sync.objects.resolution.KeepServer[source]#
Bases:
Keep the server’s copy, and drop your local edit.
Where the server has the object, the record takes the server’s copy. Where it doesn’t, because the server removed the object or refused your create, the record is removed, or deleted if the server never had it.
- class calendaring_sync.objects.resolution.Merge(content: bytes, fields: EditFields)[source]#
Bases:
Push content you merged from the conflict’s versions, over the server’s current copy.
A record in conflict holds three versions of its object: the synced copy both sides last agreed on, in
content; your local edit, inedit; and the server’s copy, inconflict. Your application merges them into new content, and reads its fields with the adapter’sedit_fields(), so they match what a sync reads from the server’s copy. Where the server removed the object, the merge goes out as a create.- fields#
Its fingerprint, containers and logical key.
- Raises:
TypeError – If a field has the wrong type.
LoneSurrogateError – If a string holds a lone surrogate.
Examples
Your application keeps the room your edit set and the time the server’s copy moved to, in one copy. An adapter would read its fields; these are made up:
>>> from calendaring_sync import EditFields, Merge >>> content = b"Team meeting, room 3, moved" >>> merged = Merge(content, EditFields("fp-merged")) >>> merged.fields.fingerprint 'fp-merged'
- calendaring_sync.objects.resolution.Resolution: TypeAlias = calendaring_sync.objects.resolution.KeepServer | calendaring_sync.objects.resolution.KeepLocal | calendaring_sync.objects.resolution.Merge | calendaring_sync.objects.resolution.Delete | calendaring_sync.objects.resolution.UpdateHolder#
Any one of the five.
- class calendaring_sync.objects.resolution.ResolvedConflicts(resolved: tuple[int, ...] = (), left_open: tuple[int, ...] = (), changed: tuple[int, ...] = ())[source]#
Bases:
objectWhat
resolve_conflicts()did with each conflict.Each field holds local keys, in the order the conflicts were passed to your resolver.
- class calendaring_sync.objects.resolution.UpdateHolder[source]#
Bases:
Push a refused edit as a change to the object that holds its logical key.
Only for a create or a change refused because another object holds its logical key. A refused create then leaves nothing, and the object a refused change was for stays as it was synced.
A scope as the store keeps it.
- class calendaring_sync.objects.scope.Scope(id: str, token: str | None, fingerprint_scheme: str, media_type: str, last_sync_at: datetime | None = None, last_full_listing_at: datetime | None = None, listing_token: str | None = None)[source]#
Bases:
objectOne scope.
- token#
The scope’s sync token, or
Nonebefore its first full listing finishes and after a reset.- Type:
str | None
- fingerprint_scheme#
The fingerprint scheme of the scope’s records.
- Type:
- last_sync_at#
When an ingest of the scope last finished, or
None.- Type:
datetime.datetime | None
- last_full_listing_at#
When a full listing of the scope last finished, or
None.- Type:
datetime.datetime | None
An entry in the change feed.
- class calendaring_sync.objects.feed.FeedEntry(number: int, scope: str, kind: FeedEntryKind, local_key: int | None, object_id: str | None, at: datetime)[source]#
Bases:
objectOne feed entry.
- kind#
What happened.
- local_key#
The local key of the record the entry is about,
Nonefor a dropped scope. The record may since have been purged, or deleted if it was a create the server never had.- Type:
int | None
- object_id#
The record’s object id when the entry was written,
Nonefor a dropped scope.- Type:
str | None
- at#
When the entry was written.
- Type:
- class calendaring_sync.objects.feed.FeedEntryKind(*values)[source]#
Bases:
StrEnumWhat a feed entry records.
- CHANGED = 'changed'#
A remote change was applied, to an object that’s new, changed, or back on the server.
- CONFLICT_RESOLVED = 'conflict_resolved'#
An open conflict ended, and the record holds what it ended with.
- CONFLICT_UPDATED = 'conflict_updated'#
The server’s copy in an open conflict changed.
- PUSHED = 'pushed'#
the record holds your edit as synced, or is removed for a delete.
- Type:
A push reached the server
- PUSH_FAILED = 'push_failed'#
A push didn’t go through, and the edit is pending again for a later sync.
- REMOVED = 'removed'#
A remote removal was applied.
What drop_scope() discards.
- class calendaring_sync.objects.dropped_scope.DroppedScope(scope: str, pending: tuple[Record, ...], conflicts: tuple[Record, ...])[source]#
Bases:
objectThe local work a drop discarded, for your application to save or show.
- pending#
The records that held a pending edit, including ones in flight.
- Type: