一种方便配置Python应用程序的方法,使得遵循最佳实践变得容易且自然,并解决了在使用例如'configparser'库时遇到的各种常见问题。
项目描述
PyConfig
- TL;DR
PyConfig帮助您轻松编写可配置的应用程序,并在加载时处理配置验证。它允许最终用户选择他们的配置语言以及是否使用文件或环境变量或两者都使用。该库旨在使最佳实践成为做事情的自然方式,并消除编写和维护配置选项文档的需要。
- STL;INRAOT (Still Too Long; I’m Not Reading All Of That)
像configparser一样,但更酷。更安全。并有点自动完成。
通过示例介绍
您可以在下面找到库的完整指南,但对于简单的用例,可能只需查看一个示例就足够了,所以我们就从那里开始。
在这个例子中,我们假设构建一个问候用户并退出的应用程序。用户可以通过 --name 参数提供姓名。问候还包括根据是否将要下雨给出出门或待在家中的建议。
$ python -m demo
Hello, world! It's a beautiful day outside. Have fun!
$ python -m demo --name Dave
Hello, Dave! You should probably stay home today...
安装 PyConfig(包名 nx_config)
使用 pip
$ pip install nx_config
或使用 poetry
$ poetry add nx_config
创建配置类及其部分类
首先向您的应用程序中添加一个新文件,例如 config.py。在其中,您将定义一些“部分类”(这些是 ConfigSection 的子类)和一个“配置类”(这是 Config 的子类),然后初始化它的全局实例(请向下查看为什么这样做是可以的)
# demo/config.py
from datetime import timedelta
from typing import Optional
from nx_config import Config, ConfigSection, URL, SecretString, validate
class GreetingSection(ConfigSection):
num_exclamation_marks: int = 1
all_caps: bool = False
@validate
def positive_exclamation_marks(self):
if self.num_exclamation_marks <= 0:
raise ValueError("Number of exclamation marks must be positive!")
class WeatherSection(ConfigSection):
service_url: URL
username: Optional[str] = None
password: Optional[SecretString] = None
timeout_s: float = 70.0
@validate
def username_and_password_go_together(self):
if (self.username is None) ^ (self.password is None):
raise ValueError("Must either provide both username and password or neither of them!")
def timeout(self) -> timedelta:
return timedelta(seconds=self.timeout_s)
class DemoConfig(Config):
greet: GreetingSection
weather: WeatherSection
config = DemoConfig()
以下是可配置的内容
在“world”或用户的姓名后添加多少感叹号。
整个问候是否全部大写。
将用于获取天气数据(降雨概率)的哪个网络服务。
天气服务的用户凭证。
对天气服务的请求的客户端超时。
请注意,username 和 password 是可选类型,即可以是 None(某些天气服务可能是免费的)。此外,每个部分中的某些条目有默认值,而其他则没有(这意味着用户必须通过配置文件或环境变量提供值)。
这里我们看到 URL 和 SecretString 类型。此类条目的值是普通的 Python 字符串。这些类型提示用于向用户传达意图,并允许 PyConfig 执行验证和其他特殊行为。例如,类型为 SecretString 的条目不允许有默认值(除非它是可选的且默认值为 None)。此外,当打印配置或仅打印部分时,类型为 SecretString 的条目将被替换为星号 "*****"。
使用 @validate() 注解的方法将在配置加载后自动调用(理想情况下在应用程序启动时)。每个都用于验证单个部分,部分可以有多个验证器。
条目 timeout_s 和上面的方法 timeout 的组合帮助我们避免用户的不确定性,同时能够使用无单位类型:实际配置字段 timeout_s 的名称清楚地告诉用户他们必须提供 秒 的值,但在我们的代码中我们使用 timeout 方法,因此我们只使用 timedelta 对象,永远不必担心测量单位。
最后,使用全局配置对象可能看起来很危险(特别是在 Python 中),但 Config 和 ConfigSection 对象始终*是不可变的,因此没有必要担心全局 状态。
**: 配置内容的突变有两种方式。一种是使用 fill_config() 或 fill_config_from_path() 加载。另一种是使用 test_utils.update_section()。您可以在您的仓库中快速找到这些函数的所有用法。加载函数理想情况下只使用一次,并且仅在启动时使用。在生产代码中使用 test_utils 模块应该完全禁止!
在您的代码中使用配置
我们的应用程序的核心将在 greet.py 模块中实现,其中我们多次使用全局配置
# demo/greet.py
from datetime import timedelta
from random import random
from typing import Mapping
from demo.config import config
def _get_rain_probability(
url: str, params: Mapping[str, str], timeout: timedelta
) -> float:
return random() # Just as reliable as a weather service...
def greet(name: str):
msg = f"Hello, {name}" + ("!" * config.greet.num_exclamation_marks) # <= config used here
if config.greet.all_caps: # <= and here
msg = msg.upper()
if config.weather.username is None: # <= here too
params = {}
else:
params = {
"username": config.weather.username, # <= and here
"password": config.weather.password, # <= and again
}
rain_prob = _get_rain_probability(
url=config.weather.service_url, # <= once more
params=params,
timeout=config.weather.timeout(), # <= last time
)
if rain_prob > 0.5:
msg += " You should probably stay home today..."
else:
msg += " It's a beautiful day outside. Have fun!"
print(msg)
您的IDE可能为部分名称和部分内的条目提供自动完成功能。与通常的字典方法(例如,使用 configparser)相比,您不太可能以这种方式犯输入错误。即使您这样做,您也将尝试获取一个不存在的属性,在PyConfig中,配置和部分的属性由类声明确定(它们不依赖于用户在运行时提供的配置文件)。这意味着如果您的代码通过了测试并且没有出现 AttributeError,那么在生产环境中,无论用户在配置文件中输入什么,您都可以确信您不会遇到 AttributeError。
在启动时加载配置
# demo/__main__.py
from argparse import ArgumentParser
from demo.config import config
from demo.greet import greet
from nx_config import add_cli_options, resolve_config_path, fill_config_from_path
parser = ArgumentParser()
parser.add_argument("--name")
add_cli_options(parser, config_t=type(config))
args = parser.parse_args()
fill_config_from_path(config, path=resolve_config_path(cli_args=args))
greet(name=args.name or "world")
这里的魔法发生在 fill_config_from_path() 中。此函数将读取配置文件并将对应值填充到 config 对象的条目中。路径可以是硬编码的(不推荐),或者您可以不带参数使用 resolve_config_path(),在这种情况下,路径通过 CONFIG_PATH 环境变量提供(更好),或者您可以使用上面的 argparse.ArgumentParser 允许用户将配置路径作为CLI参数提供(最佳)。辅助函数 add_cli_options() 将添加 --config-path 选项(以及其他一些选项),resolve_config_path() 将尝试读取它。如果用户在命令行上没有提供路径,resolve_config_path() 仍然使用 CONFIG_PATH 环境变量作为后备。
配置文件的格式将由路径的扩展名确定(例如,.yaml 用于 YAML)。请注意,不提供配置文件(既不通过 --config-path 也不通过 CONFIG_PATH)是可以的(并且是一种常见做法)。在这种情况下,配置值将从名为 SECTIONNAME__ENTRYNAME 的环境变量中读取(双下划线!)。即使提供了配置文件,值也可以通过以下方式覆盖,如下所示。
编写配置文件
上面提到的 add_cli_options() 函数还添加了一个 --generate-config 选项,该选项将打印模板配置文件并退出。它应如下使用
$ python -m demo --generate-config=yaml > demo/config.yaml
在此示例中,它将生成以下文件
# demo/config.yaml
greet:
#num_exclamation_marks:
#all_caps:
weather:
service_url:
#username:
#password:
#timeout_s:
所有条目和所有部分都存在,但具有默认值的条目已被注释掉,这样您可以清楚地知道程序运行所需填写的内容。我们可以在文件中填写 service_url,例如
service_url: www.weatherservice24.com/rain
并使用它来运行我们的应用程序。我们仍然可以使用规范命名的环境变量(如 GREET__NUM_EXCLAMATION_MARKS)来更改其他条目(甚至覆盖此文件中的值)
$ export GREET__NUM_EXCLAMATION_MARKS=5
$ python -m demo --name Dave --config-path demo/config.yaml
Hello, Dave!!!!! It's a beautiful day outside. Have fun!
为什么?
PyConfig有什么好处?为什么您要费心学习使用另一个库,当 configparser 已经做得相当不错时?此外:Python已经有许多配置库了!PyConfig有什么不同之处?
避免硬编码路径
configparser.ConfigParser.read() 方法接受一个字符串或 PathLike(或多个)作为参数。我见过并参与了许多项目,其中这个参数被写成了硬编码的、版本控制的字符串。这当然在大多数情况下都不是一个好主意。这使得在本地尝试代码或自动部署到多个服务器变得困难,可能会导致与使用相同路径的(因此无法独立配置的)不同应用程序发生冲突,因为权限不足等原因而造成麻烦。它还使得使用不同配置运行同一应用程序变得令人烦恼且缓慢。
那些项目中的大多数开发人员都知道这是一个坏主意,也知道如何避免它(例如,从 CLI 参数或环境变量中获取路径),但是(a)这些解决方案需要一些额外的工作,并且(b)它们需要教会用户如何为每个应用程序提供配置路径……!
PyConfig为此提供了两种非常简单的解决方案,使最佳实践几乎成为最容易做的事情。首先,您可以使用不带参数的函数 resolve_config_path()。如果定义了,这将返回一个来自 CONFIG_PATH 环境变量的 pathlib.Path,否则返回 None。通过使用 argparse.ArgumentParser 和 add_cli_options(),您可以允许最终用户通过 --config-path CLI 选项或 CONFIG_PATH 环境变量提供配置路径
parser = ArgumentParser()
add_cli_options(parser, config_t=DemoConfig)
args = parser.parse_args()
path = resolve_config_path(cli_args=args)
如果您有多个共享环境变量的应用程序,或者您为单个应用程序使用多个配置类(很少需要),则可以向 CLI 选项和路径环境变量添加前缀
parser = ArgumentParser()
add_cli_options(parser, prefix="demo", config_t=DemoConfig)
args = parser.parse_args()
path = resolve_config_path("demo", cli_args=args)
现在将使用 CLI 选项 --demo-config-path 和环境变量 DEMO_CONFIG_PATH。
最重要的是,这种解决方案为用户提供了一种标准化的方式来提供配置文件,通过遵循简单命名约定的参数,为所有使用 PyConfig 的应用程序。
不可变性
有些人可能会争辩说,在上面的例子中,我们不应该创建一个 全局 的 config 对象,该对象仅在启动时加载,而应该创建和加载一个 config 对象,并在 __main__.py 中将其注入到 greet 调用中。在大多数情况下,我会同意这个建议。但这旨在避免全局 状态,即可以在代码的任何地方读取和修改的全局变量,通常会导致问题。
在Config实例的情况下,我们不必担心。配置对象、其每个部分以及每个条目都是不可变的,所以实例只是一个常量的命名空间。部分条目支持的类型也都是不可变的,包括支持的集合类型tuple和frozenset。
许多配置库允许在任何时候自由修改配置对象,这在长时间运行的服务中尤其有问题。如果发生关键错误甚至崩溃,你无法保证启动时提供的配置仍然是正在使用的配置。当前配置可能与你在配置文件中看到的值完全不同。这使得理解和复制错误变得困难。在PyConfig中,通过在项目中搜索fill_config()和fill_config_from_path()的使用,很容易检查配置是否可以更改。理想情况下,它将在启动时只加载一次,但即使你的应用程序允许在运行时更新配置,协调此逻辑至少是容易找到的。此外,请参阅下面的“日志”部分,它可以帮助你轻松调试应用程序。
为了便于使用不同的配置进行测试,我们添加了函数test_utils.update_section()(只能通过test_utils模块导入,不能直接从nx_config导入)
# tests/test_greeting.py
from unittest import TestCase
from nx_config.test_utils import update_section
from demo.config import config
class DemoTests(TestCase):
def setUp(self):
... # load your base config values for testing
def test_something(self):
update_section(config.greet, num_exclamation_marks=7)
... # call code that uses config
再次,你可以轻松地扫描你的项目以查找test_utils的使用。显然,它应该仅在测试中使用,绝不应该在生产代码中使用。就是这样!fill_config()、fill_config_from_path()和test_utils.update_section()是修改配置实例的唯一方式。
*, ** 和 ***:当然……这是Python……总有一些阴暗的方式通过篡改配置和部分的内部属性来作弊。让我们假设你的项目所有贡献者都是善良的成年人。
配置文件格式
与许多配置库不同,PyConfig完全将你的代码(以及你的配置选项的建模)与最终用户允许选择的配置输入格式分开。你只需编写Python,不必再想YAML、INI、JSON、.ENV或任何其他格式。你的代码与配置格式无关。
PyConfig目前支持YAML、INI和环境变量。然而,它被设计成易于扩展,我们将倾听社区意见,看看其他哪些格式是好的候选者。当添加新的格式时,作为开发者,你只需要安装最新版本,你的最终用户就可以开始享受额外的灵活性,即使你的代码保持不变。
这种选择的自由对于使用不同编程语言的公司来说也可能很有趣。他们可以选择为所有项目定义一个单一的、公司范围内的“配置语言”。这对每个人都很方便,并允许例如在生产中(例如,使用不同服务的凭据、常见的URL等)使用集中的配置文件。同时,如果需要的话,个别程序员仍然可以选择不同的“配置语言”进行本地测试。
记录配置选项
使用PyConfig的一个最大优势是,配置模型的内容(即它应该有哪些部分、每个部分应该有哪些条目、它们的类型应该是什么等)仅定义在代码中。
以configparser为例,通常有3种独立的“定义”配置选项。第一种是源代码中配置映射的用法,它分布在整个代码库中,并不总是容易找到。第二种是为最终用户编写的文档,通常为PDF或Markdown格式,列出所有部分、条目、类型以及如何使用每个条目。第三种有时是最终用户可以复制并填写所选值的模板 INI文件。这3种“定义”需要维护并保持同步,但这很少发生。开发者往往会删除使用配置值的代码,或者添加使用全新的配置条目的代码,或者更改条目的默认值……却忘记更新文档或INI模板。即使你非常小心,投入大量精力保持文档更新,有经验的最终用户仍然不会信任你的文档,因为他们已经陷入过这个陷阱多次。
这就是PyConfig的用途!代码,即你的类定义,是配置选项的唯一定义。它是最终真相,总是最新的,并详细记录了配置的每个细节,包括类型、默认值和有效性标准。如果你为配置类和部分类添加文档字符串,它们更有可能保持更新,因为它们紧邻它们引用的代码。一些工具甚至支持直接在类属性下方直接支持文档字符串,所以不妨试试。
如果你对一个argparse.ArgumentParser应用add_cli_options()函数,你的最终用户将免费获得--generate-config CLI选项,他们可以使用该选项为任何支持的文件格式生成配置模板,例如。
$ python -m demo --generate-config=yaml
greet:
#num_exclamation_marks:
#all_caps:
weather:
service_url:
#username:
#password:
#timeout_s:
使用add_cli_options()还会添加--config-help CLI选项。它显示一个专门记录应用程序配置模型的消息,随后是针对PyConfig配置(面向最终用户)的备忘录式、一般性说明。
这意味着你的应用程序需要的所有文档(就配置选项而言)都很容易、自动地从你的类定义中生成,并且总是最新的!即使你想在网站或GitHub上直接提供文档,你也可以设置在每次发布后重新生成它的管道。无需维护。
你的项目贡献者会更高兴:他们只需查看Python代码,只有一个模块(通常称为config.py),没有任何额外的PDF或Markdown文件或网页,并且他们可以保证在那里找到所有相关、最新的信息。
自动验证和启动失败
PyConfig始终将配置输入与在ConfigSection子类声明中使用的类型提示进行验证。在环境变量或INI文件的情况下,值最初被解释为字符串,因此“检查类型”意味着检查提供的字符串是否可以转换为预期的类型(例如,字符串"3.14"对于float是可接受的,但对于UUID则不行)。在YAML或JSON文件的情况下,例如,已经存在标准库将它们解析为不同类型的Python对象,因此仅进行较小的转换(例如,str转换为Path或list转换为frozenset),具体取决于提供的类型提示。
另外两项独特的自动检查是
用户必须为每个没有默认值的字段提供值。
密钥不能有默认值。它们必须始终由最终用户提供。(但是 Optional[SecretString] 可以有默认 None,tuple[SecretString, ...] 可以有默认 ()` 等。)
除此之外,您还可以通过 @validate() 注解将验证方法(单个参数 self,无返回值)添加到您的部分类中。这些方法将在 fill_config() 或 fill_config_from_path()(参见上面的示例)中填写部分值后立即调用。
如果您使用 PyConfig 并遵循在应用程序启动时(仅此一次)加载所有配置的最佳实践,那么您永远不必担心无效的配置值在您的长时间运行的服务启动后数天造成麻烦,在深夜或您的即将结束的假期期间。您可以用其他配置库做同样的事情吗?当然可以。PyConfig 只是有利于方便。
日志记录(和密钥)
您可以使用 Config 和 ConfigSection 子类的 __str__ 方法轻松打印它们。该方法生成内联描述,而 __repr__ 方法给出多行和缩进版本。此外,密钥(即被 SecretString 类型注释的部分)会自动用星号屏蔽,包括可选的密钥和密钥集合。
以下是从上面的 DemoConfig 类使用的示例输出
>>> print(str(config)) DemoConfig(greet=GreetingSection(num_exclamation_marks=1, all_caps=False), weather=WeatherSection(service_url='www.weatherservice24.dummy', username='Dave', password='*****', timeout_s=70.0)) >>> print(str(config.greet)) GreetingSection(num_exclamation_marks=1, all_caps=False) >>> print(repr(config)) DemoConfig( greet=GreetingSection( num_exclamation_marks=1, all_caps=False, ), weather=WeatherSection( service_url='www.weatherservice24.dummy', username='Dave', password='*****', timeout_s=70.0, ), ) >>> print(repr(config.greet)) GreetingSection( num_exclamation_marks=1, all_caps=False, )
这两种格式都可用于编写日志消息,您确实应该利用这一点并在某些情况下记录应用程序的配置。一个不错的主意是在启动时加载配置后立即记录配置。另一种方法是,在发生严重错误时记录配置(这对于调试来说更方便,因为所有重要信息都捆绑在一起,与错误消息一起)。如果您总是记录整个配置(或者至少是整个部分),那么您就不必担心意外泄露最终用户的密钥。
选择哪个方法使用哪种格式是为了考虑调试。在 REPL 中,如果您只输入要检查的对象,则结果将使用 __repr__ 打印。
>>> config.weather WeatherSection( service_url='www.weatherservice24.dummy', username='Dave', password='*****', timeout_s=70.0, )
如果您使用 PyCharm,控制台和调试器上的“变量”视图会使用 __str__ 在变量名旁边显示值,而在这种情况下,一行描述更合适。
*:只有当您使用 Config 和 ConfigSection 的 __str__ 和 __repr__ 方法时,密钥才会被屏蔽。请记住,my_config.my_section.my_secret 的实际值只是一个普通的内置 str,因此如果您在日志中打印它,则它将 不会 被屏蔽!
使用属性而不是字符串
使用属性(cfg.a_section.an_entry)而不是像许多配置库中使用的字符串映射样式(cfg["a_section"]["an_entry"])来表示部分和部分条目,这不仅是更短、更美观和更容易输入,而且还有更多好处。
您的IDE可以帮助您使用点自动补全功能,以(a)显示可用的部分和部分条目,以及(b)避免输入错误。这尤其重要,因为即使您的配置在启动时经过彻底验证,但在使用配置时的输入错误可能会在很久以后的某个时候造成麻烦,那时没有人会注意并及时采取行动。(当然,在您的公司这种情况绝对不会发生,因为您的每个项目都有100%的代码覆盖率……)
理论上,IDE还能做更多的事情。如果您在这样的一些属性中犯了输入错误(因为没有使用自动补全),静态分析器可以突出显示并警告您。如果您决定更改部分或部分条目的名称,IDE可以帮助进行自动重构。不幸的是,我们还没有能够使其与PyConfig部分和条目一起工作。我们知道这是由于IDE的限制以及PyConfig在幕后使用了很多魔法,但我们仍在努力了解为什么它不起作用。
尽管如此,自动补全+更短+更漂亮就足够了,足以让属性优于映射。
通过环境变量进行方便的配置
有些情况下使用文件来配置应用程序可能会很烦人,例如在本地终端上快速测试和实验时,总是只更改一个或两个配置选项。
使用PyConfig,您可以始终使用环境变量覆盖文件中的任何配置。标准命名约定是SECTIONNAME__ENTRYNAME(是的,双下划线,这使部分名称或条目名称中也包含下划线时的分隔更清晰)。在上面的示例中,我们已经看到了如何通过设置环境变量GREET__NUM_EXCLAMATION_MARKS来覆盖config.greet.num_exclamation_marks条目。
如果您在一个应用程序中有多个配置或多个应用程序共享一些环境变量,也可以使用前缀使变量名称更具体。例如,您可以使用环境变量FOO__GREET__NUM_EXCLAMATION_MARKS,并在加载配置时传递一个env_prefix参数给fill_config_from_path(),如下所示:fill_config_from_path(config, path=..., env_prefix="FOO")。
最后,配置文件的路径也可以通过环境变量提供,即CONFIG_PATH。同样,可以使用前缀使此名称更具体。例如,您可以使用变量BAR_CONFIG_PATH,并通过resolve_config_path("bar", cli_args=...)获取路径。注意:如果在这种情况下使用cli_args参数,resolve_config_path()将查找选项--bar-config-path而不是--config-path,因此请确保在通过调用add_cli_options()以prefix参数添加选项到argparse.ArgumentParser时使用相同的前缀,如下所示:add_cli_options(parser, prefix="bar", config_t=type(config))。
支持最有用的类型
在加载配置值之后,您应该理想上能够直接使用它们,而无需首先将它们转换为其他内容。大多数用例应该由PyConfig已经支持的类型覆盖(并且可能会有更多类型)。
基础支持的数据类型包括 int(整数)、float(浮点数)、bool(布尔值)、str(字符串)、datetime.datetime(日期时间)、uuid.UUID(UUID)、pathlib.Path(路径)、nx_config.SecretString(秘密字符串)和 nx_config.URL(URL)。
集合支持的数据类型在所有Python版本中为 typing.Tuple[base, ...] 和 typing.FrozenSet[base],在Python 3.9及以后版本中为 tuple[base, ...] 和 frozenset[base](其中 base 是上述支持的任意一种基础数据类型)。请注意,这里元组类型中的省略号(...)表示的是字面意义上的任意长度元组,其中所有元素都是相同类型的。
可选支持的数据类型为 typing.Optional[base_or_coll](其中 base_or_coll 可以是上述基础或集合支持类型中的任意一种)。请注意,“可选”必须是最外层,即您 不能 有可选元素集合,如 tuple[Optional[int], ...]。
但是,如果您想使用自定义类型,您可能需要付出更多的努力。例如,如果您想使用一个无单位的 Temperature 类型,您的最终用户将必须提供带有单位的值(例如 surface_temp_celsius: float),然后您需要自行转换它(例如,在相同部分的 def surface_temp(self) -> Temperature 方法中)。
关于导入的说明
生产代码中所需的PyConfig的所有内容都可以(并且应该)直接从 nx_config 模块导入
from nx_config import Config, ConfigSection, SecretString, fill_config, ...
测试中所需的PyConfig的所有内容都可以(并且应该)直接从 nx_config.test_utils 模块导入
from nx_config.test_utils import update_section
这就是全部了。 如果您发现自己从其他子模块导入内容:这可能是针对您的。我已经尽力将其他所有内容都保护在下划线之后,但可能有一些内容已经或将来会遗漏。
关于配置库与配置应用
通常没有太多意义直接在库中使用文件和环境变量的配置。配置应由应用程序请求并接收,然后应用程序可以将任何必要的值 注入 到库的类和函数中。库至少应该给应用程序提供将所有相关值作为输入参数的 可能性。这使得编写测试更加容易和方便,甚至对性能也很重要。
我看到过一些库提供在初始化时解析配置文件(使用默认、硬编码的路径)的类。非常了解的用户很少在他们的应用程序中初始化这样的对象,并且尽可能地保留它们。但大多数用户只是假设初始化的成本接近于零,并且每次需要时都会创建一个新的对象,不知不觉地重复解析文件并丢弃信息。
应用程序编写者应该对如何以及何时读取和解析文件拥有最终控制权。
将一个Config子类添加到库中是一个非常糟糕的想法。这会迫使应用程序开发者使用该类为特定库配置,然后为他们的配置选项使用不同的类。将一个ConfigSection子类添加到库中可以成为应用程序开发者的一项友好特性,他们可以在自己的Config类中使用这些部分。但即使这样也可能带有一些僵化性:应用程序开发者可能只想让用户对库的配置有一定的控制权,但库提供的ConfigSection可能会给他们提供全面控制。
保持简单:在应用程序中使用PyConfig。在库中使用注入。
关于pydantic的说明
如果你对pydantic不熟悉:它是一个通用的“建模”Python库,几乎提供了PyConfig所能做的一切,并且更多(真的)。它更加强大、灵活,功能丰富,可以非常出色地用于配置。它也比PyConfig更加成熟和久远。
当我第一次遇到pydantic时,我对它与PyConfig某些部分的相似性感到非常惊讶,比如它们提供的@validator()注解、NamedTuple-风格的类声明,甚至还有SecretStr类型!在这个最后的情况下,nx_config.SecretString类型在运行时会变成普通的str,而pydantic.SecretStr类型是str的包装器,你需要调用get_secret_value()方法来使用包装的字符串。但这甚至更有趣,因为我正是采用了这种方法在PyConfig的第一个版本中,只是我的方法被命名为get_value_at_own_peril(),并且返回受保护的成员_dont_you_dare_use_me。然后一些同事说他们觉得使用密钥字符串很麻烦,让我改变了主意。
我对pydantic没有任何批评,并且我真诚地不认为其他库是“竞争”。我们都在同一战线上。但我确实认为在某些时候应该使用pydantic,而在另一些时候则应使用PyConfig。如果你已经在项目中使用pydantic,或者你非常熟悉它,或者你实际上需要它来对非配置的事物进行建模,那么请务必使用它。
然而,如果你只是想寻找一种更好、更安全地向应用程序添加配置的方法,那么也许你应该看看PyConfig。它是最小化、单一用途和简单的。几乎没有学习曲线,并且这个包相当小,没有不必要的功能。它还强制执行不可变性,这在pydantic中是可选的。在我看来,你需要知道你在做什么,并且在使用pydantic(特别是在应用程序配置方面)时要自律,而PyConfig则自然地引导你走向最佳实践。但是,嘿,我肯定是偏袒的……
详细文档
PyConfig的完整文档仍在建设中,可以在此找到。
常见问题解答
- 为什么我不能将部分嵌套到其他部分中?
这并不是一个容易的设计选择。在编写 PyConfig 时的一个最重要的要求是它应该支持 INI 文件,而且只能支持(真正地)1 级嵌套。最终,尽管这个问题经常被问到,但在配置中几乎没有使用更深层嵌套的场景。而且在少数我看到这样的场景中,问题可以通过在应用程序中使用多个 Config 子类来优雅地解决。
- 为什么我不能在 Config 子类中直接添加条目?为什么所有条目都必须在某个部分中?
首先,这会给实现增加更多的复杂性。其次,INI 不允许没有部分的条目。第三,这实际上并不是一个问题。你总是可以只添加一个 通用 部分到你的配置中。
- 为什么不支持字典作为部分条目的类型?
INI。几乎总是 INI。我选择支持可迭代的类型 tuple 和 frozenset,因为将逗号分隔的值解释为序列非常常见且自然,而这些类型在配置中非常有用。此外,我已经看到了几个项目,其中配置值是通过逗号分隔转换为序列的,但是开发人员必须自己解析字符串。
对于字典,没有这样的简单、优雅且常见的表示。幸运的是,作为部分条目的字典需求也非常少。
- 关于环境变量的标准命名:如果我有一个名为 foo__bar 的部分和一个名为 baz 的条目,同时还有一个名为 foo 的部分和一个名为 bar__baz 的条目会发生什么?
老实说,我还没有想过这个问题。可能会发生一些不好的事情。
- 这些问题的确是经常被问到的,还是你边走边编的?
是的。
项目详情
下载文件
下载您平台的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。
源分布
构建分布
nx_config-0.2.0b5.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 2a07a602482d4144dd6c5ef965384297ed20fa5782c8693c060d00c29da718c0 |
|
MD5 | 98d91cbd2e2eacc2ba5e650386e8505a |
|
BLAKE2b-256 | 技术文档内容d3f22b39a716fc0890248d622a9f7c63cc2de6deadd9379d043235fb6b71508c |