conda config 和 context#

context 对象是 conda 代码库许多部分的核心。它充当设置的集中存储库。您通常导入单例并直接访问其(许多)属性

from conda.base.context import context

context.quiet
# False

此单例从一系列不同的可能来源初始化。从低到高的优先级

  1. 硬编码在 Context 类中的默认值。这些通过类属性定义。

  2. 配置文件 (.condarc) 中定义的值,这些文件有自己的 优先级

  3. 由相应的命令行参数设置的值(如果有)。

  4. 由其对应的 CONDA_* 环境变量定义的值(如果存在)。

实现此行为的机制是一个复杂的对象,涉及多种类型的对象。

Context 类的解剖#

conda.base.context.Context 是特定于 conda 的子类,它是应用程序无关的 conda.common.configuration.Configuration 类。此类实现了每个已定义属性的实例化优先级顺序,以及整体验证逻辑和帮助消息报告。但这只是它,它仅仅是 ParameterLoader 对象的存储,而 ParameterLoader 对象又在每个属性中实例化相关的 Parameter 子类。大致如下

class MyConfiguration(Configuration):
    string_field = ParameterLoader(PrimitiveParameter("default", str))
    list_of_int_field = ParameterLoader(SequenceParameter([1, 2, 3], int))
    map_of_foat_values_field = ParameterLoader(MapParameter({"key": 1.0}, float))

MyConfiguration 实例化时,这些类属性由 .raw_data 字典填充,该字典已填充来自上述优先级链的值。raw_data 字典包含 RawParameter 对象,这些对象被子类化以处理其来源(YAML 文件、环境变量、命令行标志)的细节。每个 ParameterLoader 对象都会将 RawParameter 对象传递给其相关 Parameter 子类的 .load() 方法,这些子类设计为返回其相应的 LoadedParameter 对象对应项。

这有点令人困惑,但委托是这样发生的

  1. Configuration 子类解析可能来源的原始值,并将它们存储为相关的 RawParameter 对象,这些对象可以是

    • EnvRawParameter:对于那些来自环境变量的对象

    • ArgParseRawParameter:对于那些来自命令行标志的对象

    • YamlRawParameter:对于那些来自配置文件的对象

    • DefaultValueRawParameter:对于那些来自赋予 ParameterLoader 的默认值的对象

  2. 每个 Configuration 属性都是一个 ParameterLoader,它通过 __get__ 实现 property 协议。这意味着,在属性访问(例如 MyConfiguration.string_field)时,ParameterLoader 可以执行加载逻辑。这意味着在原始数据中查找潜在的类型匹配,将它们加载为 LoadedParameter 对象,并以适当的优先级顺序合并它们。

合并策略取决于 (Loaded)Parameter 子类型。以下是可用子类型的列表

  • PrimitiveParameter:保存类型为 strintfloatcomplexboolNoneType 的单个标量值。

  • SequenceParameter:保存其他 Parameter 对象的迭代器 (list)。

  • MapParameter:保存其他 Parameter 对象的映射 (dict)。

  • ObjectParameter:保存一个对象,其属性设置为 Parameter 对象。

Parameter 对象的主要目标是实现如何将原始值类型化并将其转换为 Loaded 对应项。这些实现了验证例程,并定义了如何合并同一键的参数

  • PrimitiveLoadedParameter:优先级最高的值替换现有值。

  • SequenceLoadedParameter:扩展且不重复,保持优先级。

  • MapLoadedParameter:级联更新,保留最高优先级。

  • ObjectLoadedParameter:与 Map 相同。

完成所有这些操作后,LoadedParameter 对象被类型化:这是执行类型验证的时间。如果一切顺利,您就可以很好地获得值。如果不是,则会引发验证错误。

请注意,结果会被缓存,以便更快地进行后续访问。这意味着,即使您更改了负责给定设置的环境变量的值,这也不会反映在 context 对象中,除非您使用 conda.base.context.reset_context() 刷新它。

不要修改 Context 对象!

ParameterLoader 未实现 property 协议的 __set__ 方法,因此您可以自由覆盖在 Configuration 子类中定义的属性。您可能会认为这将在通过验证机制后重新定义值,但这并非如此。您只需使用原始值完全覆盖它,这可能不是您想要的。

相反,请将 context 对象视为不可变的。如果您需要在运行时更改设置,这可能是一个坏主意。唯一可以接受这种情况的情况是在测试期间。

在不同来源中设置值#

设置值的可能来源背后有一些魔力。这些如何与最终的 Configuration 对象联系起来,起初可能并不明显。对于每个 RawParameter 子类,这是不同的

  • DefaultValueRawParameter:用户永远不会看到这个。它只包装传递给 ParameterLoader 类的默认值。忽略是安全的。

  • YamlRawParameter:这个接受 YAML 文件并将其解析为字典。此文件中的键必须与 Configuration 类中的属性名称完全匹配(或其别名之一)。一旦正确设置,匹配就会自动发生。值的解析方式取决于 YAML 加载器,由 conda 在内部设置。

  • EnvRawParameter:来自某些环境变量的值可以进入 Configuration 实例,前提是它们的格式为 <APP_NAME>_<PARAMETER_NAME>,全部大写。应用程序名称由 Configuration 子类定义。参数名称由类中的属性名称定义,转换为大写。例如,context.ignore_pinned 可以使用 CONDA_IGNORE_PINNED 设置。变量的值根据类型以不同的方式解析

    • PrimitiveParameter 很容易。环境变量字符串被解析为预期的类型。布尔值有点不同,因为有几个字符串被识别为布尔值,并且不区分大小写

      • True 可以使用 trueyesony 设置。

      • False 可以使用 falseoffnnononnone""(空字符串)设置。

    • SequenceParameter 可以指定自己的分隔符(例如 ,),因此环境变量字符串被处理成列表。

    • MapParameterObjectParameter 不支持使用环境变量设置。

  • ArgParseRawParameter:这些有点不同,因为没有自动机制将给定的命令行标志与 context 对象联系起来。这意味着,如果您向 Context 类添加新设置,并且希望在 CLI 中将其作为命令行标志提供,则必须自己添加它。如果是这种情况,请参阅 conda.cli.conda_argparse 并确保 argparse.Argumentdest 值与 Context 中的属性名称匹配。这样,Configuration.__init__ 可以获取 argparse.Namespace 对象,将其转换为字典,并使其通过加载机制。