backup-tool/app/specs/parser.py

115 lines
2.6 KiB
Python
Raw Normal View History

2021-01-15 13:01:17 +01:00
import yaml
from pathlib import Path
from specs import Spec
from typing import List, Dict
class InvalidKeyError(Exception):
def __init__(self, key):
message = "Invalid key: {}".format(key)
super().__init__(key)
class MissingKeyError(Exception):
def __init__(self, key):
message = "Missing key: {}".format(key)
super().__init__(key)
def parse_specs_file(path: Path) -> List[Spec]:
"""
Parse a YAML file defining backup specs.
Args:
path: path to the specs file
Returns:
A list of specs
"""
# Skeleton of a spec config
# If a value is None, this means it doesn't have a default value and must be
# defined
spec_skel = {
"source": None,
"destination": None,
"limit": None,
"volume": False,
"notify": {
"title": "Backup Notification",
"events": ["failure"]
}
}
# Read YAML file
with open(path, "r") as yaml_file:
data = yaml.load(yaml_file, Loader=yaml.Loader)
# Check specs section exists
if "specs" not in data:
raise MissingKeyError("specs")
# Allow for default notify settings
if "notify" in data:
spec_skel["notify"] = data["notify"]
specs = []
# Check format for each spec
for key in data["specs"]:
specs.append(Spec.from_dict(key, combine_with_skeleton(
data["specs"][key], spec_skel)
))
return specs
def combine_with_skeleton(data: Dict, skel: Dict) -> Dict:
"""
Compare a dict with a given skeleton dict, and fill in default values where
needed.
"""
# First, check for illegal keys
for key in data:
if key not in skel:
raise InvalidKeyError(key)
# Then, check the default values
for key, value in skel.items():
if key not in data:
# Raise error if there's not default value
if value is None:
raise MissingKeyError(key)
# Replace with default value
data[key] = value
# Error if value is not same type as default value
elif type(data[key]) != type(value) and value is not None:
raise TypeError("Invalid value type")
# Recurse into dicts
elif type(value) == dict:
data[key] = combine_with_skeleton(data[key], value)
return data
# Test cases
if __name__ == "__main__":
d1 = {
"a": 5
}
s1 = {
"a": 7,
"b": 2
}
r1 = {
"a": 5,
"b": 2
}
assert combine_with_skeleton(d1, s1) == r1