1600 allow providing default value in config (#1604)
This commit is contained in:
committed by
GitHub
parent
421559a2a6
commit
2abb943d32
@@ -412,6 +412,26 @@ Below is an example of how to integrate system environment variables in pygeoapi
|
||||
host: ${MY_HOST}
|
||||
port: ${MY_PORT}
|
||||
|
||||
Multiple environment variables are supported as follows:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
data: ${MY_HOST}:${MY_PORT}
|
||||
|
||||
It is also possible to define a default value for a variable in case it does not exist in
|
||||
the environment using a syntax like: ``value: ${ENV_VAR:-the default}``
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
server:
|
||||
bind:
|
||||
host: ${MY_HOST:-localhost}
|
||||
port: ${MY_PORT:-5000}
|
||||
metadata:
|
||||
identification:
|
||||
title:
|
||||
en: This is pygeoapi host ${MY_HOST} and port ${MY_PORT:-5000}, nice to meet you!
|
||||
|
||||
|
||||
Hierarchical collections
|
||||
------------------------
|
||||
|
||||
+28
-12
@@ -163,23 +163,39 @@ def yaml_load(fh: IO) -> dict:
|
||||
:returns: `dict` representation of YAML
|
||||
"""
|
||||
|
||||
# support environment variables in config
|
||||
# https://stackoverflow.com/a/55301129
|
||||
path_matcher = re.compile(r'.*\$\{([^}^{]+)\}.*')
|
||||
# # support environment variables in config
|
||||
# # https://stackoverflow.com/a/55301129
|
||||
|
||||
def path_constructor(loader, node):
|
||||
env_var = path_matcher.match(node.value).group(1)
|
||||
if env_var not in os.environ:
|
||||
msg = f'Undefined environment variable {env_var} in config'
|
||||
raise EnvironmentError(msg)
|
||||
return get_typed_value(os.path.expandvars(node.value))
|
||||
env_matcher = re.compile(
|
||||
r'.*?\$\{(?P<varname>\w+)(:-(?P<default>[^}]+))?\}')
|
||||
|
||||
def env_constructor(loader, node):
|
||||
result = ""
|
||||
current_index = 0
|
||||
raw_value = node.value
|
||||
for match_obj in env_matcher.finditer(raw_value):
|
||||
groups = match_obj.groupdict()
|
||||
varname_start = match_obj.span('varname')[0]
|
||||
result += raw_value[current_index:(varname_start-2)]
|
||||
if (var_value := os.getenv(groups['varname'])) is not None:
|
||||
result += var_value
|
||||
elif (default_value := groups.get('default')) is not None:
|
||||
result += default_value
|
||||
else:
|
||||
raise EnvironmentError(
|
||||
f'Could not find the {groups["varname"]!r} environment '
|
||||
f'variable'
|
||||
)
|
||||
current_index = match_obj.end()
|
||||
else:
|
||||
result += raw_value[current_index:]
|
||||
return get_typed_value(result)
|
||||
|
||||
class EnvVarLoader(yaml.SafeLoader):
|
||||
pass
|
||||
|
||||
EnvVarLoader.add_implicit_resolver('!path', path_matcher, None)
|
||||
EnvVarLoader.add_constructor('!path', path_constructor)
|
||||
|
||||
EnvVarLoader.add_implicit_resolver('!env', env_matcher, None)
|
||||
EnvVarLoader.add_constructor('!env', env_constructor)
|
||||
return yaml.load(fh, Loader=EnvVarLoader)
|
||||
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ from datetime import datetime, date, time
|
||||
from decimal import Decimal
|
||||
from contextlib import nullcontext as does_not_raise
|
||||
from copy import deepcopy
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from pyproj.exceptions import CRSError
|
||||
@@ -77,6 +79,40 @@ def test_yaml_load(config):
|
||||
util.yaml_load(fh)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('env,input_config,expected', [
|
||||
pytest.param({}, 'foo: something', {'foo': 'something'}, id='no-env-expansion'), # noqa E501
|
||||
pytest.param({'FOO': 'this'}, 'foo: ${FOO}', {'foo': 'this'}), # noqa E501
|
||||
pytest.param({'FOO': 'this'}, 'foo: the value is ${FOO}', {'foo': 'the value is this'}, id='no-need-for-yaml-tag'), # noqa E501
|
||||
pytest.param({}, 'foo: ${FOO:-some default}', {'foo': 'some default'}), # noqa E501
|
||||
pytest.param({'FOO': 'this', 'BAR': 'that'}, 'composite: ${FOO}:${BAR}', {'composite': 'this:that'}), # noqa E501
|
||||
pytest.param({}, 'composite: ${FOO:-default-foo}:${BAR:-default-bar}', {'composite': 'default-foo:default-bar'}), # noqa E501
|
||||
pytest.param(
|
||||
{
|
||||
'HOST': 'fake-host',
|
||||
'USER': 'fake',
|
||||
'PASSWORD': 'fake-pass',
|
||||
'DB': 'fake-db'
|
||||
},
|
||||
'connection: postgres://${USER}:${PASSWORD}@${HOST}:${PORT:-5432}/${DB}', # noqa E501
|
||||
{
|
||||
'connection': 'postgres://fake:fake-pass@fake-host:5432/fake-db'
|
||||
},
|
||||
id='multiple-no-need-yaml-tag'
|
||||
),
|
||||
])
|
||||
def test_yaml_load_with_env_variables(
|
||||
env: dict[str, str], input_config: str, expected):
|
||||
|
||||
def mock_get_env(env_var_name):
|
||||
result = env.get(env_var_name)
|
||||
return result
|
||||
|
||||
with mock.patch('pygeoapi.util.os') as mock_os:
|
||||
mock_os.getenv.side_effect = mock_get_env
|
||||
loaded_config = util.yaml_load(StringIO(input_config))
|
||||
assert loaded_config == expected
|
||||
|
||||
|
||||
def test_str2bool():
|
||||
assert not util.str2bool(False)
|
||||
assert not util.str2bool('0')
|
||||
|
||||
Reference in New Issue
Block a user