mrkeyoor.com_
Thu 06 Aug 01:00 UTC
PyPISecurityupdated 05 Aug 2026

keyring

keyring is a thin, uniform Python front end over whatever secret store the operating system already runs: macOS Keychain, the Freedesktop Secret Service (GNOME Keyring and friends), KDE KWallet, and Windows Credential Locker. The whole core API is four functions keyed by a service name and a username: set_password, get_password, delete_password, and get_credential. It picks a backend automatically by priority, and third-party packages can register their own backends through entry points, which is how 1Password, Bitwarden, and pass integrations plug in. It also installs a keyring command so shell scripts and CI can read the same secrets. Most Python developers already have it installed because pip, twine, and poetry depend on it.

Verdict

The correct choice for developer-facing tools that should not leave tokens in plaintext dotfiles, and the wrong choice for anything running without a desktop session. Judge it by whether a human is logged in on the machine, not by how nice the four-function API looks.

API stability5/5get_password, set_password, delete_password, and set_keyring have been the same for a decade; the major version bumps track dropped Python versions and backend removals, not signature changes.
Docs4/5The README is unusually practical, with transcripts for Ubuntu, headless Linux, Docker, and tox, plus an explicit security-considerations section that admits three of four backends have had no analysis; the readthedocs API reference is thinner than the README.
Maintenance4/5Jason Coombs releases steadily and 25.7.0 landed November 2025 with KWallet 6 support, but there are 80 open issues on top of open PRs and the backlog skews toward Linux desktop edge cases that are hard to reproduce.
Ecosystem5/5About 56.5M weekly downloads, a dependency of pip, twine, and poetry, and an entry-point system that a healthy set of third-party backends (1Password, Bitwarden, pass, cryptfile) already targets.

Use it if

  • You are writing a desktop or developer CLI tool and want the user's API token in the OS keychain instead of a plaintext dotfile in their home directory
  • You need the same code path on macOS, Windows, and a GNOME or KDE Linux desktop without writing three platform branches
  • You want a pluggable secret source: switching the whole app to 1Password or pass is installing a backend package, not editing your code
  • You want the secret reachable from both Python and the shell, since the bundled keyring command reads and writes the same entries
Skip it if

Setup reality

pip install keyring works everywhere, but working is platform-specific. macOS and Windows are the easy cases and need nothing extra. On a Linux desktop you get SecretStorage and jeepney pulled in automatically, which means cryptography is compiled or downloaded too, and the KWallet backend additionally wants dbus-python, which the README says to install as a system package because pip compilation often fails. Headless Linux needs gnome-keyring-daemon running inside a D-Bus session and opened with a password piped to its stdin, and in Docker the README suggests --privileged to dodge permission errors. Under tox you must add DBUS_SESSION_BUS_ADDRESS to pass_env or you get a NoKeyringError that looks like a bug in your code. Run keyring diagnose to find which backend was picked and where the keyringrc.cfg config file lives, because both are non-obvious.

Patterns

Store and read a secretset-and-get-password

import keyring

keyring.set_password('my-cli', 'api-token', token)

token = keyring.get_password('my-cli', 'api-token')

The first argument is a namespace you invent, not anything the OS knows about; pick something specific to your tool or you will collide with another program in the same keychain.

get_password returns None, it does not raisehandle-missing-secret

import keyring

token = keyring.get_password('my-cli', 'api-token')
if token is None:
    token = prompt_and_store()

A missing entry and a genuinely empty stored value both come back falsy, so compare against None explicitly if an empty secret is meaningful to you.

Delete an entry without blowing up on logout twicedelete-password

import keyring
from keyring.errors import PasswordDeleteError

try:
    keyring.delete_password('my-cli', 'api-token')
except PasswordDeleteError:
    pass  # already gone

Unlike get_password, delete_password raises when the entry does not exist, so an idempotent logout command needs this try block.

Degrade gracefully when there is no keychainsurvive-no-backend

import os
import keyring
from keyring.errors import NoKeyringError

def read_token():
    env = os.environ.get('MYCLI_TOKEN')
    if env:
        return env
    try:
        return keyring.get_password('my-cli', 'api-token')
    except NoKeyringError:
        return None

This is the single most important pattern in the library. Checking the environment variable first also gives CI and container users a documented way in without touching a keychain.

Turn keyring off for CI and containersdisable-in-ci

export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring
pip install -r requirements.txt

This is the fix for pip, poetry, or twine hanging on a credential prompt in a build. The Null backend returns empty values so callers fall back to their own behavior; keyring --disable makes it permanent for that user.

Find out which backend is actually in useinspect-active-backend

import keyring

print(keyring.get_keyring())
# <keyring.backends.macOS.Keyring object at 0x...>

From the shell, keyring diagnose prints the selected backend and the path of keyringrc.cfg, which is the fastest way to explain why a colleague's machine behaves differently from yours.

Pin the backend from codeforce-a-backend

import keyring
import keyring.backends.SecretService

keyring.set_keyring(keyring.backends.SecretService.Keyring())

Auto-selection is by backend priority and can change when a user installs another keyring package; pin it only if you truly need one backend, since pinning breaks cross-platform use.

Read a username and password togetherget-credential

import keyring

cred = keyring.get_credential('my-cli', None)
if cred is not None:
    print(cred.username, cred.password)

Passing None for the username asks the backend for whatever credential it has for that service. Since 25.5.0 this returns None when a named username is not found instead of a partially filled object.

Keep more than one field per entrystore-structured-secret

import json
import keyring

keyring.set_password('my-cli', 'oauth', json.dumps({
    'access_token': access,
    'refresh_token': refresh,
    'expires_at': expires_at,
}))

raw = keyring.get_password('my-cli', 'oauth')
session = json.loads(raw) if raw else {}

The API only stores one string, so JSON is the usual workaround. Keep it small: backends have their own size limits and a refresh token blob is close to the ceiling on some of them.

Swap in a fake backend in testscustom-backend-for-tests

import keyring
import keyring.backend

class MemoryKeyring(keyring.backend.KeyringBackend):
    priority = 1
    def __init__(self):
        self._data = {}
    def set_password(self, service, username, password):
        self._data[(service, username)] = password
    def get_password(self, service, username):
        return self._data.get((service, username))
    def delete_password(self, service, username):
        del self._data[(service, username)]

keyring.set_keyring(MemoryKeyring())

A backend needs a priority attribute and those three methods; get_credential is optional. This keeps your test suite from touching the developer's real keychain, which otherwise triggers OS password prompts mid-run.

Read and write from the shellcli-usage

keyring set my-cli api-token   # prompts, does not echo
TOKEN=$(keyring get my-cli api-token)
keyring del my-cli api-token

python -m keyring get my-cli api-token  # same thing, exact interpreter

Prefer python -m keyring in scripts: the bare keyring command may resolve to a different virtualenv than the Python you are running, and the two see different keyring configurations.

Point a backend at a non-default storebackend-properties-from-env

export KEYRING_PROPERTY_KEYCHAIN=/Users/me/Library/Keychains/ci.keychain-db
python -m keyring get my-cli api-token

Any KEYRING_PROPERTY_NAME variable sets the lowercased property on the active backend at init; keychain works on macOS and appid on the Secret Service backend.

Alternatives

PackageRegistryPick it when
keyrings.altPyPIYou need a file-based backend on a machine with no OS keychain and you accept the weaker guarantees
python-dotenvPyPIThe target is a server or container where a per-user keychain does not exist and env files are the norm
hvacPyPISecrets belong to the deployment rather than the user and you already run HashiCorp Vault