ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

dlt 自定义配置提供器实战:用 YAML Profile + 环境变量占位符替换 secrets.toml

dlt 自定义配置提供器实战:用 YAML Profile + 环境变量占位符替换 secrets.toml dlt 自定义配置提供器实战用 YAML Profile 环境变量占位符替换 secrets.toml【免费下载链接】dltdata load tool (dlt) is an open source Python library that makes data loading easy ️项目地址: https://gitcode.com/GitHub_Trending/dl/dlt导读dltdata load tool默认通过config.toml/secrets.toml与系统环境变量解析配置但在多环境开发、生产或需要模板化配置的场景下这远远不够灵活。本文基于仓库中的官方示例 custom_config_provider.md讲解如何用一份支持多 Profileprod/dev的 YAML 文件替代 toml 配置文件并通过{{ PLACEHOLDER }}占位符自动注入环境变量中的密钥。读完本文你将掌握实现自定义配置加载器Loader、实例化并注册CustomLoaderDocProvider提供器的完整流程并理解 dlt 配置提供器链的底层解析机制。示例要解决的问题dlt 的配置解析依赖一组被称为config providers配置提供器的对象它们负责按固定的查询链提供配置项与密钥——例如读取环境变量、读取secrets.toml文件等。默认提供器均以 toml 为配置载体而本示例演示的是将配置集中存放在一个YAML 文件profiles.yaml中该文件内含多个可切换的 Profileprod与dev按需选取文件中的敏感值使用类 Jinja 占位符{{GITHUB_API_KEY}}书写加载时被替换为对应的环境变量值将上述逻辑封装为一个自定义 Provider 并注册到 dlt 的解析链中使 dlt 像使用标准 Provider 一样使用它。示例的核心价值在于你不再需要把secrets.toml提交到仓库或手工维护多份配置文件只需维护一份 YAML 模板 各环境的环境变量即可完成配置与密钥的分离和按环境切换。完整源码与配套文件1. profiles.yaml带 Profile 的配置文档配套的配置文件位于 docs/website/docs/examples/profiles.yamlprod: sources: github_api: # source level github: # resource level url: https://github.com/api api_key: {{GITHUB_API_KEY}} dev: sources: github_api: url: https://github.com/api api_key: # no keys in dev env注意两处细节层级结构顶层是 Profile 名prod/dev向下依次是sources→ source 名github_api→ resource 名github→ 字段名这与 dlt 标准的config.toml节层级完全一致占位符prod下api_key的值为{{GITHUB_API_KEY}}加载时会被替换成同名环境变量的值dev下则留空表示开发环境不注入真实密钥。2. 主代码加载、替换、注册、验证示例主代码见 custom_config_provider.md 全文按功能拆解如下。导入与模块级配置import os import re import dlt import yaml import functools from dlt.common.configuration.providers import CustomLoaderDocProvider from dlt.common.utils import map_nested_values_in_place # config for all resources found in this file will be grouped in this source level config section __source_name__ github_api__source_name__ github_api让本文件中所有资源resource的配置统一归入sources.github_api这一 source 级节与profiles.yaml中的sources.github_api键对应。占位符求值函数def eval_placeholder(value): Replaces jinja placeholders {{ PLACEHOLDER }} with environment variables if isinstance(value, str): def replacer(match): return os.environ[match.group(1)] return re.sub(r\{\{\s*(\w)\s*\}\}, replacer, value) return valueeval_placeholder用正则\{\{\s*(\w)\s*\}\}匹配所有{{ 名称 }}形态的占位符并直接从os.environ中取出对应环境变量的值完成替换。注意它只处理字符串值非字符串原样返回——这决定了占位符必须以字符串形式写在 YAML 中。YAML 加载器选 Profile 递归替换def loader(profile_name: str): Loads yaml file from profiles.yaml in current working folder, selects profile, replaces placeholders with env variables and returns Python dict with final config path os.path.abspath(profiles.yaml) with open(path, r, encodingutf-8) as f: config yaml.safe_load(f) # get the requested environment config config.get(profile_name, None) if config is None: raise RuntimeError( fProfile with name {profile_name} not found in {os.path.abspath(path)} ) # evaluate all placeholders # NOTE: this method only works with placeholders wrapped as strings in yaml. use jinja lib for real templating return map_nested_values_in_place(eval_placeholder, config)loader是三层职责的叠加读取yaml.safe_load解析当前目录下的profiles.yaml选择 Profile按profile_name取出对应节若找不到抛出带绝对路径的RuntimeError便于定位问题递归替换map_nested_values_in_place实现于 dlt/common/utils.py其行为有测试覆盖见 tests/common/test_utils.py会遍历整个嵌套 dict对每个叶子值应用eval_placeholder从而一次性完成所有占位符的替换。代码注释特别提醒这种方式只适用于 YAML 中以字符串包裹的占位符若需要真正的模板语法条件、循环等应改用 Jinja 之类的模板库。消费配置的 resourcedlt.resource def github(url: str dlt.config.value, api_keydlt.secrets.value): # just return the injected config and secret yield url, api_keygithub资源使用 dlt 的注入标记声明两个参数url由dlt.config.value注入非敏感配置api_key由dlt.secrets.value注入敏感密钥。当 dlt 解析该资源时会按提供器链查询sources.github_api.github.url与sources.github_api.github.api_key。切换 Profile 并注册 Provider# mock env variables to fill placeholders in profiles.yaml os.environ[GITHUB_API_KEY] secret_key # mock expected var # set the active profile explicitly (normally this comes from config.toml or an env var) dlt.config[dlt_config_profile_name] prod # dlt standard providers work at this point (we have the profile name in config) profile_name dlt.config[dlt_config_profile_name] # instantiate custom provider using prod profile # NOTE: all placeholders (ie. GITHUB_API_KEY) will be evaluated in next line! provider CustomLoaderDocProvider(profiles, functools.partial(loader, profile_name)) # register provider, it will be added as the last one in chain dlt.config.register_provider(provider)这段代码说明三个要点os.environ[GITHUB_API_KEY] secret_key只是模拟环境变量实际场景中应通过部署平台注入活动 Profile 名通过dlt.config[dlt_config_profile_name] prod写入配置生产环境中它通常来自config.toml或环境变量从而做到配置文件里不写死 Profile使用functools.partial(loader, profile_name)把选哪个 Profile固化为无参调用形式正好匹配CustomLoaderDocProvider期望的Callable[[], Dict[str, Any]]签名。注意注释强调所有占位符在下一行实例化 Provider 时就会被求值因为构造函数内部会立即调用loader()。注册后即可生效并验证# your pipeline will now be able to use your yaml provider # p Pipeline(...) # p.run(...) # show the final config # print(provider.to_yaml()) # or if you like toml # print(provider.to_toml()) # the registered provider now resolves config and secrets for the github_api source assert dlt.config[sources.github_api.github.url] https://github.com/api assert dlt.secrets[sources.github_api.github.api_key] secret_key注册之后自定义 Provider 就会参与 dlt 后续所有配置解析包括Pipeline(...)/p.run(...)中对 source、resource 参数的注入。示例末尾用两个断言验证dlt.config能读到 YAML 中的urldlt.secrets能读到经占位符替换后的api_key。同时provider.to_yaml()/provider.to_toml()可以把最终替换后的配置导出为 YAML 或 TOML 格式便于调试与审计。源码级剖析CustomLoaderDocProvider 内部机制类层次与能力CustomLoaderDocProvider定义在 dlt/common/configuration/providers/doc.py它继承自BaseDocProvider同文件 L12-L157。构造函数签名如下def __init__( self, name: str, loader: Callable[[], Dict[str, Any]], supports_secrets: bool True, locations: Sequence[str] None, ) - None:参数含义参数含义默认值nameProvider 名称会出现在异常与 trace 中示例中为profiles必填loader用户提供的无参函数返回包含配置/密钥的 Python dict典型做法是读取字符串如文件→ 解析如 toml/yaml→ 加工 → 返回 dict必填supports_secrets是否允许存放密钥。False时若查询到密钥值会触发ValueNotSecretExceptionTruelocations人类可读的配置来源位置列表用于在配置未解析时生成有意义的错误信息None构造时super().__init__(loader())会立即调用 loader 并缓存返回的 dict即示例注释所说的占位符在实例化那一刻就被求值之后对配置的查询都在这个 dict 上进行。BaseDocProvider 提供的查询与写入能力get_value(key, hint, pipeline_name, *sections)按pipeline_name sections 组成的路径在 dict 中逐层下钻取值取不到时返回(None, full_key)不抛异常由上层解析逻辑判断是否缺失set_value(...)写入配置。若目标位置已是 dict 且新值也是 dict则递归合并见 doc.py L93-L127set_fragment(key, value_or_fragment, ...)把一段 toml/yaml/json 片段解析后合并进配置文档简单值则回退到set_valuepreserve()上下文管理器退出时恢复进入前的配置文档用于临时覆盖配置而不污染全局状态to_toml()/to_yaml()把内部 dict 序列化为 TOML / YAML 字符串示例代码中注释掉了这两个调用用于展示最终配置supports_sectionsTrue声明该 Provider 支持按节section查询dlt 会为它枚举所有合法的节组合路径。在提供器链中的位置与注册语义dlt.config.register_provider(provider)的实际逻辑见 dlt/common/configuration/accessors.py L118-L122它把 Provider 追加到Container()[PluggableRunContext].providers。而 dlt/common/configuration/specs/config_providers_context.py L95-L98 中的add_provider规定def add_provider(self, provider: ConfigProvider) - None: if provider.name in self: raise DuplicateConfigProviderException(provider.name) self.providers.append(provider)即Provider 以追加到链尾的方式注册且名称必须唯一重复名称会抛DuplicateConfigProviderException。文档注释it will be added as the last one in chain正是对这一实现的表述自定义 Provider 的优先级最低只有前面的标准 Provider环境变量、toml 文件等都解析不到时才会轮到它。解析链如何工作一次配置查询的完整路径标准提供器清单dlt 内置的标准提供器统一从 dlt/common/configuration/providers/init.py 导出包括EnvironProvider环境变量ConfigTomlProvider/SecretsTomlProviderconfig.toml/secrets.toml文件SettingsTomlProvider可合并多目录 toml 的基类VaultDocProvider、GoogleSecretsProvider、AwsSecretsManagerProviderVault / Google / AWS 密钥管理服务ContextProvider上下文注入。这些 Provider 均实现抽象基类ConfigProvider见 dlt/common/configuration/providers/provider.py定义的get_value、supports_secrets、supports_sections、name等接口。有意思的是ConfigTomlProvider与SecretsTomlProvider本身也是CustomLoaderDocProvider的子类经由SettingsTomlProvider继承见 toml.py L58-L110——也就是说本示例自定义加载器 文档型 Provider的模式正是 dlt 内置 toml 支持所采用的同一套机制。查询顺序与节路径构建dlt 的解析核心位于 dlt/common/configuration/resolve.py_resolve_single_valueL510-L573从Container中取出提供器列表按注册顺序依次查询一旦某个 Provider 返回非空值立即停止first match wins对支持节section的 Provider_build_section_lookup_pathsL576-L614会按从最具体到最不具体的顺序生成候选路径例如sources.github_api.github→sources.github_api→sources→ 根若存在 pipeline 名还会先以 pipeline 名作为顶层节查询一次resolve_single_provider_valueL617-L660逐路径调用provider.get_value并且有一个安全约束若从supports_secretsFalse的 Provider 中解析到密钥类型值is_secret_hint(hint)为真会抛出ValueNotSecretException防止把密钥误存在非安全提供器中。这就是示例中dlt.config[sources.github_api.github.url]与dlt.secrets[sources.github_api.github.api_key]能被解析的原因dlt.config/dlt.secrets这两个访问器见 dlt/common/configuration/accessors.py内部就是按上述链路查询的其中dlt.secrets只查询supports_secretsTrue的提供器。单元测试佐证仓库测试 tests/common/configuration/test_toml_provider.py L787-L816 的test_custom_loader完整复现了这一模式定义一个从config.yml读取并yaml.safe_load的 loader实例化CustomLoaderDocProvider(yaml, loader, True)断言其name、supports_secrets、to_toml()/to_yaml()输出然后add_provider注册最后用resolve_configuration成功解析destination.postgres节的凭证——证明自定义 Provider 注册后能真实参与标准配置解析流程。实战要点与注意事项Profile 名的来源示例用dlt.config[dlt_config_profile_name]传递 Profile 名这是示例自定义的键源码中并无内置。生产中建议通过config.toml或环境变量注入该值避免在代码里硬编码环境。占位符替换时机CustomLoaderDocProvider构造时立即调用 loader因此占位符求值发生在注册之前。若环境变量在注册后才设置将取不到值。模板能力边界正则占位符只支持{{ 单词 }}形态真正的模板逻辑条件、循环、嵌套引用需要引入 Jinja 等模板引擎在 loader 内部完成渲染。安全分层dlt.secrets只查询支持密钥的 ProviderCustomLoaderDocProvider默认supports_secretsTrue因此 YAML 中的api_key能被当作密钥解析。若你的 YAML 只放非敏感配置应显式传supports_secretsFalse。名称唯一性add_provider会拒绝重复名称注册前应避免与现有 Provider 重名且新 Provider 追加在链尾优先级最低仅在前置提供器未命中时生效。与内置 toml 提供器的取舍本方案适合一份模板文件 环境变量注入的多环境场景若项目已经依赖config.toml/secrets.toml的现有生态如dlt init生成的模板可继续使用标准提供器自定义 Provider 可作为补充或渐进迁移的桥梁。调试手段利用provider.to_yaml()/provider.to_toml()输出替换后的最终配置配置缺失时dlt 的LookupTrace会记录每个 Provider 的查询路径与结果便于定位是哪一层没有命中。小结本示例展示了 dlt 配置系统的可扩展性借助CustomLoaderDocProvider你可以把任意来源YAML、远程文件、密钥服务等接入 dlt 的标准解析链并让dlt.config/dlt.secrets、source/resource 参数注入、Pipeline.run等所有上层机制透明地使用它。掌握加载器loader→ 文档型 Provider → 注册进解析链这一模式后你就能按自己的规范设计多环境、模板化的配置体系同时继续享受 dlt 提供的密钥安全校验、section 路径解析与配置追踪能力。进一步阅读示例源码 docs/website/docs/examples/custom_config_provider.md、配套 YAML docs/website/docs/examples/profiles.yaml、Provider 实现 dlt/common/configuration/providers/doc.py、解析内核 dlt/common/configuration/resolve.py 及测试用例 tests/common/configuration/test_toml_provider.py。【免费下载链接】dltdata load tool (dlt) is an open source Python library that makes data loading easy ️项目地址: https://gitcode.com/GitHub_Trending/dl/dlt创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表