Skip to content

Commit

Permalink
Merge pull request #640 from oda-hub/structured-parameter
Browse files Browse the repository at this point in the history
StructuredParameter and PhosphorosTableParameter
  • Loading branch information
dsavchenko authored Jan 10, 2024
2 parents 420e273 + db727fc commit 7257b06
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 7 deletions.
88 changes: 87 additions & 1 deletion cdci_data_analysis/analysis/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
from inspect import signature
from .exceptions import RequestNotUnderstood

from jsonschema import validate, ValidationError

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -454,8 +456,17 @@ def reprJSONifiable(self):
restrictions['min_value'] = self._min_value
if getattr(self, '_max_value', None) is not None:
restrictions['max_value'] = self._max_value
if getattr(self, 'schema', None) is not None:
restrictions['schema'] = self.schema
if restrictions:
reprjson[0]['restrictions'] = restrictions
if getattr(self, 'owl_uris', None):
if isinstance(self.owl_uris, str):
reprjson[0]['owl_uri'] = [ self.owl_uris ]
elif isinstance(self.owl_uris, tuple):
reprjson[0]['owl_uri'] = list(self.owl_uris)
else:
reprjson[0]['owl_uri'] = self.owl_uris
if self.par_format_name is not None:
reprjson.append(dict(name=self.par_format_name, units="str", value=self.par_format))
return reprjson
Expand Down Expand Up @@ -964,4 +975,79 @@ def value(self, v):
elif v in self._true_rep:
self._value = True
else:
raise RequestNotUnderstood(f'Wrong value for boolean parameter {self.name}')
raise RequestNotUnderstood(f'Wrong value for boolean parameter {self.name}')

class StructuredParameter(Parameter):
owl_uris = ("http://odahub.io/ontology#StructuredParameter")

def __init__(self, value=None, name=None, schema=None):

self.schema = schema

if self.schema is None:
logger.warning("Parameter %s: Schema is not defined, will allow any structure.", name)

super().__init__(value=value,
name=name)

def check_schema(self):
if self.schema is not None:
validate(self._value, self.schema)

def additional_check(self):
# should raise AssertionError if wrong
pass

def check_value(self):
try:
self.check_schema()
self.additional_check()
except (AssertionError, ValidationError):
raise RequestNotUnderstood('Wrong value of structured parameter %s', self.name)


class PhosphorosFiltersTable(StructuredParameter):
owl_uris = ('http://odahub.io/ontology#PhosphorosFiltersTable')

def __init__(self, value=None, name=None):

# TODO: either list or the whole schema may be loaded from the external file, purely based on URI.
# If there is no additional check, this would allow to avoid even having the class.
#
# But for the time being, as agreed, we will keep the hardcoded dedicated class.
filter_list = ["CFHT|MegaCam.g", "CFHT|MegaCam.gri", "CFHT|MegaCam.i", "CFHT|MegaCam.r", "CFHT|MegaCam.u", "CFHT|MegaCam.z",
"CTIO|DECam.Y", "CTIO|DECam.g", "CTIO|DECam.i", "CTIO|DECam.r", "CTIO|DECam.u", "CTIO|DECam.z",
"Euclid|NISP.H", "Euclid|NISP.J", "Euclid|NISP.Y", "Euclid|VIS.vis",
"GAIA|GAIA3.G", "GAIA|GAIA3.Gbp", "GAIA|GAIA3.Grp",
"GALEX|GALEX.FUV", "GALEX|GALEX.NUV",
"LSST|LSST.g", "LSST|LSST.i", "LSST|LSST.r", "LSST|LSST.u", "LSST|LSST.y", "LSST|LSST.z",
"PAN-STARRS|PS1.g", "PAN-STARRS|PS1.i", "PAN-STARRS|PS1.open", "PAN-STARRS|PS1.r", "PAN-STARRS|PS1.w", "PAN-STARRS|PS1.y", "PAN-STARRS|PS1.z",
"VIRCAM|VISTA.H", "VIRCAM|VISTA.J", "VIRCAM|VISTA.Ks", "VIRCAM|VISTA.NB118", "VIRCAM|VISTA.NB980", "VIRCAM|VISTA.NB990", "VIRCAM|VISTA.Y", "VIRCAM|VISTA.Z",
"SLOAN|SDSS.g", "SLOAN|SDSS.i", "SLOAN|SDSS.r", "SLOAN|SDSS.u", "SLOAN|SDSS.z",
"Subaru|HSC.Y", "Subaru|HSC.g", "Subaru|HSC.i", "Subaru|HSC.r", "Subaru|HSC.z",
"UKIRT|WFCAM.H", "UKIRT|WFCAM.J", "UKIRT|WFCAM.K", "UKIRT|WFCAM.Y", "UKIRT|WFCAM.Z",
"WISE|WISE.W1", "WISE|WISE.W2", "WISE|WISE.W3", "WISE|WISE.W4"]

schema = {"type": "object",
"properties": {
"filter": {"type": "array",
"minItems": 1,
"uniqueItems": True,
"items": {"enum": filter_list}},
"flux": {"type": "array",
"minItems": 1,
"uniqueItems": True,
"items": {"type": "string", "minLength": 1}},
"flux_error": {"type": "array",
"minItems": 1,
"uniqueItems": True,
"items": {"type": "string", "minLength": 1}}},
"additionalProperties": False,
"required": ["filter", "flux", "flux_error"]
}

super().__init__(value=value, name=name, schema=schema)

def additional_check(self):
assert len(self._value['filter']) == len(self._value['flux']) == len(self._value['flux_error'])

3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"nbformat",
"giturlparse",
"sentry-sdk",
"validators"
"validators",
"jsonschema"
]

test_req = [
Expand Down
68 changes: 63 additions & 5 deletions tests/test_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
InputProdList,
DetectionThreshold,
String,
Boolean
Boolean,
PhosphorosFiltersTable
)
from cdci_data_analysis.analysis.exceptions import RequestNotUnderstood

Expand Down Expand Up @@ -354,16 +355,19 @@ def test_parameter_meta_data():
bool_parameter = Boolean(value = True, name = 'bool')
assert bounded_parameter.reprJSONifiable() == [{'name': 'bounded',
'units': None, 'value': 1.0,
'restrictions': {'min_value': 0.1, 'max_value': 2.0}}]
'restrictions': {'min_value': 0.1, 'max_value': 2.0},
'owl_uri': ["http://www.w3.org/2001/XMLSchema#float", "http://odahub.io/ontology#Float"]}]
assert choice_parameter.reprJSONifiable() == [{'name': 'choice',
'units': 'str',
'value': 'spam',
'restrictions': {'allowed_values': ['spam', 'eggs', 'hams']}}]
'restrictions': {'allowed_values': ['spam', 'eggs', 'hams']},
'owl_uri': ["http://www.w3.org/2001/XMLSchema#str", "http://odahub.io/ontology#String"]}]
assert bool_parameter.reprJSONifiable() == [{'name': 'bool',
'units': None,
'value': True,
'restrictions': {'allowed_values': ['True', 'true', 'yes', '1', True,
'False', 'false', 'no', '0', False]}}]
'False', 'false', 'no', '0', False]},
'owl_uri': ["http://www.w3.org/2001/XMLSchema#bool","http://odahub.io/ontology#Boolean"]}]

@pytest.mark.fast
@pytest.mark.parametrize('inval, iswrong, expected',
Expand Down Expand Up @@ -589,4 +593,58 @@ def test_time_interval_bounds():
ti = TimeInterval(value=3, units='s', min_value = 1, max_value=5)
assert ti.value == 3
with pytest.raises(RequestNotUnderstood):
TimeInterval(value=10, units='s', min_value = 1, max_value=5)
TimeInterval(value=10, units='s', min_value = 1, max_value=5)

@pytest.mark.fast
def test_valid_phosphoros_table():
tab_value = {'filter': ["Euclid|VIS.vis", "Euclid|NISP.Y"],
'flux': ["FLUX_DETECTION_TOTAL", "FLUX_Y_TOTAL"],
'flux_error': ["FLUXERR_DETECTION_TOTAL", "FLUXERR_Y_TOTAL"]}

pht = PhosphorosFiltersTable(tab_value)

assert pht.value == tab_value

@pytest.mark.fast
@pytest.mark.parametrize("tab_value", [
# missing value
{'filter': ["Euclid|VIS.vis", "Euclid|NISP.Y"],
'flux': ["FLUX_DETECTION_TOTAL", "FLUX_Y_TOTAL"],
'flux_error': ["FLUXERR_DETECTION_TOTAL"]},
# empty value
{'filter': ["Euclid|VIS.vis", "Euclid|NISP.Y"],
'flux': ["FLUX_DETECTION_TOTAL", "FLUX_Y_TOTAL"],
'flux_error': ["FLUXERR_DETECTION_TOTAL", ""]},
# None value
{'filter': ["Euclid|VIS.vis", "Euclid|NISP.Y"],
'flux': ["FLUX_DETECTION_TOTAL", None],
'flux_error': ["FLUXERR_DETECTION_TOTAL", "FLUXERR_Y_TOTAL"]},
# Wrong format
{'filter': ["Euclid|VIS.vis", "Euclid|NISP.Y"],
'flux': [463, "FLUX_Y_TOTAL"],
'flux_error': ["FLUXERR_DETECTION_TOTAL", "FLUXERR_Y_TOTAL"]},
# unknown filter name
{'filter': ["Euclid|VIS.vis", "UNKNOWN"],
'flux': ["FLUX_DETECTION_TOTAL", "FLUX_Y_TOTAL"],
'flux_error': ["FLUXERR_DETECTION_TOTAL", "FLUXERR_Y_TOTAL"]},
# missing column
{'filter': ["Euclid|VIS.vis", "Euclid|NISP.Y"],
'flux': ["FLUX_DETECTION_TOTAL", "FLUX_Y_TOTAL"],
},
# Empty table
{'filter': [],
'flux': [],
'flux_error': []},
])
def test_invalid_phosphoros_table(tab_value):
with pytest.raises(RequestNotUnderstood):
PhosphorosFiltersTable(tab_value)


0 comments on commit 7257b06

Please sign in to comment.