跳转到主要内容

自动配置应用程序对象。

项目描述

tree-config

配置以树状方式嵌套的对象。

更多信息: https://matham.github.io/tree-config/index.html

Supported Python versions Latest Version on PyPI Coverage status Github action status

安装

tree-config 可以通过以下方式安装

pip install tree-config

配置使用

tree-config 可以将所有类的可配置属性导出到yaml文件,然后加载该文件并将值恢复/应用到属性中。例如:

class App:

    _config_props_ = ('name', )

    _config_children_ = {'app panel': 'panel'}

    def __init__(self):
        self.name = 'Desk'
        self.panel = AppPanel()

class AppPanel:

    _config_props_ = ('color', )

    color = 'A4FF67'

将自动配置 namecolor

以下是一个示例指南,展示了控制配置的多种方法。请参阅 配置API,包括完整的 Configuration 类详情。

请参阅仓库中的示例目录以获取以下示例的完整可运行代码。

基本属性

此示例有一个包含两个可配置面板的应用程序类。 _config_props_ 列出了类的可配置属性,而 _config_children_ 构造了可配置对象的树。

class App:

    _config_props_ = ('size', 'name')

    _config_children_ = {'app panel': 'panel1', 'home panel': 'panel2'}

    def __init__(self):
        self.size = 12
        self.name = 'Desk'

        self.panel1 = AppPanel()
        self.panel2 = HomePanel()


class AppPanel:

    _config_props_ = ('color', )

    color = 'A4FF67'


class HomePanel:

    _config_props_ = ('shape', )

    shape = 'circle'

然后,运行

from tree_config import dump_config, read_config_from_object
app = App()
dump_config('basic_example.yaml', read_config_from_object(app))
print(f'Shape is: {app.panel2.shape}')

创建一个 basic_example.yaml 文件,内容如下

app panel: {color: A4FF67}
home panel: {shape: circle}
name: Desk
size: 12

并打印 Shape is: circle。如果我们想加载一个之前包含形状为 "square" 的yaml文件并将其应用到实例上,我们执行以下操作

from tree_config import load_config, apply_config
app = App()
apply_config(app, load_config(app, 'basic_example.yaml'))
print(f'Shape is: {app.panel2.shape}')

这会打印出 Shape is: square

钩子属性发现

`_config_props_` 和 `_config_children_` 是在类上定义的,而不是在实例上定义的。当 `tree-config` 使用它们时,它会遍历整个类层次结构,并从所有超类中累积它们的值,因为子类不会覆盖它们,而是向它们添加。

如果 `_config_props` 和/或 `_config_children` 在类或实例上定义,`tree-config` 将直接使用该值,而不是分别遍历 `_config_props_` 和/或 `_config_children_`。

例如,以下代码

from tree_config import dump_config, read_config_from_object


class App:

    _config_children_ = {'app panel': 'panel1', 'home panel': 'panel2'}

    def __init__(self):
        self.panel1 = AppPanel()
        self.panel2 = HomePanel()


class RootPanel:

    _config_props_ = ('size', 'name')

    size = 12

    name = 'Desk'


class AppPanel(RootPanel):

    _config_props_ = ('color', )

    color = 'A4FF67'


class HomePanel(AppPanel):

    _config_props_ = ('shape', )

    shape = 'circle'

    group = 'window'

    _config_props = ('group', 'size')

运行时

app = App()
# now get and save config to yaml file
dump_config('hook_properties.yaml', read_config_from_object(app))

将生成以下 yaml 文件

app panel:
  color: A4FF67
  name: Desk
  size: 12
home panel:
  group: window
  size: 12

注意,`app panel` 包含了 `RootPanel` 和 `AppPanel` 的属性,而 `home panel` 只包含在 `_config_props` 中列出的属性。《_config_children_》的行为类似。

自定义值钩子

我们可能希望将属性获取/设置过程挂钩以更改值,在它保存之前或再次应用之前。

例如,假设我们有一个存储命名元组的属性,我们需要将其导出为列表(因为 yaml 不理解命名元组),并在恢复时再次创建命名元组。《get_config_property` 和 《apply_config_property` 是所需的钩子方法,如果类中存在,将自动使用它们

from collections import namedtuple
from tree_config import dump_config, load_config, apply_config, \
    read_config_from_object

Point = namedtuple('Point', ['x', 'y'])


class App:

    _config_props_ = ('point', 'name')

    point = Point(11, 34)

    name = ''

    def get_config_property(self, name):
        if name == 'point':
            return tuple(self.point)
        return getattr(self, name)

    def apply_config_property(self, name, value):
        if name == 'point':
            self.point = Point(*value)
        else:
            setattr(self, name, value)

然后,运行

from tree_config import dump_config, read_config_from_object
app = App()
dump_config('custom_value_example.yaml', read_config_from_object(app))
print(f'point is: {app.point}')

创建一个包含以下内容的 《custom_value_example.yaml` 文件

name: ''
point: [11, 34]

并打印 `point is: Point(x=11, y=34)`。如果我们想再次加载并应用 yaml 文件,我们做

from tree_config import load_config, apply_config
app = App()
apply_config(app, load_config(app, 'custom_value_example.yaml'))
print(f'point is: {app.point}')

这反过来会再次打印 `point is: Point(x=11, y=34)`。

有关类似挂钩应用子对象的应用,请参阅 `apply_config_child`。默认情况下,如果没有提供,则使用 `apply_config`,因此如果覆盖,这也应该用于基本案例。

自定义标签(序列化)

Yaml 支持使用自定义标签在文件中表示任意对象。这使全局支持对象成为可能,而无需在它们使用的每个地方使用 《get_config_property` / 《apply_config_property`。

使用上面的点示例

from collections import namedtuple
from tree_config import dump_config, load_config, apply_config, \
    read_config_from_object
from ruamel.yaml import BaseConstructor, BaseRepresenter

Point = namedtuple('Point', ['x', 'y'])

yaml_tag = '!tree_config_example_point'

# encoder
def _represent_point(representer: BaseRepresenter, val):
    return representer.represent_sequence(yaml_tag, tuple(val))

# decoder
def _construct_point(constructor: BaseConstructor, tag, node):
    return Point(*constructor.construct_sequence(node))

# tell yaml how to represent a Point
def register_point_yaml_support() -> None:
    BaseRepresenter.add_multi_representer(Point, _represent_point)
    BaseConstructor.add_multi_constructor(yaml_tag, _construct_point)


class App:

    _config_props_ = ('point', 'name')

    point = Point(11, 34)

    name = ''

现在,在运行上一节中的 tree-config 导出/加载代码之前调用

register_point_yaml_support()

它将生成以下内容的 yaml 文件

name: ''
point: !tree_config_example_point [11, 34]

有关其他自定义,请参阅 《yaml_dumps` 和 《yaml_loads`。大多数函数都接受 `yaml_dump_str` / 《yaml_load_str` 以允许进一步自定义 yaml 对象。请参阅 《tree_config.yaml` 中的 《register_torch_yaml_support` 以获取更完整的示例以及一些可以直接注册的内置可选表示器。

应用后分派

在将配置应用于对象及其子对象之后,如果存在,tree-config 将调用对象的 《post_config_applied` 方法。例如。

from tree_config import dump_config, load_config, apply_config, \
    read_config_from_object


class App:

    _config_props_ = ('size', 'name')

    _config_children_ = {'app panel': 'panel'}

    size = 12

    name = 'Desk'

    def __init__(self):
        self.panel = Panel()

    def apply_config_property(self, name, value):
        print('applying', name)
        setattr(self, name, value)

    def post_config_applied(self):
        print('done applying app')


class Panel:

    _config_props_ = ('color', )

    color = 'A4FF67'

    def apply_config_property(self, name, value):
        print('applying', name)
        setattr(self, name, value)

    def post_config_applied(self):
        print('done applying panel')

然后,使用

# create app and set properties
app = App()

# now get and save config to yaml file
dump_config('post_apply_dispatch.yaml', read_config_from_object(app))
# load config and apply it
apply_config(app, load_config(app, 'post_apply_dispatch.yaml'))

保存并再次应用 yaml,打印以下内容

applying color
done applying panel
applying name
applying size
done applying app

可配置类

上述示例使用了 duck typing 方法来处理这些特殊的配置/钩子方法,并且所有这些方法都是可选的。tree-config 还提供了一个 《Configurable` 超类,它定义了所有这些方法并具有适当的默认值。

从 《Configurable` 继承没有好处,但它确实提供了一个包含所有特殊配置方法的基类。此外,它为每个类缓存属性/配置子对象的列表,因此一旦检索到,就不需要遍历树,这与 duck typing 方法不同,该方法在每次保存/应用时都会重新计算。

自动文档

除了配置之外,tree-config 还可以钩入 Sphinx 文档生成构建步骤,生成列出所有可由应用程序配置的属性的文档,并为每个属性显示文档字符串。这对于想要使用配置 yaml 文件配置这些属性的用户很有帮助。

示例目录有一个完整的文档示例。

给定一个根对象(例如示例中的 App),我们可以在 conf.py 中添加回调,当 Sphinx 遇到在 _config_props_ 中列出的属性时被调用。然后回调将这些属性的文档字符串保存到 yaml 文件中。

随后,在构建完成后,tree-config 可以遍历所有可配置的属性,并从根对象或类开始,从 yaml 文件中提取文档字符串,并创建包含这些文档字符串的 rst 文件。

例如,从下面的代码开始:

class App:
    """The app."""

    _config_props_ = ('size', 'name')

    _config_children_ = {'app panel': 'panel1', 'home panel': 'panel2'}

    size = 55
    """Some filename."""

    name = ''
    """Some name."""

    panel1: 'AppPanel' = None
    """The app panel."""

    panel2: 'HomePanel' = None
    """The home panel."""

    def __init__(self, size, name, color, shape):
        self.size = size
        self.name = name

        self.panel1 = AppPanel()
        self.panel1.color = color
        self.panel2 = HomePanel()
        self.panel2.shape = shape


class AppPanel:
    """The app panel."""

    _config_props_ = ('color', )

    color = ''
    """Color of the app."""


class HomePanel:
    """The home panel."""

    _config_props_ = ('shape', )

    shape = ''
    """Shape of the home."""

然后,我们在 conf.py 文件的顶部添加以下内容

import os
import sys
from functools import partial
sys.path.insert(0, os.path.abspath('../'))
from config_example import App
from tree_config.doc_gen import create_doc_listener, write_config_props_rst

添加到 sys.path 中的确切路径取决于代码所在的位置,或者如果它是一个不需要安装的 Python 包。

我们还需要将 'sphinx.ext.autodoc' 添加到扩展列表中。最后,在 conf.py 的末尾添加

def setup(app):
    # dump all config_example package/subpackages config docstrings to config_prop_docs.yaml
    create_doc_listener(app, 'config_example', 'config_prop_docs.yaml')

    # then get docstrings from yaml file, walk all config properties from App and
    # dump formatted config docs to source/config.rst
    app.connect(
        'build-finished', partial(
            write_config_props_rst, App, 'config_example',
            filename='config_prop_docs.yaml', rst_filename='source/config.rst')
    )

最后,我们将 config.rst(将在源目录下自动创建的文件名)添加到 Sphinx 生成的 index.rst 中。我们还需要在索引或它引用的文件中添加所有类的自动文档引用,否则我们不会得到相关的文档字符串。我们添加如下:

.. toctree::
   :maxdepth: 2
   :caption: Contents:

   config.rst


API
===

.. automodule:: config_example
   :members:

index.rst 中。

最后,我们运行

echo $'Config\n===========' > source/config.rst
make html
make html

首先,我们创建了一个几乎为空的 config.rst 文件。否则,Sphinx 在生成时不会包含它。然后我们运行了两次 make html,第一次自动生成以下 config_prop_docs.yaml 文件

config_example.App:
  name:
  - Some name.
  - ''
  size:
  - Some filename.
  - ''
config_example.AppPanel:
  color:
  - Color of the app.
  - ''
config_example.HomePanel:
  shape:
  - Shape of the home.
  - ''

第二次 make html 从这个 yaml 文件中提取文档字符串,并使用以下内容创建 config.rst 文件:

CONFIG_EXAMPLE Config
=====================

The following are the configuration options provided by the app. It can be configured by changing appropriate values in the ``config.yaml`` settings file. The options default to the default value of the classes for each of the options.

`name`:
 Default value::

  ''

 Some name.

`size`:
 Default value::

  55

 Some filename.


home panel
----------

`shape`:
 Default value::

  ''

 Shape of the home.


app panel
---------

`color`:
 Default value::

  ''

 Color of the app.

这个 rst 文件将由 Sphinx 自动渲染为漂亮的 html,并与其他文档一起显示,看起来如下:


CONFIG_EXAMPLE 配置

以下是由应用程序提供的配置选项。可以通过更改 config.yaml 设置文件中相应的值来配置它们。选项的默认值是每个选项的类的默认值。

name:

默认值

''

一些名称。

size:

默认值

55

一些文件名。

home panel

shape:

默认值

''

家的形状。

app panel

color:

默认值

''

应用程序的颜色。


类与实例

上述配置示例从 App 实例 中保存配置。也可以使用 App 来转储 yaml。主要区别在于,对于实例方法(如 apply_config_childget_config_propertyapply_config_propertypost_config_applied),这些方法是实例方法,将被跳过且不被使用。

此外,与实例不同,如果 _config_children_ 列表中的子属性值为 None,则会失败,对于类,如果定义了属性的类型提示,则将回退到类型提示。

使用 App 类而不是 App() 实例,在文档构建期间非常有帮助,此时可能无法实例化完整的 App(参见上面使用带有类型提示的类实例的文档示例)。

重用其他项目文档

由于我们依赖于 autodoc 来生成 config_prop_docs.yaml,tree-config 提供了一种机制来重用我们依赖的其他项目的文档字符串。

例如,假设我们依赖于定义可配置类的remote1remote2项目,我们的项目通过继承和扩展它们,进一步增加了可配置属性。此外,假设这些远程项目像示例中那样将它们的可配置docstrings输出到config_prop_docs.yaml,并在它们sphinx生成的文档根目录中提供,例如在github-pages上。

然后,tree-config提供了工具将这些docstrings合并到我们的文档中,从而能够从它们创建config.rst,如下所示

echo $'Config\n===========' > source/config.rst
python -m tree_config.doc_gen download \
    -u "https://user.github.io/remote1/config_prop_docs.yaml" -o config_prop_docs.yaml
python -m tree_config.doc_gen download -f config_prop_docs.yaml \
    -u "https://matham.github.io/remote2/config_prop_docs.yaml" -o config_prop_docs.yaml
make html
make html

这将从我们的依赖项下载并合并yaml文件,将其添加到我们的文档中,并生成config.rst

项目详情


下载文件

下载适合您平台的应用程序。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分发

tree-config-1.0.2.tar.gz (26.8 kB 查看哈希值)

上传时间 源代码

构建分发

tree_config-1.0.2-py2.py3-none-any.whl (24.0 kB 查看哈希值)

上传时间 Python 2 Python 3

支持者

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页面