Metadata-Version: 2.4
Name: py-generic-config
Version: 0.2.0
Summary: 解耦、通用的Python配置中心加载器与类型安全注解
Author-email: Elysia <feng89310@gmail.com>
Project-URL: Homepage, https://github.com/Elysia125/py-generic-config
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyyaml>=6.0
Requires-Dist: python-dotenv>=1.0.0
Provides-Extra: nacos
Requires-Dist: v2nacos>=0.1.0; extra == "nacos"
Dynamic: license-file

# py-generic-config

`py-generic-config` 是一个通用的 Python 配置管理库。它提供了**本地 YAML 解析**、**环境变量插值**、**面向对象类型安全配置类 (`@configclass`)**、**远程配置中心接入 (Nacos 等)** 以及 **双层配置热更新监听**能力。

---

## ✨ 核心特性

- 🛠 **双源加载**：支持本地 `settings.yaml` + 远程配置中心（Nacos/自定义 Provider）双重加载与覆盖。
- 🔍 **环境变量自动替换**：支持 `${ENV_VAR:default_value}` 语法自动解析与默认值兜底。
- 🛡 **类型安全配置类**：通过 `@configclass` 装饰器与 `Annotated[T, Value(...)]` 实现配置项自动类型转换、默认值与深层嵌套。
- 🔄 **解耦配置源 (Provider 模式)**：内置 Nacos 2.x 配置源，同时支持轻松扩展 Consul、Apollo、Etcd 或自定义配置源。
- 🔔 **双层热更新监听机制**：
  - **模块级 Hook (`register_reload_hook`)**：配置变更时，一键自动重新加载所有配置类或重连数据库。
  - **Key 级 Watcher (`watch/unwatch`)**：精确监听指定 `key`（如 `database.host`）的值变更，获取 `(new_val, old_val)`。

---

## 📦 安装指南

### 1. 基础安装（仅本地 YAML 配置）
```bash
pip install .
```

### 2. 带有 Nacos 远程配置中心支持的安装
```bash
pip install .[nacos]
# 或直接安装 SDK
pip install v2nacos
```

---

## 🚀 快速上手

### 1. 准备本地配置文件

在项目根目录创建 `settings.yaml`（或使用CONFIG_PATH环境变量指定路径）：

```yaml
app:
  name: "my-service"
  debug: ${APP_DEBUG:true}
  database:
    host: ${DB_HOST:127.0.0.1}
    port: 3306
    username: "root"
    password: "${DB_PWD:123456}"
```

### 2. 定义类型安全的配置类

使用 `@configclass` 装饰器定义 Python 配置类，属性将自动映射并类型转换：

```python
from typing import Annotated
from py_generic_config import configclass, Value

# @configclass("database") 加不加这个无所谓，默认按照变量名映射
class DBConfig:
    host: Annotated[str, Value("host", "localhost")]
    port: Annotated[int, Value("port", 3306)]
    username: Annotated[str, Value("username")]
    password: Annotated[str, Value("password")]
    
@configclass("app")
class AppConfig:
    name: Annotated[str, Value("name")]
    debug: Annotated[bool, Value("debug", False)]
    database: Annotated[DBConfig, Value("database")] # 嵌套配置类（也可以不用Annotated，直接使用DBConfig）

# 获取配置值（支持类型自动转换，如 str -> int/bool）
print(AppConfig.database.host)  # 输出: 127.0.0.1
print(AppConfig.database.port)  # 输出: 3306 (int 类型)
```

---

## 🌐 远程配置接入与热更新 (Nacos)

可以通过 `ConfigLoader` 接入 Nacos 远程配置中心，并支持配置热更新。

```python
import asyncio
import logging
from py_generic_config import (
    ConfigLoader, 
    NacosConfigProvider, 
    reload_configclass,
    yaml_config
)

logging.basicConfig(level=logging.INFO)

# 1. 实例化 Nacos Provider
nacos_provider = NacosConfigProvider(
    ip="127.0.0.1",
    port=8848,
    namespace="dev-namespace",
    username="nacos",
    password="nacos_password",
    group="DEFAULT_GROUP"
)

# 2. 创建 ConfigLoader 并注入 Provider
loader = ConfigLoader(provider=nacos_provider)

# ==================== 热更新监听设置 ====================

# 🎯 方式 A：注册模块级重载 Hook (全量/配置类刷新)
def on_config_reload():
    reload_configclass(AppConfig)  # 重新刷新配置类属性
    print(f"🔄 配置类已刷新！当前数据库Host: {AppConfig.database.host}")

loader.register_reload_hook(on_config_reload)


# 🎯 方式 B：注册 Key 级细粒度 Watcher (精确监听某个键)
def on_db_host_change(new_val, old_val):
    print(f"📢 数据库 Host 发生了变更: {old_val} -> {new_val}")

loader.watch("database.host", on_db_host_change)

# ========================================================

async def main():
    # 3. 初始化 Loader 并拉取远程配置
    await loader.initialize(data_ids=["app_config.yaml"])

    # 4. 获取配置（自动将远程拉取的配置与本地配置合并）
    print("应用名称:", yaml_config.get("app.name"))
    print("数据库 Host:", AppConfig.database.host)

    # 保持程序运行以接收 Nacos 长轮询推送
    try:
        await asyncio.Event().wait()
    finally:
        await loader.close()

if __name__ == "__main__":
    asyncio.run(main())
```

---

## 🛠 进阶用法

### 1. 扩展自定义配置源 (Custom ConfigProvider)

如果你使用的不是 Nacos，而是 Consul、Apollo、Etcd 或自研配置中心，只需继承 `BaseConfigProvider` 实现 4 个抽象方法：

```python
from py_generic_config import BaseConfigProvider, ConfigLoader

class MyConsulProvider(BaseConfigProvider):
    async def init(self) -> None:
        # 初始化 Consul 客户端连接
        pass

    async def get_config(self, data_id: str, group: str = "DEFAULT_GROUP") -> str:
        # 从 Consul 获取 YAML/JSON 文本内容
        return "database:\n  host: 10.0.0.1"

    async def add_config_listener(self, data_id: str, listener, group: str = "DEFAULT_GROUP") -> None:
        # 注册 Consul 监听逻辑，当配置改变时调用 listener(data_id, new_content)
        pass

    async def close(self) -> None:
        # 关闭连接
        pass

# 使用自定义 Provider
loader = ConfigLoader(provider=MyConsulProvider())
```

### 2. 取消 Key 级监听 (`unwatch`)

```python
# 注册监听
loader.watch("database.host", on_db_host_change)

# 在不需要时取消监听
loader.unwatch("database.host", on_db_host_change)
```

### 3. 本地覆盖文件 (`config_overrides.yaml`)

库默认支持读取 `config_overrides.yaml`（路径可通过环境变量 `CONFIG_OVERRIDES_PATH` 修改），该文件内的配置项优先级高于 `settings.yaml`，常用于本地开发阶段临时覆盖配置而不污染 Git 仓库。

---

## 🏗 架构设计

```text
+-------------------------------------------------------------+
|                       业务代码 (Your App)                     |
|    DBConfig.host  /  yaml_config.get()  /  reload_hooks     |
+------------------------------+------------------------------+
                               |
+------------------------------v------------------------------+
|                     ConfigLoader (加载器)                    |
|   - 依赖注入 Provider                                        |
|   - 监听与 Diff 计算 (_compute_diff)                           |
|   - 触发 ReloadHooks 与 Watchers                            |
+------------------------------+------------------------------+
                               |
       +-----------------------+-----------------------+
       |                                               |
+------v----------------------+             +----------v----------+
|  BaseConfigProvider (抽象源) |             |  YamlConfig (本地)  |
|  - NacosConfigProvider      |             |  - settings.yaml    |
|  - MyCustomProvider         |             |  - ${ENV} 环境变量  |
+-----------------------------+             +---------------------+
```

---

## 📄 许可证

本项目基于 [MIT 许可证](LICENSE) 协议开源。详细许可内容请参阅项目根目录下的 [LICENSE](LICENSE) 文件，或参考 [MIT 官方开源协议说明](https://opensource.org/licenses/MIT)。

你可以自由地修改、分发和商业化使用本项目，但请保留原有的版权声明和许可声明哦 (๑•̀ㅂ•́)و✧！
