从环境文件读取环境变量
- 2025-03-26 09:09:00
- admin 原创
- 18
问题描述:
我想在本地环境中运行通常在 Docker 容器中运行的 Python 脚本。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'
我可以编写我的解析器,但我想知道是否有任何现有的模块/工具可以从配置文件中读取环境变量?
解决方案 1:
我使用Python Dotenv 库。只需安装该库pip install python-dotenv
,创建一个.env
包含环境变量的文件,然后在代码中导入环境变量,如下所示:
import os
from dotenv import load_dotenv
load_dotenv()
MY_ENV_VAR = os.getenv('MY_ENV_VAR')
print(MY_ENV_VAR)
来自.env
文件:
MY_ENV_VAR="This is my env var content."
当我需要在 Docker 系统之外测试代码并准备将其再次返回到 Docker 时,我就会这样做。
由esmaeelE添加
您还可以检查环境变量是否存在。
print('MY_ENV_VAR' in os.environ) # True of False
print(os.environ['MY_ENV_VAR']) # Print contents of variable
print(os.environ.get('MY_ENV_VAR')) # Its better when variable not existed
python -dotenv包可以在 Debian 上使用
apt install python3-dotenv
解决方案 2:
如果您的系统/环境/工作流支持使用 shell 脚本,您可以创建一个包装这两个操作的脚本:
获取 .env 文件并将其导出为环境变量
使用
set -a
选项“每个创建或修改的变量或函数都被赋予导出属性并标记为导出到后续命令的环境”。
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
使用 .env 文件时,所有environ.get
调用都会返回None
。
但是,当您将其包装在 shell 脚本中时,该脚本首先将 .env 文件加载到环境变量中,然后运行 Python 脚本,Python 脚本现在应该能够正确读取环境变量。它还确保导出的环境变量仅作为 Python 应用程序/脚本执行的一部分而存在。
与其他流行答案相比,这不需要任何外部 Python 库。
解决方案 3:
这也可能适合你:
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)
在评论中,你会发现几种不同的方法来保存环境变量,以及一些解析选项,例如删除前导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('
', '')
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("{}={}
".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
解决方案 4:
您可以使用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
解决方案 5:
那么,如何获得更紧凑的解决方案呢:
import os
with open('.docker-compose-env', 'r') as fh:
vars_dict = dict(
tuple(line.replace('
', '').split('='))
for line in fh.readlines() if not line.startswith('#')
)
print(vars_dict)
os.environ.update(vars_dict)
解决方案 6:
Dewald Abrie 发布了一个很好的解决方案。
以下是忽略断线的轻微修改(`
`)
def get_env_data_as_dict(path: str) -> dict:
with open(path, 'r') as f:
return dict(tuple(line.replace('
', '').split('=')) for line
in f.readlines() if not line.startswith('#'))
print(get_env_data_as_dict('../db.env'))
解决方案 7:
仅使用 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)
解决方案 8:
我不建议直接.env
从程序中读取文件。12要素应用程序中配置部分的理念是从环境变量中读取配置- 即,而不是从文件中读取。
.env
文件只是将这些变量放入环境中的一种便捷方式(见下文)。如果您开始在代码中直接读取文件,那么您就跳过了环境变量这一步,并会产生所有后果,基本上又回到了原点,从文件中读取配置。
因此,您应该做的是,使用dotenv-cli之类的工具来读取.env
文件,将变量导出到环境中,并使用临时修改的环境运行您的应用程序,如下所示:
$ dotenv yourapp
解决方案 9:
在无法使用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
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
参数覆盖。
解决方案 10:
我认为您应该使用外部工具来为您管理环境。
这样,您就可以轻松使用像 1password cli 这样的秘密管理器来从加密保险库中加载环境变量,就像这样
op run --env-file=.env -- python your_script.py
话虽如此load_dotenv
,如果环境中存在 .env 变量,则不会加载它们,但其他一些解决方案却不是这样。
如果您没有任何外部工具,只需使用“bash”:
set -o allexport; source .env; set +o allexport
解决方案来自:从键/值对文件设置环境变量
解决方案 11:
对于这些类型的文件, python-decouple是一个不错的选择。虽然它通常用于 django 应用程序,但它可以扩展到任意环境文件。
from decouple import Config, RepositoryEnv
env=Config(RepositoryEnv('.env'))
env.get('DB_ADDR') #=> 'rethinkdb'
env.get('DB_PORT') #=> '28015'
env.get('DB_NAME') #=> 'ipercron'
解决方案 12:
如果您正在使用 Jupyter Notebook,您可以使用这个魔法并指向该.env
文件。
%reload_ext dotenv
%dotenv path/to/env_vars.env
文档在这里。
解决方案 13:
我对此并不感到自豪,但是,与其他答案相比,如果一行以注释结尾(在同一行的数据之后),它似乎有效。改编自@Tom的答案。
# have to parse manually since dotenv package is not available
def get_env_data_as_dict(dotenv_path):
result = {}
with open(dotenv_path) as file_obj:
lines = file_obj.read().splitlines() # Removes
from lines
for line in lines:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
if "#" in line:
line = line.split("#")[0].strip()
key, value = line.split("=", maxsplit=1)
result[key] = value
return result