Python 脚本如何从环境文件中读取环境变量

我想在本地环境中运行一个Python脚本,通常在Docker容器中运行。docker-compose.yml指定了一个env_file,它看起来(部分)如下所示:

DB_ADDR=rethinkdb
DB_PORT=28015
DB_NAME=ipercron

为了在本地运行,我希望将这些行转换为

os.environ['DB_ADDR'] = 'rethinkdb'
os.environ['DB_PORT'] = '28015'
os.environ['DB_NAME'] = 'ipercron'

我可以编写我的解析器,但我想知道是否有任何现有的模块/工具可以从配置文件中读取环境变量?

261546 次浏览

你可以使用ConfigParser。示例可以找到在这里

但是这个库期望你的key=value数据出现在某个[heading]下。例如:

[mysqld]
user = mysql  # Key with values
pid-file = /var/run/mysqld/mysqld.pid
skip-external-locking
old_passwords = 1
skip-bdb      # Key without value
skip-innodb

只使用python std

import re


envre = re.compile(r'''^([^=]+)\s+?=\s+?(?:[\s"']*)(.+?)(?:[\s"']*)$''')
result = {}
with open('/etc/os-release') as ins:
for line in ins:
match = envre.match(line)
if match is not None:
result[match.group(1)] = match.group(2)

这也适用于你:

env_vars = [] # or dict {}
with open(env_file) as f:
for line in f:
if line.startswith('#') or not line.strip():
continue
# if 'export' not in line:
#     continue
# Remove leading `export `, if you have those
# then, split name / value pair
# key, value = line.replace('export ', '', 1).strip().split('=', 1)
key, value = line.strip().split('=', 1)
# os.environ[key] = value  # Load to local environ
# env_vars[key] = value # Save to a dict, initialized env_vars = {}
env_vars.append({'name': key, 'value': value}) # Save to a list


print(env_vars)

在注释中,你会发现一些保存env变量的不同方法,以及一些解析选项,例如,去掉开头的export关键字。另一种方法是使用python-dotenv库。欢呼。

更新:我设置了自己的envvar_utils.py来处理从字符串等的转换。

"""Utility functions for dealing with env variables and reading variables from env file"""
import os
import logging
import json


BOOLEAN_TYPE = 'boolean'
INT_TYPE = 'int'
FLOAT_TYPE = 'float'
STRING_TYPE = 'str'
LIST_TYPE = 'list'
DICT_TYPE = 'dict'




def get_envvars(env_file='.env', set_environ=True, ignore_not_found_error=False, exclude_override=()):
"""
Set env vars from a file
:param env_file:
:param set_environ:
:param ignore_not_found_error: ignore not found error
:param exclude_override: if parameter found in this list, don't overwrite environment
:return: list of tuples, env vars
"""
env_vars = []
try:


with open(env_file) as f:
for line in f:
line = line.replace('\n', '')


if not line or line.startswith('#'):
continue


# Remove leading `export `
if line.lower().startswith('export '):
key, value = line.replace('export ', '', 1).strip().split('=', 1)
else:
try:
key, value = line.strip().split('=', 1)
except ValueError:
logging.error(f"envar_utils.get_envvars error parsing line: '{line}'")
raise


if set_environ and key not in exclude_override:
os.environ[key] = value


if key in exclude_override:
env_vars.append({'name': key, 'value': os.getenv(key)})
else:
env_vars.append({'name': key, 'value': value})
except FileNotFoundError:
if not ignore_not_found_error:
raise


return env_vars




def create_envvar_file(env_file_path, envvars):
"""
Writes envvar file using env var dict
:param env_file_path: str, path to file to write to
:param envvars: dict, env vars
:return:
"""
with open(env_file_path, "w+") as f:
for key, value in envvars.items():
f.write("{}={}\n".format(key, value))
return True




def convert_env_var_flag_to(env_var_name, required_type, default_value):
"""
Convert env variable string flag values to required_type
:param env_var_name: str, environment variable name
:param required_type: str, required type to cast the env var to
:param default_value: boolean, default value to use if the environment variable is not available
:return: environment variable value in required type
"""
env_var_orginal_value = os.getenv(env_var_name, default_value)
env_var_value = ""
try:
if required_type == INT_TYPE:
env_var_value = int(env_var_orginal_value)
elif required_type == FLOAT_TYPE:
env_var_value = float(env_var_orginal_value)
elif required_type == BOOLEAN_TYPE:
env_var_value = bool(int(env_var_orginal_value))
elif required_type == STRING_TYPE:
env_var_value = str(env_var_orginal_value)
elif required_type == LIST_TYPE:
env_var_value = env_var_orginal_value.split(',') if len(env_var_orginal_value) > 0 else default_value
elif required_type == DICT_TYPE:
try:
env_var_value = json.loads(env_var_orginal_value) if env_var_orginal_value else default_value
except Exception as e:
logging.error(f"convert_env_var_flag_to: failed loading {env_var_orginal_value} error {e}")
env_var_value = default_value
else:
logging.error("Unrecognized type {} for env var {}".format(required_type, env_var_name))


except ValueError:
env_var_value = default_value
logging.warning("{} is {}".format(env_var_name, env_var_orginal_value))


return env_var_value

下面是一个更紧凑的解决方案:

import os


with open('.docker-compose-env', 'r') as fh:
vars_dict = dict(
tuple(line.replace('\n', '').split('='))
for line in fh.readlines() if not line.startswith('#')
)


print(vars_dict)
os.environ.update(vars_dict)

我使用Python Dotenv库。只需要安装pip install python-dotenv库,用你的环境变量创建一个.env文件,然后像这样在你的代码中导入环境变量:

import os
from dotenv import load_dotenv


load_dotenv()


MY_ENV_VAR = os.getenv('MY_ENV_VAR')

.env文件中:

MY_ENV_VAR="This is my env var content."

当我需要在docker系统外测试代码并准备再次将其返回到docker时,我就是这样做的。

Dewald Abrie发布了一个很好的解决方案

这里有一个轻微的修改,忽略了折线(\n)

def get_env_data_as_dict(path: str) -> dict:
with open(path, 'r') as f:
return dict(tuple(line.replace('\n', '').split('=')) for line
in f.readlines() if not line.startswith('#'))


print(get_env_data_as_dict('../db.env'))

如果你的系统/环境/工作流支持使用shell脚本,你可以创建一个脚本来包装这两个操作:

  1. 获取。env文件并将其导出为环境变量
    • 使用set -a选项,其中"
  2. 调用包含普通os.environ.get代码的Python脚本/应用程序

env文件样本(config.env):

TYPE=prod
PORT=5000

Python示例代码(test.py):

import os


print(os.environ.get('TYPE'))
print(os.environ.get('PORT'))

示例bash脚本(run.sh):

#!/usr/bin/env bash


set -a
source config.env
set +a


python3 test.py

示例运行:

$ tree
.
├── config.env
├── run.sh
└── test.py


$ echo $TYPE


$ echo $PORT


$ python3 test.py
None
None


$ ./run.sh
prod
5000

当你直接运行Python脚本(python3 test.py)而没有source-ing .env文件时,所有environ.get调用都会返回None

但是,当您将它包装在一个shell脚本中,该脚本首先将.env文件加载到环境变量中,然后再运行Python脚本时,Python脚本现在应该能够正确地读取环境变量。它还确保导出的env变量仅作为Python应用程序/脚本执行的一部分存在。

其他常见答案相比,它不需要任何外部Python库。

在不可能使用python-dotenv的情况下,我使用了类似以下的东西:

import os


def load_env_file(dotenv_path, override=False):
with open(dotenv_path) as file_obj:
lines = file_obj.read().splitlines()  # Removes \n from lines


dotenv_vars = {}
for line in lines:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue


key, value = line.split("=", maxsplit=1)
dotenv_vars.setdefault(key, value)


if override:
os.environ.update(dotenv_vars)
else:
for key, value in dotenv_vars.items():
os.environ.setdefault(key, value)

它读取给定的文件并解析包含"="象征在他们身上。 符号前的值是键,符号后的值是值

具有与env文件中相同键的当前环境变量既可以保持不变,也可以用override参数覆盖。

我认为您应该把它留给外部工具来为您管理环境。

这样,您就可以轻松地使用像1password cli这样的秘密管理器从加密库中加载环境变量

op run --env-file=.env -- python your_script.py

尽管如此,load_dotenv足够聪明,不加载环境中存在的.env变量,但其他一些解决方案没有。

如果你没有任何外部工具,可以使用'bash':

set -o allexport; source .env; set +o allexport

解从:从键/值对文件中设置环境变量