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: object

One change set, or one truncated page of it, for one scope.

Variables:
Raises:
changes: Mapping[str, Changed | Removed | Unchanged]#
full_listing: bool = False#
scope: str#
since: str | None#
token: str#
truncated: bool = False#

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: object

The object changed on the server, or is new there.

Variables:
Raises:

Examples

An adapter reports an object the server changed, and one it removed, in a change set from token t1 to t2:

>>> 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'
containers: frozenset[str] = frozenset({})#
content: bytes#
fingerprint: str#
logical_key: str | None = None#
version: str | None#
class calendaring_sync.objects.change.Removed[source]#

Bases: object

The object is gone from the server.

class calendaring_sync.objects.change.Unchanged[source]#

Bases: object

The 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: object

What the store keeps about one object on the server.

From version to logical_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 in edit, and the server’s newer copy, when it conflicts with your edit, in conflict. A record in conflict therefore holds all three versions of the object: the synced copy, yours, and the server’s.

Variables:
claims: int = 0#
conflict: Conflict | None = None#
containers: frozenset[str]#
content: bytes | None#
edit: LocalEdit | None = None#
fingerprint: str | None#
property is_create: bool#

Whether the record is an object you created that the server doesn’t have yet.

last_entry: int#
local_key: int#
logical_key: str | None#
object_id: str | None#
scope: str#
property server_version: str | None#

The version of the server’s copy, None if the store knows none.

That’s the conflict’s while the record is in conflict, and None once the server removed the object.

state: RecordState#
synced_at: datetime | None#
version: str | None#
class calendaring_sync.objects.record.RecordState(*values)[source]#

Bases: StrEnum

The five states a record can be in.

CONFLICT = 'conflict'#

Holds an open conflict.

IN_FLIGHT = 'in_flight'#

Its push was claimed, and how it ended isn’t known yet.

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: StrEnum

What 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: object

One pending edit of a record.

Variables:
containers: frozenset[str]#
content: bytes | None#
fingerprint: str | None#
kind: EditKind#
logical_key: str | None#
recorded_at: datetime#

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: object

The 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’s content is the object as both sides last agreed on it, edit.content is yours, and remote_content is 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, None if 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, None if 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, None if 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; None otherwise.

classmethod from_change(kind: ConflictKind, change: Changed | Removed, opened_at: datetime) → Self[source]#

Return a conflict whose server side is what a change reported.

holder: str | None = None#
kind: ConflictKind#
opened_at: datetime#
remote_containers: frozenset[str]#
remote_content: bytes | None#
remote_fingerprint: str | None#
remote_logical_key: str | None#
remote_version: str | None#
class calendaring_sync.objects.conflict.ConflictKind(*values)[source]#

Bases: StrEnum

How 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: object

The 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. None keeps the record’s id.

Raises:

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'
object_id: str | None = None#
class calendaring_sync.objects.push.Gone[source]#

Bases: object

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: object

The 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; None when it doesn’t.

Raises:
holder: str | None = None#
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: object

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 this object changed, and otherwise it’s pushed again.

class calendaring_sync.objects.push.Unknown[source]#

Bases: object

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: object

Delete the object, whatever the server’s side holds.

class calendaring_sync.objects.resolution.KeepEdit[source]#

Bases: object

Push 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: object

Push new content you made from both sides, over the server’s current copy.

Build fingerprint, containers and logical_key with the adapter’s function for them, as for change().

Variables:
Raises:

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)
containers: frozenset[str] = frozenset({})#
content: bytes#
fingerprint: str#
logical_key: str | None = 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: object

Keep 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: object

Push 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: object

One scope.

Variables:
  • id (str) – The id the adapter gave the scope.

  • token (str | None) – The scope’s sync token, or None before its first full listing finishes and after a reset.

  • fingerprint_scheme (str) – The fingerprint scheme of the scope’s records.

  • media_type (str) – The media type of the scope’s content.

  • 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.

fingerprint_scheme: str#
id: str#
last_full_listing_at: datetime | None = None#
last_sync_at: datetime | None = None#
listing_token: str | None = None#
media_type: str#
token: str | 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: object

One feed entry.

Variables:
  • number (int) – The entry’s number. Each entry’s is larger than the one before it.

  • scope (str) – The id of the scope the entry is about.

  • kind (calendaring_sync.objects.feed.FeedEntryKind) – What happened.

  • local_key (int | None) – The local key of the record the entry is about, None for 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, None for a dropped scope.

  • at (datetime.datetime) – When the entry was written.

at: datetime#
kind: FeedEntryKind#
local_key: int | None#
number: int#
object_id: str | None#
scope: str#
class calendaring_sync.objects.feed.FeedEntryKind(*values)[source]#

Bases: StrEnum

What a feed entry records.

CHANGED = 'changed'#

the object is new, changed, or back on the server.

Type:

A remote change was applied

CONFLICT_OPENED = 'conflict_opened'#

A conflict was stored.

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.

SCOPE_DROPPED = 'scope_dropped'#

A scope was dropped, with every record in it.

What drop_scope() discards.

class calendaring_sync.objects.dropped_scope.DroppedScope(scope: str, pending: tuple[Record, ...], conflicts: tuple[Record, ...])[source]#

Bases: object

The local work a drop discarded, for your application to save or show.

Variables:
conflicts: tuple[Record, ...]#
pending: tuple[Record, ...]#
scope: str#

See Errors for the exception hierarchy.

Last updated on 2026-10-21.