diff --git a/CHANGES.rst b/CHANGES.rst index 70cdfb1b..51a17b73 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,8 @@ Enhancements and Fixes ---------------------- +- Support the flat representation of the MANGO observation dates (year, mjd, jd, iso) [#726] + - Add DEFAULT_JOB_POLL_TIMEOUT constant [#721] - Pass session through to DatalinkService requests (#715) @@ -12,7 +14,7 @@ Enhancements and Fixes - Provide an API for SoftId-compliant management of the 'User-Agent' header [#719] - + Deprecations and Removals ------------------------- diff --git a/pyvo/mivot/features/sky_coord_builder.py b/pyvo/mivot/features/sky_coord_builder.py index c9713c72..2fa20308 100644 --- a/pyvo/mivot/features/sky_coord_builder.py +++ b/pyvo/mivot/features/sky_coord_builder.py @@ -89,17 +89,12 @@ def _get_time_instance(self, hk_field, besselian=False): ----- MappingError: if the Time instance cannot be built for some reason """ - # Process complex type "mango:DateTime" - if hk_field['dmtype'] == "mango:DateTime": - representation = hk_field['representation']['value'] - timestamp = hk_field['dateTime']['value'] - # Process complex type "coords:epoch" used for the space frame equinox - elif hk_field['dmtype'] == "coords:Epoch": + if hk_field['dmtype'] == "coords:Epoch": representation = 'yr' if "unit" not in hk_field else hk_field.get("unit") timestamp = hk_field['value'] # Process simple attribute else: - representation = hk_field.get("unit") + representation = hk_field.get("dmtype") timestamp = hk_field.get("value") if not representation or not timestamp: @@ -124,15 +119,16 @@ def _build_time_instance(self, timestamp, representation, besselian=False): timestamp: string or number The timestamp must comply with the given representation representation: string - year, iso, ... (See MANGO primitive types derived from ivoa:timeStamp) + mango:year, mango:iso, mango:mjd, mango:jd + (See MANGO primitive types derived from ivoa:timeStamp) besselian: boolean (optional) Flag telling to use the besselain calendar. We assume it to only be - relevant for FK5 frame + relevant for FK4 frame returns ------- Time instance or None """ - if representation in ("year", "yr", "y"): + if representation in ("mango:year", "yr", "y"): # it the timestamp is numeric, we infer its format from the besselian flag if isinstance(timestamp, numbers.Number): return Time(f"{('B' if besselian else 'J')}{timestamp}", @@ -159,10 +155,12 @@ def _build_time_instance(self, timestamp, representation, besselian=False): return None # in the following cases, the calendar (B or J) is given by the besselian flag # We force to use the string representation to avoid breaking unit tests. - elif representation in ("mjd", "jd", "iso"): - time = Time(f"{timestamp}", format=representation) + elif representation in ("mango:mjd", "mango:jd", "mango:iso"): + astropyformat = representation.split(":")[1] + time = Time(f"{timestamp}", format=astropyformat) return (Time(time.byear_str) if besselian else time) + print("================= 4") return None def _get_space_frame(self): diff --git a/pyvo/mivot/tests/data/reference/mango_object.xml b/pyvo/mivot/tests/data/reference/mango_object.xml index 8055aa3a..6fcf5cdc 100644 --- a/pyvo/mivot/tests/data/reference/mango_object.xml +++ b/pyvo/mivot/tests/data/reference/mango_object.xml @@ -186,10 +186,7 @@ - - - - + diff --git a/pyvo/mivot/tests/data/simbad-cone-mivot.xml b/pyvo/mivot/tests/data/simbad-cone-mivot.xml index b0987713..a2a11658 100644 --- a/pyvo/mivot/tests/data/simbad-cone-mivot.xml +++ b/pyvo/mivot/tests/data/simbad-cone-mivot.xml @@ -41,10 +41,7 @@ - - - - + diff --git a/pyvo/mivot/tests/test_mango_annoter.py b/pyvo/mivot/tests/test_mango_annoter.py index 6c5c38c7..58477f49 100644 --- a/pyvo/mivot/tests/test_mango_annoter.py +++ b/pyvo/mivot/tests/test_mango_annoter.py @@ -15,6 +15,7 @@ from pyvo.mivot.version_checker import check_astropy_version from pyvo.mivot.utils.xml_utils import XmlUtils from pyvo.mivot.writer.instances_from_models import InstancesFromModels +from pyvo.mivot.utils.exceptions import MappingError # Enable MIVOT-specific features in the pyvo library @@ -70,6 +71,17 @@ def add_color(builder): builder.add_mango_color(filter_ids=filter_ids, mapping=mapping, semantics=semantics) +@pytest.mark.filterwarnings("ignore:root:::") +def add_color_without_definition(builder): + + filter_ids = {"high": "GAIA/GAIA3.Grp/AB", "low": "GAIA/GAIA3.Grvs/AB"} + mapping = {"value": 8.76, + "error": {"class": "PErrorAsym1D", "low": 1, "high": 3} + } + semantics = {"description": "very nice color", "uri": "vocabulary#term", "label": "term"} + builder.add_mango_color(filter_ids=filter_ids, mapping=mapping, semantics=semantics) + + @pytest.mark.filterwarnings("ignore:root:::") def add_photometry(builder): photcal_id = "GAIA/GAIA3.Grvs/AB" @@ -82,13 +94,13 @@ def add_photometry(builder): builder.add_mango_brightness(photcal_id=photcal_id, mapping=mapping, semantics=semantics) -def add_epoch_positon(builder): +def add_epoch_position(builder): frames = {"spaceSys": {"spaceRefFrame": "ICRS", "refPosition": 'BARYCENTER', "equinox": None}, "timeSys": {"timescale": "TCB", "refPosition": 'BARYCENTER'}} mapping = {"longitude": "_RAJ2000", "latitude": "_DEJ2000", "pmLongitude": "pmRA", "pmLatitude": "pmDE", "parallax": "Plx", "radialVelocity": "RV", - "obsDate": {"representation": "mjd", "dateTime": 579887.6}, + "obsDate": {"dmtype": "mango:mjd", "value": 579887.6}, "correlations": {"isCovariance": True, "longitudeLatitude": "RADEcor", "latitudePmLongitude": "DEpmRAcor", "latitudePmLatitude": "DEpmDEcor", @@ -109,6 +121,19 @@ def add_epoch_positon(builder): builder.add_mango_epoch_position(frames=frames, mapping=mapping, semantics=semantics) +def add_epoch_position_with_wrong_date(builder): + frames = {"spaceSys": {"spaceRefFrame": "ICRS", "refPosition": 'BARYCENTER', "equinox": None}, + "timeSys": {"timescale": "TCB", "refPosition": 'BARYCENTER'}} + mapping = {"longitude": "_RAJ2000", "latitude": "_DEJ2000", + "obsDate": {"representation": "mango:mjd", "value": 579887.6}, + } + semantics = {"description": "6 parameters position", + "uri": "https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#astronomical-location", + "label": "Astronomical location"} + + builder.add_mango_epoch_position(frames=frames, mapping=mapping, semantics=semantics) + + @pytest.mark.usefixtures("mocked_fps_grvs", "mocked_fps_grp") @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_all_properties(): @@ -151,12 +176,19 @@ def test_all_properties(): }) add_color(builder) add_photometry(builder) - add_epoch_positon(builder) + add_epoch_position(builder) builder.pack_into_votable(schema_check=False) assert XmlUtils.strip_xml(builder._annotation.mivot_block) == ( XmlUtils.strip_xml(get_pkg_data_contents("data/reference/mango_object.xml")) ) + builder = InstancesFromModels(votable, dmid="DR3Name") + with pytest.raises(MappingError): + add_epoch_position_with_wrong_date(builder) + + with pytest.raises(MappingError): + add_color_without_definition(builder) + @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_extraction_from_votable_header(): diff --git a/pyvo/mivot/tests/test_sky_coord_builder.py b/pyvo/mivot/tests/test_sky_coord_builder.py index f9bf98bd..7687a55c 100644 --- a/pyvo/mivot/tests/test_sky_coord_builder.py +++ b/pyvo/mivot/tests/test_sky_coord_builder.py @@ -47,7 +47,7 @@ "ref": "pmDE", }, "obsDate": { - "dmtype": "ivoa:RealQuantity", + "dmtype": "mango:year", "value": 1991.25, "unit": "yr", "ref": None, @@ -100,7 +100,7 @@ "ref": "parallax", }, "obsDate": { - "dmtype": "ivoa:RealQuantity", + "dmtype": "mango:year", "value": 1991.25, "unit": "yr", "ref": None, @@ -292,13 +292,13 @@ def test_time_representation(): """ # work with a copy to not alter other test functions mydict = deepcopy(vizier_equin_dict) - mydict["obsDate"]["unit"] = "mjd" + mydict["obsDate"]["dmtype"] = "mango:mjd" mivot_instance = MivotInstance(**mydict) scb = SkyCoordBuilder(mivot_instance) scoo = scb.build_sky_coord() assert scoo.obstime.jyear_str == "J1864.331" - mydict["obsDate"]["unit"] = "jd" + mydict["obsDate"]["dmtype"] = "mango:jd" mydict["obsDate"]["value"] = "2460937.36" mivot_instance = MivotInstance(**mydict) scb = SkyCoordBuilder(mivot_instance) @@ -306,10 +306,33 @@ def test_time_representation(): assert scoo.obstime.jyear_str == "J2025.715" mydict = deepcopy(vizier_equin_dict) - mydict["obsDate"]["unit"] = "iso" - mydict["obsDate"]["dmtype"] = "ivoa:string" + mydict["obsDate"]["dmtype"] = "mango:iso" mydict["obsDate"]["value"] = "2025-05-03" mivot_instance = MivotInstance(**mydict) scb = SkyCoordBuilder(mivot_instance) scoo = scb.build_sky_coord() assert scoo.obstime.jyear_str == "J2025.335" + + mydict = deepcopy(vizier_equin_dict) + mydict["obsDate"]["dmtype"] = "mango:year" + mydict["obsDate"]["value"] = "B356" + mivot_instance = MivotInstance(**mydict) + scb = SkyCoordBuilder(mivot_instance) + with pytest.raises(MappingError): + scb.build_sky_coord() + + mydict = deepcopy(vizier_equin_dict) + mydict["obsDate"]["dmtype"] = "mango:year" + mydict["obsDate"]["value"] = "turlutu" + mivot_instance = MivotInstance(**mydict) + scb = SkyCoordBuilder(mivot_instance) + with pytest.raises(MappingError): + scb.build_sky_coord() + + mydict = deepcopy(vizier_equin_dict) + mydict["obsDate"]["dmtype"] = "turlututu" + mydict["obsDate"]["value"] = "turlututu" + mivot_instance = MivotInstance(**mydict) + scb = SkyCoordBuilder(mivot_instance) + with pytest.raises(MappingError): + scb.build_sky_coord() diff --git a/pyvo/mivot/writer/mango_object.py b/pyvo/mivot/writer/mango_object.py index 617cb0ac..423ba947 100644 --- a/pyvo/mivot/writer/mango_object.py +++ b/pyvo/mivot/writer/mango_object.py @@ -7,7 +7,7 @@ from pyvo.mivot.utils.mivot_utils import MivotUtils from pyvo.mivot.writer.instance import MivotInstance from pyvo.mivot.glossary import ( - IvoaType, ModelPrefix, Roles, CoordSystems) + IvoaType, ModelPrefix, Roles) class Property(MivotInstance): @@ -127,36 +127,6 @@ def _add_epoch_position_errors(self, **errors): mapping)) return err_instance - def _add_epoch_position_epoch(self, **mapping): - """ - Private method building and returning the observation date (DateTime) of the EpohPosition. - - Parameters - ---------- - mapping: dict(representation, datetime) - Mapping of the DateTime fields - - Returns - ------- - `Property` - The EpochPosition observation date instance - """ - datetime_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:DateTime", - dmrole=f"{ModelPrefix.mango}:EpochPosition.obsDate") - - representation = mapping.get("representation") - value = mapping["dateTime"] - if representation not in CoordSystems.time_formats: - raise MappingError(f"epoch representation {representation} not supported. " - f"Take on of {CoordSystems.time_formats}") - datetime_instance.add_attribute(IvoaType.string, - f"{ModelPrefix.mango}:DateTime.representation", - value=MivotUtils.as_literal(representation)) - datetime_instance.add_attribute(IvoaType.datetime, - f"{ModelPrefix.mango}:DateTime.dateTime", - value=value) - return datetime_instance - def add_epoch_position(self, space_frame_id, time_frame_id, mapping, semantics): """ Add an ``EpochPosition`` instance to the properties of the current ``MangoObject``. @@ -184,7 +154,11 @@ def add_epoch_position(self, space_frame_id, time_frame_id, mapping, semantics): MivotUtils.populate_instance(ep_instance, "EpochPosition", mapping, self._table, IvoaType.RealQuantity) if "obsDate" in mapping: - ep_instance.add_instance(self._add_epoch_position_epoch(**mapping["obsDate"])) + if "dmtype" not in mapping["obsDate"] or "value" not in mapping["obsDate"]: + raise MappingError("obsDate requires both 'dmtype' and 'value' keys") + ep_instance.add_attribute(dmtype=mapping["obsDate"]["dmtype"], + dmrole="mango:EpochPosition.obsDate", + value=mapping["obsDate"]["value"]) if "correlations" in mapping: ep_instance.add_instance(self._add_epoch_position_correlations(**mapping["correlations"])) if "errors" in mapping: