synology_apm_repo.sdk.storage.s3 module¶
S3Store — a real, executable ObjectStore implementation for
S3-compatible object storage, one of the two backends this project’s own
object-storage samples are laid out for.
aioboto3/botocore are imported lazily, inside __init__ and each
method, rather than at module scope: aioboto3 is always installed (a
required dependency), but it’s a substantial import graph (botocore,
aiohttp, …), and this module is imported unconditionally by
storage/__init__.py, so a module-level import would make every caller
of storage pay that cost even if it never touches S3Store. Python
caches the import after the first successful call, so the repeated
import statements below cost a dict lookup, not a re-import.
This and AzureStore use aioboto3/async rather than a thread around
plain boto3 because both sit on aiohttp, where many outstanding
network round-trips genuinely overlap on one thread — a real gain a
thread-pool wrapper around a synchronous client wouldn’t provide. One
consequence: aioboto3’s client is an async context manager, so this
class creates it lazily on first use and owns its teardown via
S3Store.aclose (called by Session.close()) — forgetting that would
leak an aiohttp connector. The constructor itself stays synchronous
(it only builds an aioboto3.Session, which does no I/O).
Two contract differences from LocalFsStore, both because S3 genuinely
has no directory entities (listdir’s and exists’s own docstrings
cover the “directory” side of that): S3Store.read treats an
out-of-range Range request as the ObjectStore contract’s own
short-read-at-EOF case (b"") rather than S3’s own InvalidRange
client error, to match the same contract every other backend already
provides.
Every client this module builds goes through _with_default_timeouts,
which caps botocore’s own long batch-job timeouts down to values an
interactive caller — the TUI’s connect dialog, in particular — can actually
wait through when an endpoint is unreachable.
- class synology_apm_repo.sdk.storage.s3.S3Store(bucket, *, client=None, **client_kwargs)¶
Bases:
objectAn S3 (or S3-compatible — MinIO, Synology C2, …) bucket, addressed by
"/"-separated paths relative to the bucket root.client, if given, is used as-is (tests inject a fake or otherwise pre-configured async client this way, already entered); otherwise one is built lazily on first use fromclient_kwargs(region_name,endpoint_url— for S3-compatible but non-AWS backends,aws_access_key_id/aws_secret_access_key, …), passed straight through toaioboto3.Session().client("s3", **client_kwargs).- async aclose()¶
Release the underlying
aiohttpconnector.Required, not optional, for a client this class created itself. An injected
clientis left alone: whoever created it owns closing it. Safe to call more than once.
- async read(path, offset=0, length=None)¶
- async size(path)¶
- async exists(path)¶
Truefor either an object exactly atpath, or a “directory” — a prefix with at least one object under it.ObjectStore.exists()’s own contract explicitly covers both (layout.pyrelies on it:db/@dataare never objects in their own right on an object-storage backend, only prefixes with real objects underneath — ahead_objectcheck alone would wrongly report every such “directory” as absent).
- async listdir(path)¶
An absent “directory” (a prefix with zero objects under it) and an empty one are indistinguishable — S3 has no real directory entities, only prefixes — so both correctly report
[]rather than one of them raisingNotFoundError.
- async synology_apm_repo.sdk.storage.s3.list_buckets(**client_kwargs)¶
Every bucket visible to these credentials — a bucket-less operation
S3Storehas no method for, since all four of its methods are scoped to one chosen bucket. Builds its own transient client the same lazy-import wayS3Storedoes and closes it before returning; there is no lifecycle for a caller to manage beyond this one call.client_kwargsis exactly what a caller would otherwise pass toS3Store. Raises whatever the underlyingaioboto3call raises (e.g.ClientErrorforAccessDeniedwhen the credentials aren’t authorized to list buckets at the account level) — unhandled, the same as every other backend-specific exceptionS3Store’s own methods let propagate.