Metadata-Version: 2.4
Name: soliconfig
Version: 0.1.0
Summary: Official Python client for soliconfig — remote config (config.fetch) and secrets (secrets.pull) over the REST core.
Project-URL: Homepage, https://soliconfig.com
Project-URL: Documentation, https://soliconfig.com/docs/integrations/python
Project-URL: Source, https://github.com/vennyx-org/soliconfig.com
Author-email: soliconfig <hello@soliconfig.com>
License: MIT
License-File: LICENSE
Keywords: config,feature-flags,remote-config,sdk,secrets,soliconfig
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
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: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx<1,>=0.24
Provides-Extra: dev
Requires-Dist: httpx>=0.24; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Description-Content-Type: text/markdown

# soliconfig — Python SDK

Official Python client for [soliconfig](https://soliconfig.com). A thin wrapper
over the same REST core as the JS SDK (`@vennyx/soliconfig`): server-evaluated
remote config (`config.fetch`) and decrypted secrets (`secrets.pull`).

- Minimal dependencies — only [`httpx`](https://www.python-httpx.org/).
- Python 3.9+.
- Fully type-hinted.

## Install

```bash
pip install soliconfig
```

## Authentication

Every request authenticates with an environment-scoped **`sc_` API key**
(machine identity) sent as `Authorization: Bearer sc_...`. Create one from the
dashboard and provide it to the client.

```python
from soliconfig import Client

client = Client(api_key="sc_live_...")
```

`base_url` defaults to `https://api.soliconfig.com`; override it for self-hosted
or test setups.

## Remote config — `config.fetch`

Server-evaluated, single-shot fetch. The server resolves every parameter for the
evaluation context you pass and returns the result — no local state.

```python
result = client.config.fetch(
    context={
        "randomizationId": "user-42",   # required
        "country": "TR",                # optional
        "platform": "web",              # optional
        "customSignals": {"tier": "pro"},
    },
    template_type="client",             # "client" (default) or "server"
)

print(result.version)                   # e.g. 7

# Values are typed strings on the wire; .value() casts by valueType
banner = result.value("welcome_banner", default=False)   # -> bool
limit = result.value("max_items", default=10)            # -> int/float
config = result.value("feature_config", default={})      # -> parsed JSON

# Or fetch-then-read one key in a single call:
theme = client.config.get("theme", {"randomizationId": "user-42"}, default="light")
```

`fetch` returns a `ConfigFetchResult` with `.version` and `.evaluated`
(`dict[str, EvaluatedValue]`). Each `EvaluatedValue` has `.value`, `.value_type`,
`.source`, `.matched_condition`, and `.cast()`.

### Quota / entitlement errors

The config gate can reject a fetch with HTTP `429` and a machine-readable code:

```python
from soliconfig import SoliconfigApiError

try:
    result = client.config.fetch(context={"randomizationId": "user-42"})
except SoliconfigApiError as err:
    if err.code == "quota_exceeded":     # free plan hard cap
        ...
    elif err.code == "overage_blocked":  # unpaid overage on a paid plan
        ...
    else:
        raise
```

## Secrets — `secrets.pull`

Pulls the latest **decrypted** secrets for an environment. Decryption happens
**server-side** via the vault endpoint, so the Python client needs no private
key and no crypto dependency — just the `sc_` key.

```python
secrets = client.secrets.pull(org="org_abc", environment="env_xyz")

print(secrets.version)              # version of the env values
db_url = secrets["DATABASE_URL"]    # SecretPullResult behaves like a mapping
for name, value in secrets.items():
    ...
```

> **`requireApproval` must be `false`.** Server-side decrypt with an `sc_` key
> only works when the environment vault has `requireApproval=false`. If approval
> is required, the API returns `202 pending` and `secrets.pull` raises
> `SoliconfigApprovalRequiredError` (a human admin must approve each decrypt).
> The `sc_` key must also have `read` or `admin` scope — `encrypt-only` keys
> can write but never decrypt.

```python
from soliconfig import SoliconfigApprovalRequiredError

try:
    secrets = client.secrets.pull(org="org_abc", environment="env_xyz")
except SoliconfigApprovalRequiredError as err:
    print("Pending approval:", err.approval_id)
```

## Errors

| Class | When |
| --- | --- |
| `SoliconfigError` | Base class for all SDK errors. |
| `SoliconfigApiError` | Any non-2xx response. Carries `.status` and (for `429`) `.code`. |
| `SoliconfigApprovalRequiredError` | Vault `requireApproval=true`; carries `.approval_id`. |

## Resource cleanup

`Client` holds an `httpx` connection pool. Close it when done, or use it as a
context manager:

```python
with Client(api_key="sc_...") as client:
    result = client.config.fetch(context={"randomizationId": "user-42"})
```

## License

MIT
