如何在 Python 中重置字典的值?

How to reset values over a dictionary in Python?

我有一个 YAML 文件无法提交到我的存储库,因为它包含密码和敏感信息。它看起来像这样:

devops:
  branch: somebranch

password:
  provider:
    digital_ocean:
      token:
        ""
    aws:
      bob:
        access_key_id:
          "XXX"
        secret_access_key:
          "XXX"
      jim:
        access_key_id:
          "XXX"
        secret_access_key:
          "XXX"
  dev:
    bob:
      "secret"
    jim:
      "another secret"
  app:
    mom:
      zookeeper:
        "XXX"
      admin:
        "XXX"

我正在尝试在 Python 中开发一个脚本,如果我可以清除我的文件中的所有密码。这个我可以提交到我的存储库中,它在处理后应该是这样的:

devops:
  branch: somebranch

password:
  provider:
    digital_ocean:
      token:
        ""
    aws:
      bob:
        access_key_id:
          ""
        secret_access_key:
          ""
      jim:
        access_key_id:
          ""
        secret_access_key:
          ""
  dev:
    bob:
      ""
    jim:
      ""
  app:
    mom:
      zookeeper:
        ""
      admin:
        ""

我知道可以像这样将值设置到字典中:

import os
import yaml

with open(os.environ['DEVOPS_HOME'] + "/vagrant/server/settings.yml") as f:
    settings = yaml.load(f)

for key in settings.keys():
    settings[key]=0

但是,这是我的输出:

{'password': 0, 'devops': 0}

有没有办法遍历我的字典并仅为我的密码设置值?或者我应该更改我的 YAML 文件的结构?

您需要手动将密码部分中的每个字符串设置为空字符串。例如,如果您的字典如下:

{
    'public_stuff': 'this should not be changed',
    'password':
    {
        'category1':
         {
            'user': 'password'
         },
         'other_user': 'other_password',
    },
}

您可以在清除所有字符串值的函数中递归执行此操作。如果涉及到字典值,它会对该值本身执行函数。

def clear_password(content):
    for key in content:
        if isinstance(content[key], str):
            content[key] = ""
        elif isinstance(content[key], dict):
            clear_password(content[key])

clear_password(settings["password"])

在 Python 2 中,str 可能需要 basestringunicode

值所需要的键 "wiping" 命名似乎没有规律,但它们似乎有一个共同点,就是它们都是键下的叶值密码。这使得递归成为一个选项,可以在不指定每个完整密钥路径的情况下全部擦除它们 ¹:

import sys
import ruamel.yaml

yaml_str = """\
nas:
devops:
  branch: somebranch

password:
  provider:
    digital_ocean:
      token:
        ""
    aws:
      bob:
        access_key_id:
          "XXX"
        secret_access_key:
          "XXX"
      jim:
        access_key_id:
          "XXX"
        secret_access_key:
          "XXX"
  dev:
    bob:
      "secret"
    jim:
      "another secret"
  app:
    mom:
      zookeeper:
        "XXX"
      admin:
        "XXX"
"""

def wipe_pass(data, key):
    """wipe the value if it is a string instance"""
    if isinstance(data[key], type("")):
        data[key] = ruamel.yaml.scalarstring.DoubleQuotedScalarString("")
        return
    if isinstance(data[key], dict):
        for k in data[key]:
            wipe_pass(data[key], k)
        return
    raise NotImplementedError   # e.g. a YAML sequence

data = ruamel.yaml.round_trip_load(yaml_str, preserve_quotes=True)
wipe_pass(data, 'password')
ruamel.yaml.round_trip_dump(data, sys.stdout)

给出:

devops:
  branch: somebranch

password:
  provider:
    digital_ocean:
      token: ""
    aws:
      bob:
        access_key_id: ""
        secret_access_key: ""
      jim:
        access_key_id: ""
        secret_access_key: ""
  dev:
    bob: ""
    jim: ""
  app:
    mom:
      zookeeper: ""
      admin: ""

请注意,您的原始 YAML 具有不一致的键值对格式,其中值不是映射。这里的输出与你原来的 branch: somebranch 对一致。

ruamel.yaml.scalarstring.DoubleQuotedScalarString("") 是获得 "" 作为输出所必需的。如果您只是分配 "",您将获得 YAML 文件中空字符串的默认 '' 单引号。

您可以尝试使用 PyYAML 执行上述操作,但您会丢失任何注释、无保证的键顺序、丢失 password: 之前的空行,甚至可能更多。这使得它在很大程度上无法用于必须在提交之间进行最小更改的往返数据(加载、修改、转储)。


¹ 这是使用 ruamel.yaml YAML 1.2 解析器完成的,我是其中的作者。