config-server


Nameconfig-server JSON
Version 0.6.4 PyPI version JSON
download
home_page
SummaryTo build a extendable config server.
upload_time2023-12-01 09:13:51
maintainer
docs_urlNone
authorlaorange
requires_python>=3.8, <3.12
license
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            <div align="center">
  <h1>参数网络配置系统</h1>
  <h2>使用手册</h2>
</div>

# 声明

版权所有 © 2023 成都研码帮信息技术有限公司。所有权利保留。未经明确的书面授权,不得用于商业用途。

# 引言

## 定义

系统名称:**参数网络配置系统**(以下简称“本系统”)。

## 编写目的

本手册较详细地介绍了本系统的应用程序接口与使用方法,用户通过查看本手册,可以了解如何使用本系统。

# 系统介绍

## 系统简介

在程序设计的功能中,随着版本的迭代,程序的功能会逐渐累计;功能越多的程序,一般越需要用户自行选择启用哪些功能,并在一定程度上可以决定程序执行的流程等,这样的过程可以称为对程序进行“配置”。

不同的程序面向不同的使用人群,不同的使用人群也会有不同的配置方式。例如,没有程序设计基础的普通用户更偏向于使用携带用户操作界面的配置页面,部分具有程序设计基础的用户更偏向于使用命令行参数输入、载入配置文件等配置方式,部分开发者更偏向于使用软件开发工具包来按照需求定制程序配置方案。

另一方面,随着程序功能增多,配置参数之间极可能出现耦合和约束关系。由于目前行业内尚未有公开的、成熟的、针对携带复杂参数的程序的软件开发工具,因此开发本系统以填补行业空缺,为复杂配置参数的场景提供解决方案。

由于 python 语言有着“胶水语言”的特性,能很好地与其他编程语言(如 C,Java,Fortran 等)编写的程序进行交互,并且它的学习门槛低,应用范围广, 本系统形成了一套 python 语言的软件开发工具包。

本系统将复杂参数的关系以树状图进行整理,以 yaml 格式的配置文件为标准,开发者可以通过该系统定制包含复杂约束关系的参数配置系统,可以生成网页端的配置页面、命令行操作接口、配置文件接口、网站后台接口等多种接入方案,可满足不同用户的使用习惯。

## 系统流程图

```mermaid
%%{init: {
'theme': 'base',
'themeVariables': {
'fontSize': '24px',
'fontFamily': "SimSun, serif",
'background': '#efefef',
'primaryColor': '#fff',
'primaryTextColor': '#000',
'primaryBorderColor': '#000',
'lineColor': '#000',
'secondaryColor': '#000',
'tertiaryColor': '#000'}}}%%
flowchart TB
    a(开始)
    b[通过思维导图整理参数结构]
    c[配置python环境]
    d[载入本系统]
    e[定义参数结点]
    f[运行程序]
    g(结束)
    a --> b --> c --> d --> e --> f --> g
```

## 设计概念

通过思维导图的整理后的参数结构是树状的,而树状的数据可分为**根节点**、**子节点**、**叶子节点**,如下图所示:

```mermaid
mindmap
  root((根节点))
    叶子节点
    子节点
      叶子节点
      叶子节点
      子节点
        叶子节点
        叶子节点
    子节点
      叶子节点
      叶子节点
```

在配置文件中,"根节点"对应配置文件本身,"子节点"对应若干个参数所在的分组,"叶子节点"对应某个参数,如下图所示:

```mermaid
mindmap
  root((配置文件))
    一级参数
    一级分组
      二级参数
      二级参数
      二级分组
        三级参数
        三级参数
    一级分组
      二级参数
      二级参数
```

在参数说明文档应以章节号的形式体现参数的分组结果,在参数说明文档的每小节对应某个分组或参数。

例如,以下是参数说明文档目录的示例:

```Plain
1. 一级参数
2. 一级分组
    2.1 二级参数
    2.2 二级参数
    2.3 二级分组
        2.3.1 三级参数
        2.3.2 三级参数
3. 一级分组
    3.1 二级参数
    3.2 二级参数
```

# 快速入门

本小节中将通过构建一个用户信息管理程序为案例,详细讲解如何使用本系统。

## 参数结构

```mermaid
mindmap
  UserSystem((用户系统))
    UserName))用户名((
    Gender))性别((
    Age))年龄((
    AuthInfo[账号信息(多选一)]
      EmailLogin[邮箱登录]
        Email))邮箱地址((
        Password))密码((
      PhoneLogin[手机登录]
        Phone))手机号码((
        Password))密码((
```

## 参数文档

### 用户名

用户名是用户在系统中的唯一标识符。

### 性别

可选“男”、“女”或“保密”,默认值为“保密”。

### 年龄

|   选项   |  说明  |
|:------:|:----:|
| 0-18岁  | 未成年人 |
| 19-44岁 | 青年人  |
| 45-59岁 | 中年人  |
| 60岁以上  |  老人  |

### 账号信息

为了方便和安全考虑,系统提供了**两种不同的登录方式**供用户选择(多选一):

**邮箱登录**:若使用该方式,用户需要填写:

1. 邮箱地址: 用户的有效电子邮箱地址;
2. 密码:与邮箱地址关联的密码。

**手机号登录**: 若使用该方式,用户需要填写:

1. 手机号码: 用户的有效手机号码;
2. 密码:与手机号码关联的密码。

## 程序定义

```python
from typing import Literal
from enum import Enum

import pydantic  # 支持Pydantic内置的类型标注

from config_server import (
    set_title,
    set_doc,
    add_union_field,
    ConfigLeaf,
    ConfigNode,
)


@set_title("用户名")  # 通过装饰器设置标题
@set_doc("用户名是用户在系统中的唯一标识符")  # 通过装饰器设置字段的详细说明
class UserName(ConfigLeaf):
    root: str  # 只是做了类型标注,但没有赋值,表示该字段为必填字段


@set_title("性别")
class Gender(ConfigLeaf):
    root: Literal["男", "女", "保密"] = "保密"  # 直接赋值,表示默认值为“保密”


@set_title("年龄")
@set_doc(
    """
    |   选项   |   说明   |
    | :------: | :-----: |
    |  0-18岁  | 未成年人 |
    | 19-44岁  |  青年人  |
    | 45-59岁  |  中年人  |
    | 60岁以上  |   老人   |
    """
)
class Age(ConfigLeaf):
    # 当选项较多时,也通过枚举类来定义可选项
    class AgeEnum(Enum):
        young = "0-18岁"
        middle = "19-44岁"
        old = "45-59岁"
        elderly = "60岁以上"

    root: AgeEnum = AgeEnum.middle.value


# 在全局作用域中定义(叶)子节点,以便复用(例如,手机/邮箱登录都需要定义密码)
@set_title("密码")
class Password(ConfigLeaf):
    root: str

    # 通过重写`verify_constraints`方法来实现自定义的约束,
    # 本方法会在实例化时自动调用;若验证不通过,请抛出异常;
    # 如果需要表示跨节点的约束条件,请在其最近的共同父节点中定义。
    def verify_constraints(self) -> None:
        if len(self.root) < 8:
            raise ValueError("密码长度不能小于8位")


@set_title("邮箱登录")
class EmailLogin(ConfigNode):
    @set_title("邮箱地址")
    class Email(ConfigLeaf):
        # 可通过pydantic.constr来添加正则表达式约束
        root: pydantic.constr(pattern=r"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$")

    address: Email
    password: Password


@set_title("手机登录")
class PhoneLogin(ConfigNode):
    @set_title("手机号码")
    class Phone(ConfigLeaf):
        root: str

    phone: Phone
    password: Password


@set_title("注册信息")
@set_doc(
    """
    提供了两种不同的登录方式供用户选择:

    | 登录方式 |      说明          |
    | :---:  | :----------------: |
    |  邮箱   | 用户可以使用邮箱登录  |
    |  手机   | 用户可以使用手机号登录 |
"""
)  # 详细说明支持markdown语法
@add_union_field(  # 通过装饰器添加多类型字段(多选一)
    field_name="root",  # 该字段在添加后的字段名
    field_title="注册信息",  # 该字段的标题
    field_doc="用户在网站上注册时所填写的信息",  # 该字段的详细说明
    types=[EmailLogin, PhoneLogin],  # 该字段可以接受的类型,此处为两种不同的登录方式
    discriminator="login_method",  # 在子节点中,通过discriminator字段来区分不同的类型
)
class AuthInfo(ConfigLeaf):  # 当节点只有一个字段时,可以直接使用ConfigLeaf,并将唯一字段名设置为`root`
    ...


@set_title("用户系统")
class UserSystem(ConfigNode):
    username: UserName
    gender: Gender
    age: Age
    auth_info: AuthInfo
```

### 命令行生成配置文件

当程序完成定义时,可以通过调用 `mock_cli` 和 `model_dump_yaml` 方法,能在命令行中操作后生成配置文件:

```python
UserSystem.mock_cli().model_dump_yaml(comment=True)
```

命令行执行效果,如下图所示:

<img src="./assets/img/cli.png" alt="命令行执行效果" style="zoom:50%;" title="命令行执行效果"/>

### 程序拓展接口

当程序完成定义时,可以通过调用 `get_api_router` 方法来启动后端服务开启标准化的应用程序接口,以下是一个简单的案例:

```python
import uvicorn
import fastapi

app = fastapi.FastAPI()
app.include_router(UserSystem.get_api_router("/"))
uvicorn.run(app, host="127.0.0.1", port=8000, log_level="info")
```

<img src="./assets/img/backend.png" alt="在命令行开启后端服务" style="zoom: 67%;" title="在命令行开启后端服务"/>

### 用户界面

本系统包含一个配套的网页端用户界面,以上案例对应的页面如下图所示:

<img src="./assets/img/demo.png" alt="网页端用户界面" style="zoom: 50%;" title="网页端用户界面"/>

当点击表单字段前方的 `?` 标识时,会出现该字段的详细说明,如下图所示:

<img src="./assets/img/show-doc.png" alt="字段的详细说明" style="zoom:50%;" title="字段的详细说明"/>

当选择多类型字段的值后,其子节点的内容也会随之发生变化,如下图所示:

<img src="./assets/img/one-of-field.png" alt="多类型字段变动" style="zoom: 50%;" title="多类型字段变动"/>

当必填字段没有填写时,网页会给出提示:

<img src="./assets/img/required-no-fill.png" alt="必填项未填提醒" style="zoom: 50%;" title="必填项未填提醒"/>

当已填字段未通过校验时,网页会给出提示:

<img src="./assets/img/validation-error.png" alt="校验错误提醒" style="zoom:50%;" title="校验错误提醒"/>

在验证通过时,可以生成 yaml 格式的配置文件,如下图所示:

<img src="./assets/img/yaml-editor.png" alt="yaml配置文件编辑器" style="zoom:50%;" title="yaml配置文件编辑器"/>

# 程序接口

本系统的核心部分就是其配置节点(ConfigNode)和配置叶子节点(ConfigLeaf),它们共同构成了配置的树形结构。

## 节点类(ConfigNode)

节点类是 `pydantic.BaseModel` 的超集。作为配置系统的核心,用于构建、验证、转换、序列化配置树的结构。

### 验证YAML文件

使用`model_validate_yaml`方法,开发人员可以通过传递YAML文件的路径进行验证。如果验证成功,将返回一个有效的ConfigNode实例。

### 模拟方法

ConfigNode类还提供了多个模拟方法,如`mock`,`mock_with_default`,`mock_with_blank`和`mock_cli`。这些方法可以根据字段类型和默认值生成模拟实例。

### YAML序列化

使用`model_dump_yaml`方法,可以将ConfigNode实例转储为YAML字符串。`model_dump_mock_yaml`方法提供了模拟实例的转储功能。

### 生成后端接口路由

`get_api_router`方法可用于获取API路由。

### 约束验证

`verify_constraints`方法用于验证约束条件,可以根据需要覆盖。

## 叶节点类(ConfigLeaf)

叶节点类继承自根模型和节点类,用于构建配置树的末端节点。

## 装饰器

### 设置标题(set_title)

通过传递标题字符串,可以设置模型的标题。

```python
@set_title("标题")
class ExampleModel(ConfigNode):
    ...
```

### 设置说明(set_doc)

该装饰器用于设置模型的详细说明。

```python
@set_doc("这是一个示例模型")
class ExampleModel(ConfigNode):
    ...
```

### 设置鉴别字段(set_discriminator)

此装饰器可以设置模型的区分器字段名。

```python
@set_discriminator("type")
class ExampleModel(ConfigNode):
    ...
```

### 添加复合类型字段(add_union_field)

此装饰器可以将具有多种类型的字段添加到模型中,还可以通过可选参数设置字段标题和文档。

```python
@add_union_field("field_name", [Type1, Type2])
class ExampleModel(ConfigNode):
    ...
```

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "config-server",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8, <3.12",
    "maintainer_email": "",
    "keywords": "",
    "author": "laorange",
    "author_email": "laorange6666@gmail.com",
    "download_url": "",
    "platform": null,
    "description": "<div align=\"center\">\n  <h1>\u53c2\u6570\u7f51\u7edc\u914d\u7f6e\u7cfb\u7edf</h1>\n  <h2>\u4f7f\u7528\u624b\u518c</h2>\n</div>\n\n# \u58f0\u660e\n\n\u7248\u6743\u6240\u6709 \u00a9 2023 \u6210\u90fd\u7814\u7801\u5e2e\u4fe1\u606f\u6280\u672f\u6709\u9650\u516c\u53f8\u3002\u6240\u6709\u6743\u5229\u4fdd\u7559\u3002\u672a\u7ecf\u660e\u786e\u7684\u4e66\u9762\u6388\u6743\uff0c\u4e0d\u5f97\u7528\u4e8e\u5546\u4e1a\u7528\u9014\u3002\n\n# \u5f15\u8a00\n\n## \u5b9a\u4e49\n\n\u7cfb\u7edf\u540d\u79f0\uff1a**\u53c2\u6570\u7f51\u7edc\u914d\u7f6e\u7cfb\u7edf**\uff08\u4ee5\u4e0b\u7b80\u79f0\u201c\u672c\u7cfb\u7edf\u201d\uff09\u3002\n\n## \u7f16\u5199\u76ee\u7684\n\n\u672c\u624b\u518c\u8f83\u8be6\u7ec6\u5730\u4ecb\u7ecd\u4e86\u672c\u7cfb\u7edf\u7684\u5e94\u7528\u7a0b\u5e8f\u63a5\u53e3\u4e0e\u4f7f\u7528\u65b9\u6cd5\uff0c\u7528\u6237\u901a\u8fc7\u67e5\u770b\u672c\u624b\u518c\uff0c\u53ef\u4ee5\u4e86\u89e3\u5982\u4f55\u4f7f\u7528\u672c\u7cfb\u7edf\u3002\n\n# \u7cfb\u7edf\u4ecb\u7ecd\n\n## \u7cfb\u7edf\u7b80\u4ecb\n\n\u5728\u7a0b\u5e8f\u8bbe\u8ba1\u7684\u529f\u80fd\u4e2d\uff0c\u968f\u7740\u7248\u672c\u7684\u8fed\u4ee3\uff0c\u7a0b\u5e8f\u7684\u529f\u80fd\u4f1a\u9010\u6e10\u7d2f\u8ba1\uff1b\u529f\u80fd\u8d8a\u591a\u7684\u7a0b\u5e8f\uff0c\u4e00\u822c\u8d8a\u9700\u8981\u7528\u6237\u81ea\u884c\u9009\u62e9\u542f\u7528\u54ea\u4e9b\u529f\u80fd\uff0c\u5e76\u5728\u4e00\u5b9a\u7a0b\u5ea6\u4e0a\u53ef\u4ee5\u51b3\u5b9a\u7a0b\u5e8f\u6267\u884c\u7684\u6d41\u7a0b\u7b49\uff0c\u8fd9\u6837\u7684\u8fc7\u7a0b\u53ef\u4ee5\u79f0\u4e3a\u5bf9\u7a0b\u5e8f\u8fdb\u884c\u201c\u914d\u7f6e\u201d\u3002\n\n\u4e0d\u540c\u7684\u7a0b\u5e8f\u9762\u5411\u4e0d\u540c\u7684\u4f7f\u7528\u4eba\u7fa4\uff0c\u4e0d\u540c\u7684\u4f7f\u7528\u4eba\u7fa4\u4e5f\u4f1a\u6709\u4e0d\u540c\u7684\u914d\u7f6e\u65b9\u5f0f\u3002\u4f8b\u5982\uff0c\u6ca1\u6709\u7a0b\u5e8f\u8bbe\u8ba1\u57fa\u7840\u7684\u666e\u901a\u7528\u6237\u66f4\u504f\u5411\u4e8e\u4f7f\u7528\u643a\u5e26\u7528\u6237\u64cd\u4f5c\u754c\u9762\u7684\u914d\u7f6e\u9875\u9762\uff0c\u90e8\u5206\u5177\u6709\u7a0b\u5e8f\u8bbe\u8ba1\u57fa\u7840\u7684\u7528\u6237\u66f4\u504f\u5411\u4e8e\u4f7f\u7528\u547d\u4ee4\u884c\u53c2\u6570\u8f93\u5165\u3001\u8f7d\u5165\u914d\u7f6e\u6587\u4ef6\u7b49\u914d\u7f6e\u65b9\u5f0f\uff0c\u90e8\u5206\u5f00\u53d1\u8005\u66f4\u504f\u5411\u4e8e\u4f7f\u7528\u8f6f\u4ef6\u5f00\u53d1\u5de5\u5177\u5305\u6765\u6309\u7167\u9700\u6c42\u5b9a\u5236\u7a0b\u5e8f\u914d\u7f6e\u65b9\u6848\u3002\n\n\u53e6\u4e00\u65b9\u9762\uff0c\u968f\u7740\u7a0b\u5e8f\u529f\u80fd\u589e\u591a\uff0c\u914d\u7f6e\u53c2\u6570\u4e4b\u95f4\u6781\u53ef\u80fd\u51fa\u73b0\u8026\u5408\u548c\u7ea6\u675f\u5173\u7cfb\u3002\u7531\u4e8e\u76ee\u524d\u884c\u4e1a\u5185\u5c1a\u672a\u6709\u516c\u5f00\u7684\u3001\u6210\u719f\u7684\u3001\u9488\u5bf9\u643a\u5e26\u590d\u6742\u53c2\u6570\u7684\u7a0b\u5e8f\u7684\u8f6f\u4ef6\u5f00\u53d1\u5de5\u5177\uff0c\u56e0\u6b64\u5f00\u53d1\u672c\u7cfb\u7edf\u4ee5\u586b\u8865\u884c\u4e1a\u7a7a\u7f3a\uff0c\u4e3a\u590d\u6742\u914d\u7f6e\u53c2\u6570\u7684\u573a\u666f\u63d0\u4f9b\u89e3\u51b3\u65b9\u6848\u3002\n\n\u7531\u4e8e python \u8bed\u8a00\u6709\u7740\u201c\u80f6\u6c34\u8bed\u8a00\u201d\u7684\u7279\u6027\uff0c\u80fd\u5f88\u597d\u5730\u4e0e\u5176\u4ed6\u7f16\u7a0b\u8bed\u8a00\uff08\u5982 C\uff0cJava\uff0cFortran \u7b49\uff09\u7f16\u5199\u7684\u7a0b\u5e8f\u8fdb\u884c\u4ea4\u4e92\uff0c\u5e76\u4e14\u5b83\u7684\u5b66\u4e60\u95e8\u69db\u4f4e\uff0c\u5e94\u7528\u8303\u56f4\u5e7f\uff0c \u672c\u7cfb\u7edf\u5f62\u6210\u4e86\u4e00\u5957 python \u8bed\u8a00\u7684\u8f6f\u4ef6\u5f00\u53d1\u5de5\u5177\u5305\u3002\n\n\u672c\u7cfb\u7edf\u5c06\u590d\u6742\u53c2\u6570\u7684\u5173\u7cfb\u4ee5\u6811\u72b6\u56fe\u8fdb\u884c\u6574\u7406\uff0c\u4ee5 yaml \u683c\u5f0f\u7684\u914d\u7f6e\u6587\u4ef6\u4e3a\u6807\u51c6\uff0c\u5f00\u53d1\u8005\u53ef\u4ee5\u901a\u8fc7\u8be5\u7cfb\u7edf\u5b9a\u5236\u5305\u542b\u590d\u6742\u7ea6\u675f\u5173\u7cfb\u7684\u53c2\u6570\u914d\u7f6e\u7cfb\u7edf\uff0c\u53ef\u4ee5\u751f\u6210\u7f51\u9875\u7aef\u7684\u914d\u7f6e\u9875\u9762\u3001\u547d\u4ee4\u884c\u64cd\u4f5c\u63a5\u53e3\u3001\u914d\u7f6e\u6587\u4ef6\u63a5\u53e3\u3001\u7f51\u7ad9\u540e\u53f0\u63a5\u53e3\u7b49\u591a\u79cd\u63a5\u5165\u65b9\u6848\uff0c\u53ef\u6ee1\u8db3\u4e0d\u540c\u7528\u6237\u7684\u4f7f\u7528\u4e60\u60ef\u3002\n\n## \u7cfb\u7edf\u6d41\u7a0b\u56fe\n\n```mermaid\n%%{init: {\n'theme': 'base',\n'themeVariables': {\n'fontSize': '24px',\n'fontFamily': \"SimSun, serif\",\n'background': '#efefef',\n'primaryColor': '#fff',\n'primaryTextColor': '#000',\n'primaryBorderColor': '#000',\n'lineColor': '#000',\n'secondaryColor': '#000',\n'tertiaryColor': '#000'}}}%%\nflowchart TB\n    a(\u5f00\u59cb)\n    b[\u901a\u8fc7\u601d\u7ef4\u5bfc\u56fe\u6574\u7406\u53c2\u6570\u7ed3\u6784]\n    c[\u914d\u7f6epython\u73af\u5883]\n    d[\u8f7d\u5165\u672c\u7cfb\u7edf]\n    e[\u5b9a\u4e49\u53c2\u6570\u7ed3\u70b9]\n    f[\u8fd0\u884c\u7a0b\u5e8f]\n    g(\u7ed3\u675f)\n    a --> b --> c --> d --> e --> f --> g\n```\n\n## \u8bbe\u8ba1\u6982\u5ff5\n\n\u901a\u8fc7\u601d\u7ef4\u5bfc\u56fe\u7684\u6574\u7406\u540e\u7684\u53c2\u6570\u7ed3\u6784\u662f\u6811\u72b6\u7684\uff0c\u800c\u6811\u72b6\u7684\u6570\u636e\u53ef\u5206\u4e3a**\u6839\u8282\u70b9**\u3001**\u5b50\u8282\u70b9**\u3001**\u53f6\u5b50\u8282\u70b9**\uff0c\u5982\u4e0b\u56fe\u6240\u793a\uff1a\n\n```mermaid\nmindmap\n  root((\u6839\u8282\u70b9))\n    \u53f6\u5b50\u8282\u70b9\n    \u5b50\u8282\u70b9\n      \u53f6\u5b50\u8282\u70b9\n      \u53f6\u5b50\u8282\u70b9\n      \u5b50\u8282\u70b9\n        \u53f6\u5b50\u8282\u70b9\n        \u53f6\u5b50\u8282\u70b9\n    \u5b50\u8282\u70b9\n      \u53f6\u5b50\u8282\u70b9\n      \u53f6\u5b50\u8282\u70b9\n```\n\n\u5728\u914d\u7f6e\u6587\u4ef6\u4e2d\uff0c\"\u6839\u8282\u70b9\"\u5bf9\u5e94\u914d\u7f6e\u6587\u4ef6\u672c\u8eab\uff0c\"\u5b50\u8282\u70b9\"\u5bf9\u5e94\u82e5\u5e72\u4e2a\u53c2\u6570\u6240\u5728\u7684\u5206\u7ec4\uff0c\"\u53f6\u5b50\u8282\u70b9\"\u5bf9\u5e94\u67d0\u4e2a\u53c2\u6570\uff0c\u5982\u4e0b\u56fe\u6240\u793a\uff1a\n\n```mermaid\nmindmap\n  root((\u914d\u7f6e\u6587\u4ef6))\n    \u4e00\u7ea7\u53c2\u6570\n    \u4e00\u7ea7\u5206\u7ec4\n      \u4e8c\u7ea7\u53c2\u6570\n      \u4e8c\u7ea7\u53c2\u6570\n      \u4e8c\u7ea7\u5206\u7ec4\n        \u4e09\u7ea7\u53c2\u6570\n        \u4e09\u7ea7\u53c2\u6570\n    \u4e00\u7ea7\u5206\u7ec4\n      \u4e8c\u7ea7\u53c2\u6570\n      \u4e8c\u7ea7\u53c2\u6570\n```\n\n\u5728\u53c2\u6570\u8bf4\u660e\u6587\u6863\u5e94\u4ee5\u7ae0\u8282\u53f7\u7684\u5f62\u5f0f\u4f53\u73b0\u53c2\u6570\u7684\u5206\u7ec4\u7ed3\u679c\uff0c\u5728\u53c2\u6570\u8bf4\u660e\u6587\u6863\u7684\u6bcf\u5c0f\u8282\u5bf9\u5e94\u67d0\u4e2a\u5206\u7ec4\u6216\u53c2\u6570\u3002\n\n\u4f8b\u5982\uff0c\u4ee5\u4e0b\u662f\u53c2\u6570\u8bf4\u660e\u6587\u6863\u76ee\u5f55\u7684\u793a\u4f8b\uff1a\n\n```Plain\n1. \u4e00\u7ea7\u53c2\u6570\n2. \u4e00\u7ea7\u5206\u7ec4\n    2.1 \u4e8c\u7ea7\u53c2\u6570\n    2.2 \u4e8c\u7ea7\u53c2\u6570\n    2.3 \u4e8c\u7ea7\u5206\u7ec4\n        2.3.1 \u4e09\u7ea7\u53c2\u6570\n        2.3.2 \u4e09\u7ea7\u53c2\u6570\n3. \u4e00\u7ea7\u5206\u7ec4\n    3.1 \u4e8c\u7ea7\u53c2\u6570\n    3.2 \u4e8c\u7ea7\u53c2\u6570\n```\n\n# \u5feb\u901f\u5165\u95e8\n\n\u672c\u5c0f\u8282\u4e2d\u5c06\u901a\u8fc7\u6784\u5efa\u4e00\u4e2a\u7528\u6237\u4fe1\u606f\u7ba1\u7406\u7a0b\u5e8f\u4e3a\u6848\u4f8b\uff0c\u8be6\u7ec6\u8bb2\u89e3\u5982\u4f55\u4f7f\u7528\u672c\u7cfb\u7edf\u3002\n\n## \u53c2\u6570\u7ed3\u6784\n\n```mermaid\nmindmap\n  UserSystem((\u7528\u6237\u7cfb\u7edf))\n    UserName))\u7528\u6237\u540d((\n    Gender))\u6027\u522b((\n    Age))\u5e74\u9f84((\n    AuthInfo[\u8d26\u53f7\u4fe1\u606f\uff08\u591a\u9009\u4e00\uff09]\n      EmailLogin[\u90ae\u7bb1\u767b\u5f55]\n        Email))\u90ae\u7bb1\u5730\u5740((\n        Password))\u5bc6\u7801((\n      PhoneLogin[\u624b\u673a\u767b\u5f55]\n        Phone))\u624b\u673a\u53f7\u7801((\n        Password))\u5bc6\u7801((\n```\n\n## \u53c2\u6570\u6587\u6863\n\n### \u7528\u6237\u540d\n\n\u7528\u6237\u540d\u662f\u7528\u6237\u5728\u7cfb\u7edf\u4e2d\u7684\u552f\u4e00\u6807\u8bc6\u7b26\u3002\n\n### \u6027\u522b\n\n\u53ef\u9009\u201c\u7537\u201d\u3001\u201c\u5973\u201d\u6216\u201c\u4fdd\u5bc6\u201d\uff0c\u9ed8\u8ba4\u503c\u4e3a\u201c\u4fdd\u5bc6\u201d\u3002\n\n### \u5e74\u9f84\n\n|   \u9009\u9879   |  \u8bf4\u660e  |\n|:------:|:----:|\n| 0-18\u5c81  | \u672a\u6210\u5e74\u4eba |\n| 19-44\u5c81 | \u9752\u5e74\u4eba  |\n| 45-59\u5c81 | \u4e2d\u5e74\u4eba  |\n| 60\u5c81\u4ee5\u4e0a  |  \u8001\u4eba  |\n\n### \u8d26\u53f7\u4fe1\u606f\n\n\u4e3a\u4e86\u65b9\u4fbf\u548c\u5b89\u5168\u8003\u8651\uff0c\u7cfb\u7edf\u63d0\u4f9b\u4e86**\u4e24\u79cd\u4e0d\u540c\u7684\u767b\u5f55\u65b9\u5f0f**\u4f9b\u7528\u6237\u9009\u62e9\uff08\u591a\u9009\u4e00\uff09\uff1a\n\n**\u90ae\u7bb1\u767b\u5f55**\uff1a\u82e5\u4f7f\u7528\u8be5\u65b9\u5f0f\uff0c\u7528\u6237\u9700\u8981\u586b\u5199\uff1a\n\n1. \u90ae\u7bb1\u5730\u5740\uff1a \u7528\u6237\u7684\u6709\u6548\u7535\u5b50\u90ae\u7bb1\u5730\u5740\uff1b\n2. \u5bc6\u7801\uff1a\u4e0e\u90ae\u7bb1\u5730\u5740\u5173\u8054\u7684\u5bc6\u7801\u3002\n\n**\u624b\u673a\u53f7\u767b\u5f55**\uff1a \u82e5\u4f7f\u7528\u8be5\u65b9\u5f0f\uff0c\u7528\u6237\u9700\u8981\u586b\u5199\uff1a\n\n1. \u624b\u673a\u53f7\u7801\uff1a \u7528\u6237\u7684\u6709\u6548\u624b\u673a\u53f7\u7801\uff1b\n2. \u5bc6\u7801\uff1a\u4e0e\u624b\u673a\u53f7\u7801\u5173\u8054\u7684\u5bc6\u7801\u3002\n\n## \u7a0b\u5e8f\u5b9a\u4e49\n\n```python\nfrom typing import Literal\nfrom enum import Enum\n\nimport pydantic  # \u652f\u6301Pydantic\u5185\u7f6e\u7684\u7c7b\u578b\u6807\u6ce8\n\nfrom config_server import (\n    set_title,\n    set_doc,\n    add_union_field,\n    ConfigLeaf,\n    ConfigNode,\n)\n\n\n@set_title(\"\u7528\u6237\u540d\")  # \u901a\u8fc7\u88c5\u9970\u5668\u8bbe\u7f6e\u6807\u9898\n@set_doc(\"\u7528\u6237\u540d\u662f\u7528\u6237\u5728\u7cfb\u7edf\u4e2d\u7684\u552f\u4e00\u6807\u8bc6\u7b26\")  # \u901a\u8fc7\u88c5\u9970\u5668\u8bbe\u7f6e\u5b57\u6bb5\u7684\u8be6\u7ec6\u8bf4\u660e\nclass UserName(ConfigLeaf):\n    root: str  # \u53ea\u662f\u505a\u4e86\u7c7b\u578b\u6807\u6ce8\uff0c\u4f46\u6ca1\u6709\u8d4b\u503c\uff0c\u8868\u793a\u8be5\u5b57\u6bb5\u4e3a\u5fc5\u586b\u5b57\u6bb5\n\n\n@set_title(\"\u6027\u522b\")\nclass Gender(ConfigLeaf):\n    root: Literal[\"\u7537\", \"\u5973\", \"\u4fdd\u5bc6\"] = \"\u4fdd\u5bc6\"  # \u76f4\u63a5\u8d4b\u503c\uff0c\u8868\u793a\u9ed8\u8ba4\u503c\u4e3a\u201c\u4fdd\u5bc6\u201d\n\n\n@set_title(\"\u5e74\u9f84\")\n@set_doc(\n    \"\"\"\n    |   \u9009\u9879   |   \u8bf4\u660e   |\n    | :------: | :-----: |\n    |  0-18\u5c81  | \u672a\u6210\u5e74\u4eba |\n    | 19-44\u5c81  |  \u9752\u5e74\u4eba  |\n    | 45-59\u5c81  |  \u4e2d\u5e74\u4eba  |\n    | 60\u5c81\u4ee5\u4e0a  |   \u8001\u4eba   |\n    \"\"\"\n)\nclass Age(ConfigLeaf):\n    # \u5f53\u9009\u9879\u8f83\u591a\u65f6\uff0c\u4e5f\u901a\u8fc7\u679a\u4e3e\u7c7b\u6765\u5b9a\u4e49\u53ef\u9009\u9879\n    class AgeEnum(Enum):\n        young = \"0-18\u5c81\"\n        middle = \"19-44\u5c81\"\n        old = \"45-59\u5c81\"\n        elderly = \"60\u5c81\u4ee5\u4e0a\"\n\n    root: AgeEnum = AgeEnum.middle.value\n\n\n# \u5728\u5168\u5c40\u4f5c\u7528\u57df\u4e2d\u5b9a\u4e49\uff08\u53f6\uff09\u5b50\u8282\u70b9\uff0c\u4ee5\u4fbf\u590d\u7528\uff08\u4f8b\u5982\uff0c\u624b\u673a/\u90ae\u7bb1\u767b\u5f55\u90fd\u9700\u8981\u5b9a\u4e49\u5bc6\u7801\uff09\n@set_title(\"\u5bc6\u7801\")\nclass Password(ConfigLeaf):\n    root: str\n\n    # \u901a\u8fc7\u91cd\u5199`verify_constraints`\u65b9\u6cd5\u6765\u5b9e\u73b0\u81ea\u5b9a\u4e49\u7684\u7ea6\u675f\uff0c\n    # \u672c\u65b9\u6cd5\u4f1a\u5728\u5b9e\u4f8b\u5316\u65f6\u81ea\u52a8\u8c03\u7528\uff1b\u82e5\u9a8c\u8bc1\u4e0d\u901a\u8fc7\uff0c\u8bf7\u629b\u51fa\u5f02\u5e38\uff1b\n    # \u5982\u679c\u9700\u8981\u8868\u793a\u8de8\u8282\u70b9\u7684\u7ea6\u675f\u6761\u4ef6\uff0c\u8bf7\u5728\u5176\u6700\u8fd1\u7684\u5171\u540c\u7236\u8282\u70b9\u4e2d\u5b9a\u4e49\u3002\n    def verify_constraints(self) -> None:\n        if len(self.root) < 8:\n            raise ValueError(\"\u5bc6\u7801\u957f\u5ea6\u4e0d\u80fd\u5c0f\u4e8e8\u4f4d\")\n\n\n@set_title(\"\u90ae\u7bb1\u767b\u5f55\")\nclass EmailLogin(ConfigNode):\n    @set_title(\"\u90ae\u7bb1\u5730\u5740\")\n    class Email(ConfigLeaf):\n        # \u53ef\u901a\u8fc7pydantic.constr\u6765\u6dfb\u52a0\u6b63\u5219\u8868\u8fbe\u5f0f\u7ea6\u675f\n        root: pydantic.constr(pattern=r\"^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$\")\n\n    address: Email\n    password: Password\n\n\n@set_title(\"\u624b\u673a\u767b\u5f55\")\nclass PhoneLogin(ConfigNode):\n    @set_title(\"\u624b\u673a\u53f7\u7801\")\n    class Phone(ConfigLeaf):\n        root: str\n\n    phone: Phone\n    password: Password\n\n\n@set_title(\"\u6ce8\u518c\u4fe1\u606f\")\n@set_doc(\n    \"\"\"\n    \u63d0\u4f9b\u4e86\u4e24\u79cd\u4e0d\u540c\u7684\u767b\u5f55\u65b9\u5f0f\u4f9b\u7528\u6237\u9009\u62e9\uff1a\n\n    | \u767b\u5f55\u65b9\u5f0f |      \u8bf4\u660e          |\n    | :---:  | :----------------: |\n    |  \u90ae\u7bb1   | \u7528\u6237\u53ef\u4ee5\u4f7f\u7528\u90ae\u7bb1\u767b\u5f55  |\n    |  \u624b\u673a   | \u7528\u6237\u53ef\u4ee5\u4f7f\u7528\u624b\u673a\u53f7\u767b\u5f55 |\n\"\"\"\n)  # \u8be6\u7ec6\u8bf4\u660e\u652f\u6301markdown\u8bed\u6cd5\n@add_union_field(  # \u901a\u8fc7\u88c5\u9970\u5668\u6dfb\u52a0\u591a\u7c7b\u578b\u5b57\u6bb5\uff08\u591a\u9009\u4e00\uff09\n    field_name=\"root\",  # \u8be5\u5b57\u6bb5\u5728\u6dfb\u52a0\u540e\u7684\u5b57\u6bb5\u540d\n    field_title=\"\u6ce8\u518c\u4fe1\u606f\",  # \u8be5\u5b57\u6bb5\u7684\u6807\u9898\n    field_doc=\"\u7528\u6237\u5728\u7f51\u7ad9\u4e0a\u6ce8\u518c\u65f6\u6240\u586b\u5199\u7684\u4fe1\u606f\",  # \u8be5\u5b57\u6bb5\u7684\u8be6\u7ec6\u8bf4\u660e\n    types=[EmailLogin, PhoneLogin],  # \u8be5\u5b57\u6bb5\u53ef\u4ee5\u63a5\u53d7\u7684\u7c7b\u578b\uff0c\u6b64\u5904\u4e3a\u4e24\u79cd\u4e0d\u540c\u7684\u767b\u5f55\u65b9\u5f0f\n    discriminator=\"login_method\",  # \u5728\u5b50\u8282\u70b9\u4e2d\uff0c\u901a\u8fc7discriminator\u5b57\u6bb5\u6765\u533a\u5206\u4e0d\u540c\u7684\u7c7b\u578b\n)\nclass AuthInfo(ConfigLeaf):  # \u5f53\u8282\u70b9\u53ea\u6709\u4e00\u4e2a\u5b57\u6bb5\u65f6\uff0c\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528ConfigLeaf\uff0c\u5e76\u5c06\u552f\u4e00\u5b57\u6bb5\u540d\u8bbe\u7f6e\u4e3a`root`\n    ...\n\n\n@set_title(\"\u7528\u6237\u7cfb\u7edf\")\nclass UserSystem(ConfigNode):\n    username: UserName\n    gender: Gender\n    age: Age\n    auth_info: AuthInfo\n```\n\n### \u547d\u4ee4\u884c\u751f\u6210\u914d\u7f6e\u6587\u4ef6\n\n\u5f53\u7a0b\u5e8f\u5b8c\u6210\u5b9a\u4e49\u65f6\uff0c\u53ef\u4ee5\u901a\u8fc7\u8c03\u7528 `mock_cli` \u548c `model_dump_yaml` \u65b9\u6cd5\uff0c\u80fd\u5728\u547d\u4ee4\u884c\u4e2d\u64cd\u4f5c\u540e\u751f\u6210\u914d\u7f6e\u6587\u4ef6\uff1a\n\n```python\nUserSystem.mock_cli().model_dump_yaml(comment=True)\n```\n\n\u547d\u4ee4\u884c\u6267\u884c\u6548\u679c\uff0c\u5982\u4e0b\u56fe\u6240\u793a\uff1a\n\n<img src=\"./assets/img/cli.png\" alt=\"\u547d\u4ee4\u884c\u6267\u884c\u6548\u679c\" style=\"zoom:50%;\" title=\"\u547d\u4ee4\u884c\u6267\u884c\u6548\u679c\"/>\n\n### \u7a0b\u5e8f\u62d3\u5c55\u63a5\u53e3\n\n\u5f53\u7a0b\u5e8f\u5b8c\u6210\u5b9a\u4e49\u65f6\uff0c\u53ef\u4ee5\u901a\u8fc7\u8c03\u7528 `get_api_router` \u65b9\u6cd5\u6765\u542f\u52a8\u540e\u7aef\u670d\u52a1\u5f00\u542f\u6807\u51c6\u5316\u7684\u5e94\u7528\u7a0b\u5e8f\u63a5\u53e3\uff0c\u4ee5\u4e0b\u662f\u4e00\u4e2a\u7b80\u5355\u7684\u6848\u4f8b\uff1a\n\n```python\nimport uvicorn\nimport fastapi\n\napp = fastapi.FastAPI()\napp.include_router(UserSystem.get_api_router(\"/\"))\nuvicorn.run(app, host=\"127.0.0.1\", port=8000, log_level=\"info\")\n```\n\n<img src=\"./assets/img/backend.png\" alt=\"\u5728\u547d\u4ee4\u884c\u5f00\u542f\u540e\u7aef\u670d\u52a1\" style=\"zoom: 67%;\" title=\"\u5728\u547d\u4ee4\u884c\u5f00\u542f\u540e\u7aef\u670d\u52a1\"/>\n\n### \u7528\u6237\u754c\u9762\n\n\u672c\u7cfb\u7edf\u5305\u542b\u4e00\u4e2a\u914d\u5957\u7684\u7f51\u9875\u7aef\u7528\u6237\u754c\u9762\uff0c\u4ee5\u4e0a\u6848\u4f8b\u5bf9\u5e94\u7684\u9875\u9762\u5982\u4e0b\u56fe\u6240\u793a\uff1a\n\n<img src=\"./assets/img/demo.png\" alt=\"\u7f51\u9875\u7aef\u7528\u6237\u754c\u9762\" style=\"zoom: 50%;\" title=\"\u7f51\u9875\u7aef\u7528\u6237\u754c\u9762\"/>\n\n\u5f53\u70b9\u51fb\u8868\u5355\u5b57\u6bb5\u524d\u65b9\u7684 `?` \u6807\u8bc6\u65f6\uff0c\u4f1a\u51fa\u73b0\u8be5\u5b57\u6bb5\u7684\u8be6\u7ec6\u8bf4\u660e\uff0c\u5982\u4e0b\u56fe\u6240\u793a\uff1a\n\n<img src=\"./assets/img/show-doc.png\" alt=\"\u5b57\u6bb5\u7684\u8be6\u7ec6\u8bf4\u660e\" style=\"zoom:50%;\" title=\"\u5b57\u6bb5\u7684\u8be6\u7ec6\u8bf4\u660e\"/>\n\n\u5f53\u9009\u62e9\u591a\u7c7b\u578b\u5b57\u6bb5\u7684\u503c\u540e\uff0c\u5176\u5b50\u8282\u70b9\u7684\u5185\u5bb9\u4e5f\u4f1a\u968f\u4e4b\u53d1\u751f\u53d8\u5316\uff0c\u5982\u4e0b\u56fe\u6240\u793a\uff1a\n\n<img src=\"./assets/img/one-of-field.png\" alt=\"\u591a\u7c7b\u578b\u5b57\u6bb5\u53d8\u52a8\" style=\"zoom: 50%;\" title=\"\u591a\u7c7b\u578b\u5b57\u6bb5\u53d8\u52a8\"/>\n\n\u5f53\u5fc5\u586b\u5b57\u6bb5\u6ca1\u6709\u586b\u5199\u65f6\uff0c\u7f51\u9875\u4f1a\u7ed9\u51fa\u63d0\u793a\uff1a\n\n<img src=\"./assets/img/required-no-fill.png\" alt=\"\u5fc5\u586b\u9879\u672a\u586b\u63d0\u9192\" style=\"zoom: 50%;\" title=\"\u5fc5\u586b\u9879\u672a\u586b\u63d0\u9192\"/>\n\n\u5f53\u5df2\u586b\u5b57\u6bb5\u672a\u901a\u8fc7\u6821\u9a8c\u65f6\uff0c\u7f51\u9875\u4f1a\u7ed9\u51fa\u63d0\u793a\uff1a\n\n<img src=\"./assets/img/validation-error.png\" alt=\"\u6821\u9a8c\u9519\u8bef\u63d0\u9192\" style=\"zoom:50%;\" title=\"\u6821\u9a8c\u9519\u8bef\u63d0\u9192\"/>\n\n\u5728\u9a8c\u8bc1\u901a\u8fc7\u65f6\uff0c\u53ef\u4ee5\u751f\u6210 yaml \u683c\u5f0f\u7684\u914d\u7f6e\u6587\u4ef6\uff0c\u5982\u4e0b\u56fe\u6240\u793a\uff1a\n\n<img src=\"./assets/img/yaml-editor.png\" alt=\"yaml\u914d\u7f6e\u6587\u4ef6\u7f16\u8f91\u5668\" style=\"zoom:50%;\" title=\"yaml\u914d\u7f6e\u6587\u4ef6\u7f16\u8f91\u5668\"/>\n\n# \u7a0b\u5e8f\u63a5\u53e3\n\n\u672c\u7cfb\u7edf\u7684\u6838\u5fc3\u90e8\u5206\u5c31\u662f\u5176\u914d\u7f6e\u8282\u70b9\uff08ConfigNode\uff09\u548c\u914d\u7f6e\u53f6\u5b50\u8282\u70b9\uff08ConfigLeaf\uff09\uff0c\u5b83\u4eec\u5171\u540c\u6784\u6210\u4e86\u914d\u7f6e\u7684\u6811\u5f62\u7ed3\u6784\u3002\n\n## \u8282\u70b9\u7c7b\uff08ConfigNode\uff09\n\n\u8282\u70b9\u7c7b\u662f `pydantic.BaseModel` \u7684\u8d85\u96c6\u3002\u4f5c\u4e3a\u914d\u7f6e\u7cfb\u7edf\u7684\u6838\u5fc3\uff0c\u7528\u4e8e\u6784\u5efa\u3001\u9a8c\u8bc1\u3001\u8f6c\u6362\u3001\u5e8f\u5217\u5316\u914d\u7f6e\u6811\u7684\u7ed3\u6784\u3002\n\n### \u9a8c\u8bc1YAML\u6587\u4ef6\n\n\u4f7f\u7528`model_validate_yaml`\u65b9\u6cd5\uff0c\u5f00\u53d1\u4eba\u5458\u53ef\u4ee5\u901a\u8fc7\u4f20\u9012YAML\u6587\u4ef6\u7684\u8def\u5f84\u8fdb\u884c\u9a8c\u8bc1\u3002\u5982\u679c\u9a8c\u8bc1\u6210\u529f\uff0c\u5c06\u8fd4\u56de\u4e00\u4e2a\u6709\u6548\u7684ConfigNode\u5b9e\u4f8b\u3002\n\n### \u6a21\u62df\u65b9\u6cd5\n\nConfigNode\u7c7b\u8fd8\u63d0\u4f9b\u4e86\u591a\u4e2a\u6a21\u62df\u65b9\u6cd5\uff0c\u5982`mock`\uff0c`mock_with_default`\uff0c`mock_with_blank`\u548c`mock_cli`\u3002\u8fd9\u4e9b\u65b9\u6cd5\u53ef\u4ee5\u6839\u636e\u5b57\u6bb5\u7c7b\u578b\u548c\u9ed8\u8ba4\u503c\u751f\u6210\u6a21\u62df\u5b9e\u4f8b\u3002\n\n### YAML\u5e8f\u5217\u5316\n\n\u4f7f\u7528`model_dump_yaml`\u65b9\u6cd5\uff0c\u53ef\u4ee5\u5c06ConfigNode\u5b9e\u4f8b\u8f6c\u50a8\u4e3aYAML\u5b57\u7b26\u4e32\u3002`model_dump_mock_yaml`\u65b9\u6cd5\u63d0\u4f9b\u4e86\u6a21\u62df\u5b9e\u4f8b\u7684\u8f6c\u50a8\u529f\u80fd\u3002\n\n### \u751f\u6210\u540e\u7aef\u63a5\u53e3\u8def\u7531\n\n`get_api_router`\u65b9\u6cd5\u53ef\u7528\u4e8e\u83b7\u53d6API\u8def\u7531\u3002\n\n### \u7ea6\u675f\u9a8c\u8bc1\n\n`verify_constraints`\u65b9\u6cd5\u7528\u4e8e\u9a8c\u8bc1\u7ea6\u675f\u6761\u4ef6\uff0c\u53ef\u4ee5\u6839\u636e\u9700\u8981\u8986\u76d6\u3002\n\n## \u53f6\u8282\u70b9\u7c7b\uff08ConfigLeaf\uff09\n\n\u53f6\u8282\u70b9\u7c7b\u7ee7\u627f\u81ea\u6839\u6a21\u578b\u548c\u8282\u70b9\u7c7b\uff0c\u7528\u4e8e\u6784\u5efa\u914d\u7f6e\u6811\u7684\u672b\u7aef\u8282\u70b9\u3002\n\n## \u88c5\u9970\u5668\n\n### \u8bbe\u7f6e\u6807\u9898\uff08set_title\uff09\n\n\u901a\u8fc7\u4f20\u9012\u6807\u9898\u5b57\u7b26\u4e32\uff0c\u53ef\u4ee5\u8bbe\u7f6e\u6a21\u578b\u7684\u6807\u9898\u3002\n\n```python\n@set_title(\"\u6807\u9898\")\nclass ExampleModel(ConfigNode):\n    ...\n```\n\n### \u8bbe\u7f6e\u8bf4\u660e\uff08set_doc\uff09\n\n\u8be5\u88c5\u9970\u5668\u7528\u4e8e\u8bbe\u7f6e\u6a21\u578b\u7684\u8be6\u7ec6\u8bf4\u660e\u3002\n\n```python\n@set_doc(\"\u8fd9\u662f\u4e00\u4e2a\u793a\u4f8b\u6a21\u578b\")\nclass ExampleModel(ConfigNode):\n    ...\n```\n\n### \u8bbe\u7f6e\u9274\u522b\u5b57\u6bb5\uff08set_discriminator\uff09\n\n\u6b64\u88c5\u9970\u5668\u53ef\u4ee5\u8bbe\u7f6e\u6a21\u578b\u7684\u533a\u5206\u5668\u5b57\u6bb5\u540d\u3002\n\n```python\n@set_discriminator(\"type\")\nclass ExampleModel(ConfigNode):\n    ...\n```\n\n### \u6dfb\u52a0\u590d\u5408\u7c7b\u578b\u5b57\u6bb5\uff08add_union_field\uff09\n\n\u6b64\u88c5\u9970\u5668\u53ef\u4ee5\u5c06\u5177\u6709\u591a\u79cd\u7c7b\u578b\u7684\u5b57\u6bb5\u6dfb\u52a0\u5230\u6a21\u578b\u4e2d\uff0c\u8fd8\u53ef\u4ee5\u901a\u8fc7\u53ef\u9009\u53c2\u6570\u8bbe\u7f6e\u5b57\u6bb5\u6807\u9898\u548c\u6587\u6863\u3002\n\n```python\n@add_union_field(\"field_name\", [Type1, Type2])\nclass ExampleModel(ConfigNode):\n    ...\n```\n",
    "bugtrack_url": null,
    "license": "",
    "summary": "To build a extendable config server.",
    "version": "0.6.4",
    "project_urls": null,
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "10c6f66e085c0c6461616625b1f01e345ff9179e615a2ca21533e693f0d2dbd4",
                "md5": "7629d989d79d2e027475a081c84d1f36",
                "sha256": "fe804d7153000c87f5091f9e3a83a9d490d41954cc404f69d5fa6c486adf0598"
            },
            "downloads": -1,
            "filename": "config_server-0.6.4-cp310-cp310-manylinux1_x86_64.whl",
            "has_sig": false,
            "md5_digest": "7629d989d79d2e027475a081c84d1f36",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": ">=3.8, <3.12",
            "size": 2024016,
            "upload_time": "2023-12-01T09:13:51",
            "upload_time_iso_8601": "2023-12-01T09:13:51.132592Z",
            "url": "https://files.pythonhosted.org/packages/10/c6/f66e085c0c6461616625b1f01e345ff9179e615a2ca21533e693f0d2dbd4/config_server-0.6.4-cp310-cp310-manylinux1_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "cde9de6e45a67b1c8513d81a991a9499fef1966adeb213962db3896111398119",
                "md5": "ffaf840efe641d26520744294cb8a881",
                "sha256": "894d246ceb02753c01f40da1c01e7993e890b476d86f3b3a9eded4f2a6f330dc"
            },
            "downloads": -1,
            "filename": "config_server-0.6.4-cp310-cp310-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "ffaf840efe641d26520744294cb8a881",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": ">=3.8, <3.12",
            "size": 1942350,
            "upload_time": "2023-12-01T09:18:05",
            "upload_time_iso_8601": "2023-12-01T09:18:05.330885Z",
            "url": "https://files.pythonhosted.org/packages/cd/e9/de6e45a67b1c8513d81a991a9499fef1966adeb213962db3896111398119/config_server-0.6.4-cp310-cp310-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "bdf361463f4f52b824d3cc647312d945661a552b79f0eb4dff47f1658624e482",
                "md5": "70dab3cdb9f6b2f623214bdb6818cf99",
                "sha256": "ae3a6aa57d1bd62930764f9a0708a964a57fe5dc9e7c025c495a93a29b96369b"
            },
            "downloads": -1,
            "filename": "config_server-0.6.4-cp311-cp311-manylinux1_x86_64.whl",
            "has_sig": false,
            "md5_digest": "70dab3cdb9f6b2f623214bdb6818cf99",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.8, <3.12",
            "size": 2036161,
            "upload_time": "2023-12-01T09:14:25",
            "upload_time_iso_8601": "2023-12-01T09:14:25.173449Z",
            "url": "https://files.pythonhosted.org/packages/bd/f3/61463f4f52b824d3cc647312d945661a552b79f0eb4dff47f1658624e482/config_server-0.6.4-cp311-cp311-manylinux1_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b227ceb79d67052b964f91971fdd346a54628b0b453e714a018371af12f5f44d",
                "md5": "9a147f81237af8c8f199aca61f2aec48",
                "sha256": "7acdbb7b75fad5421faf35b7cabbb39971d2bc004b0d3eb55f9bf413ad4900c0"
            },
            "downloads": -1,
            "filename": "config_server-0.6.4-cp311-cp311-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "9a147f81237af8c8f199aca61f2aec48",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.8, <3.12",
            "size": 1947549,
            "upload_time": "2023-12-01T09:17:32",
            "upload_time_iso_8601": "2023-12-01T09:17:32.695461Z",
            "url": "https://files.pythonhosted.org/packages/b2/27/ceb79d67052b964f91971fdd346a54628b0b453e714a018371af12f5f44d/config_server-0.6.4-cp311-cp311-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8a052e77e8dff60b1865ca81f4bb4853b6148def97d5ffe94b5547ac818b29c9",
                "md5": "f302086ae27ec31834f58b508980182c",
                "sha256": "0e98455a46bb523586f1923b02234433ae494dd2ed9edb0a7a57078c8f30eb17"
            },
            "downloads": -1,
            "filename": "config_server-0.6.4-cp38-cp38-manylinux1_x86_64.whl",
            "has_sig": false,
            "md5_digest": "f302086ae27ec31834f58b508980182c",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8, <3.12",
            "size": 2018626,
            "upload_time": "2023-12-01T09:12:47",
            "upload_time_iso_8601": "2023-12-01T09:12:47.440965Z",
            "url": "https://files.pythonhosted.org/packages/8a/05/2e77e8dff60b1865ca81f4bb4853b6148def97d5ffe94b5547ac818b29c9/config_server-0.6.4-cp38-cp38-manylinux1_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "36e82eb86823d5f47234e68870c6d92b18e08e7c786df488930bf0cbce3936ba",
                "md5": "72447fef49dbaf0bf3719e2adca7916f",
                "sha256": "fd94bf8e104e21364a416e0150bef5d97e09f2046dcefa8045e9750ed3b2292b"
            },
            "downloads": -1,
            "filename": "config_server-0.6.4-cp38-cp38-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "72447fef49dbaf0bf3719e2adca7916f",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8, <3.12",
            "size": 1937611,
            "upload_time": "2023-12-01T09:18:38",
            "upload_time_iso_8601": "2023-12-01T09:18:38.605079Z",
            "url": "https://files.pythonhosted.org/packages/36/e8/2eb86823d5f47234e68870c6d92b18e08e7c786df488930bf0cbce3936ba/config_server-0.6.4-cp38-cp38-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "478f8983f2287e991cd4e7b06c2e60fae45a0495d8239be025f3e9c3d6ff42d0",
                "md5": "d2d2c976e5a170c752d39697523ad1ae",
                "sha256": "6a7b4786a6834f1829ca73b55be0a785a6936b5d0d4b9b3841ffac08df0a5b52"
            },
            "downloads": -1,
            "filename": "config_server-0.6.4-cp39-cp39-manylinux1_x86_64.whl",
            "has_sig": false,
            "md5_digest": "d2d2c976e5a170c752d39697523ad1ae",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": ">=3.8, <3.12",
            "size": 2019420,
            "upload_time": "2023-12-01T09:13:17",
            "upload_time_iso_8601": "2023-12-01T09:13:17.564201Z",
            "url": "https://files.pythonhosted.org/packages/47/8f/8983f2287e991cd4e7b06c2e60fae45a0495d8239be025f3e9c3d6ff42d0/config_server-0.6.4-cp39-cp39-manylinux1_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d48da954fa16308eb33de07127c572762f7006dc21ac64cc7918a3bded4b1142",
                "md5": "d351008867c2d1335c9a7ddfd4b09750",
                "sha256": "bb1e8be52ae5d16a4cce1f6b50d9abb85c69dc3b014e25ed598fad04fedf9d13"
            },
            "downloads": -1,
            "filename": "config_server-0.6.4-cp39-cp39-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "d351008867c2d1335c9a7ddfd4b09750",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": ">=3.8, <3.12",
            "size": 1940770,
            "upload_time": "2023-12-01T09:18:01",
            "upload_time_iso_8601": "2023-12-01T09:18:01.890406Z",
            "url": "https://files.pythonhosted.org/packages/d4/8d/a954fa16308eb33de07127c572762f7006dc21ac64cc7918a3bded4b1142/config_server-0.6.4-cp39-cp39-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-12-01 09:13:51",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "config-server"
}
        
Elapsed time: 0.32866s