# EdpConfigProvider

A configuration provider library that abstracts fetching configuration from multiple backends (local JSON files, AWS AppConfig, AWS SSM Parameter Store) behind a unified interface.

## Installation

```bash
poetry install
```

For AWS support (AppConfig, SSM Parameter Store):

```bash
pip install edpconfigprovider[aws]
```

## Configuration Provider Type

The application uses the `CONFIG_PROVIDER_TYPE` environment variable to determine how configuration is loaded. Supported values are:

### JSON (local configuration)

- **json**: Loads configuration from a static JSON folder located beside the project folder. Use this for local development or static configuration scenarios.
    - **CONFIG_PATH**: path to `config.json`
    - **TENANT_CONFIG_PATH**: path to a JSON file mapping tenant names to their config paths. Ex: `config/tenant.json`

### AWS AppConfig

- **aws_appconfig**: Loads configuration dynamically from AWS AppConfig. Recommended for production or dynamic configuration needs.
- **aws_appconfig_agent**: Uses AWS AppConfig as a Lambda agent for configuration management, enabling advanced AppConfig features and integration.
    - General Configuration
        - **AWS_APPCONFIG_APPLICATION_NAME**: AWS AppConfig Application Name
        - **AWS_APPCONFIG_PROFILE_NAME**: AWS AppConfig Profile Name
        - **AWS_APPCONFIG_ENVIRONMENT_NAME**: AWS AppConfig Environment Name
    - Tenant configuration
        - **AWS_APPCONFIG_TENANT_APPLICATION_NAME_FORMAT**: AWS AppConfig Application Name for a tenant_name. Ex: `sample-app-tenant-{}`, where the `{}` will be replaced for the `tenant_name` variable
        - **AWS_APPCONFIG_TENANT_PROFILE_NAME**: AWS AppConfig Profile Name for tenant configuration
        - **AWS_APPCONFIG_TENANT_ENVIRONMENT_NAME**: AWS AppConfig Environment Name for tenant configuration

### AWS SSM Parameter Store

- **aws_ssm**: Loads configuration from AWS SSM Parameter Store by path. All parameters under the given path are fetched recursively and returned as a nested dictionary (path segments become nested keys). SecureString values are decrypted automatically. `StringList` values are split by comma, and values that are valid JSON are parsed into their corresponding Python types.
    - **AWS_SSM_PARAMETER_PATH**: The SSM parameter path prefix to fetch (e.g., `/myapp/config/`)
    - **AWS_SSM_RECURSIVE**: Whether to fetch parameters recursively under the path (default: `true`)
    - **AWS_SSM_SPLIT_PATH**: Whether to split parameter path segments into nested dictionary keys (default: `true`). When `false`, parameters are returned as a flat dictionary keyed by their path relative to `AWS_SSM_PARAMETER_PATH`.
    - **AWS_SSM_DECRYPT**: Whether to decrypt SecureString parameters (default: `true`)

Set the `CONFIG_PROVIDER_TYPE` variable in your environment or `.env` file to control the configuration source.

## Scripts

Setup Claude Code skills (symlinks `.claude/skills/*` into `~/.claude/skills/`):

```bash
poetry run task setup-skills
```

## Usage

### Simple configuration

```python
from edpconfigprovider.builder import ConfigProviderBuilder

builder = ConfigProviderBuilder(transform_method=lambda data: MyConfig(**data))
config = builder.build_config()
```

### Tenant-based (multi-tenant) configuration

```python
from edpconfigprovider.builder import TenantConfigProviderBuilder

builder = TenantConfigProviderBuilder(transform_method=lambda name, data: MyTenantConfig(**data))
config = builder.build_config(tenant_name="my-tenant")
```

### Custom providers

```python
from edpconfigprovider.builder._builder_registry import set_provider

set_provider("my_custom_type", my_provider_factory)
```

### Environment-based builders

`EnvConfigProviderBuilder` and `EnvTenantConfigProviderBuilder` build providers from environment variables (and/or kwargs), dispatching to JSON, AWS AppConfig, AWS AppConfig Agent, or AWS SSM based on `CONFIG_PROVIDER_TYPE`.

```python
from edpconfigprovider.util.environment import EnvConfigProviderBuilder

builder = EnvConfigProviderBuilder(transform_method=lambda data: MyConfig(**data))
config = builder.build_config()
```

`CONFIG_PROVIDER_TYPE` accepts a comma-separated list (e.g. `aws_appconfig,json`) — providers are wrapped in `FallbackConfigProvider` and tried in order.

For multi-tenant scenarios:

```python
from edpconfigprovider.util.environment import EnvTenantConfigProviderBuilder

builder = EnvTenantConfigProviderBuilder(transform_method=lambda data: MyTenantConfig(**data))
config = builder.build_config(tenant_name="acme")
```

Tenant variables use the `TENANT_` prefix (e.g. `TENANT_CONFIG_PROVIDER_TYPE`, `TENANT_AWS_APPCONFIG_APPLICATION_NAME`). Resolved values support `{tenant_name}` substitution, so a value like `sample-app-{tenant_name}` becomes `sample-app-acme`.

When a single configuration document holds entries for several tenants, set `TENANT_MULTI_CONFIG=true`. The builder then looks up the tenant entry using:

- **`TENANT_BASE_PATH`** *(optional)* — comma-separated path to descend into the document before lookup (e.g. `tenants,active`).
- **`TENANT_KEY`** *(default `key`)* — when the resolved node is a list, the field name used to match the entry (`entry[TENANT_KEY] == tenant_name`). When the resolved node is a dict, the tenant name is used directly as the key.

If the tenant is not found, `MissingTenantConfigurationException` is raised.

### Convenience helpers

Two thin wrappers in `edpconfigprovider.util.custom` cover the common cases:

```python
from edpconfigprovider.util.custom import (
    build_auto_config_mapper,
    build_auto_tenant_config_factory,
)

# Single-tenant: returns a ConfigMapper[T]
mapper = build_auto_config_mapper(transformer_method=lambda d: MyConfig(**d))
config = mapper.build_config()

# Multi-tenant: returns an EnvTenantConfigFactory[T] — call build_config(tenant_name=...) per request
factory = build_auto_tenant_config_factory(transformer_method=lambda d: MyTenantConfig(**d))
config = factory.build_config(tenant_name="acme")
```

Both helpers accept `**kwargs` that are forwarded to the underlying `ProviderParameters`, allowing per-call overrides (e.g. `aws_appconfig={"application_name": "..."}`) without touching env vars. `build_auto_config_mapper` additionally accepts `config_prefix=` to switch the env-var prefix away from the default `CONFIG_*`.

### Placeholder substitution (`replace_parameters`)

Both helpers accept a `replace_parameters: Dict[str, str]` argument that substitutes `{placeholder}` tokens in resolved configuration values. Substitution applies to values coming from environment variables and from `default_value` fallbacks.

```python
import os

os.environ["AWS_SSM_PARAMETER_PATH"] = "/app/{cluster}/config"

mapper = build_auto_config_mapper(
    transformer_method=lambda d: MyConfig(**d),
    replace_parameters={"cluster": "blue"},
)
# AWS_SSM_PARAMETER_PATH resolves to "/app/blue/config"
```

Multiple placeholders are supported (`"/{env}/{cluster}/config"` with `{"env": "dev", "cluster": "blue"}`). For tenant factories, the active `tenant_name` is injected automatically and can be combined with custom placeholders (`"/app/{cluster}/context/{tenant_name}"`).

### AWS AppConfig Agent host

`AwsAppconfigAgentProvider` resolves its target host from the `AWS_APPCONFIG_AGENT_HOST` environment variable (default `localhost`) and its port from `AWS_APPCONFIG_EXTENSION_HTTP_PORT` (default `2772`). Set `AWS_APPCONFIG_AGENT_HOST` when the agent runs as a sidecar reachable by a Docker hostname rather than `localhost`.
