Skip to content

Instantly share code, notes, and snippets.

@mkaranasou
Last active April 13, 2023 19:53
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save mkaranasou/ba83e25c835a8f7629e34dd7ede01931 to your computer and use it in GitHub Desktop.
Save mkaranasou/ba83e25c835a8f7629e34dd7ede01931 to your computer and use it in GitHub Desktop.
Python Load a yaml configuration file and resolve any environment variables
import os
import re
import yaml
def parse_config(path=None, data=None, tag='!ENV'):
"""
Load a yaml configuration file and resolve any environment variables
The environment variables must have !ENV before them and be in this format
to be parsed: ${VAR_NAME}.
E.g.:
database:
host: !ENV ${HOST}
port: !ENV ${PORT}
app:
log_path: !ENV '/var/${LOG_PATH}'
something_else: !ENV '${AWESOME_ENV_VAR}/var/${A_SECOND_AWESOME_VAR}'
:param str path: the path to the yaml file
:param str data: the yaml data itself as a stream
:param str tag: the tag to look for
:return: the dict configuration
:rtype: dict[str, T]
"""
# pattern for global vars: look for ${word}
pattern = re.compile('.*?\${(\w+)}.*?')
loader = yaml.SafeLoader
# the tag will be used to mark where to start searching for the pattern
# e.g. somekey: !ENV somestring${MYENVVAR}blah blah blah
loader.add_implicit_resolver(tag, pattern, None)
def constructor_env_variables(loader, node):
"""
Extracts the environment variable from the node's value
:param yaml.Loader loader: the yaml loader
:param node: the current node in the yaml
:return: the parsed string that contains the value of the environment
variable
"""
value = loader.construct_scalar(node)
match = pattern.findall(value) # to find all env variables in line
if match:
full_value = value
for g in match:
full_value = full_value.replace(
f'${{{g}}}', os.environ.get(g, g)
)
return full_value
return value
loader.add_constructor(tag, constructor_env_variables)
if path:
with open(path) as conf_data:
return yaml.load(conf_data, Loader=loader)
elif data:
return yaml.load(data, Loader=loader)
else:
raise ValueError('Either a path or data should be defined as input')
@programus
Copy link

This code has an error because not import re ...

import re

before the 1st line will fix it.

@mkaranasou
Copy link
Author

Thanks! Fixed it

@nandhinik
Copy link

Thanks for sharing this code. It works perfectly for my scenario also where the value is fetched from config file. Btw, can I use this code in my project?

@mkaranasou
Copy link
Author

Thanks for sharing this code. It works perfectly for my scenario also where the value is fetched from config file. Btw, can I use this code in my project?

@nandhinik glad to know you found this helpful! Of course you can use it. Let me know if I can be of any help.

@mkaranasou
Copy link
Author

An improved version of this is in the very small pyaml-env library : https://pypi.org/project/pyaml-env/
(mostly for convenience)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment