Skip to content

Commit

Permalink
Merge pull request #11 from lsst/tickets/DM-13349
Browse files Browse the repository at this point in the history
DM-13349: Define storage classes in config yaml
  • Loading branch information
timj committed Feb 1, 2018
2 parents 6b10ba1 + 68f27cc commit 02c2778
Show file tree
Hide file tree
Showing 17 changed files with 841 additions and 114 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
*.pyc
version.py
_build.*
.cache
tests/.tests
tests/test_input_datastore/
Expand All @@ -8,3 +9,4 @@ tests/test_output_datastore/
.sconsign.dblite
config.log
ups/*.cfgc
pytest_session.txt
2 changes: 1 addition & 1 deletion python/lsst/daf/butler/core/datastore.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# see <https://www.lsstcorp.org/LegalNotices/>.
#

from lsst.daf.persistence import doImport
from lsst.daf.butler.core.utils import doImport

from abc import ABCMeta, abstractmethod
from .config import Config
Expand Down
22 changes: 5 additions & 17 deletions python/lsst/daf/butler/core/fileDescriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,20 @@
class FileDescriptor(object):
"""Describes a particular file.
Attributes
Parameters
----------
location : `Location`
Storage location.
type : `cls`
Type the object will have after reading in Python (typically
`StorageClass.type`).
`StorageClass.pytype`).
parameters : `dict`
Additional parameters that can be used for reading and writing.
"""

__slots__ = ('location', 'type', 'parameters')
__slots__ = ('location', 'pytype', 'parameters')

def __init__(self, location, type=None, parameters=None):
"""Constructor
Parameters
----------
location : `Location`
Storage location.
type : `cls`
Type the object will have after reading in Python (typically
`StorageClass.type`).
parameters : `dict`
Additional parameters that can be used for reading and writing.
"""
def __init__(self, location, pytype=None, parameters=None):
self.location = location
self.type = type
self.pytype = pytype
self.parameters = parameters
48 changes: 7 additions & 41 deletions python/lsst/daf/butler/core/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

from abc import ABCMeta, abstractmethod

from lsst.daf.persistence import doImport
from .mappingFactory import MappingFactory


class Formatter(object, metaclass=ABCMeta):
Expand Down Expand Up @@ -70,14 +70,12 @@ def write(self, inMemoryDataset, fileDescriptor):
raise NotImplementedError("Type does not support writing")


class FormatterFactory(object):
class FormatterFactory:
"""Factory for `Formatter` instances.
"""

def __init__(self):
"""Constructor.
"""
self._registry = {}
self._mappingFactory = MappingFactory(Formatter)

def getFormatter(self, storageClass, datasetType=None):
"""Get a new formatter instance.
Expand All @@ -86,24 +84,18 @@ def getFormatter(self, storageClass, datasetType=None):
----------
storageClass : `StorageClass`
Get `Formatter` associated with this `StorageClass`, unless.
datasetType : `DatasetType` or `str, optional
datasetType : `DatasetType` or `str`, optional
If given, look if an override has been specified for this `DatasetType` and,
if so return that instead.
"""
if datasetType:
try:
typeName = self._registry[self._getName(datasetType)]()
except KeyError:
pass
typeName = self._registry[self._getName(storageClass)]
return self._getInstanceOf(typeName)
return self._mappingFactory.getFromRegistry(datasetType, storageClass)

def registerFormatter(self, type_, formatter):
"""Register a Formatter.
Parameters
----------
type : `str` or `StorageClass` or `DatasetType`
type_ : `str` or `StorageClass` or `DatasetType`
Type for which this formatter is to be used.
formatter : `str`
Identifies a `Formatter` subclass to use for reading and writing `Dataset`s of this type.
Expand All @@ -113,30 +105,4 @@ def registerFormatter(self, type_, formatter):
e : `ValueError`
If formatter does not name a valid formatter type.
"""
if not self._isValidFormatterStr(formatter):
raise ValueError("Not a valid Formatter: {0}".format(formatter))
self._registry[self._getName(type_)] = formatter

@staticmethod
def _getName(typeOrName):
"""Extract name of `DatasetType` or `StorageClass` as string.
"""
if isinstance(typeOrName, str):
return typeOrName
elif hasattr(typeOrName, 'name'):
return typeOrName.name
else:
raise ValueError("Cannot extract name from type")

@staticmethod
def _getInstanceOf(typeName):
cls = doImport(typeName)
return cls()

@staticmethod
def _isValidFormatterStr(formatter):
try:
f = FormatterFactory._getInstanceOf(formatter)
return isinstance(f, Formatter)
except ImportError:
return False
self._mappingFactory.placeInRegistry(type_, formatter)
158 changes: 158 additions & 0 deletions python/lsst/daf/butler/core/mappingFactory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#
# LSST Data Management System
#
# Copyright 2008-2018 AURA/LSST.
#
# 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 <https://www.lsstcorp.org/LegalNotices/>.
#
from lsst.daf.butler.core.utils import doImport


class MappingFactory:
"""
Register the mapping of some key to a python type and retrieve instances.
Enables instances of these classes to be retrieved from the factory later.
The class can be specified as an object, class or string.
If the key is an object it is converted to a string by accessing
a `name` attribute.
Parameters
----------
refType : `type`
Python reference `type` to use to ensure that items stored in the
registry create instance objects of the correct class. Subclasses
of this type are allowed. Using `None` disables the check.
"""

def __init__(self, refType):
self._registry = {}
self.refType = refType

def getFromRegistry(self, *targetClasses):
"""Get a new instance of the object stored in the registry.
Parameters
----------
*targetClasses : `str` or objects supporting `.name` attribute
Each item is tested in turn until a match is found in the registry.
Items with `None` value are skipped.
Returns
-------
instance : `object`
Instance of class stored in registry associated with the first
matching target class.
Raises
------
e : KeyError
None of the supplied target classes match an item in the registry.
"""
attempts = []
for t in (targetClasses):
if t is None:
attempts.append(t)
else:
key = self._getName(t)
attempts.append(key)
try:
typeName = self._registry[key]
except KeyError:
pass
else:
return self._getInstanceOf(typeName)
raise KeyError("Unable to find item in registry with keys: {}".format(attempts))

def placeInRegistry(self, registryKey, typeName):
"""Register a class name with the associated type.
The type name provided is validated against the reference
class, `refType` attribute, if defined.
Parameters
----------
registryKey : `str` or object supporting `.name` attribute.
Item to associate with the provided type.
typeName : `str` or Python type
Identifies a class to associate with the provided key.
Raises
------
e : `ValueError`
If instance of class is not of the expected type.
"""
if not self._isValidStr(typeName):
raise ValueError("Not a valid class string: {}".format(typeName))
keyString = self._getName(registryKey)
if keyString in self._registry:
raise ValueError("Item with key {} already registered".format(keyString))

self._registry[keyString] = typeName

@staticmethod
def _getName(typeOrName):
"""Extract name of supplied object as string.
Parameters
----------
typeOrName : `str` or object supporting `.name` attribute.
Item from which to extract a name.
Returns
-------
name : `str`
Extracted name as a string.
"""
if isinstance(typeOrName, str):
return typeOrName
elif hasattr(typeOrName, 'name'):
return typeOrName.name
else:
raise ValueError("Cannot extract name from type")

@staticmethod
def _getInstanceOf(typeOrName):
"""Given the type name or a type, instantiate an object of that type.
If a type name is given, an attempt will be made to import the type.
Parameters
----------
typeOrName : `str` or Python class
A string describing the Python class to load or a Python type.
"""
if isinstance(typeOrName, str):
cls = doImport(typeOrName)
else:
cls = typeOrName
return cls()

def _isValidStr(self, typeName):
"""Validate that the class type name provided does create instances of
objects that are of the expected type, as stored in the
`refType` attribute.
"""
if self.refType is None:
return True
try:
c = self._getInstanceOf(typeName)
except (ImportError, TypeError, AttributeError):
return False
else:
return isinstance(c, self.refType)
2 changes: 1 addition & 1 deletion python/lsst/daf/butler/core/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

from abc import ABCMeta, abstractmethod

from lsst.daf.persistence import doImport
from lsst.daf.butler.core.utils import doImport

from .config import Config

Expand Down

0 comments on commit 02c2778

Please sign in to comment.