synology_apm_repo.sdk.profiles package¶
Saved S3/Azure/SMB connection profiles — shared, identically, by the CLI and
TUI, exactly like presentation/ is shared for render-identically output.
This is cross-cutting infrastructure outside the layer stack: it
produces inputs to storage/’s S3Store/AzureStore, it does no
ObjectStore I/O itself, and it has nothing to do with Session/keys/
NodeRef.
Every function below is async def and a plain module-level function (not
a class method) — async because the underlying work (profiles.json file
I/O, OS keyring access) is exactly the kind of blocking call
LocalFsStore already wraps in asyncio.to_thread() (there is no
async-native alternative, same as pread/fstat), and blocking a TUI’s
event loop on a keyring prompt would freeze the whole UI; plain functions so
tests (and callers) can monkeypatch.setattr(this_module, "list_profiles",
fake) exactly like storage.s3.list_buckets already supports. A
config_dir keyword lets tests point at a temp directory instead of the
real per-user config path, standing in for constructor-based dependency
injection.
Two secret-aware entry points read differently, deliberately:
get_profile() never touches the keyring (so list/show-shaped
callers structurally cannot leak a secret), while load_profile() does,
returning every field flattened into one dict for refilling a connection
form.
- class synology_apm_repo.sdk.profiles.AzureProfileConfig(container, account_url=None, verify_tls=True)¶
Bases:
objectNon-secret
AzureStoreconstructor inputs.credentialis not a field here — seesecrets.py.
- class synology_apm_repo.sdk.profiles.BackendKind(value)¶
Bases:
EnumWhich backend a profile targets.
- S3 = 's3'¶
- AZURE = 'azure'¶
- SMB = 'smb'¶
- class synology_apm_repo.sdk.profiles.Profile(name, kind, config)¶
Bases:
objectOne saved profile, keyed by its user-chosen
name(unique across every backend kind). Never carries secret values.- kind: BackendKind¶
- config: S3ProfileConfig | AzureProfileConfig | SmbProfileConfig¶
- class synology_apm_repo.sdk.profiles.ProfileFieldSpec(name, strip=True, is_checkbox=False)¶
Bases:
objectOne profile field’s widget-binding shape, in canonical collection/ display order, for one backend — the single place a connection form (the TUI’s connect/saved-profile tabs today) reads a backend’s full field list from, instead of privately re-enumerating it.
stripisFalsefor a value whose leading/trailing whitespace might be significant (secret_key/credential/password) — not simply every secret field, sinceaccess_key(also keyring-backed, persecret_fields_for) has no such concern and is stripped like an ordinary field.
- class synology_apm_repo.sdk.profiles.ProfileSummary(name, kind)¶
Bases:
objectCheap listing entry — derived from
profiles.jsonalone, no keyring access needed to produce it.- kind: BackendKind¶
- class synology_apm_repo.sdk.profiles.S3ProfileConfig(bucket, endpoint=None, region=None, verify_tls=True)¶
Bases:
objectNon-secret
S3Storeconstructor inputs.access_key/secret_keyare not fields here — seesecrets.py.- property client_kwargs: dict[str, Any]¶
Everything but
bucketand the secret fields, already inS3Store/aioboto3’s own kwarg names. Callers merge the resolved secrets in on top of this dict.
- property display_fields: dict[str, str | None]¶
Ordered field-name -> value pairs for CLI/TUI display (a saved profile’s JSON and human-readable
showoutput alike) — the one place this backend’s own display field list is spelled out, so both renderers read it from here instead of each re-declaring it.
- class synology_apm_repo.sdk.profiles.SmbProfileConfig(server, share, port=445, username=None)¶
Bases:
objectNon-secret
SmbStoreconstructor inputs.passwordis not a field here — seesecrets.py.usernametakes the Windows-nativeDOMAIN\username(oruser@domainUPN) form directly —smbprotocol’s own NTLM/SPNEGO layer splits the domain back out of that single string, so there is no separatedomainfield to keep in sync with it. Nopath/sub-root field either, matchingS3ProfileConfig/AzureProfileConfig: a share, like a bucket/container, is the whole scope one profile names.
- async synology_apm_repo.sdk.profiles.build_store(name, *, config_dir=None)¶
The end-to-end convenience form: profile
name’s config and keyring secrets, resolved into an already-constructedS3Store/AzureStore/SmbStoreviastore_from_config— ready forSession.discover_remote()/open_remote(). The caller still owns callingSession.close(), which callsaclose()on it.
- synology_apm_repo.sdk.profiles.client_kwargs_with_secrets(config, secrets)¶
config.client_kwargs, overlaid with whichever ofsecret_fields_for(kind)’s fieldssecretsactually carries a truthy value for — the shared shape behind every “resolve one profile’s config plus its secrets into constructor kwargs” call site (profiles.build_store,cli/commands/profile.py::_store_from_fields,browser/screens/connect_dialog.py’ss3_client_kwargs/azure_client_kwargs— SMB has no bucket-less/container-less “Browse” counterpart calling this directly, onlysmb_config_and_secretsfeedingstore_from_configstraight from_build_smb_store).secretsmay be a superset of what applies here (a not-yet-saved connection form carries every backend’s fields at once) — only the keyssecret_fields_fornames forconfig’s own kind are ever read from it; a present-but-falsy value (an empty string from an untouched form field) is treated the same as absent, matching every prior hand-rolled version of this check.
- synology_apm_repo.sdk.profiles.config_from_fields(kind, fields)¶
fields(save_profile()’s canonical field names, e.g. as collected by a connection form or the CLI’sprofile addprompts) turned intokind’s own config dataclass — the one place this per-backend branch is spelled out, sosave_profileand any caller building a not-yet-saved config from raw fields (cli/commands/profile.py’s pre-persist connectivity check) share it instead of each re-deriving their own.
- async synology_apm_repo.sdk.profiles.delete_profile(name, *, config_dir=None)¶
Remove profile
nameentirely.The
profiles.jsonentry is removed before the best-effort keyring cleanup, so a latersave_profile()re-using this name never silently inherits stale secrets.- Raises:
ProfileNotFoundError – No such profile.
- synology_apm_repo.sdk.profiles.form_fields_for(kind)¶
kind’s full field list, in canonical order — every field a connection form binds, secrets included, unlikedisplay_fields(non-secret only, read from an already-built config instance rather than named ahead of one existing).
- async synology_apm_repo.sdk.profiles.get_profile(name, *, config_dir=None)¶
The non-secret half of profile
name.Never touches the keyring — safe for
--json/log output by construction, not by redacting something that was fetched.- Raises:
ProfileNotFoundError – No such profile.
- async synology_apm_repo.sdk.profiles.list_profiles(*, config_dir=None)¶
Every saved profile’s name and backend kind, sorted by name. Never touches the keyring.
- async synology_apm_repo.sdk.profiles.list_profiles_full(*, config_dir=None)¶
Every saved profile, sorted by name, as a full
Profilerather thanlist_profiles()’s own name+kind summary — for a caller that wants every profile’s own backend fields (profile list --verbose) without oneget_profile()call per name each re-reading and re-parsing the sameprofiles.jsonthat a single call here already read once. Never touches the keyring, same guarantee asget_profile().
- async synology_apm_repo.sdk.profiles.list_remote_items(kind, **client_kwargs)¶
Every bucket (S3) or container (Azure) reachable with
client_kwargs(already resolved, e.g. viaclient_kwargs_with_secrets) — the same account-level listing the TUI’s connect dialog uses to populate its “browse buckets/containers” picker before a bucket/container name is chosen. No SMB equivalent: unlike an S3/Azure account, an SMB server has no single account-level “list every share” operation this backend relies on, sokind is BackendKind.SMBraises rather than silently falling through to one of the other two backends’ own listing call.
- async synology_apm_repo.sdk.profiles.load_profile(name, *, config_dir=None)¶
Every field of profile
name, secrets included and unmasked, flat in the canonical field names (bucket/endpoint/region/verify_tls/access_key/secret_keyfor S3,container/account_url/verify_tls/credentialfor Azure,server/share/port/username/passwordfor SMB) — for refilling a connection form. Never used for display/logging: a caller that only needs to name or describe a profile should useget_profileinstead.
- async synology_apm_repo.sdk.profiles.save_profile(name, kind, fields, *, config_dir=None)¶
Save (or overwrite — this is an upsert, no collision check) a profile under
name.fieldsuses the same canonical namesload_profilereturns; secret fields are split out and written to the keyring before the non-secret half is committed toprofiles.json, so a profile visible inprofiles.jsonnever references secrets that were never actually written.
- async synology_apm_repo.sdk.profiles.store_from_config(kind, config, secret_source)¶
config’s fields andsecret_source’s raw credentials, merged viaclient_kwargs_with_secretsand handed to the matchingObjectStoreconstructor — the one translationbuild_store,cli/commands/profile.py’s pre-persist connectivity check, and the TUI’s connect dialog all need, whether or notconfighas ever been saved as a profile.
Submodules¶
- synology_apm_repo.sdk.profiles.config_file module
- synology_apm_repo.sdk.profiles.model module
- synology_apm_repo.sdk.profiles.secrets module