Metadata-Version: 2.4
Name: envless-sdk
Version: 0.0.2
Summary: The official Python SDK for the Envless API. Manage workspaces, products, projects, environments, variables, versions, members, roles, keys and webhooks from code, with every value encrypted on your machine.
Project-URL: Homepage, https://envless.cloud
Project-URL: Documentation, https://envless.cloud/docs/python/overview
Project-URL: Reference, https://envless.cloud/docs/python/helpers
Project-URL: Changelog, https://envless.cloud/docs/python/changelog
Project-URL: Dashboard, https://envless.cloud/dashboard
Project-URL: Status, https://status.envless.cloud
Author: Envless
License-Expression: LicenseRef-Proprietary
Keywords: aes-gcm,api,api client,config management,encrypted secrets,env,env vars,environment variables,envless,pbkdf2,sdk,secret management,secrets,secrets versioning
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: AnyIO
Classifier: Framework :: AsyncIO
Classifier: Framework :: Trio
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: anyio>=3.7
Requires-Dist: cryptography>=42
Requires-Dist: httpx<1,>=0.27
Requires-Dist: typing-extensions>=4.12
Description-Content-Type: text/markdown

<div align='center'>
   <a href='https://envless.cloud'>
        <img
            src='https://cdn-prod.envless.cloud/assets/npm/logo.svg'
            alt='Envless Logo'
            width='180'
        />
   </a>

   <br />
</div>

<p align='center'>
    End-to-end encrypted environment variables for teams. Manage workspaces, projects, environments and secrets from code.
</p>

<p align='center'>
    <a href='https://envless.cloud'>
        <b>
            Website
        </b>
    </a>
    •
    <a href='https://envless.cloud/dashboard'>
        <b>
            Dashboard
        </b>
    </a>
    •
    <a href='https://envless.cloud/docs/python/overview'>
        <b>
            Documentation
        </b>
    </a>
    •
    <a href='https://status.envless.cloud'>
        <b>
            Services Status
        </b>
    </a>
</p>

<br />

## 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
```bash
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
```python
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
```python
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
```python
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
```python
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
```python
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
```python
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:

```python
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.