""" This module defines specializations of the general tools in abstract_form,
mostly useful field types.
"""
from re import split
from cascade_at.core.form.abstract_form import (
Form, Field, SimpleTypeField, NO_VALUE)
from cascade_at.core.log import get_loggers
LOG = get_loggers(__name__)
[docs]class BoolField(SimpleTypeField):
def __init__(self, *args, **kwargs):
super().__init__(bool, *args, **kwargs)
[docs]class IntField(SimpleTypeField):
def __init__(self, *args, **kwargs):
super().__init__(int, *args, **kwargs)
[docs]class FloatField(SimpleTypeField):
def __init__(self, *args, **kwargs):
super().__init__(float, *args, **kwargs)
[docs]class StrField(SimpleTypeField):
def __init__(self, *args, **kwargs):
super().__init__(str, *args, **kwargs)
[docs]class NativeListField(SimpleTypeField):
"""Because we already have a ListField for space separated
strings which become lists, this field type should be used
when the .json config returns a native python list."""
def __init__(self, *args, **kwargs):
super().__init__(list, *args, **kwargs)
[docs]class Dummy(Field):
""" A black hole which consumes all values without error. Use to mark
sections of the configuration which have yet to be implemented and should
be ignored.
"""
[docs] def validate_and_normalize(self, instance, root=None):
return []
def process_source(self, source):
pass
def __get__(self, instance, owner):
return NO_VALUE
def __set__(self, instance, value):
pass
def is_unset(self, instance=None):
return True
[docs]class OptionField(SimpleTypeField):
""" A field which will only accept values from a predefined set.
Args:
options (list): The list of options to choose from
constructor: A function which takes a string and returns the expected
type. Behaves as the constructor for SimpleTypeField. Defaults to str
"""
def __init__(self, options, *args, constructor=str, **kwargs):
super().__init__(constructor, *args, **kwargs)
self.options = options
def _validate_and_normalize(self, instance, value):
new_value, error = super()._validate_and_normalize(instance, value)
if error:
return None, error
if new_value not in self.options:
return None, f"Invalid option '{new_value}'"
return new_value, None
[docs]class ListField(SimpleTypeField):
""" A field which takes a string containing values demarcated by some
separator and transforms them into a homogeneous list of items of an
expected type.
Args:
constructor: A function which takes a string and returns the expected
type. Behaves as the constructor for SimpleTypeField. Defaults to str
separator (str): The string to split by. Defaults to a single space.
"""
def __init__(self, *args, constructor=str, separator=" ", **kwargs):
super().__init__(constructor, *args, **kwargs)
self.separator = separator
def _validate_and_normalize(self, instance, values):
errors = []
new_values = []
for item in values:
new_value, error = super()._validate_and_normalize(instance, item)
if error:
errors.append(error)
else:
new_values.append(new_value)
if errors:
return None, "Errors in items: [" + ", ".join(errors) + "]"
return new_values, None
[docs]class StringListField(ListField):
def _validate_and_normalize(self, instance, value):
if isinstance(value, str):
values = [val.strip() for val in split(
rf'{self.separator}+', value.strip())]
else:
# This case hits when there's only a single numerical value in the
# list because Epiviz switches from strings to the actual numerical
# type in that case.
values = [value]
return super()._validate_and_normalize(instance, values)
def _to_dict_value(self, instance=None):
return " ".join([str(v) for v in self.__get__(instance)])