End-to-end encrypted environment variables for teams. Manage workspaces, projects, environments and secrets from code.
Website • Dashboard • Documentation • Services Status
Intro to the Python Package
The official Python client for the Envless API. It has every method of the TypeScript SDK, all 113 across 13 namespaces, under the same names in snake_case and typed end to end. There is a synchronous client and an asynchronous one with the same methods. It runs on Python 3.10 and newer and depends on httpx, anyio, cryptography and typing-extensions.
It carries an API key with write access, so it belongs on a server, in a script or in CI, never in a browser.
Installing
pip install envless-sdk
Or uv add envless-sdk, or poetry add envless-sdk. The package installs as envless-sdk and imports as envless; the plain envless name on PyPI belongs to an unrelated project.
Using
import os
from envless import encrypt_value, envless
passphrase = os.environ['ENVLESS_PASSPHRASE']
workspace_id = envless.me.get()['workspaceId']
envless.variables.create('api', 'production', {
'name': 'STRIPE_SECRET_KEY',
'value': encrypt_value('sk_live_51H...', passphrase, workspace_id),
})
envless is a ready made client that reads ENVLESS_TOKEN the first time it is touched. Call init(...) once at startup to configure it, or build your own with Envless(), which reads the same variable when you pass no token.
Values are encrypted on your machine, never by the server, so a plaintext value is refused. Request bodies and responses are plain dictionaries with the API's own field names, so 'defaultValue' and 'updatedAt' read exactly as they do in the API reference. Every body and response has a TypedDict in envless.types, so your editor completes the keys and a type checker catches a misspelt one.
A client of your own
from envless import Envless
with Envless(timeout=10, max_retries=2) as client:
for variable in client.variables.iterate('api', 'production', product='billing'):
print(variable['name'], variable['updatedAt'])
A client keeps one connection pool, is safe to share between threads, and closes with client.close() or a with block.
Async
import asyncio
from envless import AsyncEnvless
async def main() -> None:
async with AsyncEnvless() as client:
async for variable in client.variables.iterate('api', 'production'):
print(variable['name'])
asyncio.run(main())
AsyncEnvless has every method Envless has, with the same arguments, and runs on asyncio and trio.
Pagination
Every collection has list for one page, list_all for every page at once and iterate to stream items and stop whenever you like. A page is {'items': [...], 'pagination': {'limit', 'offset', 'totalCount', 'hasMore'}}.
Encryption
import os
from envless import decrypt_with_key, derive_workspace_key, envless
workspace_id = envless.me.get()['workspaceId']
key = derive_workspace_key(os.environ['ENVLESS_PASSPHRASE'], workspace_id)
secrets = {
variable['name']: decrypt_with_key(key, variable['value'])
for variable in envless.variables.list_all('api', 'production')
if variable['value'] is not None
}
encrypt_value and decrypt_value derive the key on every call, which costs 200,000 rounds of PBKDF2. To work with many values, derive the key once with derive_workspace_key, or load the ENVLESS_KEY your CI holds with import_workspace_key, then use encrypt_with_key and decrypt_with_key. A derived key is bound to 200,000 iterations, so an older v2: value pinning another count raises DecryptionError there and needs decrypt_value. A WorkspaceKey never prints its bytes. Ciphertext from this package and from the TypeScript SDK, the CLI and the dashboard is interchangeable.
is_ciphertext, passphrase_strength, variable_name_validation and ENCRYPTION_PARAMETERS work exactly as they do in TypeScript. rotate_workspace_passphrase(client=..., ...) re-encrypts an environment and its version history under a new passphrase, and with an AsyncEnvless you await it.
Errors
from envless import EnvlessApiError, envless
try:
envless.variables.get('api', 'production', 'MISSING')
except EnvlessApiError as error:
if error.is_not_found:
print(error.code, error.request_id)
else:
raise
An API refusal is EnvlessApiError, with status, code, resource, field, request_id and retry_after_seconds, plus is_auth, is_scope_missing, is_validation, is_not_found, is_conflict, is_rate_limited and needs_upgrade. No response at all is EnvlessNetworkError, with is_timeout when the deadline passed. A value that cannot be decrypted is DecryptionError. All of them inherit EnvlessError. An unusable variable name is refused before anything is sent, with the same EnvlessApiError the API would answer with.
Webhooks
import os
from fastapi import FastAPI, Request, Response
from envless import verify_webhook_signature
app = FastAPI()
@app.post('/webhooks/envless')
async def webhook(request: Request) -> Response:
event = verify_webhook_signature(
payload=await request.body(),
headers=request.headers,
secret=os.environ['ENVLESS_WEBHOOK_SECRET'],
)
print(event['type'], event['data'])
return Response(status_code=204)
It checks the signature in constant time and rejects a delivery more than five minutes old, then returns the parsed event, or raises WebhookVerificationError. Pass the raw body as bytes or text, since re-serialising it changes the bytes and the signature will not match. Headers can come from any framework, the lookup ignores case.
Configuring
Envless and AsyncEnvless take token, base_url, timeout, max_retries, http_client, user_agent and disable_update_notice. base_url also comes from ENVLESS_API_ENDPOINT. timeout is in seconds, bounds each attempt including the response body, and 0 turns it off. GET, HEAD, PUT and DELETE requests are retried on 408, 429, 500, 502, 503 and 504, up to three times with exponential backoff, waiting what Retry-After asks for up to 30 seconds. Writes that cannot safely repeat are not retried. Pass an httpx.Client, or an httpx.AsyncClient for the async client, as http_client to route through a proxy. Every method also takes timeout= for that one call.
An endpoint no method wraps yet is one raw.request() away, with the client's key, base URL, timeout and retries applied:
from envless import envless
envless.raw.request('/projects', method='POST', body={'name': 'Billing', 'slug': 'billing'})
When a newer version is on PyPI the client says so once on a terminal. ENVLESS_DISABLE_UPDATE_NOTICE=1 or disable_update_notice=True turns that off.
Metadata
Release files for envless-sdk 0.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| envless_sdk-0.0.2.tar.gz | 45.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| envless_sdk-0.0.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 145.7 kB
Release files / envless_sdk-0.0.2.tar.gz
| Download URL | envless_sdk-0.0.2.tar.gz |
|---|---|
| Size | 45.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
078b1443f19f14175f23f07eeb2430158640cd535143894e43d5a918516000da
|
|
BLAKE2b-256 checksum How to use checksums |
cadd4219201bf3b23f0a89829c3498c645b69c226dafbfb81c993270d84c5e4d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / envless_sdk-0.0.2-py3-none-any.whl
| Download URL | envless_sdk-0.0.2-py3-none-any.whl |
|---|---|
| Size | 100.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
14cda3c442f7b4b214129e9ce9bc4f9dffe04b25b6290aa2e5f8c15486556fed
|
|
BLAKE2b-256 checksum How to use checksums |
5369727c53ad50d3e3d2a61413b7c2b7b656d64a7253442aba14a29c1e184b63
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|