Coverage for python/astro_metadata_translator/translator.py : 59%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# This file is part of astro_metadata_translator. # # Developed for the LSST Data Management System. # This product includes software developed by the LSST Project # (http://www.lsst.org). # See the COPYRIGHT file at the top-level directory of this distribution # for details of code ownership. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Register all subclasses with the base class and create dynamic translator methods.
The metaclass provides two facilities. Firstly, every subclass of `MetadataTranslator` that includes a ``name`` class property is registered as a translator class that could be selected when automatic header translation is attempted. Only name translator subclasses that correspond to a complete instrument. Translation classes providing generic translation support for multiple instrument translators should not be named.
The second feature of this metaclass is to convert simple translations to full translator methods. Sometimes a translation is fixed (for example a specific instrument name should be used) and rather than provide a full ``to_property()`` translation method the mapping can be defined in a class variable named ``_constMap``. Similarly, for one-to-one trivial mappings from a header to a property, ``_trivialMap`` can be defined. Trivial mappings are a dict mapping a generic property to either a header keyword, or a tuple consisting of the header keyword and a dict containing key value pairs suitable for the `MetadataTranslator.quantity_from_card()` method. """
def _make_const_mapping(property_key, constant): """Make a translator method that returns a constant value.
Parameters ---------- property_key : `str` Name of the property to be calculated (for the docstring). constant : `str` or `numbers.Number` Value to return for this translator.
Returns ------- f : `function` Function returning the constant. """ return constant
else:
Returns ------- translation : `{return_type}` Translated property. """
"""Make a translator method returning a header value.
The header value can be converted to a `~astropy.units.Quantity` if desired, and can also have its value validated.
See `MetadataTranslator.validate_value()` for details on the use of default parameters.
Parameters ---------- property_key : `str` Name of the translator to be constructed (for the docstring). header_key : `str` Name of the key to look up in the header. default : `numbers.Number` or `astropy.units.Quantity`, `str`, optional If not `None`, default value to be used if the parameter read from the header is not defined or if the header is missing. minimum : `numbers.Number` or `astropy.units.Quantity`, optional If not `None`, and if ``default`` is not `None`, minimum value acceptable for this parameter. maximum : `numbers.Number` or `astropy.units.Quantity`, optional If not `None`, and if ``default`` is not `None`, maximum value acceptable for this parameter. unit : `astropy.units.Unit`, optional If not `None`, the value read from the header will be converted to a `~astropy.units.Quantity`. Only supported for numeric values.
Returns ------- t : `function` Function implementing a translator with the specified parameters. """ else:
if unit is not None: return self.quantity_from_card(header_key, unit, default=default, minimum=minimum, maximum=maximum) if header_key not in self._header and default is not None: value = default else: value = self._header[header_key] if default is not None and not isinstance(value, str): value = self.validate_value(value, default, minimum=minimum, maximum=maximum) self._used_these_cards(header_key)
# If we know this is meant to be a string, force to a string. # Sometimes headers represent items as integers which generically # we want as strings (eg OBSID). Sometimes also floats are # written as "NaN" strings. if return_type == "str" and not isinstance(value, str): value = str(value) elif return_type == "float" and not isinstance(value, float): value = float(value)
return value
# Docstring inheritance means it is confusing to specify here # exactly which header value is being used.
Returns ------- translation : `{return_type}` Translated value derived from the header. """
# Only register classes with declared names
# Go through the trival mappings for this class and create # corresponding translator methods
# Go through the constant mappings for this class and create # corresponding translator methods
"""Per-instrument metadata translation support
Parameters ---------- header : `dict`-like Representation of an instrument header that can be manipulated as if it was a `dict`. """
"""Dict of one-to-one mappings for header translation from standard property to corresponding keyword."""
"""Dict defining a constant for specified standard properties."""
"""All registered metadata translation classes."""
"""Name of instrument understood by this translation class."""
self._header = header self._used_cards = set()
def can_translate(cls, header): """Indicate whether this translation class can translate the supplied header.
Parameters ---------- header : `dict`-like Header to convert to standardized form.
Returns ------- can : `bool` `True` if the header is recognized by this class. `False` otherwise. """ raise NotImplementedError()
def determine_translator(cls, header): """Determine a translation class by examining the header
Parameters ---------- header : `dict`-like Representation of a header.
Returns ------- translator : `MetadataTranslator` Translation class that knows how to extract metadata from the supplied header.
Raises ------ ValueError None of the registered translation classes understood the supplied header. """ for name, trans in cls.translators.items(): if trans.can_translate(header): log.debug(f"Using translation class {name}") return trans else: raise ValueError("None of the registered translation classes understood this header")
"""Indicate that the supplied cards have been used for translation.
Parameters ---------- args : sequence of `str` Keywords used to process a translation. """ self._used_cards.update(set(args))
"""Cards used during metadata extraction.
Returns ------- used : `frozenset` of `str` Cards used when extracting metadata. """ return frozenset(self._used_cards)
"""Validate the supplied value, returning a new value if out of range
Parameters ---------- value : `float` Value to be validated. default : `float` Default value to use if supplied value is invalid or out of range. Assumed to be in the same units as the value expected in the header. minimum : `float` Minimum possible valid value, optional. If the calculated value is below this value, the default value will be used. maximum : `float` Maximum possible valid value, optional. If the calculated value is above this value, the default value will be used.
Returns ------- value : `float` Either the supplied value, or a default value. """ if value is None or math.isnan(value): value = default else: if minimum is not None and value < minimum: value = default elif maximum is not None and value > maximum: value = default return value
"""Calculate a Astropy Quantity from a header card and a unit.
Parameters ---------- keyword : `str` Keyword to use from header. unit : `astropy.units.UnitBase` Unit of the item in the header. default : `float`, optional Default value to use if the header value is invalid. Assumed to be in the same units as the value expected in the header. If None, no default value is used. minimum : `float` Minimum possible valid value, optional. If the calculated value is below this value, the default value will be used. maximum : `float` Maximum possible valid value, optional. If the calculated value is above this value, the default value will be used.
Returns ------- q : `astropy.units.Quantity` Quantity representing the header value. """ value = self._header[keyword] if isinstance(value, str): # Sometimes the header has the wrong type in it but this must # be a number if we are creating a quantity. value = float(value) self._used_these_cards(keyword) if default is not None: value = self.validate_value(value, default, maximum=maximum, minimum=minimum) return u.Quantity(value, unit=unit)
"""Create a an abstract translation method for this property.
Parameters ---------- property : `str` Name of the translator for property to be created. doc : `str` Description of the property. return_type : `str` Type of this property (used in the doc string).
Returns ------- m : `function` Translator method for this property. """ raise NotImplementedError(f"Translator for '{property}' undefined.")
{doc}
Returns ------- {property} : `{return_type}` The translated property. """
# Make abstract methods for all the translators methods. # Unfortunately registering them as abstractmethods does not work # as these assignments come after the class has been created. # Assigning to __abstractmethods__ directly does work but interacts # poorly with the metaclass automatically generating methods from # _trivialMap and _constMap.
abstractmethod(_make_abstract_translator_method(name, *description)))
"""Translator where all the translations are stubbed out and issue warnings.
This translator can be used as a base class whilst developing a new translator. It allows testing to proceed without being required to fully define all translation methods. Once complete the class should be removed from the inheritance tree.
"""
"""Create a an stub translation method for this property.
Parameters ---------- property : `str` Name of the translator for property to be created. doc : `str` Description of the property. return_type : `str` Type of this property (used in the doc string).
Returns ------- m : `function` Stub translator method for this property. """ warnings.warn(f"Please implement translator for property '{property}' for translator {self}", stacklevel=3) return None
{doc}
Issues a warning reminding the implementer to override this method.
Returns ------- {property} : `None` Always returns `None`. """
# Create stub translation methods |