ESBO-ETC/esbo_etc/classes/Entry.py

210 lines
7.9 KiB
Python

from typing import Union
import re
import xml.etree.ElementTree as eT
import astropy.units as u
from ..lib.logger import logger
import difflib
import os
import numpy as np
class Entry(object):
"""
A class used to represent a configuration entry.
Taken from ExoSim (https://github.com/ExoSim/ExoSimPublic)
"""
val: Union[str, bool, u.Quantity]
def __init__(self, **kwargs):
for key, value in kwargs.items():
self.__setattr__(key, value)
def __call__(self):
return self.val if hasattr(self, "val") else None
def parse(self, xml: eT.Element):
"""
Parse attributes of a XML element
Parameters
----------
xml : xml.etree.ElementTree.Element
XML element to parse the attributes from
"""
# Copy the XML attributes to object attributes
for attrib in xml.attrib.keys():
setattr(self, attrib, xml.attrib[attrib])
# parse units
attribs = list(xml.attrib.keys())
units = list(filter(re.compile(".*_unit$").match, attribs))
for unit in units:
# enable imperial units
u.imperial.enable()
var = unit.replace("_unit", "")
if hasattr(self, var):
try:
val = u.Quantity(list(map(float, getattr(self, var).split(','))), getattr(self, unit))
if len(val) == 1:
val = val[0]
setattr(self, var, val)
except (ValueError, LookupError):
logger.error("unable to convert units in entry '" + xml.tag + "': " + getattr(self, var) + " " +
getattr(self, unit), exit_=False)
# Convert boolean values
if hasattr(self, "val") and type(self.val) == str and self.val.lower() in ["false", "true"]:
self.val = (self.val.lower() == "true")
def check_quantity(self, name: str, unit: u.Unit, use_default: bool = True) -> Union[None, str]:
"""
Check a parameter as type quantity
Parameters
----------
name : str
The name of the parameter to be checked.
unit : Quantity
The default quantity to be used for conversion and equality checking.
use_default : bool
Use the given unit as default unit and try to convert strings to Quantities with this unit.
Returns
-------
mes : Union[None, str]
The error message of the check. This will be None if the check was successful.
"""
u.imperial.enable()
if not hasattr(self, name):
return "Parameter '" + name + "' not found."
attr = getattr(self, name)
if type(attr) != u.Quantity:
if unit == u.dimensionless_unscaled or use_default:
try:
self.__setattr__(name, float(attr) * unit)
except ValueError:
try:
val = list(map(u.Quantity, attr.split(',')))
if len(val) == 1:
val = val[0]
else:
val = np.array(val[:(len(val)-1)] + [val[-1].value]) << val[-1].unit
self.__setattr__(name, val)
mes = self.check_quantity(name, unit, use_default)
if mes is not None:
return mes
except (ValueError, TypeError):
return "Expected parameter '" + name + "' with unit '" + unit.to_string() + \
"' but got no unit and cannot convert '" + attr + "' to a numeric value."
else:
return "Expected parameter '" + name + "' with unit '" + unit.to_string() + "' but got no unit."
elif not attr.unit.is_equivalent(unit):
if unit == u.K and attr.unit == u.Celsius:
setattr(self, name, attr.to(unit, equivalencies=u.temperature()))
setattr(self, name + '_unit', unit.to_string())
elif unit.is_equivalent(u.m) and attr.unit.is_equivalent(u.Hz):
setattr(self, name, attr.to(unit, equivalencies=u.spectral()))
setattr(self, name + '_unit', unit.to_string())
else:
return "Expected parameter '" + name + "' with unit equivalent to '" + unit.to_string() + \
"' but got unit '" + attr.unit.to_string() + "'."
return None
def check_selection(self, name: str, choices: list) -> Union[None, str]:
"""
Check a parameter against a list of possible choices. In case of a mismatch, a recommendation will be given.
Parameters
----------
name : str
The name of the parameter to be checked.
choices : list
List of choices to be used for checking.
Returns
-------
mes : Union[None, str]
The error message of the check. This will be None if the check was successful.
"""
if not hasattr(self, name):
return "Parameter '" + name + "' not found."
attr = getattr(self, name)
if type(attr) != str:
return "Expected parameter '" + name + "' to be of type string."
if attr not in choices:
match = difflib.get_close_matches(attr, choices, 1)
if len(match) > 0:
# noinspection PyTypeChecker
return "Value '" + attr + "' not allowed for parameter '" + name + "'. Did you mean '" + \
match[0] + "'?"
else:
return "Value '" + attr + "' not allowed for parameter '" + name + "'."
return None
def check_file(self, name) -> Union[None, str]:
"""
Check a parameter to be a valid path to a file.
Parameters
----------
name : str
The name of the parameter to be checked.
Returns
-------
mes : Union[None, str]
The error message of the check. This will be None if the check was successful.
"""
if not hasattr(self, name):
return "Parameter '" + name + "' not found."
if not os.path.isfile(getattr(self, name)):
return "File '" + getattr(self, name) + "' does not exist."
def check_path(self, name) -> Union[None, str]:
"""
Check a parameter to be a valid path to a file.
Parameters
----------
name : str
The name of the parameter to be checked.
Returns
-------
mes : Union[None, str]
The error message of the check. This will be None if the check was successful.
"""
if not hasattr(self, name):
return "Parameter '" + name + "' not found."
if not os.path.isdir(getattr(self, name)):
return "Path '" + getattr(self, name) + "' does not exist."
def check_float(self, name) -> Union[None, str]:
"""
Check a parameter to be a floating point value
Parameters
----------
name : str
The name of the parameter to be checked.
Returns
-------
mes : Union[None, str]
The error message of the check. This will be None if the check was successful.
"""
if not hasattr(self, name):
return "Parameter '" + name + "' not found."
attr = getattr(self, name)
if type(attr) == float:
return None
elif type(attr) == u.Quantity:
setattr(self, name, attr.value)
elif type(attr) == str:
try:
setattr(self, name, float(attr))
except ValueError:
return "Cannot convert parameter '" + name + "' with value '" + attr + "' to a numeric value."
elif type(attr) == int:
setattr(self, name, float(attr))
else:
return "Expected parameter '" + name + "' to be numeric but got '" + type(attr) + "' instead."