pytex/PyTeX/format/config.py

145 lines
4.5 KiB
Python

from __future__ import annotations
import json
from pathlib import Path
from typing import Dict, Optional, Union, List
import yaml
def clean_list(list_: List) -> Optional[List]:
"""
Recursively removes all entries from list that are None,
empty dictionaries or empty lists.
Dicts ore lists that are None after cleaning will also be removed
Typically applied before dumping data to JSON where None values are default
and will be restored on read-in 'automatically'
:return:
:param list_: Any list
:return:
"""
ret = []
for elem in list_:
if type(elem) == list:
ret.append(clean_list(elem))
elif type(elem) == dict:
ret.append(clean_dict(elem))
elif elem is not None:
ret.append(elem)
return ret if ret != [] else None
def clean_dict(dictionary: Dict) -> Optional[Dict]:
"""
Recursively removes all entries from dictionary that are None,
empty dictionaries or empty lists.
Keys whose value is a dict / list that only contains non-valued keys will
then also be removed.
Typically applied before dumping data to JSON where None values are default
and will be restored on read-in 'automatically'
:param dictionary: Any dictionary
:return:
"""
aux: Dict = {
k: clean_dict(v) for k, v in dictionary.items() if type(v) == dict
} | {
k: clean_list(v)
for k, v in dictionary.items() if type(v) == list
} | {
k: v for k, v in dictionary.items() if type(v) != dict and type(v) != list
}
aux2: Dict = {
k: v for k, v in aux.items() if v is not None
}
return aux2 if aux2 != {} and aux2 != [] else None
class Config:
def merge_with(self, other: Config, strict: bool = False):
"""
Merges the other config into this one.
In conflicts, the called-on instance takes effect
:param other:
:param strict: whether conflicting options are allowed or not
:return: self
"""
for var in vars(self):
if not getattr(self, var):
setattr(self, var, getattr(other, var))
else:
if strict and getattr(other, var) is not None and getattr(self, var) != getattr(other, var):
raise NotImplementedError
return self
@classmethod
def from_json(cls, content: Union[Path, Dict]):
if isinstance(content, Path):
with open(content, 'r') as file:
json_content: Dict = json.load(file)
else:
json_content = content
config = cls()
config.set_from_json(json_content)
return config
@classmethod
def from_yaml(cls, content: Union[Path, str]):
if isinstance(content, Path):
with open(content, 'r') as file:
json_content: Dict = yaml.safe_load(file)
else:
json_content = yaml.safe_load(content)
return cls.from_json(json_content)
def set_from_json(self, content: Optional[Dict]):
raise NotImplementedError
def to_json(self) -> Dict:
raise NotImplementedError
def dump_as_yaml(self, filename: Path, clean_none_entries: bool = True):
with filename.open('w') as file:
if clean_none_entries:
simple_dict = clean_dict(self.to_json())
else:
simple_dict = self.to_json()
if simple_dict is not None:
yaml.dump(simple_dict, file)
else:
pass # TODO
def dump_as_json(self, filename: Path, clean_none_entries: bool = True):
with open(filename, 'w') as config:
if clean_none_entries:
simple_dict = clean_dict(self.to_json())
else:
simple_dict = self.to_json()
if simple_dict is not None:
json.dump(simple_dict, config, indent=4)
else:
pass # TODO
@classmethod
def _fill_keys(cls, dictionary: Optional[Dict]):
if dictionary is None:
return cls().to_json()
else:
return recursive_merge_dictionaries(
cls().to_json(),
dictionary
)
def recursive_merge_dictionaries(dict1: Dict, dict2: Dict) -> Dict:
aux1 = {
k: v for k, v in dict1.items() if type(v) == dict
}
aux2 = {
k: v for k, v in dict2.items() if type(v) == dict
}
merged = {
k: recursive_merge_dictionaries(v, aux2[k]) for k, v in aux1.items() if k in aux2.keys()
}
return dict1 | dict2 | merged