Metadata-Version: 2.5
Name: savcfg
Version: 0.4.6
Summary: ConfigService implementation for SAVIA software projects
Author-email: Salvador Ruiz <sruiz@gruposavia.eu>
Requires-Python: >=3.13
Requires-Dist: azure-identity>=1.25.1
Requires-Dist: azure-keyvault-secrets>=4.10.0
Description-Content-Type: text/markdown

# Savia config service

Small library for reading and writing application config values.
Main backend here is Azure Key Vault.

## Public API

Top-level exports:

```python
from savcfg import ConfigService, VariableNotFound
```

Other modules:

```python
from savcfg.edit import ConfigEditor
from savcfg.keyvault import KeyVaultVars
from savcfg.bootstrap import get_savia_config_service, get_savia_config_editor
```

## Read values

`ConfigService` takes the application name first, then one or more readers:

```python
from savcfg import ConfigService, VariableNotFound
from savcfg.read import VarsReader


class InMemoryVars(VarsReader):
    def __init__(self, data: dict[str, str]):
        self._data = data

    def list_vars(self) -> list[str]:
        return list(self._data.keys())

    def read(self, key: str) -> str:
        return self._data.get(key, "")


service = ConfigService(
    "billing",
    InMemoryVars({
        "billing.timeout": "30",
        "db": '{"host": "db.internal", "port": 5432}',
    }),
)

timeout = service.get_primitive("timeout")
db = service.get_group("db")
```

Behavior:

- `get_primitive(key)` returns the parsed JSON value.
- `get_primitive(key)` raises `VariableNotFound` when the key is missing.
- `get_group(key)` returns a `Group` object, which behaves like a dict.
- Missing groups return an empty `Group`.
- Group values must decode to a JSON object. Any other JSON type raises `TypeError`.

## Data format

Stored values are JSON text.

Examples:

- string: `"value"`
- integer: `1`
- float: `1.2`
- boolean: `true`
- null: `null`
- group: `{"token": "value"}`

String secrets in Key Vault must include the JSON quotes.

## Precedence rules

Primitive lookup:

- Checks readers in constructor order.
- Inside each reader, `app_name.key` is checked before `key`.
- First hit wins.

Group lookup:

- Reads the exact keys `key` and `app_name.key`.
- Does not build groups from dotted child keys like `group.var1`.
- Merges readers from last to first, so earlier readers override later readers.
- App-specific group data overlays shared group data.

## Azure Key Vault adapter

`KeyVaultVars` implements both the read and edit interfaces.

```python
from azure.identity import DefaultAzureCredential
from savcfg import ConfigService
from savcfg.keyvault import KeyVaultVars


credential = DefaultAzureCredential()
service = ConfigService(
    "billing",
    KeyVaultVars("savia-production", credential),
    KeyVaultVars("savia-common", credential),
)
```

Key name mapping:

- config key `group.value_name`
- Key Vault secret `group--value-name`

With `uppercase=True`, normalized keys are uppercased when listed or read.

## Edit values

`ConfigEditor` reads JSON values and writes them back with `json.dumps()`.

```python
from azure.identity import DefaultAzureCredential
from savcfg.edit import ConfigEditor
from savcfg.keyvault import KeyVaultVars


editor = ConfigEditor(KeyVaultVars("savia-development", DefaultAzureCredential()))

editor.set("feature_flags", {"new_ui": True})
value = editor.get("feature_flags")
keys = editor.list()
```

## Savia bootstrap helpers

Helpers in `savcfg.bootstrap` create ready-to-use services with `DefaultAzureCredential`:

- `get_savia_config_service("prod", app_name)` -> `savia-production` + `savia-common`
- `get_savia_config_service("pre", app_name)` -> `savia-preproduction` + `savia-common`
- `get_savia_config_service("devel", app_name)` -> `savia-development`
- `get_savia_config_editor("prod" | "pre" | "common" | "devel")`
