synology_apm_repo.sdk.storage.base module¶
The ObjectStore protocol — the sole boundary between “how bytes are
fetched” and everything above it.
Deliberately four methods, all pure byte/existence semantics. Nothing here
knows about sequence-id suffixes, SQLite, WAL files, or repository layout —
those are built on top of an ObjectStore, in this same package
(seqid, layout, sqlite, generations), not inside it. This keeps the cost of adding a new backend
(S3, Azure) to “implement four methods”, never “also learn how SQLite WAL
recovery or repository layout works”.
All paths are "/"-separated strings relative to the store’s root, never
pathlib.Path and never absolute — a backend like S3 has no notion of “the
current working directory” or OS path separators, and requiring callers to
speak in relative strings keeps that assumption from leaking upward.
- class synology_apm_repo.sdk.storage.base.ObjectStore(*args, **kwargs)¶
Bases:
ProtocolRead-only, byte-oriented access to one repository’s storage tree.
All four methods are
async def, but what that buys differs sharply per backend:LocalFsStorehas no genuinely non-blocking option —os.pread/os.open/etc. have no native async form in CPython, so its four methods are thinasyncio.to_thread()wrappers around synchronous syscalls, the same asaiofilesdoes internally.S3StoreandAzureStoreare the backends where async is real: both sit onaiohttp, so many outstanding network round-trips genuinely overlap on one thread.
Implementations MUST be safe to call concurrently, whether that concurrency is several asyncio Tasks on one event loop or several real OS threads out of
asyncio.to_thread()’s executor.Implementations MUST NOT expose any mutating method —
@runtime_checkableonly verifies the four methods below exist, not that nothing else does; the read-only guarantee rests on each implementer’s own discipline.- async read(path, offset=0, length=None)¶
Return up to
lengthbytes starting atoffsetinpath.length=Nonereads to end-of-file. RaisesNotFoundErrorifpathdoes not exist, orPermissionDeniedErrorif it exists but access was denied. Short reads at end-of-file return fewer bytes than requested, never pad or raise.
- async size(path)¶
Return the total byte length of
path.Raises
NotFoundErrorif it does not exist, orPermissionDeniedErrorif it exists but access was denied.
- async exists(path)¶
Return whether
pathexists (file or directory). Never raises for a merely-absent path — that is exactly whatFalsemeans. Also returnsFalse(rather than raisingPermissionDeniedError) when access is denied: callers use this method to probe candidate paths, often several irrelevant ones per real hit, and a probe that can raise would abort that search instead of just ruling one candidate out.
- async listdir(path)¶
Return the immediate entry names (files and subdirectories, not full paths) directly under
path, in unspecified order.Raises
NotFoundErrorifpathdoes not exist or is not a directory, orPermissionDeniedErrorif it exists but access was denied.
- class synology_apm_repo.sdk.storage.base.AsyncCloseable(*args, **kwargs)¶
Bases:
ProtocolAn
ObjectStorethat owns a real client/connector needing release —S3Store/AzureStore’saiohttpconnector, notLocalFsStore, which has nothing to close. Lets callers holding a bareObjectStore(Session.close()) release it withisinstance(store, AsyncCloseable)instead of duck-typinggetattr(store, "aclose", None).- async aclose()¶
- async synology_apm_repo.sdk.storage.base.aclose_if_possible(store)¶
await store.aclose()whenstoreisAsyncCloseable, a no-op otherwise — the guarded-close check every caller holding a bareObjectStoreit must release on its own repeats (Session.close(),recording.py’s_InstrumentedStore.aclose(), the CLI’sdumpcommand family), lives here once next to the protocol it checks.
- synology_apm_repo.sdk.storage.base.join_path(*parts)¶
Join
partsinto one store-relative path,"/"-separated, tolerating an empty/absent segment (repo_rootis often"") and any stray leading/trailing"/"a caller’s own segment happens to carry. Callers across every layer above this one build everyObjectStorepath this way — lives here, next to theObjectStoreprotocol whose own path convention it implements.
- synology_apm_repo.sdk.storage.base.backend_key(path)¶
Strip
path’s leading/trailing"/"for a backend whose own key/blob-name namespace has none — S3’s object keys and Azure’s blob names both reject a leading"/", unlike this SDK’s ownObjectStorepath convention (which tolerates one, perjoin_path). Shared byS3StoreandAzureStorerather than each defining an identical private helper.