带有 env 变量的 ConfigParser 和字符串插值
Posted
技术标签:
【中文标题】带有 env 变量的 ConfigParser 和字符串插值【英文标题】:ConfigParser and String interpolation with env variable 【发布时间】:2022-01-20 08:05:23 【问题描述】:这有点我没有使用 python 语法,我在读取带有插值的 .ini
文件时遇到问题。
这是我的ini文件:
[DEFAULT]
home=$HOME
test_home=$home
[test]
test_1=$test_home/foo.csv
test_2=$test_home/bar.csv
那几行
from ConfigParser import SafeConfigParser
parser = SafeConfigParser()
parser.read('config.ini')
print parser.get('test', 'test_1')
输出
$test_home/foo.csv
在我期待的时候
/Users/nkint/foo.csv
编辑:
我认为$
语法隐含在所谓的字符串插值中(参考manual):
在核心功能之上,SafeConfigParser 支持 插值。这意味着值可以包含格式字符串 引用同一节中的其他值,或特殊的值 默认部分。
但我错了。这种情况如何处理?
【问题讨论】:
【参考方案1】:基于@alex-markov 答案(和代码)和@srand9 评论,以下解决方案适用于环境变量和横截面引用。
请注意,插值现在基于ExtendedInterpolation
以允许横截面引用,并且基于before_read
而不是before_get
。
#!/usr/bin/env python3
import configparser
import os
class EnvInterpolation(configparser.ExtendedInterpolation):
"""Interpolation which expands environment variables in values."""
def before_read(self, parser, section, option, value):
value = super().before_read(parser, section, option, value)
return os.path.expandvars(value)
cfg = """
[paths]
foo : $HOME
[section1]
key = value
my_path = $paths:foo/path
"""
config = configparser.ConfigParser(interpolation=EnvInterpolation())
config.read_string(cfg)
print(config['section1']['my_path'])
【讨论】:
【参考方案2】:如果是 Python 3,您可以编写自定义插值:
import configparser
import os
class EnvInterpolation(configparser.BasicInterpolation):
"""Interpolation which expands environment variables in values."""
def before_get(self, parser, section, option, value, defaults):
value = super().before_get(parser, section, option, value, defaults)
return os.path.expandvars(value)
cfg = """
[section1]
key = value
my_path = $PATH
"""
config = configparser.ConfigParser(interpolation=EnvInterpolation())
config.read_string(cfg)
print(config['section1']['my_path'])
【讨论】:
嘿@Alex,我也想从其他部分获取环境变量和值。我怎样才能做到这一点?扩展插值看起来并不直接。 请将缺少的呼叫添加到super().before_get(...)
@srand9,我已经修复了示例,现在您可以使用 %-notation 来引用来自同一配置的其他部分的值。请参阅docs 了解更多详情
我有点危及您的答案,以发布我自己的答案来解决@srand9 的问题(这也是我的问题)。我希望你不介意:)【参考方案3】:
ConfigParser.get 值是字符串,即使您将值设置为整数或 True。但是 ConfigParser 有 getint、getfloat 和 getboolean。
settings.ini
[default]
home=/home/user/app
tmp=%(home)s/tmp
log=%(home)s/log
sleep=10
debug=True
配置阅读器
>>> from ConfigParser import SafeConfigParser
>>> parser = SafeConfigParser()
>>> parser.read('/home/user/app/settings.ini')
>>> parser.get('defaut', 'home')
'/home/user/app'
>>> parser.get('defaut', 'tmp')
'/home/user/app/tmp'
>>> parser.getint('defaut', 'sleep')
10
>>> parser.getboolean('defaut', 'debug')
True
编辑
确实,如果您使用os.environ
初始化SafeConfigParser
,您可以将名称值作为environ var。感谢Michele's 的回答。
【讨论】:
我无法修改 ini 语法,因为其他软件已经在使用它并且它是 $ 语法【参考方案4】:很晚了,但也许它可以帮助其他人寻找我最近得到的相同答案。此外,其中一个 cmets 是如何从其他部分获取环境变量和值。这是我在从 INI 文件读取时处理转换环境变量和多节标签的方法。
INI 文件:
[PKG]
# <VARIABLE_NAME>=<VAR/PATH>
PKG_TAG = Q1_RC1
[DELIVERY_DIRS]
# <DIR_VARIABLE>=<PATH>
NEW_DELIVERY_DIR=$DEL_PATH\ProjectName_$PKG:PKG_TAG_DELIVERY
使用 ExtendedInterpolation 的 Python 类,以便您可以使用 $PKG:PKG_TAG
类型格式。当我使用内置的os.path.expandvars()
函数(例如上面的$DEL_PATH
)将INI 读入字符串时,我添加了将Windows 环境变量转换为字符串的功能。
import os
from configparser import ConfigParser, ExtendedInterpolation
class ConfigParser(object):
def __init__(self):
"""
initialize the file parser with
ExtendedInterpolation to use $Section:option format
[Section]
option=variable
"""
self.config_parser = ConfigParser(interpolation=ExtendedInterpolation())
def read_ini_file(self, file='./config.ini'):
"""
Parses in the passed in INI file and converts any Windows environ vars.
:param file: INI file to parse
:return: void
"""
# Expands Windows environment variable paths
with open(file, 'r') as cfg_file:
cfg_txt = os.path.expandvars(cfg_file.read())
# Parses the expanded config string
self.config_parser.read_string(cfg_txt)
def get_config_items_by_section(self, section):
"""
Retrieves the configurations for a particular section
:param section: INI file section
:return: a list of name, value pairs for the options in the section
"""
return self.config_parser.items(section)
def get_config_val(self, section, option):
"""
Get an option value for the named section.
:param section: INI section
:param option: option tag for desired value
:return: Value of option tag
"""
return self.config_parser.get(section, option)
@staticmethod
def get_date():
"""
Sets up a date formatted string.
:return: Date string
"""
return datetime.now().strftime("%Y%b%d")
def prepend_date_to_var(self, sect, option):
"""
Function that allows the ability to prepend a
date to a section variable.
:param sect: INI section to look for variable
:param option: INI search variable under INI section
:return: Void - Date is prepended to variable string in INI
"""
if self.config_parser.get(sect, option):
var = self.config_parser.get(sect, option)
var_with_date = var + '_' + self.get_date()
self.config_parser.set(sect, option, var_with_date)
【讨论】:
【参考方案5】:似乎在上一个版本3.5.0
中,ConfigParser 没有读取环境变量,所以我最终提供了一个基于BasicInterpolation
的自定义插值。
class EnvInterpolation(BasicInterpolation):
"""Interpolation as implemented in the classic ConfigParser,
plus it checks if the variable is provided as an environment one in uppercase.
"""
def _interpolate_some(self, parser, option, accum, rest, section, map,
depth):
rawval = parser.get(section, option, raw=True, fallback=rest)
if depth > MAX_INTERPOLATION_DEPTH:
raise InterpolationDepthError(option, section, rawval)
while rest:
p = rest.find("%")
if p < 0:
accum.append(rest)
return
if p > 0:
accum.append(rest[:p])
rest = rest[p:]
# p is no longer used
c = rest[1:2]
if c == "%":
accum.append("%")
rest = rest[2:]
elif c == "(":
m = self._KEYCRE.match(rest)
if m is None:
raise InterpolationSyntaxError(option, section,
"bad interpolation variable reference %r" % rest)
var = parser.optionxform(m.group(1))
rest = rest[m.end():]
try:
v = os.environ.get(var.upper())
if v is None:
v = map[var]
except KeyError:
raise InterpolationMissingOptionError(option, section, rawval, var) from None
if "%" in v:
self._interpolate_some(parser, option, accum, v,
section, map, depth + 1)
else:
accum.append(v)
else:
raise InterpolationSyntaxError(
option, section,
"'%%' must be followed by '%%' or '(', "
"found: %r" % (rest,))
BasicInterpolation
和EnvInterpolation
的区别在于:
v = os.environ.get(var.upper())
if v is None:
v = map[var]
在签入map
之前,我试图在环境中找到var
。
【讨论】:
【参考方案6】:从环境中正确替换变量的技巧是对环境变量使用 $ 语法:
[DEFAULT]
test_home=$HOME
[test]
test_1=%(test_home)s/foo.csv
test_2=%(test_home)s/bar.csv
【讨论】:
【参考方案7】:如果你想扩展一些环境变量,你可以在解析StringIO
流之前使用os.path.expandvars
:
import ConfigParser
import os
import StringIO
with open('config.ini', 'r') as cfg_file:
cfg_txt = os.path.expandvars(cfg_file.read())
config = ConfigParser.ConfigParser()
config.readfp(StringIO.StringIO(cfg_txt))
【讨论】:
【参考方案8】:首先根据文档,您应该使用%(test_home)s
插入test_home
。此外,密钥不区分大小写,您不能同时使用 HOME
和 home
密钥。最后,您可以使用SafeConfigParser(os.environ)
来考虑您的环境。
from ConfigParser import SafeConfigParser
import os
parser = SafeConfigParser(os.environ)
parser.read('config.ini')
config.ini
在哪里
[DEFAULT]
test_home=%(HOME)s
[test]
test_1=%(test_home)s/foo.csv
test_2=%(test_home)s/bar.csv
【讨论】:
我无法修改 ini 语法,因为其他软件已经在使用它并且它是$
语法
您应该在使用 SafeConfigParse() 之前对文件进行预处理。但是您可以将 $
语法替换为 %()s
语法。真正的问题将是递归的home=$(HOME)
。您可以假设大写单词来自 environ 并在更改 sys.environ
dict 后替换为 __ENV__KEY 之类的内容。我现在做不到......但你可以自己托盘
这个实现让我印象深刻的一件事是,如果环境变量本身有 '%' 标记,那么解析器会抛出错误。我必须编写一个函数来过滤掉这些键/值。否则,我喜欢这个 impl。
如果您正在使用其他类型的变量以及路径变量,则使用 ConfigParser 而不是 SafeConfigParser 否则会报错 %%' 必须后跟 '%%' 或 '(',找到:%r" % (rest,))
我喜欢这个答案的简单性,但担心它如何随意导入所有环境变量而不仅仅是相关变量,所以我只将所需的子集传递给ConfigParser
:k: v for k, v in os.environ.items() if k in ('MY_ENV_1', 'MY_ENV_2')
。 以上是关于带有 env 变量的 ConfigParser 和字符串插值的主要内容,如果未能解决你的问题,请参考以下文章
使用 Docker 和 PHP 从 env 文件加载环境变量
使用configparser读取带有中文的配置文件出现UnicodeDecodeError错误