Skip to main content

KeyKosh Python SDK (keykosh-sdk)

Read configuration from your self-hosted KeyKosh (K2) platform. Zero runtime dependencies — HTTP, crypto and JSON all come from the standard library. Python 3.9+.

The SDK talks to exactly one host — your own platform. There is no vendor default URL and no callback home; base_url is required.

Install

pip install keykosh-sdk

Quick start

from keykosh import create_client

# Reads K2_BASE_URL / K2_TOKEN / K2_ENV from the environment; kwargs override.
k2 = create_client(
    base_url="https://k2.acme.com",
    env="prod",
    cache_ttl_seconds=30,     # optional in-memory TTL cache
)

cfg = k2.get_configuration()                       # full config for the default env
db_url = cfg.get_string("db.url", "postgresql://localhost/app")
debug = cfg.get_bool("feature.debug", False)

pool_size = k2.get_property("prod", "pool.size")   # single property

# Live updates: called whenever an admin changes this env's config.
unsubscribe = k2.watch("prod", lambda fresh: apply_config(fresh))

Two ways to run

Everything below follows from which of these you are:

In production (the default — K2_OFFLINE=false): the platform is the source of truth. The SDK fetches from it, and writes what it got to k2config-<env>.json as a side effect. If the platform is later unreachable, that file is served instead of raising, so a K2 outage cannot stop your app from booting. You don't manage the file; the SDK does.

On a laptop (K2_OFFLINE=true): there is no server at all. You own k2config-<env>.json, the SDK only reads it, and no network call is ever made.

K2_OFFLINE=false does not mean "no offline support". It is the mode that gives you the offline fallback — the SDK keeps the file current for you. K2_OFFLINE=true means "never contact the server", which is a different thing entirely.

To go from the first to the second, run once online and take ownership of what the SDK wrote:

K2_BASE_URL=https://k2.acme.com K2_TOKEN= K2_ENV=dev python app.py   # writes the file
cp ~/.k2/config/k2config-dev.json ./k2config-dev.json                 # repo-local wins
#   edit "_k2": { "managed": false } — the SDK will now never overwrite it
K2_OFFLINE=true python app.py                                          # no server

No CLI is needed for any of this: the SDK is the generator.

What it does

  • Token-scoped reads against GET /api/config/token/{env}/current and /properties/{key}, authenticated with Authorization: Bearer <token> (and X-API-Token).
  • Hot reload (watch()) — subscribes to the platform's change stream over Server-Sent Events and calls your handler when an admin edits config, with no polling. Runs on a daemon thread, reconnects with backoff, and falls back to polling if a proxy strips SSE, so a blocked stream degrades instead of failing. Each push also refreshes the local file.
  • TTL cache (cache_ttl_seconds) so repeated reads don't hit the network each call; serves the last value through a failed refresh.
  • Local config file (k2config-<env>.json, on every tier) — plaintext, self-describing, mode 0600, written atomically. Auth errors (401/403/404/421) always surface: they are not availability blips, so a file on disk is deliberately declined.

The file

{
  "_k2": { "org": "acme", "app": "billing", "env": "prod",
           "managed": true, "fetchedAt": "2026-08-03T18:04:11Z", "sdk": "python/1.2.0" },
  "properties": { "db.url": "postgresql://localhost:5432/billing", "db.pool": 20 }
}

Resolution order, first hit wins: $K2_CONFIG_FILE (alone, when set) → ./k2config-<env>.json$K2_CONFIG_DIR or ~/.k2/config. Naming an explicit location is exclusive of the machine default: K2_CONFIG_DIR replaces ~/.k2/config rather than preceding it, so a stale file in your home directory can never quietly satisfy a read. It does not suppress the working-directory candidate, which is always searched first — only K2_CONFIG_FILE does that. All three K2 SDKs order these identically.

The app identity lives inside the file rather than in its name, which keeps K2_APP optional and gives you one predictable string to gitignore. Set K2_APP anyway: it is what turns "which app is this?" into a checked invariant, so two apps sharing a config directory fail with K2_FILE_APP_MISMATCH naming both instead of silently serving each other's config.

The file holds secret values in clear. Add k2config-*.json to your .gitignore. Commit one only when it holds no real secrets.

Errors

Every failure raises K2Error with a stable, greppable code (plus status_code: the HTTP status, or -1 for transport/config/file errors). err.is_availability_error() is true only for K2_UNREACHABLE, K2_TIMEOUT and K2_SERVER_ERROR — the codes eligible for the file.

Code Means
K2_MISSING_BASE_URL / K2_MISSING_TOKEN / K2_INVALID_MODE misconfiguration — raised at construction, not on first read
K2_TOKEN_FILE_UNREADABLE K2_TOKEN_FILE names a file that is missing, unreadable, or empty once stripped — never downgraded to K2_MISSING_TOKEN
K2_MISSING_ENV no environment passed and no K2_ENV — raised on read, since one client can serve several envs
K2_FILE_NOT_FOUND K2_OFFLINE=true and no file; the message lists every path searched
K2_FILE_MALFORMED the file isn't valid K2 JSON — a bug, not an outage, so no fallback to the server
K2_FILE_APP_MISMATCH the file belongs to another app (set K2_CONFIG_DIR per app)
K2_FILE_STALE older than K2_OFFLINE_MAX_AGE
K2_FILE_UNMANAGED _k2.managed is false — you own it, so the SDK refused to overwrite
K2_FILE_NOT_WRITABLE read-only filesystem; logged once at WARN, never raised
K2_UNAUTHORIZED / K2_FORBIDDEN / K2_NOT_FOUND / K2_HOST_NOT_LICENSED the platform refused — never served from the file
K2_UNREACHABLE / K2_TIMEOUT / K2_SERVER_ERROR the platform was unreachable — the file is served when present
K2_REQUEST_FAILED an unexpected non-2xx; reachable and refusing, so no fallback

Configuration via environment

Var Meaning
K2_BASE_URL platform URL, e.g. https://k2.acme.com
K2_TOKEN SDK token (the one secret — never commit it)
K2_TOKEN_FILE path to a file holding the token — the Docker/Kubernetes secret-mount shape (1.2.0+)
K2_ENV default environment for the no-arg reads
K2_APP app slug — optional, but set it: it makes the file's app a checked invariant
K2_OFFLINE true ⇒ never contact the server (default false)
K2_OFFLINE_CACHE false ⇒ keep nothing on disk (default true)
K2_HOT_RELOAD falsewatch() polls instead of subscribing (default: on when online)
K2_CONFIG_DIR directory holding k2config-<env>.json (default ~/.k2/config)
K2_CONFIG_FILE one exact path — wins over everything else
K2_OFFLINE_MAX_AGE e.g. 7d — hard-refuse a file older than this (default: no limit)
K2_CACHE_TTL in-memory TTL, and the watch() polling interval when SSE is unavailable

K2_OFFLINE=true with K2_OFFLINE_CACHE=false is contradictory and raises K2_INVALID_MODE at construction.

Credential precedence: an explicit token= argument → K2_TOKENK2_TOKEN_FILEK2_TOKEN_ENC. A token file is stripped of surrounding whitespace (mounted secrets end in a newline) and read once, at token resolution — not per request. A missing, unreadable or blank file raises K2_TOKEN_FILE_UNREADABLE naming the path, never a silent fallthrough to "no token". K2_TOKEN_FILE is honoured from 1.2.0; earlier versions ignore it.

Cross-language note: the Java SDK raises K2Exception (not K2Error), classifies codes with a K2ErrorCode.Kind enum, adds snapshot(env) / getOfflineCacheAllowed(), and is not zero-dependency (Jackson). Codes and environment variables are identical across all three. See USER_MANUAL.md → Cross-language differences.

Deprecated, honored for one minor release (each logs a WARN): K2_SOURCEK2_OFFLINE (filetrue, serverfalse, autotrue iff a file exists), and K2_CACHE_DIRK2_CONFIG_DIR.

Test

python tests/test_smoke.py   # file store, error taxonomy, SSE hot reload — no network needed

Download files

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

Source Distribution

keykosh_sdk-1.2.0.tar.gz (42.7 kB view details)

Uploaded Source

Built Distribution

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

keykosh_sdk-1.2.0-py3-none-any.whl (28.3 kB view details)

Uploaded Python 3

File details

Details for the file keykosh_sdk-1.2.0.tar.gz.

File metadata

  • Download URL: keykosh_sdk-1.2.0.tar.gz
  • Upload date:
  • Size: 42.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.2

File hashes

Hashes for keykosh_sdk-1.2.0.tar.gz
Algorithm Hash digest
SHA256 85856ca8fbc8ca2cc6defb963b6ede023e8640a08683cca523f41ddbf26052f6
MD5 d078763beaaa2371801de7cb10848765
BLAKE2b-256 64cd8974a4a6c4a8cd96fcb22164049aa898668b595e730030b558a39894383e

See more details on using hashes here.

File details

Details for the file keykosh_sdk-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: keykosh_sdk-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 28.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.2

File hashes

Hashes for keykosh_sdk-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 70c890fc00b435645a2bd705079858310aa0ec0e9812bcb05e6e60f021768246
MD5 5a6856098a1e805be29c473c6882c86c
BLAKE2b-256 031caabf998d2c2fba3e77d3884d1c84b6b18d8f7ae604f59fd582508af1d4fc

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 files

1.1.0

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page