Metadata-Version: 2.4
Name: lockersm
Version: 2.0.0
Summary: Official Locker Secrets Python SDK
Author-email: CyStack <contact@locker.io>
License-Expression: Apache-2.0
Project-URL: Homepage, https://locker.io
Project-URL: Documentation, https://pypi.org/project/lockersm/
Project-URL: Support, https://support.locker.io
Project-URL: Source, https://git.cystack.org/locker/secrets-sdk/python
Keywords: locker,secrets,passwords,security
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python
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
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography>=44.0.0
Requires-Dist: requests<3,>=2.32.0
Dynamic: license-file

# Locker Secrets Python SDK

[![PyPI](https://img.shields.io/pypi/v/lockersm.svg)](https://pypi.org/project/lockersm/)
[![Python](https://img.shields.io/pypi/pyversions/lockersm.svg)](https://pypi.org/project/lockersm/)
[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)

The official Python SDK for reading and managing secrets in
[Locker Secrets Manager](https://locker.io).

The PyPI distribution is named `lockersm`; the Python import package is named
`locker`. Version 2 communicates with the Locker CLI through the stable
`locker.sdk` JSON-RPC protocol instead of parsing human-facing command output.
It supports both Locker Cloud and self-hosted Locker deployments.

## Install

```shell
python -m pip install --upgrade lockersm
```

You do not need to install the Locker CLI separately in the standard managed
mode. The SDK downloads a supported binary on first use and verifies the
release signature, SHA-256 digest, platform, architecture, and protocol
compatibility before execution.

### Support matrix

| Component | Supported versions |
| --- | --- |
| Python | 3.10, 3.11, 3.12, 3.13, 3.14 |
| Linux managed CLI | x86-64, ARM64 |
| macOS managed CLI | Intel, Apple silicon |
| Windows managed CLI | x86-64 |
| SDK protocol | `locker.sdk` v1 |

For an air-gapped or centrally managed deployment, provide an explicit Locker
CLI path with `LOCKER_CLI_PATH` or `binary_path`.

## Quick start

Create an access key in your Locker Secrets project, then expose the
credentials to the application environment.

Linux and macOS:

```shell
export LOCKER_ACCESS_KEY_ID="your-access-key-id"
export LOCKER_SECRET_ACCESS_KEY="your-secret-access-key"
```

Windows PowerShell:

```powershell
$env:LOCKER_ACCESS_KEY_ID = "your-access-key-id"
$env:LOCKER_SECRET_ACCESS_KEY = "your-secret-access-key"
```

Read a required secret:

```python
from locker import Locker

client = Locker.from_env()
database_password = client.get_required(
    "DATABASE_PASSWORD",
    environment_name="production",
)

# Pass database_password directly to the component that needs it.
# Never print or log secret values.
```

`get_required()` raises `ResourceNotFoundError` when the key does not exist.
Use `get()` only when a fallback is intentionally safe:

```python
log_level = client.get(
    "LOG_LEVEL",
    environment_name="production",
    default_value="info",
)
```

The default value is returned only for a genuine not-found response.
Authentication, permission, network, protocol, storage, and server failures
are still raised.

## Configure the client

`Locker.from_env()` is the recommended constructor. It recognizes:

| Environment variable | Purpose |
| --- | --- |
| `LOCKER_ACCESS_KEY_ID` | Project access key ID |
| `LOCKER_SECRET_ACCESS_KEY` | Project secret access key |
| `LOCKER_API_BASE` | Cloud or self-hosted API base URL |
| `LOCKER_CLI_PATH` | Absolute path to a deployment-managed CLI |
| `LOCKER_LOG` | `debug`, `info`, `warning`, or `error` |

The default cloud endpoint is
`https://api.locker.io/locker_secrets`.

Self-hosted deployment:

```python
from locker import Locker

client = Locker.from_env(
    api_base="https://secrets.example.com/locker_secrets",
)
```

Deployment-managed CLI:

```python
from locker import Locker

client = Locker.from_env(
    binary_path="/opt/locker/bin/locker",
)
```

The CLI path must be absolute and point to a regular, non-link file. Explicit
paths bypass managed updates and are never resolved through ambient `PATH`.

For migration from version 1, the SDK still recognizes `ACCESS_KEY_ID`,
`SECRET_ACCESS_KEY`, `LOCKER_ACCESS_KEY_SECRET`, and `ACCESS_KEY_SECRET`.
New deployments should use only the canonical `LOCKER_*` names.

## Secrets

### Read secret metadata

`retrieve()` returns the complete secret resource object:

```python
secret = client.retrieve(
    "DATABASE_PASSWORD",
    environment_name="production",
)
print(secret.id, secret.key, secret.environment_name)
```

Secret objects contain plaintext values. Do not serialize, print, or include
them in logs.

### List secrets

```python
for secret in client.list(environment_name="production"):
    print(secret.id, secret.key, secret.environment_name)
```

For large projects, use bounded cursor pagination:

```python
cursor = None

while True:
    page = client.list_page(
        environment_name="production",
        page_size=100,
        cursor=cursor,
    )
    for secret in page.items:
        print(secret.id, secret.key)
    cursor = page.next_cursor
    if cursor is None:
        break
```

### Create and update secrets

Read secret input without echoing it in a terminal:

```python
from getpass import getpass

created = client.create(
    key="PAYMENT_API_KEY",
    value=getpass("New secret value: "),
    environment_name="staging",
)

updated = client.modify(
    key=created.key,
    value=getpass("Updated secret value: "),
    environment_name="staging",
)
```

Secret values are sent to `locker sdk` in the JSON request on standard input.
They are not placed in process arguments or logs.

### Export secrets

`export()` returns a plaintext `str` in `dotenv` or compact `json` format:

```python
dotenv_payload = client.export(
    environment_name="production",
    output_format="dotenv",
)
```

Treat the returned string as sensitive. Avoid logs, shell arguments, command
history, and unprotected files.

Secret deletion is not part of protocol v1. The canonical `Locker` client does
not expose a delete method; legacy resource-object delete helpers fail closed
with `InvalidRequestError`.

## Environments

```python
environments = client.list_environments()
for environment in environments:
    print(environment.name, environment.external_url)
```

Use `list_environments_page()` for cursor pagination.

The two lookup methods intentionally have different not-found contracts:

- `get_environment("production")` returns `None`.
- `retrieve_environment("production")` raises `ResourceNotFoundError`.

Create or update an environment:

```python
created = client.create_environment(
    name="staging",
    external_url="https://staging.example.com",
)

updated = client.modify_environment(
    name=created.name,
    external_url="https://new-staging.example.com",
)
```

Environment deletion is not part of protocol v1.

## Errors

Locker-defined transport, protocol, authentication, and API failures derive
from `locker.error.LockerError`. Standard Python argument errors can still be
raised for locally invalid values.

```python
import logging

from locker.error import LockerError, RateLimitError

try:
    value = client.get_required("PAYMENT_API_KEY")
except RateLimitError as exc:
    logging.warning(
        "Locker request was rate limited (request_id=%s, retryable=%s)",
        exc.request_id,
        exc.retryable,
    )
    raise
except LockerError as exc:
    logging.error(
        "Locker request failed (type=%s, code=%s, kind=%s, request_id=%s)",
        type(exc).__name__,
        exc.code,
        exc.kind,
        exc.request_id,
    )
    raise
```

| RPC code | Exception | Meaning |
| ---: | --- | --- |
| `-32001` | `AuthenticationError` | Credentials were rejected |
| `-32003` | `PermissionDeniedError` | Access is not permitted |
| `-32004` | `ResourceNotFoundError` | Resource does not exist |
| `-32029` | `RateLimitError` | Request was rate limited |
| `-32050` | `APIConnectionError` | Locker API could not be reached |
| `-32051` | `APIServerError` | Locker API failed the request |
| `-32060` | `LocalStorageError` | Local secure state failed |
| `-32700` to `-32600` | `ProtocolError` | Invalid JSON-RPC exchange |

Each exception exposes `code`, `kind`, `retryable`, and `request_id`. Avoid
logging exception bodies or application data around a secret operation.

## CLI management and verification

Importing `locker` and constructing `Locker` are side-effect free. The SDK
does not create a cache directory, access the network, or start a process until
the first operation or an explicit `install_cli()` call.

Managed mode:

1. Checks the signed release channel on first use and at most once every six
   hours after a successful check.
2. Verifies the embedded Locker Ed25519 trust root, signed latest document,
   signed manifest, artifact size, SHA-256, detached signature, executable
   header, and protocol range.
3. Installs releases into immutable
   `~/.locker/sdk-cli/python/bin/releases/<version>/` directories.
4. Switches the current pointer only after complete verification.
5. Rejects rollback attempts and same-version mutation.

Force an immediate signed update check:

```python
installed_path = client.install_cli()
```

If the release service is temporarily unreachable, a previously accepted
binary may be reused only after its cached metadata and artifact are verified
again. Invalid signatures, rollback state, incompatible platforms, and
protocol errors always fail closed.

## Logging

Set `LOCKER_LOG` or the constructor's `log` argument:

```shell
export LOCKER_LOG="info"
```

```python
client = Locker.from_env(log="info")
```

SDK logs contain operational metadata such as method, request ID, duration,
and process status. The SDK never emits credentials, request or response
bodies, custom headers, or secret values.

## Migrating to 2.x

Version 2:

- Requires Python 3.10 or newer.
- Uses the stable `locker.sdk` JSON-RPC protocol v1.
- Downloads only signed Locker CLI releases in managed mode.
- Uses canonical `LOCKER_ACCESS_KEY_ID` and
  `LOCKER_SECRET_ACCESS_KEY` variables.
- Adds `Locker.from_env()`, `get_required()`, typed pagination, and explicit
  CLI lifecycle methods.
- Returns a default value only for `ResourceNotFoundError`.

Applications must use this SDK or the `locker sdk` protocol. Do not parse
human-facing CLI output.

## Versioning and releases

The package follows Semantic Versioning and publishes canonical PEP 440
versions:

- PyPI package: `MAJOR.MINOR.PATCH`
- Git tag and GitLab Release: `vMAJOR.MINOR.PATCH`

Every accepted merge commit on protected `main` automatically runs the complete
release flow: derive version, build deterministic wheel and source
distribution, verify metadata and the CLI trust root, publish to PyPI, and
create the matching source tag and GitLab Release. There is no manual publish
or pre-created tag step.

The version is deterministic for the commit's first-parent position on
`main`. Retrying the same pipeline reuses the same version, and concurrent
main pipelines cannot assign the same version. Release builds and source tags
share the version through `setuptools-scm`, so installing a tag reproduces its
published package version. CI rejects direct, fast-forward, rebase, or
single-parent updates to the release line.

## Development

Install the reviewed development toolchain:

```shell
python -m pip install -r requirements-dev.txt
```

Run the supported-Python test matrix:

```shell
tox
```

Useful focused commands:

```shell
tox -e py310
tox -e lint
tox -e type
python scripts/verify_ci_supply_chain.py
```

Integration tests are opt-in. A protocol handshake needs an explicit released
CLI:

```text
LOCKER_TEST_CLI_PATH=/absolute/path/to/locker
```

Live vault tests additionally require:

```text
LOCKER_RUN_INTEGRATION=1
LOCKER_TEST_ACCESS_KEY_ID=your-test-access-key
LOCKER_TEST_SECRET_ACCESS_KEY=your-test-secret-key
```

Run them with `tox -e integration`.

## Security

If you discover a security issue, email
[contact@locker.io](mailto:contact@locker.io). Do not disclose
vulnerabilities in a public issue.

General product documentation and support are available at
[support.locker.io](https://support.locker.io).

## License

Copyright CyStack Corporation.

Licensed under the
[Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).
Locker is developed and maintained by CyStack.
