From 7136f98a69c87e614389f15cda224b3698f5da97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrianna=20Pi=C5=84ska?= Date: Wed, 19 Apr 2023 17:39:05 +0200 Subject: [PATCH 1/3] Added validator for checking a type; fixed logic and description of iterable validator; simplified JSON serializer logic --- carta/util.py | 10 ++++++--- carta/validation.py | 52 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/carta/util.py b/carta/util.py index 1f176de..7d31bd2 100644 --- a/carta/util.py +++ b/carta/util.py @@ -85,14 +85,18 @@ def __init__(self, target, variable): def __repr__(self): return f"Macro('{self.target}', '{self.variable}')" + def json(self): + """The JSON serialization of this object.""" + return {"macroTarget": self.target, "macroVariable": self.variable} + class CartaEncoder(json.JSONEncoder): - """A custom encoder to JSON which correctly serialises :obj:`carta.util.Macro` objects and numpy arrays.""" + """A custom encoder to JSON which correctly serialises custom objects with a ``json`` method, and numpy arrays.""" def default(self, obj): """ This method is overridden from the parent class and performs the substitution.""" - if isinstance(obj, Macro): - return {"macroTarget": obj.target, "macroVariable": obj.variable} + if hasattr(obj, "json") and callable(obj.json): + return obj.json() if type(obj).__module__ == "numpy" and type(obj).__name__ == "ndarray": # The condition is a workaround to avoid importing numpy return obj.tolist() diff --git a/carta/validation.py b/carta/validation.py index d77f19d..eede55c 100644 --- a/carta/validation.py +++ b/carta/validation.py @@ -43,6 +43,47 @@ def description(self): return "UNKNOWN" +class InstanceOf(Parameter): + """A parameter which is an instance of the provided type or tuple of types. + + This validator uses ``isinstance``, and has the same behaviour. An instance of a child class is also an instance of a parent class. + + Parameters + ---------- + types : type or tuple of types + """ + + def __init__(self, types): + if not isinstance(types, tuple): + types = (types,) + self.types = types + + def validate(self, value, parent): + """Check if the value is an instance of the provided type or types. + + See :obj:`carta.validation.Parameter.validate` for general information about this method. + """ + if not isinstance(value, self.types): + raise TypeError(f"{value} has type {type(value)} but {self.description} was expected.") + + @property + def description(self): + """A human-readable description of this parameter descriptor. + + Returns + ------- + string + The description. + """ + names = [t.__name__ for t in self.types] + + if len(names) == 1: + return f"an instance of {names[0]}" + + names = names[:-2] + [" or ".join(names[-2:])] + return f"an instance of {', '.join(names)}" + + class String(Parameter): """A string parameter. @@ -443,8 +484,13 @@ def validate(self, value, parent): See :obj:`carta.validation.Parameter.validate` for general information about this method. """ - for v in value: - self.param.validate(v, parent) + try: + for v in value: + self.param.validate(v, parent) + except TypeError as e: + if str(e).endswith("object is not iterable"): + raise ValueError(f"{value} is not iterable, but {self.description} was expected.") + raise e @property def description(self): @@ -455,7 +501,7 @@ def description(self): string The description. """ - return f"an iterable of {self.param.description}" + return f"an iterable in which each element is {self.param.description}" COLORNAMES = ('aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgrey', 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'grey', 'green', 'greenyellow', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgrey', 'lightgreen', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen') From a4a5d9e741b17b74c316692eafbed1ce297729bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrianna=20Pi=C5=84ska?= Date: Wed, 19 Apr 2023 21:39:53 +0200 Subject: [PATCH 2/3] removed unused logger import --- carta/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/carta/image.py b/carta/image.py index 9e8fba1..f5ade06 100644 --- a/carta/image.py +++ b/carta/image.py @@ -5,7 +5,7 @@ import posixpath from .constants import Colormap, Scaling, SmoothingMode, ContourDashMode, Polarization -from .util import logger, Macro, cached +from .util import Macro, cached from .validation import validate, Number, Color, Constant, Boolean, NoneOr, IterableOf, Evaluate, Attr, Attrs, OneOf From d2fbc6c011de0ea52f9c7a645971f95aa994d093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrianna=20Pi=C5=84ska?= Date: Wed, 19 Apr 2023 21:40:42 +0200 Subject: [PATCH 3/3] added optional size bounds to IterableOf --- carta/validation.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/carta/validation.py b/carta/validation.py index eede55c..f29f045 100644 --- a/carta/validation.py +++ b/carta/validation.py @@ -469,21 +469,32 @@ class IterableOf(Parameter): ---------- param : :obj:`carta.validation.Parameter` The parameter descriptor. + min_size : integer, optional + The minimum size. + max_size : integer, optional + The maximum size. Attributes ---------- param : :obj:`carta.validation.Parameter` The parameter descriptor. + min_size : integer, optional + The minimum size. + max_size : integer, optional + The maximum size. """ - def __init__(self, param): + def __init__(self, param, min_size=None, max_size=None): self.param = param + self.min_size = min_size + self.max_size = max_size def validate(self, value, parent): """Check if each element of the iterable can be validated with the given descriptor. See :obj:`carta.validation.Parameter.validate` for general information about this method. """ + try: for v in value: self.param.validate(v, parent) @@ -492,6 +503,14 @@ def validate(self, value, parent): raise ValueError(f"{value} is not iterable, but {self.description} was expected.") raise e + if self.min_size is not None: + if len(value) < self.min_size: + raise ValueError(f"{value} has {len(value)} elements, but must have at least {self.min_size}.") + + if self.max_size is not None: + if len(value) > self.max_size: + raise ValueError(f"{value} has {len(value)} elements, but may have at most {self.max_size}.") + @property def description(self): """A human-readable description of this parameter descriptor. @@ -501,7 +520,17 @@ def description(self): string The description. """ - return f"an iterable in which each element is {self.param.description}" + size = [] + size_desc = "" + + if self.min_size is not None: + size.append(f"at least {self.min_size} elements") + if self.max_size is not None: + size.append(f"at most {self.max_size} elements") + + if size: + size_desc = f"with {' and '.join(size)} " + return f"an iterable {size_desc}in which each element is {self.param.description}" COLORNAMES = ('aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgrey', 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'grey', 'green', 'greenyellow', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgrey', 'lightgreen', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen')