etcd-dynamic-config


Nameetcd-dynamic-config JSON
Version 0.1.0 PyPI version JSON
download
home_pageNone
SummaryA Python library for managing etcd-based configurations with caching and real-time updates
upload_time2025-09-10 16:52:49
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseMIT
keywords etcd configuration cache watcher async
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Etcd Dynamic Config

[![PyPI version](https://badge.fury.io/py/etcd-dynamic-config.svg)](https://pypi.org/project/etcd-dynamic-config/)
[![Python versions](https://img.shields.io/pypi/pyversions/etcd-dynamic-config.svg)](https://pypi.org/project/etcd-dynamic-config/)
[![License](https://img.shields.io/pypi/l/etcd-dynamic-config.svg)](https://github.com/ton5169/etcd-dynamic-config/blob/main/LICENSE)

A robust Python library for managing etcd-based configurations with caching, real-time updates, and graceful fallbacks.

## Features

- ๐Ÿš€ **High Performance**: In-memory caching for fast configuration access
- ๐Ÿ”„ **Real-time Updates**: Automatic watching for configuration changes
- ๐Ÿ›ก๏ธ **Resilient**: Graceful fallbacks to local environment variables
- ๐Ÿ”’ **Secure**: Support for TLS and authentication
- ๐Ÿงต **Thread-safe**: Safe concurrent access to configuration data
- ๐Ÿ“Š **Observable**: Structured logging and monitoring support
- ๐ŸŽฏ **Type-safe**: Built-in type coercion and validation

## Installation

```bash
pip install etcd-dynamic-config
```

For development with extra tools:

```bash
pip install etcd-dynamic-config[dev]
```

## Quick Start

### Basic Usage

```python
import asyncio
from etcd_dynamic_config import etcd_config

async def main():
    # Start the configuration manager
    success = await etcd_config.start()
    if success:
        # Get all configurations
        configs = await etcd_config.get_all_configs()
        print(f"Database URL: {configs.get('postgres_dsn')}")

        # Access specific config values
        api_url = configs.get('categorization_api_url')
        print(f"API URL: {api_url}")

    # Clean shutdown
    await etcd_config.stop()

# Run the example
asyncio.run(main())
```

### Environment Variables

Set these environment variables to configure etcd connection:

```bash
# Etcd connection settings
export EtcdSettings__HostName="http://localhost:2379"
export EtcdSettings__UserName="your-username"
export EtcdSettings__Password="your-password"
export EtcdSettings__RootKey="/APPS/ControlUnit"

# Optional: Use local environment variables instead of etcd
export USE_LOCAL_CONFIG="false"

# Optional: TLS settings
export EtcdSettings__CaCertPath="/path/to/ca-cert.pem"
```

### Local Development

For local development, set `USE_LOCAL_CONFIG=true` and define configurations as environment variables:

```bash
export USE_LOCAL_CONFIG="true"
export CATEGORIZATION_API_URL="http://localhost:8000"
export POSTGRES_DSN="postgresql://user:pass@localhost:5432/db"
export LOG_LEVEL="DEBUG"
```

## Advanced Usage

### Custom Configuration Keys

#### Using Built-in ControlUnit Client

```python
from etcd_dynamic_config import EtcdClient

# Use the built-in ControlUnit client (backward compatible)
client = EtcdClient()

# Get specific configuration values
key_map = client.get_control_unit_key_map()
print("Available config keys:", list(key_map.values()))

# Direct key access
values = client.get_values_by_keys(["/APPS/ControlUnit/LogLevel"])
print("Log level:", values.get("/APPS/ControlUnit/LogLevel"))
```

#### Creating Custom Clients

```python
from etcd_dynamic_config import BaseEtcdClient

class MyAppEtcdClient(BaseEtcdClient):
    def get_config_prefix(self) -> str:
        return "/apps/myapp/prod"

    def _build_etcd_key_map(self) -> Dict[str, str]:
        base = self.get_config_prefix()
        return {
            f"{base}/DatabaseUrl": "database_url",
            f"{base}/RedisUrl": "redis_url",
            f"{base}/ApiKey": "api_key",
        }

    def _build_env_var_map(self) -> Dict[str, str]:
        return {
            "database_url": "MYAPP_DATABASE_URL",
            "redis_url": "MYAPP_REDIS_URL",
            "api_key": "MYAPP_API_KEY",
        }

# Use custom client
client = MyAppEtcdClient(use_local_config=True)
config = client.get_config()
print("Database URL:", config.get("database_url"))
```

### Async Configuration Manager

```python
import asyncio
from etcd_dynamic_config import EtcdConfig

async def config_worker():
    config_manager = EtcdConfig()

    # Start with watching
    await config_manager.start()

    try:
        while True:
            configs = await config_manager.get_all_configs()

            # Your application logic here
            api_token = configs.get('categorization_api_token')
            if api_token:
                print(f"Using API token: {api_token[:10]}...")

            await asyncio.sleep(60)  # Check every minute

    finally:
        await config_manager.stop()

asyncio.run(config_worker())
```

### Error Handling

```python
import asyncio
from etcd_dynamic_config import etcd_config

async def robust_config_access():
    try:
        await etcd_config.start()

        configs = await etcd_config.get_all_configs()

        # Safe access with defaults
        timeout = configs.get('ai_http_timeout_seconds', 30.0)
        max_conn = configs.get('ai_http_max_connections', 10)

        print(f"Timeout: {timeout}s, Max connections: {max_conn}")

    except Exception as e:
        print(f"Configuration error: {e}")
        # Fallback to hardcoded defaults
        timeout = 30.0
        max_conn = 10

    finally:
        await etcd_config.stop()
```

## Configuration Schema

The library doesn't impose any specific configuration schema - **you define your own keys!**

### Defining Your Configuration Schema

```python
from etcd_dynamic_config import BaseEtcdClient

class MyAppClient(BaseEtcdClient):
    def get_config_prefix(self) -> str:
        return "/myapp/production"

    def _build_etcd_key_map(self) -> Dict[str, str]:
        base = self.get_config_prefix()
        return {
            # Your custom etcd keys -> internal names
            f"{base}/Database/Host": "db_host",
            f"{base}/Database/Port": "db_port",
            f"{base}/Database/Name": "db_name",
            f"{base}/Cache/RedisUrl": "redis_url",
            f"{base}/API/SecretKey": "api_secret",
            f"{base}/Features/EnableCache": "enable_cache",
            f"{base}/Monitoring/LogLevel": "log_level",
        }

    def _build_env_var_map(self) -> Dict[str, str]:
        return {
            # Internal names -> environment variables
            "db_host": "MYAPP_DB_HOST",
            "db_port": "MYAPP_DB_PORT",
            "db_name": "MYAPP_DB_NAME",
            "redis_url": "MYAPP_REDIS_URL",
            "api_secret": "MYAPP_API_SECRET",
            "enable_cache": "MYAPP_ENABLE_CACHE",
            "log_level": "MYAPP_LOG_LEVEL",
        }
```

### Custom Type Coercion

```python
def _coerce_config_value(self, internal_name: str, value):
    """Define your own type coercion rules."""
    if internal_name == "db_port":
        return int(value) if value else 5432
    elif internal_name == "enable_cache":
        return str(value).lower() in ("1", "true", "yes", "on")
    elif internal_name == "api_secret":
        # Don't log secrets in plain text
        return str(value) if value else ""

    # Use default coercion for other values
    return super()._coerce_config_value(internal_name, value)
```

### Your Configuration Documentation

Create documentation for **your** configuration keys:

| Your Etcd Key                            | Environment Variable | Type | Default   | Description           |
| ---------------------------------------- | -------------------- | ---- | --------- | --------------------- |
| `/myapp/production/Database/Host`        | `MYAPP_DB_HOST`      | str  | localhost | Database host         |
| `/myapp/production/Database/Port`        | `MYAPP_DB_PORT`      | int  | 5432      | Database port         |
| `/myapp/production/Cache/RedisUrl`       | `MYAPP_REDIS_URL`    | str  | -         | Redis connection URL  |
| `/myapp/production/Features/EnableCache` | `MYAPP_ENABLE_CACHE` | bool | false     | Enable caching        |
| `/myapp/production/Monitoring/LogLevel`  | `MYAPP_LOG_LEVEL`    | str  | INFO      | Application log level |

See [examples/schema_documentation_example.py](examples/schema_documentation_example.py) for a complete example of documenting and implementing a custom configuration schema.

### Built-in ControlUnit Client

For backward compatibility, the library includes a pre-configured client for ControlUnit applications:

```python
from etcd_dynamic_config import EtcdClient, etcd_client

# Uses the ControlUnit schema automatically
client = EtcdClient()
config = client.get_config()

# Access ControlUnit-specific keys
api_url = config.get("categorization_api_url")
db_dsn = config.get("postgres_dsn")
```

The ControlUnit client handles these keys automatically (see [ControlUnitEtcdClient](https://github.com/ton5169/etcd-dynamic-config/blob/main/etcd_dynamic_config/core/control_unit.py) for details).

## Architecture

### Components

1. **EtcdClient**: Low-level etcd operations

   - Connection management
   - Authentication and TLS
   - Key-value operations
   - Watching capabilities

2. **EtcdConfig**: High-level configuration management
   - Caching layer
   - Type coercion
   - Real-time updates
   - Health monitoring

### Thread Safety

All operations are thread-safe:

- Configuration cache uses `threading.RLock()`
- Async operations properly handle concurrency
- Watcher callbacks are serialized

### Error Recovery

The library implements several recovery mechanisms:

- Automatic reconnection on auth failures
- Watcher restart on inactivity
- Fallback to local environment variables
- Graceful degradation on etcd unavailability

## Development

### Setup

```bash
# Clone the repository
git clone https://github.com/ton5169/etcd-dynamic-config.git
cd etcd-dynamic-config

# Install development dependencies
pip install -e .[dev]

# Run tests
pytest

# Format code
black etcd_dynamic_config/
isort etcd_dynamic_config/

# Type checking
mypy etcd_dynamic_config/
```

### Testing

```bash
# Run all tests
pytest

# Run with coverage
pytest --cov=etcd_dynamic_config --cov-report=html

# Run specific test file
pytest tests/test_client.py

# Run integration tests
pytest -m integration
```

### Building Documentation

```bash
# Install docs dependencies
pip install -e .[docs]

# Build docs
cd docs
make html
```

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests for new functionality
5. Ensure all tests pass
6. Submit a pull request

## License

MIT License - see [LICENSE](LICENSE) file for details.

## Extensibility & Customization

### Creating Custom Clients

The library provides `BaseEtcdClient` for creating custom implementations:

```python
from etcd_dynamic_config import BaseEtcdClient

class MyServiceClient(BaseEtcdClient):
    def get_config_prefix(self) -> str:
        return "/services/my-service"

    def _build_etcd_key_map(self) -> Dict[str, str]:
        base = self.get_config_prefix()
        return {
            f"{base}/DatabaseUrl": "database_url",
            f"{base}/RedisUrl": "redis_url",
            f"{base}/ApiKey": "api_key",
            f"{base}/MaxWorkers": "max_workers",
        }

    def _build_env_var_map(self) -> Dict[str, str]:
        return {
            "database_url": "MYAPP_DATABASE_URL",
            "redis_url": "MYAPP_REDIS_URL",
            "api_key": "MYAPP_API_KEY",
            "max_workers": "MYAPP_MAX_WORKERS",
        }

    def _coerce_config_value(self, name: str, value):
        if name == "max_workers":
            return int(value) if value else 4
        return super()._coerce_config_value(name, value)

# Use your custom client
client = MyServiceClient(use_local_config=True)
config = client.get_config()
```

### Available Classes

- **BaseEtcdClient**: Abstract base for custom implementations
- **ControlUnitEtcdClient**: Pre-configured for ControlUnit (default)
- **EtcdClient**: Alias for ControlUnitEtcdClient (backward compatibility)
- **EtcdConfig**: High-level configuration manager

### Key Benefits

- **๐ŸŽฏ Universal**: Works with any etcd key structure
- **๐Ÿ”ง Customizable**: Easy to adapt for different applications
- **๐Ÿ”„ Backward Compatible**: Existing code continues to work
- **๐Ÿ—๏ธ Extensible**: Clean architecture for future enhancements

## Changelog

See [CHANGELOG.md](CHANGELOG.md) for version history.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "etcd-dynamic-config",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "Anton Irshenko <a_irshenko@example.com>",
    "keywords": "etcd, configuration, cache, watcher, async",
    "author": null,
    "author_email": "Anton Irshenko <a_irshenko@example.com>",
    "download_url": "https://files.pythonhosted.org/packages/4f/9d/6b1e5ef6161143615412e9035707b8424d004645881fea69ecc9858e56e9/etcd_dynamic_config-0.1.0.tar.gz",
    "platform": null,
    "description": "# Etcd Dynamic Config\n\n[![PyPI version](https://badge.fury.io/py/etcd-dynamic-config.svg)](https://pypi.org/project/etcd-dynamic-config/)\n[![Python versions](https://img.shields.io/pypi/pyversions/etcd-dynamic-config.svg)](https://pypi.org/project/etcd-dynamic-config/)\n[![License](https://img.shields.io/pypi/l/etcd-dynamic-config.svg)](https://github.com/ton5169/etcd-dynamic-config/blob/main/LICENSE)\n\nA robust Python library for managing etcd-based configurations with caching, real-time updates, and graceful fallbacks.\n\n## Features\n\n- \ud83d\ude80 **High Performance**: In-memory caching for fast configuration access\n- \ud83d\udd04 **Real-time Updates**: Automatic watching for configuration changes\n- \ud83d\udee1\ufe0f **Resilient**: Graceful fallbacks to local environment variables\n- \ud83d\udd12 **Secure**: Support for TLS and authentication\n- \ud83e\uddf5 **Thread-safe**: Safe concurrent access to configuration data\n- \ud83d\udcca **Observable**: Structured logging and monitoring support\n- \ud83c\udfaf **Type-safe**: Built-in type coercion and validation\n\n## Installation\n\n```bash\npip install etcd-dynamic-config\n```\n\nFor development with extra tools:\n\n```bash\npip install etcd-dynamic-config[dev]\n```\n\n## Quick Start\n\n### Basic Usage\n\n```python\nimport asyncio\nfrom etcd_dynamic_config import etcd_config\n\nasync def main():\n    # Start the configuration manager\n    success = await etcd_config.start()\n    if success:\n        # Get all configurations\n        configs = await etcd_config.get_all_configs()\n        print(f\"Database URL: {configs.get('postgres_dsn')}\")\n\n        # Access specific config values\n        api_url = configs.get('categorization_api_url')\n        print(f\"API URL: {api_url}\")\n\n    # Clean shutdown\n    await etcd_config.stop()\n\n# Run the example\nasyncio.run(main())\n```\n\n### Environment Variables\n\nSet these environment variables to configure etcd connection:\n\n```bash\n# Etcd connection settings\nexport EtcdSettings__HostName=\"http://localhost:2379\"\nexport EtcdSettings__UserName=\"your-username\"\nexport EtcdSettings__Password=\"your-password\"\nexport EtcdSettings__RootKey=\"/APPS/ControlUnit\"\n\n# Optional: Use local environment variables instead of etcd\nexport USE_LOCAL_CONFIG=\"false\"\n\n# Optional: TLS settings\nexport EtcdSettings__CaCertPath=\"/path/to/ca-cert.pem\"\n```\n\n### Local Development\n\nFor local development, set `USE_LOCAL_CONFIG=true` and define configurations as environment variables:\n\n```bash\nexport USE_LOCAL_CONFIG=\"true\"\nexport CATEGORIZATION_API_URL=\"http://localhost:8000\"\nexport POSTGRES_DSN=\"postgresql://user:pass@localhost:5432/db\"\nexport LOG_LEVEL=\"DEBUG\"\n```\n\n## Advanced Usage\n\n### Custom Configuration Keys\n\n#### Using Built-in ControlUnit Client\n\n```python\nfrom etcd_dynamic_config import EtcdClient\n\n# Use the built-in ControlUnit client (backward compatible)\nclient = EtcdClient()\n\n# Get specific configuration values\nkey_map = client.get_control_unit_key_map()\nprint(\"Available config keys:\", list(key_map.values()))\n\n# Direct key access\nvalues = client.get_values_by_keys([\"/APPS/ControlUnit/LogLevel\"])\nprint(\"Log level:\", values.get(\"/APPS/ControlUnit/LogLevel\"))\n```\n\n#### Creating Custom Clients\n\n```python\nfrom etcd_dynamic_config import BaseEtcdClient\n\nclass MyAppEtcdClient(BaseEtcdClient):\n    def get_config_prefix(self) -> str:\n        return \"/apps/myapp/prod\"\n\n    def _build_etcd_key_map(self) -> Dict[str, str]:\n        base = self.get_config_prefix()\n        return {\n            f\"{base}/DatabaseUrl\": \"database_url\",\n            f\"{base}/RedisUrl\": \"redis_url\",\n            f\"{base}/ApiKey\": \"api_key\",\n        }\n\n    def _build_env_var_map(self) -> Dict[str, str]:\n        return {\n            \"database_url\": \"MYAPP_DATABASE_URL\",\n            \"redis_url\": \"MYAPP_REDIS_URL\",\n            \"api_key\": \"MYAPP_API_KEY\",\n        }\n\n# Use custom client\nclient = MyAppEtcdClient(use_local_config=True)\nconfig = client.get_config()\nprint(\"Database URL:\", config.get(\"database_url\"))\n```\n\n### Async Configuration Manager\n\n```python\nimport asyncio\nfrom etcd_dynamic_config import EtcdConfig\n\nasync def config_worker():\n    config_manager = EtcdConfig()\n\n    # Start with watching\n    await config_manager.start()\n\n    try:\n        while True:\n            configs = await config_manager.get_all_configs()\n\n            # Your application logic here\n            api_token = configs.get('categorization_api_token')\n            if api_token:\n                print(f\"Using API token: {api_token[:10]}...\")\n\n            await asyncio.sleep(60)  # Check every minute\n\n    finally:\n        await config_manager.stop()\n\nasyncio.run(config_worker())\n```\n\n### Error Handling\n\n```python\nimport asyncio\nfrom etcd_dynamic_config import etcd_config\n\nasync def robust_config_access():\n    try:\n        await etcd_config.start()\n\n        configs = await etcd_config.get_all_configs()\n\n        # Safe access with defaults\n        timeout = configs.get('ai_http_timeout_seconds', 30.0)\n        max_conn = configs.get('ai_http_max_connections', 10)\n\n        print(f\"Timeout: {timeout}s, Max connections: {max_conn}\")\n\n    except Exception as e:\n        print(f\"Configuration error: {e}\")\n        # Fallback to hardcoded defaults\n        timeout = 30.0\n        max_conn = 10\n\n    finally:\n        await etcd_config.stop()\n```\n\n## Configuration Schema\n\nThe library doesn't impose any specific configuration schema - **you define your own keys!**\n\n### Defining Your Configuration Schema\n\n```python\nfrom etcd_dynamic_config import BaseEtcdClient\n\nclass MyAppClient(BaseEtcdClient):\n    def get_config_prefix(self) -> str:\n        return \"/myapp/production\"\n\n    def _build_etcd_key_map(self) -> Dict[str, str]:\n        base = self.get_config_prefix()\n        return {\n            # Your custom etcd keys -> internal names\n            f\"{base}/Database/Host\": \"db_host\",\n            f\"{base}/Database/Port\": \"db_port\",\n            f\"{base}/Database/Name\": \"db_name\",\n            f\"{base}/Cache/RedisUrl\": \"redis_url\",\n            f\"{base}/API/SecretKey\": \"api_secret\",\n            f\"{base}/Features/EnableCache\": \"enable_cache\",\n            f\"{base}/Monitoring/LogLevel\": \"log_level\",\n        }\n\n    def _build_env_var_map(self) -> Dict[str, str]:\n        return {\n            # Internal names -> environment variables\n            \"db_host\": \"MYAPP_DB_HOST\",\n            \"db_port\": \"MYAPP_DB_PORT\",\n            \"db_name\": \"MYAPP_DB_NAME\",\n            \"redis_url\": \"MYAPP_REDIS_URL\",\n            \"api_secret\": \"MYAPP_API_SECRET\",\n            \"enable_cache\": \"MYAPP_ENABLE_CACHE\",\n            \"log_level\": \"MYAPP_LOG_LEVEL\",\n        }\n```\n\n### Custom Type Coercion\n\n```python\ndef _coerce_config_value(self, internal_name: str, value):\n    \"\"\"Define your own type coercion rules.\"\"\"\n    if internal_name == \"db_port\":\n        return int(value) if value else 5432\n    elif internal_name == \"enable_cache\":\n        return str(value).lower() in (\"1\", \"true\", \"yes\", \"on\")\n    elif internal_name == \"api_secret\":\n        # Don't log secrets in plain text\n        return str(value) if value else \"\"\n\n    # Use default coercion for other values\n    return super()._coerce_config_value(internal_name, value)\n```\n\n### Your Configuration Documentation\n\nCreate documentation for **your** configuration keys:\n\n| Your Etcd Key                            | Environment Variable | Type | Default   | Description           |\n| ---------------------------------------- | -------------------- | ---- | --------- | --------------------- |\n| `/myapp/production/Database/Host`        | `MYAPP_DB_HOST`      | str  | localhost | Database host         |\n| `/myapp/production/Database/Port`        | `MYAPP_DB_PORT`      | int  | 5432      | Database port         |\n| `/myapp/production/Cache/RedisUrl`       | `MYAPP_REDIS_URL`    | str  | -         | Redis connection URL  |\n| `/myapp/production/Features/EnableCache` | `MYAPP_ENABLE_CACHE` | bool | false     | Enable caching        |\n| `/myapp/production/Monitoring/LogLevel`  | `MYAPP_LOG_LEVEL`    | str  | INFO      | Application log level |\n\nSee [examples/schema_documentation_example.py](examples/schema_documentation_example.py) for a complete example of documenting and implementing a custom configuration schema.\n\n### Built-in ControlUnit Client\n\nFor backward compatibility, the library includes a pre-configured client for ControlUnit applications:\n\n```python\nfrom etcd_dynamic_config import EtcdClient, etcd_client\n\n# Uses the ControlUnit schema automatically\nclient = EtcdClient()\nconfig = client.get_config()\n\n# Access ControlUnit-specific keys\napi_url = config.get(\"categorization_api_url\")\ndb_dsn = config.get(\"postgres_dsn\")\n```\n\nThe ControlUnit client handles these keys automatically (see [ControlUnitEtcdClient](https://github.com/ton5169/etcd-dynamic-config/blob/main/etcd_dynamic_config/core/control_unit.py) for details).\n\n## Architecture\n\n### Components\n\n1. **EtcdClient**: Low-level etcd operations\n\n   - Connection management\n   - Authentication and TLS\n   - Key-value operations\n   - Watching capabilities\n\n2. **EtcdConfig**: High-level configuration management\n   - Caching layer\n   - Type coercion\n   - Real-time updates\n   - Health monitoring\n\n### Thread Safety\n\nAll operations are thread-safe:\n\n- Configuration cache uses `threading.RLock()`\n- Async operations properly handle concurrency\n- Watcher callbacks are serialized\n\n### Error Recovery\n\nThe library implements several recovery mechanisms:\n\n- Automatic reconnection on auth failures\n- Watcher restart on inactivity\n- Fallback to local environment variables\n- Graceful degradation on etcd unavailability\n\n## Development\n\n### Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/ton5169/etcd-dynamic-config.git\ncd etcd-dynamic-config\n\n# Install development dependencies\npip install -e .[dev]\n\n# Run tests\npytest\n\n# Format code\nblack etcd_dynamic_config/\nisort etcd_dynamic_config/\n\n# Type checking\nmypy etcd_dynamic_config/\n```\n\n### Testing\n\n```bash\n# Run all tests\npytest\n\n# Run with coverage\npytest --cov=etcd_dynamic_config --cov-report=html\n\n# Run specific test file\npytest tests/test_client.py\n\n# Run integration tests\npytest -m integration\n```\n\n### Building Documentation\n\n```bash\n# Install docs dependencies\npip install -e .[docs]\n\n# Build docs\ncd docs\nmake html\n```\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Make your changes\n4. Add tests for new functionality\n5. Ensure all tests pass\n6. Submit a pull request\n\n## License\n\nMIT License - see [LICENSE](LICENSE) file for details.\n\n## Extensibility & Customization\n\n### Creating Custom Clients\n\nThe library provides `BaseEtcdClient` for creating custom implementations:\n\n```python\nfrom etcd_dynamic_config import BaseEtcdClient\n\nclass MyServiceClient(BaseEtcdClient):\n    def get_config_prefix(self) -> str:\n        return \"/services/my-service\"\n\n    def _build_etcd_key_map(self) -> Dict[str, str]:\n        base = self.get_config_prefix()\n        return {\n            f\"{base}/DatabaseUrl\": \"database_url\",\n            f\"{base}/RedisUrl\": \"redis_url\",\n            f\"{base}/ApiKey\": \"api_key\",\n            f\"{base}/MaxWorkers\": \"max_workers\",\n        }\n\n    def _build_env_var_map(self) -> Dict[str, str]:\n        return {\n            \"database_url\": \"MYAPP_DATABASE_URL\",\n            \"redis_url\": \"MYAPP_REDIS_URL\",\n            \"api_key\": \"MYAPP_API_KEY\",\n            \"max_workers\": \"MYAPP_MAX_WORKERS\",\n        }\n\n    def _coerce_config_value(self, name: str, value):\n        if name == \"max_workers\":\n            return int(value) if value else 4\n        return super()._coerce_config_value(name, value)\n\n# Use your custom client\nclient = MyServiceClient(use_local_config=True)\nconfig = client.get_config()\n```\n\n### Available Classes\n\n- **BaseEtcdClient**: Abstract base for custom implementations\n- **ControlUnitEtcdClient**: Pre-configured for ControlUnit (default)\n- **EtcdClient**: Alias for ControlUnitEtcdClient (backward compatibility)\n- **EtcdConfig**: High-level configuration manager\n\n### Key Benefits\n\n- **\ud83c\udfaf Universal**: Works with any etcd key structure\n- **\ud83d\udd27 Customizable**: Easy to adapt for different applications\n- **\ud83d\udd04 Backward Compatible**: Existing code continues to work\n- **\ud83c\udfd7\ufe0f Extensible**: Clean architecture for future enhancements\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for version history.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A Python library for managing etcd-based configurations with caching and real-time updates",
    "version": "0.1.0",
    "project_urls": {
        "Changelog": "https://github.com/ton5169/etcd-dynamic-config/blob/main/CHANGELOG.md",
        "Documentation": "https://github.com/ton5169/etcd-dynamic-config#readme",
        "Homepage": "https://github.com/ton5169/etcd-dynamic-config",
        "Issues": "https://github.com/ton5169/etcd-dynamic-config/issues",
        "Repository": "https://github.com/ton5169/etcd-dynamic-config"
    },
    "split_keywords": [
        "etcd",
        " configuration",
        " cache",
        " watcher",
        " async"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "9b1db4e483b6e3213d34ab48fc480181c4219f25b5238f2436c4f499e2a25833",
                "md5": "28cf77b1921010371f2a35a19caee5be",
                "sha256": "e746d18d2b687c29e9ffbd2df89fbd6008de235dd3389d7c978097bdc0ed11b2"
            },
            "downloads": -1,
            "filename": "etcd_dynamic_config-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "28cf77b1921010371f2a35a19caee5be",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 20418,
            "upload_time": "2025-09-10T16:52:48",
            "upload_time_iso_8601": "2025-09-10T16:52:48.278196Z",
            "url": "https://files.pythonhosted.org/packages/9b/1d/b4e483b6e3213d34ab48fc480181c4219f25b5238f2436c4f499e2a25833/etcd_dynamic_config-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "4f9d6b1e5ef6161143615412e9035707b8424d004645881fea69ecc9858e56e9",
                "md5": "4e4eb03383de94c063167bd6c106291d",
                "sha256": "1ea2931bf44da7baa024df05de1d81b1282988ab740cce213a2639f36d04b96a"
            },
            "downloads": -1,
            "filename": "etcd_dynamic_config-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "4e4eb03383de94c063167bd6c106291d",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 24415,
            "upload_time": "2025-09-10T16:52:49",
            "upload_time_iso_8601": "2025-09-10T16:52:49.537908Z",
            "url": "https://files.pythonhosted.org/packages/4f/9d/6b1e5ef6161143615412e9035707b8424d004645881fea69ecc9858e56e9/etcd_dynamic_config-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-09-10 16:52:49",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "ton5169",
    "github_project": "etcd-dynamic-config",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "etcd-dynamic-config"
}
        
Elapsed time: 1.47539s