-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Lift get_config_data from npg_notifications. Improve error handling b…
…y checking for a necessary config file. Use a dataclass to validate all properties are set in conf.
- Loading branch information
1 parent
d3dca7b
commit 7377b3f
Showing
3 changed files
with
152 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import configparser | ||
import json | ||
import pathlib | ||
from dataclasses import dataclass | ||
|
||
"""Common utility functions for the package.""" | ||
|
||
DEFAULT_CONF_FILE_TYPE = "ini" | ||
|
||
|
||
def get_config_data(conf_file_path: str, conf_file_section: str = None): | ||
""" | ||
Parses a configuration file and returns its content. | ||
Allows for two types of configuration files, 'ini' and 'json'. The type of | ||
the file is determined from the extension of the file name. In case of no | ||
extension an 'ini' type is assumed. | ||
The content of the file is not cached, so subsequent calls to get data from | ||
the same configuration file result in re-reading and re-parsing of the file. | ||
Args: | ||
conf_file_path: | ||
A configuration file with database connection details. | ||
conf_file_section: | ||
The section of the configuration file. Optional. Should be defined | ||
for 'ini' files. | ||
Returns: | ||
For an 'ini' file returns the content of the given section of the file as | ||
a dictionary. | ||
For a 'json' file, if the conf_file_section argument is not defined, the | ||
content of a file as a Python object is returned. If the conf_file_section | ||
argument is defined, the object returned by the parser is assumed to be | ||
a dictionary that has the value of the 'conf_file_section' argument as a key. | ||
The value corresponding to this key is returned. | ||
""" | ||
|
||
path = pathlib.Path(conf_file_path) | ||
|
||
conf_file_extension = path.suffix | ||
if conf_file_extension: | ||
conf_file_extension = conf_file_extension[1:] | ||
else: | ||
conf_file_extension = DEFAULT_CONF_FILE_TYPE | ||
|
||
if conf_file_extension == DEFAULT_CONF_FILE_TYPE: | ||
if not conf_file_section: | ||
raise Exception( | ||
"'conf_file_section' argument is not given, " | ||
"it should be defined for '{DEFAULT_CONF_FILE_TYPE}' " | ||
"configuration file." | ||
) | ||
|
||
config = configparser.ConfigParser() | ||
with open(conf_file_path) as cf: | ||
if not cf.readable(): | ||
raise Exception(f"{path} has a permissions issue") | ||
|
||
config.read_file(cf) | ||
|
||
return {i[0]: i[1] for i in config[conf_file_section].items()} | ||
|
||
elif conf_file_extension == "json": | ||
conf: dict = json.load(conf_file_path) | ||
if conf_file_section: | ||
if isinstance(conf, dict) is False: | ||
raise Exception(f"{conf_file_path} does not have sections.") | ||
if conf_file_section in conf.keys: | ||
conf = conf[conf_file_section] | ||
else: | ||
raise Exception( | ||
f"{conf_file_path} does not contain {conf_file_section} key" | ||
) | ||
|
||
return conf | ||
|
||
else: | ||
raise Exception(f"Parsing for '{conf_file_extension}' files is not implemented") | ||
|
||
|
||
@dataclass(frozen=True, kw_only=True) | ||
class PorchClientConfig: | ||
""" | ||
Suggested config file content for interacting with a Porch server instance | ||
""" | ||
|
||
api_url: str | ||
pipeline_name: str | ||
pipeline_uri: str | ||
pipeline_version: str | ||
npg_porch_token: str | ||
|
||
@classmethod | ||
def from_config_file(cls, conf_file_path: str, conf_file_section: str = "PORCH"): | ||
conf = get_config_data(conf_file_path, conf_file_section=conf_file_section) | ||
return cls(**conf) |
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,17 @@ | ||
[STUFF] | ||
|
||
logging = INFO | ||
|
||
[PORCH] | ||
|
||
api_url = https://porch.dnapipelines.sanger.ac.uk | ||
pipeline_name = test_pipeline | ||
pipeline_uri = https://test.pipeline.com | ||
pipeline_version = 9.9.9 | ||
npg_porch_token = 0123456789abcdef0123456789abcdef | ||
|
||
[PARTIALPORCH] | ||
|
||
api_url = https://porch.dnapipelines.sanger.ac.uk | ||
pipeline_name = test_pipeline | ||
pipeline_uri = https://test.pipeline.com |
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,37 @@ | ||
from pytest import raises | ||
|
||
from npg_porch_cli.config import PorchClientConfig, get_config_data | ||
|
||
|
||
def test_config_file_reading(): | ||
conf = get_config_data( | ||
conf_file_path="tests/data/conf.ini", conf_file_section="STUFF" | ||
) | ||
|
||
assert conf == {"logging": "INFO"} | ||
|
||
|
||
def test_conf_obj(): | ||
config_obj = PorchClientConfig.from_config_file("tests/data/conf.ini") | ||
assert config_obj.pipeline_name == "test_pipeline" | ||
assert config_obj.pipeline_version == "9.9.9" | ||
|
||
with raises(Exception): | ||
PorchClientConfig.from_config_file( | ||
"tests/data/conf.ini", conf_file_section="ABSENT" | ||
) | ||
|
||
with raises(Exception): | ||
PorchClientConfig.from_config_file("notafile", conf_file_section="ABSENT") | ||
|
||
with raises(TypeError) as e: | ||
PorchClientConfig.from_config_file( | ||
"tests/data/conf.ini", conf_file_section="STUFF" | ||
) | ||
assert e.match("unexpected keyword argument 'logging'") | ||
|
||
with raises(Exception) as e: | ||
PorchClientConfig.from_config_file( | ||
"tests/data/conf.ini", conf_file_section="PARTIALPORCH" | ||
) | ||
assert e.match("missing 2 required keyword-only arguments") |