calendaring_sync.adapters.base module#

The base classes every adapter is built on.

Adapter holds the order of a sync, written once for every protocol: open a session, read and ingest what changed, then push the pending edits. An adapter subclasses it, fills in how its protocol reads and sends, and adds SyncAdapter or AsyncAdapter for the public methods.

Both variants run the same steps. A step is a generator that yields what a client call returned, the answer itself from a synchronous client or a coroutine from an asynchronous one, or an InStore for a call into the store, and gets each result sent back. SyncAdapter runs the steps as they come; AsyncAdapter awaits the coroutines and runs the store calls as its description says.

class calendaring_sync.adapters.base.Adapter(replica, scope_id)[source]#

Bases: ABC

Syncs one scope with a Replica, for any protocol.

A sync opens a session, reads what changed on the server and ingests it, then pushes the pending edits one at a time: claimed, sent, settled. When the server refuses the scope’s sync token, the scope is reset and listed in full in a new session. An adapter subclasses it together with SyncAdapter or AsyncAdapter, which add the public methods.

A subclass sets these class attributes, which open each session:

  • _fingerprint_scheme: the name of the adapter’s fingerprint scheme.

  • _media_type: the media type of the scope’s content.

  • _guarded_by_version: True if a push applies only while the object still has the version the edit was based on.

  • _unique_logical_key: True if no two objects in the scope can share a logical key.

  • _guard_covers_scope: True if the server checks a push against the whole scope rather than the object, so after one rejection every later push would be rejected too, and they wait for the next sync.

It writes these methods for its protocol, each a generator of Steps except _waits(), _new_object_id() and _edit_fields():

It may also write _gone(), which says whether an error from the server means the scope is gone; a request made through _ask() then raises ScopeGoneError for it.

These names start with an underscore because your application calls the adapter’s public methods: a read or a send called on its own would skip the session that orders them.

Parameters:
  • replica (Replica) – Where the sync state is kept.

  • scope_id (str) – The scope’s id.

property scope_id: str#

The scope this adapter syncs.

property needs_full_listings: bool#

Whether the server can lose changes without refusing the scope’s token.

If it’s True, an incremental sync may miss a change or a removal, so schedule a sync with full_listing=True now and then, such as once a day. False by default; an adapter whose server can lose changes says so.

edit_fields(content)[source]#

Read an object’s fields from its content, the same way a sync reads the server’s copy.

Use it for content your application made itself, such as a Merge. Reads nothing from the store or the server.

Raises:

ValueError – If the content can’t be an object of the scope, as for a create.

Return type:

EditFields

_ask(request)[source]#

Make one request about the scope, and return the server’s answer.

request is a function making the call, so a synchronous client’s error is raised inside this step too.

Raises:

ScopeGoneError – If _gone() says the request’s error means the server no longer has the scope.

Return type:

Generator[Any, Any, Any]

_gone(error)[source]#

Say why the scope is gone, if error means the server no longer has it.

Return type:

str | None

Returns:

The ScopeGoneError message, or None for any other error, which is raised unchanged. The base class returns None.

abstractmethod _read(session, scope, *, full_listing)[source]#

Fetch what changed in the scope, ingest it, and say how the read ended.

Without a token in scope.token, or with full_listing, list every object; otherwise ask for the changes since the token. Ingest each ChangeSet with yield InStore(session.ingest, (change_set,)).

Parameters:
  • session (Session) – The open session to ingest into.

  • scope (Scope) – The scope as the store holds it.

  • full_listing (bool) – Whether the caller asked for a full listing.

Return type:

Generator[Any, Any, ReadResult]

Returns:

ReadResult.INVALID_TOKEN if the server refused the token, before anything was ingested; ReadResult.TRUNCATED if the server cut the changes short; ReadResult.COMPLETE otherwise.

abstractmethod _send(claimed)[source]#

Send one claimed edit and return how it ended.

An error raised here reaches the caller and leaves the push in flight, since the request may have reached the server; the next complete ingest settles it.

Parameters:

claimed (Record) – The record claim() returned, holding the edit to send.

Return type:

Generator[Any, Any, Applied | Rejected | Gone | LogicalKeyTaken | NotAllowed | Unknown]

abstractmethod _waits(record)[source]#

Whether a pending edit can’t be sent yet, so it waits for a later sync.

Parameters:

record (Record) – A record with a pending edit.

Return type:

bool

abstractmethod _new_object_id()[source]#

Return an object id for a create, or None if the server picks it.

Return type:

str | None

abstractmethod _edit_fields(content, *, create)[source]#

Read an edit’s fields from its content, the same way a sync reads the server’s copy.

Parameters:
  • content (bytes) – The edit’s content.

  • create (bool) – Whether the edit is a create, which may need more of the content, such as a logical key.

Raises:

ValueError – If the content can’t be an object of the scope.

Return type:

EditFields

class calendaring_sync.adapters.base.SyncAdapter(replica, scope_id)[source]#

Bases: Adapter

The public methods of an adapter for a synchronous client.

Your application calls sync() when it decides to sync, and records its edits with create() and change(). Deletes and discards need nothing from the content, so they go to delete() and discard(). The adapter’s class says what content it takes and which of its client’s errors reach you; the next sync settles a push such an error interrupted. Every method also raises the store’s errors, as Replica describes.

sync(*, full_listing=False)[source]#

Sync the scope: ingest what changed on the server, then push your edits.

A scope with no token is listed in full. When the server refuses the token, the scope is reset and listed in full. Your edits are pushed only once everything the server reported was ingested. Each step runs in a store transaction of its own.

Parameters:

full_listing (bool) – List every object instead of asking for changes.

Raises:
Return type:

None

create(content)[source]#

Record a create of an object with this content, and return its local key.

The next sync() sends it. Runs in a write transaction.

Raises:
Return type:

int

change(local_key, content)[source]#

Record a change of an object to this content; the next sync() sends it.

Reads the record, then writes the change, each in a transaction of its own. A change to a create that wasn’t sent yet stays a create, so the content must pass what a create’s does.

Raises:
Return type:

None

waiting()[source]#

Return the pending edits the next sync() won’t send, because it can’t yet.

Runs in a read transaction.

Raises:
Return type:

list[Record]

class calendaring_sync.adapters.base.AsyncAdapter(replica, scope_id)[source]#

Bases: Adapter

The public methods of an adapter for an asynchronous client.

The same as SyncAdapter’s, as coroutines. Each store call runs in a worker thread, so a long ingest doesn’t block the event loop.

sync(*, full_listing=False)[source]#

See sync().

Return type:

None

create(content)[source]#

See create().

Return type:

int

change(local_key, content)[source]#

See change().

Return type:

None

waiting()[source]#

See waiting().

Return type:

list[Record]

calendaring_sync.adapters.base.Steps#

The steps of a sync, as a generator that yields client answers or InStore calls, is sent back what each one returned, and returns its result.

alias of Generator[Any, Any, _T]

class calendaring_sync.adapters.base.InStore(call, args=(), kwargs=dict())[source]#

Bases: object

A call into the store, which an asynchronous adapter runs in a worker thread.

call#

The function to call, such as session.ingest.

Type:

Callable[..., Any]

args#

Its positional arguments.

Type:

tuple[Any, ...]

kwargs#

Its keyword arguments.

Type:

Mapping[str, Any]

Examples

A step that ingests a change set:

yield InStore(session.ingest, (change_set,))
run()[source]#

Make the call and return what it returned.

Return type:

Any

class calendaring_sync.adapters.base.ReadResult(*values)[source]#

Bases: Enum

How the read part of a sync ended, which decides what the sync does next.

COMPLETE = 'complete'#

Every change was ingested, so the pushes go out.

TRUNCATED = 'truncated'#

The server cut the changes short. The pushes wait, and the next sync continues from the checkpoint.

INVALID_TOKEN = 'invalid token'#

The server refused the scope’s sync token, so the scope is reset and listed in full.

Last updated on 2026-11-11.