Интерполяция ConfigParser и String с переменной env
Это немного я вне синтаксиса 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
РЕДАКТИРОВАТЬ:
Я предположил, что $
синтаксис был неявно включен в так называемую интерполяцию строк (см. руководство):
Помимо основной функциональности, SafeConfigParser поддерживает интерполяцию. Это означает, что значения могут содержать строки формата, которые ссылаются на другие значения в том же разделе, или значения в специальном разделе DEFAULT.
Но я ошибаюсь. Как справиться с этим делом?
9 ответов
Прежде всего, согласно документации, которую вы должны использовать %(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
Вы можете написать пользовательскую интерполяцию в случае 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):
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'])
Хитрость для правильной подстановки переменных из окружения заключается в использовании синтаксиса ${} для переменных окружения:
[DEFAULT]
test_home=${HOME}
[test]
test_1=%(test_home)s/foo.csv
test_2=%(test_home)s/bar.csv
Если вы хотите расширить некоторые переменные окружения, вы можете сделать это, используя os.path.expandvars
до разбора StringIO
поток:
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))
Значения 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('defauts', 'home')
'/home/user/app'
>>> parser.get('defauts', 'tmp')
'/home/user/app/tmp'
>>> parser.getint('defauts', 'sleep')
10
>>> parser.getboolean('defauts', 'debug')
True
редактировать
В самом деле, вы можете получить значения имен как environment Var, если вы инициализируете SafeConfigParser
с os.environ
, Спасибо за ответ Мишель.
Довольно поздно, но, возможно, это может помочь кому-то еще искать те же ответы, которые я недавно получил. Кроме того, один из комментариев был о том, как получить переменные окружения и значения из других разделов. Вот как я имею дело с преобразованием переменных среды и многосекционных тегов при чтении из INI-файла.
INI FILE:
[PKG]
# <VARIABLE_NAME>=<VAR/PATH>
PKG_TAG = Q1_RC1
[DELIVERY_DIRS]
# <DIR_VARIABLE>=<PATH>
NEW_DELIVERY_DIR=${DEL_PATH}\ProjectName_${PKG:PKG_TAG}_DELIVERY
Класс Python, который использует ExtendedInterpolation, чтобы вы могли использовать ${PKG:PKG_TAG}
форматирование типа. Я добавляю возможность конвертировать переменные среды Windows, когда я читаю в INI, в строку, используя встроенный os.path.expandvars()
функция, такая как ${DEL_PATH}
выше.
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)
На основе ответа (и кода) @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'])
Похоже в последней версии 3.5.0
ConfigParser не считывал переменные env, поэтому в итоге я предоставил пользовательскую интерполяцию, основанную на 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]
где я пытаюсь найти var
в окружающей среде перед проверкой в map
,
Ниже приведено простое решение, которое
- Можно использовать значение по умолчанию, если не указана переменная среды.
- Переопределяет переменные переменными среды (если они найдены)
- не требует пользовательской реализации интерполяции
Пример:my_config.ini
[DEFAULT]
HOST=http://www.example.com
CONTEXT=${HOST}/auth/
token_url=${CONTEXT}/oauth2/token
КонфигПарсер:
import os
import configparser
config = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
ini_file = os.path.join(os.path.dirname(__file__), 'my_config.ini')
# replace variables with environment variables(if exists) before loading ini file
with open(ini_file, 'r') as cfg_file:
cfg_env_txt = os.path.expandvars(cfg_file.read())
config.read_string(cfg_env_txt)
print(config['DEFAULT']['token_url'])
Выход:
- Если переменная среды отсутствует или присутствует, эта конфигурация примет значение по умолчанию.
- пользователь может переопределить значение по умолчанию, создав
$HOST
,$CONTEXT
переменная среды - хорошо работает с докер-контейнером