objects: Change sets, records, edits, conflicts, push outcomes, resolutions, scopes, and feed entries#
The data an adapter hands the core, such as change sets and how each push ended, the resolutions your application chooses for conflicts, and what the core returns.
What an adapter hands the core after asking the server what changed.
- 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.
- Variables:
scope (str) – The id of the scope it covers.
since (str | None) – 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.token (str) – 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.
changes (collections.abc.Mapping[str, calendaring_sync.objects.change.Changed | calendaring_sync.objects.change.Removed | calendaring_sync.objects.change.Unchanged]) – Each object id mapped to how the object changed. An object appears once.
full_listing (bool) – Whether it lists every object in the scope, rather than only what changed.
truncated (bool) – Whether more pages follow it.
- Raises:
TypeError – If a field has the wrong type.
ValueError – If the scope id, a token or an object id holds a lone surrogate.
The three ways an object can appear in a change set.
- calendaring_sync.objects.change.Change = 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.
- Variables:
version (str | None) – The object’s version, or
Noneif the server gives none.fingerprint (str) – The object’s fingerprint, computed by the adapter.
content (bytes) – The object’s content, the bytes exactly as the server sent them.
containers (frozenset[str]) – The containers the object belongs to.
logical_key (str | None) – The object’s logical key, if it has one.
- Raises:
TypeError – If a field has the wrong type.
ValueError – 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.- Variables:
local_key (int) – The local key the core gave the record. It never changes, and is never given to another record.
object_id (str | None) – The object id, or
Noneuntil the server assigns one to an object you created.state (calendaring_sync.objects.record.RecordState) – The record’s state.
version (str | None) – 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.fingerprint (str | None) – The fingerprint of
content, orNoneafter a fingerprint scheme change until the object is listed again.content (bytes | None) – The synced content, the bytes the server sent, in the scope’s media type.
Nonefor an object you created that the server doesn’t have yet.containers (frozenset[str]) – The containers of the synced copy.
logical_key (str | None) – The logical key of the synced copy, if it has one.
synced_at (datetime.datetime | None) – When an ingest last applied a change to the record, or
Noneif none has.last_entry (int) – The number of the last change feed entry about the record,
0if there’s none.edit (calendaring_sync.objects.edit.LocalEdit | None) – The record’s pending edit, held while it’s pending, in flight or in conflict, and
Noneotherwise.conflict (calendaring_sync.objects.conflict.Conflict | None) – The server’s side of an open conflict, held while the record is in conflict, and
Noneotherwise.claims (int) – How many times a push of the record was claimed. How a push ended only counts while no later claim has been made.
- property is_create: bool#
Whether the record is an object you created that the server doesn’t have yet.
- 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, and
Noneonce the server removed the object.
- state: RecordState#
- 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.
A local edit your application recorded and the server doesn’t have yet.
- 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.
- Variables:
kind (calendaring_sync.objects.edit.EditKind) – What the edit does.
content (bytes | None) – The object’s content after the edit,
Nonefor a delete.fingerprint (str | None) – The fingerprint of
content,Nonefor a delete, or after a fingerprint scheme change until the object is edited again.containers (frozenset[str]) – The containers the object belongs to after the edit.
logical_key (str | None) – The object’s logical key after the edit, if it has one.
recorded_at (datetime.datetime) – When the edit was last recorded.
The server’s side of a conflict, kept on its record until it’s 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.- Variables:
kind (calendaring_sync.objects.conflict.ConflictKind) – How the two sides disagree.
remote_version (str | None) – 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.remote_fingerprint (str | None) – 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.remote_content (bytes | None) – The server’s copy,
Noneif the server removed the object.remote_containers (frozenset[str]) – The containers of the server’s copy.
remote_logical_key (str | None) – The logical key of the server’s copy, if it has one.
opened_at (datetime.datetime) – When the conflict was first stored.
holder (str | None) – For a create refused because another object holds its logical key, that object’s object id when the server named it;
Noneotherwise.
- classmethod from_change(kind: ConflictKind, change: Changed | Removed, opened_at: datetime) Self[source]#
Return a conflict whose server side is what a change reported.
- kind: ConflictKind#
- class calendaring_sync.objects.conflict.ConflictKind(*values)[source]#
Bases:
StrEnumHow the server’s side 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 because another object holds its logical key.
- 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:
objectThe server applied the edit.
- Variables:
object_id (str | None) – 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.- Raises:
TypeError – If the id isn’t a string or
None.ValueError – 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:
objectThe 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:
objectThe server refused a create because another object holds its logical key.
- Variables:
holder (str | None) – The object id of the object that holds the key, when the server names it;
Nonewhen it doesn’t.- Raises:
TypeError – If the id isn’t a string or
None.ValueError – If the id holds a lone surrogate.
- calendaring_sync.objects.push.PushOutcome = 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:
objectThe server refused the write because something changed since your last sync.
The edit stays pending. The next ingest makes it a conflict only if this object changed, and otherwise it’s pushed again.
- class calendaring_sync.objects.push.Unknown[source]#
Bases:
objectThe 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:
objectDelete the object, whatever the server’s side holds.
- class calendaring_sync.objects.resolution.KeepEdit[source]#
Bases:
objectPush your edit as it is, over the server’s current copy.
Where the server removed the object, a change is pushed as a create.
- class calendaring_sync.objects.resolution.Merge(content: bytes, *, fingerprint: str, containers: frozenset[str] = frozenset({}), logical_key: str | None = None)[source]#
Bases:
objectPush new content you made from both sides, over the server’s current copy.
Build
fingerprint,containersandlogical_keywith the adapter’s function for them, as forchange().- Variables:
fingerprint (str) – Its fingerprint.
containers (frozenset[str]) – The containers it belongs to.
logical_key (str | None) – Its logical key, if it has one.
- Raises:
TypeError – If a field has the wrong type.
ValueError – If a string holds a lone surrogate.
Examples
Your application keeps what both sides changed in one copy:
>>> from calendaring_sync import Merge >>> merged = Merge(b"Team meeting, room 3, moved", fingerprint="fp-merged") >>> merged.containers, merged.logical_key (frozenset(), None)
- calendaring_sync.objects.resolution.Resolution = calendaring_sync.objects.resolution.TakeServer | calendaring_sync.objects.resolution.KeepEdit | 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.TakeServer[source]#
Bases:
objectKeep the server’s side and drop your edit.
For a create the server refused because another object holds its logical key, the server has nothing of yours, so the create is discarded.
- class calendaring_sync.objects.resolution.UpdateHolder[source]#
Bases:
objectPush a create the server refused as a change to the object that holds its logical key.
Only for a create refused because another object holds its logical key.
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.
- Variables:
token (str | None) – The scope’s sync token, or
Nonebefore its first full listing finishes and after a reset.fingerprint_scheme (str) – The fingerprint scheme of the scope’s records.
last_sync_at (datetime.datetime | None) – When an ingest of the scope last finished, or
None.last_full_listing_at (datetime.datetime | None) – When a full listing of the scope last finished, or
None.listing_token (str | None) – The token of the full listing in progress, or
None. A page of any other listing is refused, so two processes listing the scope at once can’t mix their results.
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.
- Variables:
number (int) – The entry’s number. Each entry’s is larger than the one before it.
kind (calendaring_sync.objects.feed.FeedEntryKind) – What happened.
local_key (int | None) – 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.object_id (str | None) – The record’s object id when the entry was written,
Nonefor a dropped scope.at (datetime.datetime) – When the entry was written.
- kind: FeedEntryKind#
- class calendaring_sync.objects.feed.FeedEntryKind(*values)[source]#
Bases:
StrEnumWhat a feed entry records.
- CHANGED = 'changed'#
the object is new, changed, or back on the server.
- Type:
A remote change was applied
- CONFLICT_RESOLVED = 'conflict_resolved'#
An open conflict ended, and the record holds what it ended with.
- CONFLICT_UPDATED = 'conflict_updated'#
The server’s side of 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.
- Variables:
pending (tuple[calendaring_sync.objects.record.Record, ...]) – The records that held a pending edit, including ones in flight.
conflicts (tuple[calendaring_sync.objects.record.Record, ...]) – The records that held an open conflict.
See Errors for the exception hierarchy.