Source code for lsst.daf.persistence.mapper

#!/usr/bin/env python

#
# LSST Data Management System
# Copyright 2008, 2009, 2010 LSST Corporation.
#
# This product includes software developed by the
# LSST Project (http://www.lsst.org/).
#
# 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 LSST License Statement and
# the GNU General Public License along with this program.  If not,
# see <http://www.lsstcorp.org/LegalNotices/>.
#
from builtins import object, super


from . import Policy

"""This module defines the Mapper base class."""


class Mapper(object):
    """Mapper is a base class for all mappers.

    Subclasses may define the following methods:

    map_{datasetType}(self, dataId, write)
        Map a dataset id for the given dataset type into a ButlerLocation.
        If write=True, this mapping is for an output dataset.

    query_{datasetType}(self, key, format, dataId)
        Return the possible values for the format fields that would produce
        datasets at the granularity of key in combination with the provided
        partial dataId.

    std_{datasetType}(self, item)
        Standardize an object of the given data set type.

    Methods that must be overridden:

    keys(self)
        Return a list of the keys that can be used in data ids.

    Other public methods:

    __init__(self)

    getDatasetTypes(self)

    map(self, datasetType, dataId, write=False)

    queryMetadata(self, datasetType, key, format, dataId)

    canStandardize(self, datasetType)

    standardize(self, datasetType, item, dataId)

    validate(self, dataId)
    """

    @staticmethod
    def Mapper(cfg):
        '''Instantiate a Mapper from a configuration.
        In come cases the cfg may have already been instantiated into a Mapper, this is allowed and
        the input var is simply returned.

        :param cfg: the cfg for this mapper. It is recommended this be created by calling
                    Mapper.cfg()
        :return: a Mapper instance
        '''
        if isinstance(cfg, Policy):
            return cfg['cls'](cfg)
        return cfg

    def __new__(cls, *args, **kwargs):
        """Create a new Mapper, saving arguments for pickling.

        This is in __new__ instead of __init__ to save the user
        from having to save the arguments themselves (either explicitly,
        or by calling the super's __init__ with all their
        *args,**kwargs.  The resulting pickling system (of __new__,
        __getstate__ and __setstate__ is similar to how __reduce__
        is usually used, except that we save the user from any
        responsibility (except when overriding __new__, but that
        is not common).
        """
        self = super().__new__(cls)
        self._arguments = (args, kwargs)
        return self

    def __init__(self, **kwargs):
        pass

    def __getstate__(self):
        return self._arguments

    def __setstate__(self, state):
        self._arguments = state
        args, kwargs = state
        self.__init__(*args, **kwargs)

    def keys(self):
        raise NotImplementedError("keys() unimplemented")

    def queryMetadata(self, datasetType, format, dataId):
        """Get possible values for keys given a partial data id.

        :param datasetType: see documentation about the use of datasetType
        :param key: this is used as the 'level' parameter
        :param format:
        :param dataId: see documentation about the use of dataId
        :return:
        """
        func = getattr(self, 'query_' + datasetType)

        val = func(format, self.validate(dataId))
        return val

    def getDatasetTypes(self):
        """Return a list of the mappable dataset types."""

        list = []
        for attr in dir(self):
            if attr.startswith("map_"):
                list.append(attr[4:])
        return list

    def map(self, datasetType, dataId, write=False):
        """Map a data id using the mapping method for its dataset type.

        Parameters
        ----------
        datasetType : string
            The datasetType to map
        dataId : DataId instance
            The dataId to use when mapping
        write : bool, optional
            Indicates if the map is being performed for a read operation
            (False) or a write operation (True)

        Returns
        -------
        ButlerLocation or a list of ButlerLocation
            The location(s) found for the map operation. If write is True, a
            list is returned. If write is False a single ButlerLocation is
            returned.

        Raises
        ------
        NoResults
            If no locaiton was found for this map operation, the derived mapper
            class may raise a lsst.daf.persistence.NoResults exception. Butler
            catches this and will look in the next Repository if there is one.
        """
        func = getattr(self, 'map_' + datasetType)
        return func(self.validate(dataId), write)

    def canStandardize(self, datasetType):
        """Return true if this mapper can standardize an object of the given
        dataset type."""

        return hasattr(self, 'std_' + datasetType)

    def standardize(self, datasetType, item, dataId):
        """Standardize an object using the standardization method for its data
        set type, if it exists."""

        if hasattr(self, 'std_' + datasetType):
            func = getattr(self, 'std_' + datasetType)
            return func(item, self.validate(dataId))
        return item

    def validate(self, dataId):
        """Validate a dataId's contents.

        If the dataId is valid, return it.  If an invalid component can be
        transformed into a valid one, copy the dataId, fix the component, and
        return the copy.  Otherwise, raise an exception."""

        return dataId

    def backup(self, datasetType, dataId):
        """Rename any existing object with the given type and dataId.

        Not implemented in the base mapper.
        """
        raise NotImplementedError("Base-class Mapper does not implement backups")

    def getRegistry(self):
        """Get the registry"""
        return None