-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Loading branch information
1 parent
b6b18b3
commit 4a859f9
Showing
7 changed files
with
207 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,7 @@ | |
|
||
databases: | ||
pg: | ||
dsn: env:PG_DATABASE_DSN | ||
dsn: !env PG_DATABASE_DSN | ||
|
||
metrics: | ||
pg_process: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import os | ||
from pathlib import Path | ||
import typing as t | ||
|
||
import yaml | ||
|
||
|
||
def load_yaml_config(path: Path) -> t.Any: | ||
"""Load a YAML document from a file.""" | ||
|
||
class ConfigLoader(yaml.SafeLoader): | ||
"""Subclass supporting tags.""" | ||
|
||
base_path: t.ClassVar[Path] | ||
|
||
def config_loader(path: Path) -> type[ConfigLoader]: | ||
class ConfigLoaderWithPath(ConfigLoader): | ||
base_path = path | ||
|
||
return ConfigLoaderWithPath | ||
|
||
def tag_env(loader: ConfigLoader, node: yaml.nodes.ScalarNode) -> t.Any: | ||
env = loader.construct_scalar(node) | ||
value = os.getenv(env) | ||
if value is None: | ||
raise yaml.scanner.ScannerError( | ||
"while processing 'env' tag", | ||
None, | ||
f"variable {env} undefined", | ||
loader.get_mark(), # type: ignore | ||
) | ||
return yaml.safe_load(value) | ||
|
||
def tag_file(loader: ConfigLoader, node: yaml.nodes.ScalarNode) -> str: | ||
path = loader.base_path / loader.construct_scalar(node) | ||
if not path.is_file(): | ||
raise yaml.scanner.ScannerError( | ||
"while processing 'file' tag", | ||
None, | ||
f"file {path} not found", | ||
loader.get_mark(), # type: ignore | ||
) | ||
return path.read_text().strip() | ||
|
||
def tag_include( | ||
loader: ConfigLoader, node: yaml.nodes.ScalarNode | ||
) -> t.Any: | ||
path = loader.base_path / loader.construct_scalar(node) | ||
if not path.is_file(): | ||
raise yaml.scanner.ScannerError( | ||
"while processing 'include' tag", | ||
None, | ||
f"file {path} not found", | ||
loader.get_mark(), # type: ignore | ||
) | ||
with path.open() as fd: | ||
return yaml.load(fd, config_loader(path.parent)) | ||
|
||
ConfigLoader.add_constructor("!env", tag_env) | ||
ConfigLoader.add_constructor("!file", tag_file) | ||
ConfigLoader.add_constructor("!include", tag_include) | ||
|
||
with path.open() as fd: | ||
return yaml.load(fd, config_loader(path.parent)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
from pathlib import Path | ||
from textwrap import dedent | ||
import typing as t | ||
|
||
import pytest | ||
import yaml | ||
|
||
from query_exporter.yaml import load_yaml_config | ||
|
||
|
||
class TestLoadYAMLConfig: | ||
def test_load(self, tmp_path: Path) -> None: | ||
config = tmp_path / "config.yaml" | ||
config.write_text( | ||
dedent( | ||
""" | ||
a: b | ||
c: d | ||
""" | ||
) | ||
) | ||
assert load_yaml_config(config) == {"a": "b", "c": "d"} | ||
|
||
@pytest.mark.parametrize("env_value", ["foo", 3, False, {"foo": "bar"}]) | ||
def test_load_env( | ||
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, env_value: t.Any | ||
) -> None: | ||
monkeypatch.setenv("FOO", yaml.dump(env_value)) | ||
config = tmp_path / "config.yaml" | ||
config.write_text("x: !env FOO") | ||
assert load_yaml_config(config) == {"x": env_value} | ||
|
||
def test_load_env_not_found(self, tmp_path: Path) -> None: | ||
config = tmp_path / "config.yaml" | ||
config.write_text("x: !env FOO") | ||
with pytest.raises(yaml.scanner.ScannerError) as err: | ||
load_yaml_config(config) | ||
assert "variable FOO undefined" in str(err.value) | ||
|
||
def test_load_file_relative_path(self, tmp_path: Path) -> None: | ||
(tmp_path / "foo.txt").write_text("some text") | ||
config = tmp_path / "config.yaml" | ||
config.write_text("x: !file foo.txt") | ||
assert load_yaml_config(config) == {"x": "some text"} | ||
|
||
def test_load_file_absolute_path(self, tmp_path: Path) -> None: | ||
text_file = tmp_path / "foo.txt" | ||
text_file.write_text("some text") | ||
config = tmp_path / "config.yaml" | ||
config.write_text(f"x: !file {text_file.absolute()!s}") | ||
assert load_yaml_config(config) == {"x": "some text"} | ||
|
||
def test_load_file_not_found(self, tmp_path: Path) -> None: | ||
config = tmp_path / "config.yaml" | ||
config.write_text("x: !file not-here.txt") | ||
with pytest.raises(yaml.scanner.ScannerError) as err: | ||
load_yaml_config(config) | ||
assert f"file {tmp_path / 'not-here.txt'} not found" in str(err.value) | ||
|
||
def test_load_include_relative_path(self, tmp_path: Path) -> None: | ||
(tmp_path / "foo.yaml").write_text("foo: bar") | ||
config = tmp_path / "config.yaml" | ||
config.write_text("x: !include foo.yaml") | ||
assert load_yaml_config(config) == {"x": {"foo": "bar"}} | ||
|
||
def test_load_include_absolute_path(self, tmp_path: Path) -> None: | ||
other_file = tmp_path / "foo.yaml" | ||
other_file.write_text("foo: bar") | ||
config = tmp_path / "config.yaml" | ||
config.write_text(f"x: !include {other_file.absolute()!s}") | ||
assert load_yaml_config(config) == {"x": {"foo": "bar"}} | ||
|
||
def test_load_include_multiple(self, tmp_path: Path) -> None: | ||
subdir = tmp_path / "subdir" | ||
subdir.mkdir() | ||
(subdir / "bar.yaml").write_text("[a, b, c]") | ||
(subdir / "foo.yaml").write_text("foo: !include bar.yaml") | ||
config = tmp_path / "config.yaml" | ||
config.write_text("x: !include subdir/foo.yaml") | ||
assert load_yaml_config(config) == {"x": {"foo": ["a", "b", "c"]}} | ||
|
||
def test_load_include_not_found(self, tmp_path: Path) -> None: | ||
config = tmp_path / "config.yaml" | ||
config.write_text("x: !include not-here.yaml") | ||
with pytest.raises(yaml.scanner.ScannerError) as err: | ||
load_yaml_config(config) | ||
assert f"file {tmp_path / 'not-here.yaml'} not found" in str(err.value) |