calendaring_sync.store.base module#

The store interface, and the storage interface a store keeps its data in.

A store only keeps data. Every sync rule lives in Replica, and every rule about how a store behaves, such as its transactions, its checks and its errors, lives in StateStore. Where the data goes is a Storage, which implements the same small set of typed operations and nothing more.

class calendaring_sync.store.base.StateStore(storage)[source]#

Bases: object

Where the core keeps each scope, its records, and the change feed.

It keeps them in a Storage, and adds every rule a store follows, whichever storage it uses. Every operation runs inside transaction(): one that only reads runs inside any transaction, and one that writes needs a transaction opened with write=True. A store is safe to share between threads: one write transaction runs at a time, and where the storage sets reads_beside_writes, transactions that only read run in other threads meanwhile instead of waiting for it.

Besides the errors each method lists, every operation, transaction() and close() can raise:

  • TypeError, for an argument of the wrong type, before anything is read or written; a record or scope to write is checked field by field

  • StoreError, if the store can’t read or write its data

  • UnexpectedStoreError, for any other failure inside the storage, which is a bug

No exception of the storage underneath reaches the caller.

Parameters:

storage (Storage) – Where the store keeps its data.

transaction(*, write=True)[source]#

Run the operations inside the block as one transaction.

Everything the block wrote is kept if it ends normally, and nothing is kept if it raises.

Parameters:

write (bool) – False for a block that only reads. Such a block can’t write, and a store may run it beside a write in another thread or process.

Raises:
  • TransactionError – If a transaction is already open in this thread.

  • StoreBusyError – If another process kept the store busy for too long.

  • StoreError – If the store was closed, or can’t start or keep the transaction.

Return type:

Iterator[None]

get_scope(scope_id)[source]#

Return the scope with this id, or None.

Runs inside any transaction.

Raises:

TransactionRequiredError – If no transaction is open in this thread.

Return type:

Scope | None

put_scope(scope)[source]#

Insert the scope, or replace the one with its id.

Runs inside a write transaction.

Raises:
Return type:

None

delete_scope(scope_id)[source]#

Delete the scope and every record in it.

Runs inside a write transaction.

Raises:
Return type:

None

scopes()[source]#

Return every scope, ordered by id.

Runs inside any transaction.

Raises:

TransactionRequiredError – If no transaction is open in this thread.

Return type:

list[Scope]

insert_record(record)[source]#

Insert a new record and return the local key it got.

The record’s own local_key is ignored. A key is never given out twice, even after the record it went to was deleted. Runs inside a write transaction.

Raises:
  • RecordValueError – If the record holds what its state can’t. A record holds an edit exactly when it’s pending, in flight or in conflict, the server’s copy in a conflict exactly when it’s in conflict, and no synced copy exactly when its edit is a create. An edit holds a copy of the object unless it’s a delete, a conflict’s edit and server’s copy match its kind, and only a refused create’s conflict names a holder. The record is checked first, so this is raised outside a transaction too.

  • TransactionRequiredError – If no transaction is open in this thread.

  • TransactionPermissionError – If the transaction only reads.

  • UnknownScopeError – If the record’s scope doesn’t exist.

  • DuplicateObjectError – If another record in the scope has the record’s object id.

Return type:

int

update_record(record)[source]#

Replace the record with the same local key.

Runs inside a write transaction.

Raises:
Return type:

None

delete_record(local_key)[source]#

Delete the record with this local key.

Runs inside a write transaction.

Raises:
Return type:

None

get_record(local_key)[source]#

Return the record with this local key, or None.

Runs inside any transaction.

Raises:

TransactionRequiredError – If no transaction is open in this thread.

Return type:

Record | None

find_record(scope_id, object_id)[source]#

Return the scope’s record with this object id, or None.

Runs inside any transaction.

Raises:

TransactionRequiredError – If no transaction is open in this thread.

Return type:

Record | None

find_by_logical_key(scope_id, logical_key)[source]#

Return the scope’s records with this logical key, ordered by local key.

Matches as Replica.find_by_logical_key describes. Runs inside any transaction.

Raises:

TransactionRequiredError – If no transaction is open in this thread.

Return type:

list[Record]

scope_records(scope_id, *, states=None)[source]#

Return the scope’s records, ordered by local key.

Runs inside any transaction.

Parameters:
  • scope_id (str) – The scope to read.

  • states (frozenset[RecordState] | None) – Only the records in one of these states; None for every record. An empty set always returns an empty list.

Raises:

TransactionRequiredError – If no transaction is open in this thread.

Return type:

list[Record]

removed_records(through)[source]#

Return the removed records no feed entry after through is about.

through is the number of a change feed entry. Each record keeps the number of the last entry about it, so these are the records removed by that entry or an earlier one, ordered by local key. Runs inside any transaction.

Raises:

TransactionRequiredError – If no transaction is open in this thread.

Return type:

list[Record]

append_entry(scope_id, kind, local_key, object_id, at)[source]#

Write the next change feed entry and return it.

Its number is one more than the last number written. Runs inside a write transaction.

Raises:
Return type:

FeedEntry

entries(after, limit)[source]#

Return up to limit kept entries numbered above after, in order.

Runs inside any transaction.

Raises:

TransactionRequiredError – If no transaction is open in this thread.

Return type:

list[FeedEntry]

feed_bounds()[source]#

Return the number the feed is trimmed through, and the last number written.

Runs inside any transaction.

Raises:

TransactionRequiredError – If no transaction is open in this thread.

Return type:

tuple[int, int]

trim_entries(through)[source]#

Delete every entry numbered at or below through.

feed_bounds() reports the highest through trimmed so far. Runs inside a write transaction.

Raises:
Return type:

None

close()[source]#

Close the store. Every later transaction raises StoreError.

Transactions open in other threads end first: closing waits for them. Closing a closed store does nothing.

Raises:
  • TransactionError – If this thread has a transaction open.

  • StoreError – If the store couldn’t release what it holds open. It counts as closed anyway.

Return type:

None

class calendaring_sync.store.base.Storage[source]#

Bases: ABC

Where a StateStore keeps its data.

To keep sync state somewhere new, such as in another database, implement this and a StorageTransaction, and pass the storage to StateStore. The store adds every check, transaction rule, thread rule and error a store needs, so a storage only reads and writes.

reads_beside_writes: bool = False#

Whether a transaction that only reads can run while another thread’s write transaction is open. With False, every transaction waits for the one before it to end. Set it to True only where a read sees the data as the last commit left it until it ends, whatever a write does meanwhile; reads in other threads then run at the same time too, so begin() and the transactions it returns must be safe for that.

abstractmethod begin(*, write)[source]#

Start a transaction and return it.

The store runs one write transaction at a time, and transactions that only read beside it only if reads_beside_writes is set. If this raises, it leaves nothing open.

Return type:

StorageTransaction

close()[source]#

Release what the storage holds open.

The store calls it once, after every transaction ended.

Return type:

None

error(exc)[source]#

Return the store error for an exception the storage raised, or None.

Turn the failures of whatever the storage uses, such as a file it can’t open, into StoreError or one of its subclasses. The store reports any exception this returns None for as a bug in the storage.

Return type:

StoreError | None

class calendaring_sync.store.base.StorageTransaction(*, write)[source]#

Bases: ABC

One transaction on a Storage, which Storage.begin() returns.

It holds whatever its transaction needs, such as a connection or a view of the data, so a storage keeps no state per thread. Its data operations are the ones StateStore offers, without the checks: StateStore calls them only inside this transaction, after it checked the arguments, the records and whether the transaction may write, and turns every exception they raise into a store error. Each returns what the StateStore method of the same name returns.

Parameters:

write (bool) – Whether the transaction may write.

write#

Whether the transaction may write.

abstractmethod commit()[source]#

Keep what the transaction wrote, and end it.

Return type:

None

abstractmethod rollback()[source]#

Discard what the transaction wrote, and end it.

It’s also called after commit() raised, so it ends whatever the transaction still holds open.

Return type:

None

abstractmethod get_scope(scope_id)[source]#

Do what StateStore.get_scope() does, without its checks.

Return type:

Scope | None

abstractmethod put_scope(scope)[source]#

Do what StateStore.put_scope() does, without its checks.

Return type:

None

abstractmethod delete_scope(scope_id)[source]#

Do what StateStore.delete_scope() does, without its checks.

Return type:

None

abstractmethod scopes()[source]#

Do what StateStore.scopes() does, without its checks.

Return type:

list[Scope]

abstractmethod insert_record(record)[source]#

Do what StateStore.insert_record() does, without its checks.

Return type:

int

abstractmethod update_record(record)[source]#

Do what StateStore.update_record() does, without its checks.

Return type:

None

abstractmethod delete_record(local_key)[source]#

Do what StateStore.delete_record() does, without its checks.

Return type:

None

abstractmethod get_record(local_key)[source]#

Do what StateStore.get_record() does, without its checks.

Return type:

Record | None

abstractmethod find_record(scope_id, object_id)[source]#

Do what StateStore.find_record() does, without its checks.

Return type:

Record | None

abstractmethod find_by_logical_key(scope_id, logical_key)[source]#

Do what StateStore.find_by_logical_key() does, without its checks.

Return type:

list[Record]

abstractmethod scope_records(scope_id)[source]#

Return every record of the scope, ordered by local key.

Return type:

list[Record]

scope_records_in(scope_id, states)[source]#

Return the scope’s records in one of states, ordered by local key.

This filters what scope_records() returns. A storage that can filter where it keeps its records, such as with an index, overrides it. It never gets an empty set.

Return type:

list[Record]

abstractmethod removed_records(through)[source]#

Do what StateStore.removed_records() does, without its checks.

Return type:

list[Record]

abstractmethod append_entry(scope_id, kind, local_key, object_id, at)[source]#

Do what StateStore.append_entry() does, without its checks.

Return type:

FeedEntry

abstractmethod entries(after, limit)[source]#

Do what StateStore.entries() does, without its checks.

Return type:

list[FeedEntry]

abstractmethod feed_bounds()[source]#

Do what StateStore.feed_bounds() does, without its checks.

Return type:

tuple[int, int]

abstractmethod trim_entries(through)[source]#

Do what StateStore.trim_entries() does, without its checks.

Return type:

None

Last updated on 2026-11-11.