Skip to main content

Python client for Selectel Secrets Manager (sync + async)

Project description

selectel-sm

Tests Lint codecov PyPI - Python Version

A typed Python client for Selectel Secrets Manager, with both synchronous and asynchronous clients sharing a single transport-agnostic core.

⚠️ Disclaimer

This is a non-commercial, community project built out of pure enthusiasm. I am not an employee of Selectel and have no affiliation with them whatsoever. I built this simply because I couldn't find a maintained library for working with Selectel's Secrets Manager.

Installation

pip install selectel-sm          # library
pip install "selectel-sm[cli]"   # + CLI (typer + rich)

Requires Python 3.12+.

Authentication

Secrets Manager requires a project-scoped IAM token. (The public docs mention an account-scoped token, but in practice SM rejects it — a project-scoped token is required.) The library obtains one for you from service-user credentials, caches it, and refreshes it before expiry:

from selectel_sm import SecretsManagerClient

with SecretsManagerClient.from_credentials(
    region="ru-7",
    account_id="123456",
    username="my-service-user",
    password="...",
    project_name="my-project",
) as client:
    secret = client.secrets.get("database_password")
    print(secret.value)  # -> b"..."

Or bring your own project-scoped token (the client introspects it to discover the service catalog):

client = SecretsManagerClient.from_token(region="ru-7", token="gAAAAAB...")

The async client mirrors the same API:

from selectel_sm import AsyncSecretsManagerClient

async with AsyncSecretsManagerClient.from_credentials(region="ru-7", ...) as client:
    secret = await client.secrets.get("database_password")

Endpoint resolution

The Secrets Manager URL is not hardcoded. After authenticating, the library reads the service catalog returned in the Keystone token and resolves the secrets-manager endpoint for the configured region and interface (default public). Set sm_base_url on the client to bypass catalog resolution (e.g. for testing).

Usage

All operations live under client.secrets (and identically on the async client with await).

Secrets

# Create a secret with its first version. `value` is plain data (bytes or str);
# it is base64-encoded for you before being sent.
client.secrets.create("api_key", "s3cr3t", description="Third-party API key")

# Read the current value.
secret = client.secrets.get("api_key")
secret.value          # b"s3cr3t"
secret.description    # "Third-party API key"  ("" from the API becomes None)
secret.version        # the current SecretVersion

# Update / clear the description (None clears it).
client.secrets.update_description("api_key", "Rotated key")

# List all secrets (metadata only — no values).
for summary in client.secrets.list():
    print(summary.name, summary.type, summary.description, summary.created_at)

# Delete a secret and all of its versions.
client.secrets.delete("api_key")

Versions

# Add a new version. Pass activate=True to make it the current version.
client.secrets.create_version("api_key", "rotated-secret", activate=True)

# The secret plus metadata for all of its versions (no values).
sv = client.secrets.get_versions("api_key")
sv.versions           # tuple[SecretVersion, ...]
sv.current            # the version flagged is_current, if any

# A single version, including its value.
version = client.secrets.get_version("api_key", version_id=1)
version.value         # b"..."

# Make a specific version current.
client.secrets.activate_version("api_key", version_id=1)

Error handling

Every error derives from SelectelSMError, so you can catch broadly or narrowly:

from selectel_sm import NotFoundError, SelectelSMError

try:
    client.secrets.get("does-not-exist")
except NotFoundError:
    ...
except SelectelSMError:
    ...

HTTP statuses map to BadRequestError (400), ForbiddenError (403), NotFoundError (404), ConflictError (409), and ServerError (5xx). Authentication and endpoint-resolution problems raise AuthenticationError and EndpointNotFoundError respectively.

Command-line interface

Installing the [cli] extra adds a selectel-sm command (built on typer + rich). It is designed for humans on a developer machine, but degrades gracefully into scripted/CI use.

pip install "selectel-sm[cli]"

Authentication & profiles

Log in once to create a profile. Non-secret context (region, project, username, …) is stored in a TOML config at $XDG_CONFIG_HOME/selectel-sm/config.toml; the password and cached token live only in your OS keyring — never on disk.

selectel-sm login --region ru-7 --account-id 123456 \
    --project my-project --username my-service-user      # prompts for the password

selectel-sm whoami                  # active profile + cached-token status
selectel-sm profile list            # all profiles (marks the default)
selectel-sm profile use prod        # switch the default profile
selectel-sm logout                  # clears keyring secrets, keeps profile metadata

Each profile chooses a persistence policy: keyring (credentials + auto-refreshed token in the OS keyring — convenient, the default for a workstation) or none (nothing persisted; credentials come from the environment or a prompt — correct for servers/CI). --no-store (or SELECTEL_SM_NO_STORE=1) forces "persist nothing" for a single run.

For zero-config automation, set SELECTEL_SM_* environment variables and skip login entirely — an ephemeral, non-persisting profile is synthesized from the environment:

export SELECTEL_SM_REGION=ru-7
export SELECTEL_SM_TOKEN=gAAAAAB...            # or USERNAME/PASSWORD/ACCOUNT_ID/PROJECT
selectel-sm secrets list

Working with secrets

selectel-sm secrets list                       # metadata only — never values   [-o table|json]
selectel-sm secrets create api_key --stdin     # value via --stdin, --file, or a hidden prompt
selectel-sm secrets set-description api_key "Rotated key"
selectel-sm secrets delete api_key --yes       # destructive → confirmation (or --yes)

Reading a value is deliberately guarded so it never lands in your terminal/logs by accident:

selectel-sm secrets get api_key                # metadata + a MASK (••••••) — no value
selectel-sm secrets get api_key --reveal       # show the value
selectel-sm secrets get api_key --copy         # copy to the clipboard, print nothing
export API_KEY=$(selectel-sm secrets get api_key --raw)   # raw bytes to stdout, no newline
selectel-sm secrets get api_key -o json --reveal          # value as base64 (safe to log w/o --reveal)

The value is never a positional argument (it would leak into shell history). Versions are managed under a subgroup; reading a specific version's value still goes through get:

selectel-sm secrets version list api_key       # all versions (marks the current one)
selectel-sm secrets version add api_key --activate --file ./new-value
selectel-sm secrets version activate api_key 2
selectel-sm secrets get api_key --version 2 --reveal

Scripting

Errors print to stderr; machine output and secret values go to stdout, so pipes stay clean. The exit code reflects the failure: 3 auth, 4 not found, 5 forbidden, 6 conflict, 7 server, 8 bad request, 9 endpoint resolution, 10 network, 2 usage, 1 other. Confirmations fail closed: a destructive command in a non-interactive shell errors out unless --yes is given (it never hangs on a prompt or deletes silently).

A note on quirks

Selectel's Secrets Manager API has a few undocumented behaviors this client handles for you, for example:

  • Listing requires a ?list=<any value> flag and a trailing slash (/v1/?list=true), otherwise it returns 404 page not found.
  • Secret values must be valid base64 — the client always encodes plain input for you.
  • activate version is documented as 204 No Content but actually returns 200 with the version's metadata.

Support & maintenance

Because I don't work with Selectel, I may not be aware of the latest changes to their API, and something could break unexpectedly. I do my best to keep this library up to date, but that isn't always possible. Contributions, bug reports, and help with its development are very welcome — please open an issue or a pull request.

Development

uv sync
uv run ruff check . && uv run ruff format --check .
uv run mypy selectel_sm
uv run pytest

License

MIT © Andrew Krylov

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

selectel_sm-0.2.0.tar.gz (101.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

selectel_sm-0.2.0-py3-none-any.whl (53.5 kB view details)

Uploaded Python 3

File details

Details for the file selectel_sm-0.2.0.tar.gz.

File metadata

  • Download URL: selectel_sm-0.2.0.tar.gz
  • Upload date:
  • Size: 101.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: Hatch/1.17.0 {"ci":null,"cpu":"x86_64","distro":{"id":"resolute","libc":{"lib":"glibc","version":"2.43"},"name":"Ubuntu","version":"26.04"},"implementation":{"name":"CPython","version":"3.14.4"},"installer":{"name":"hatch","version":"1.17.0"},"openssl_version":"OpenSSL 3.5.5 27 Jan 2026","python":"3.14.4","system":{"name":"Linux","release":"7.0.0-22-generic"}} HTTPX2/2.4.0

File hashes

Hashes for selectel_sm-0.2.0.tar.gz
Algorithm Hash digest
SHA256 26b400b33450659cd0bee3d9d05e88c4e05edbfbd19e38359bc20d69841a61ed
MD5 fcada85f6b91d0ec494ca7b8c33d79bf
BLAKE2b-256 dcc73a40eaffd0af280da3ad08189d08433c100a057ea7a71282b5d3ee0306d3

See more details on using hashes here.

File details

Details for the file selectel_sm-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: selectel_sm-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 53.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: Hatch/1.17.0 {"ci":null,"cpu":"x86_64","distro":{"id":"resolute","libc":{"lib":"glibc","version":"2.43"},"name":"Ubuntu","version":"26.04"},"implementation":{"name":"CPython","version":"3.14.4"},"installer":{"name":"hatch","version":"1.17.0"},"openssl_version":"OpenSSL 3.5.5 27 Jan 2026","python":"3.14.4","system":{"name":"Linux","release":"7.0.0-22-generic"}} HTTPX2/2.4.0

File hashes

Hashes for selectel_sm-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b106c33b91dd4316ca9a7eb0976762e538ed461c91585b6e494abd755470e4cf
MD5 fbc6edaca387bbc5547958880e318e9a
BLAKE2b-256 c98793c71e6b9022d813a427acf184b34df84408916cc0ef7d61f71aa20bc489

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page