LSSTApplications  10.0-2-g4f67435,11.0.rc2+1,11.0.rc2+12,11.0.rc2+3,11.0.rc2+4,11.0.rc2+5,11.0.rc2+6,11.0.rc2+7,11.0.rc2+8
LSSTDataManagementBasePackage
registry.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 import collections
23 import copy
24 
25 from .config import Config, FieldValidationError, _typeStr
26 from .configChoiceField import ConfigInstanceDict, ConfigChoiceField
27 
28 __all__ = ("Registry", "makeRegistry", "RegistryField", "registerConfig", "registerConfigurable")
29 
30 class ConfigurableWrapper(object):
31  """A wrapper for configurables
32 
33  Used for configurables that don't contain a ConfigClass attribute,
34  or contain one that is being overridden.
35  """
36  def __init__(self, target, ConfigClass):
37  self.ConfigClass = ConfigClass
38  self._target = target
39 
40  def __call__(self, *args, **kwargs):
41  return self._target(*args, **kwargs)
42 
43 
44 class Registry(collections.Mapping):
45  """A base class for global registries, mapping names to configurables.
46 
47  There are no hard requirements on configurable, but they typically create an algorithm
48  or are themselves the algorithm, and typical usage is as follows:
49  - configurable is a callable whose call signature is (config, ...extra arguments...)
50  - All configurables added to a particular registry will have the same call signature
51  - All configurables in a registry will typically share something important in common.
52  For example all configurables in psfMatchingRegistry return a psf matching
53  class that has a psfMatch method with a particular call signature.
54 
55  A registry acts like a read-only dictionary with an additional register method to add items.
56  The dict contains configurables and each configurable has an instance ConfigClass.
57 
58  Example:
59  registry = Registry()
60  class FooConfig(Config):
61  val = Field(dtype=int, default=3, doc="parameter for Foo")
62  class Foo(object):
63  ConfigClass = FooConfig
64  def __init__(self, config):
65  self.config = config
66  def addVal(self, num):
67  return self.config.val + num
68  registry.register("foo", Foo)
69  names = registry.keys() # returns ("foo",)
70  fooConfigurable = registry["foo"]
71  fooConfig = fooItem.ConfigClass()
72  foo = fooConfigurable(fooConfig)
73  foo.addVal(5) # returns config.val + 5
74  """
75 
76  def __init__(self, configBaseType=Config):
77  """Construct a registry of name: configurables
78 
79  @param configBaseType: base class for config classes in registry
80  """
81  if not issubclass(configBaseType, Config):
82  raise TypeError("configBaseType=%s must be a subclass of Config" % _typeStr(configBaseType,))
83  self._configBaseType = configBaseType
84  self._dict = {}
85 
86  def register(self, name, target, ConfigClass=None):
87  """Add a new item to the registry.
88 
89  @param target A callable 'object that takes a Config instance as its first argument.
90  This may be a Python type, but is not required to be.
91  @param ConfigClass A subclass of pex_config Config used to configure the configurable;
92  if None then configurable.ConfigClass is used.
93 
94  @note: If ConfigClass is provided then then 'target' is wrapped in a new object that forwards
95  function calls to it. Otherwise the original 'target' is stored.
96 
97  @raise AttributeError if ConfigClass is None and target does not have attribute ConfigClass
98  """
99  if name in self._dict:
100  raise RuntimeError("An item with name %r already exists" % name)
101  if ConfigClass is None:
102  wrapper = target
103  else:
104  wrapper = ConfigurableWrapper(target, ConfigClass)
105  if not issubclass(wrapper.ConfigClass, self._configBaseType):
106  raise TypeError("ConfigClass=%s is not a subclass of %r" % \
107  (_typeStr(wrapper.ConfigClass), _typeStr(self._configBaseType)))
108  self._dict[name] = wrapper
109 
110  def __getitem__(self, key): return self._dict[key]
111  def __len__(self): return len(self._dict)
112  def __iter__(self): return iter(self._dict)
113  def __contains__(self, key): return key in self._dict
114 
115  def makeField(self, doc, default=None, optional=False, multi=False):
116  return RegistryField(doc, self, default, optional, multi)
117 
118 class RegistryAdaptor(collections.Mapping):
119  """Private class that makes a Registry behave like the thing a ConfigChoiceField expects."""
120 
121  def __init__(self, registry):
122  self.registry = registry
123 
124  def __getitem__(self, k): return self.registry[k].ConfigClass
125  def __iter__(self): return iter(self.registry)
126  def __len__(self): return len(self.registry)
127  def __contains__(self, k): return k in self.registry
128 
129 class RegistryInstanceDict(ConfigInstanceDict):
130  def __init__(self, config, field):
131  ConfigInstanceDict.__init__(self, config, field)
132  self.registry = field.registry
133 
134  def _getTarget(self):
135  if self._field.multi:
136  raise FieldValidationError(self._field, self._config,
137  "Multi-selection field has no attribute 'target'")
138  return self._field.typemap.registry[self._selection]
139  target = property(_getTarget)
140 
141  def _getTargets(self):
142  if not self._field.multi:
143  raise FieldValidationError(self._field, self._config,
144  "Single-selection field has no attribute 'targets'")
145  return [self._field.typemap.registry[c] for c in self._selection]
146  targets = property(_getTargets)
147 
148  def apply(self, *args, **kw):
149  """Call the active target(s) with the active config as a keyword arg
150 
151  If this is a multi-selection field, return a list obtained by calling
152  each active target with its corresponding active config.
153 
154  Additional arguments will be passed on to the configurable target(s)
155  """
156  if self.active is None:
157  msg = "No selection has been made. Options: %s" % \
158  (" ".join(self._field.typemap.registry.keys()))
159  raise FieldValidationError(self._field, self._config, msg)
160  if self._field.multi:
161  retvals = []
162  for c in self._selection:
163  retvals.append(self._field.typemap.registry[c](*args, config=self[c], **kw))
164  return retvals
165  else:
166  return self._field.typemap.registry[self.name](*args, config=self[self.name], **kw)
167  def __setattr__(self, attr, value):
168  if attr =="registry":
169  object.__setattr__(self, attr, value)
170  else:
171  ConfigInstanceDict.__setattr__(self, attr, value)
172 
173 class RegistryField(ConfigChoiceField):
174  instanceDictClass = RegistryInstanceDict
175 
176  def __init__(self, doc, registry, default=None, optional=False, multi=False):
177  types = RegistryAdaptor(registry)
178  self.registry = registry
179  ConfigChoiceField.__init__(self, doc, types, default, optional, multi)
180 
181  def __deepcopy__(self, memo):
182  """Customize deep-copying, want a reference to the original registry.
183  WARNING: this must be overridden by subclasses if they change the
184  constructor signature!
185  """
186  other = type(self)(doc=self.doc, registry=self.registry,
187  default=copy.deepcopy(self.default),
188  optional=self.optional, multi=self.multi)
189  other.source=self.source
190  return other
191 
192 def makeRegistry(doc, configBaseType=Config):
193  """A convenience function to create a new registry.
194 
195  The returned value is an instance of a trivial subclass of Registry whose only purpose is to
196  customize its doc string and set attrList.
197  """
198  cls = type("Registry", (Registry,), {"__doc__": doc})
199  return cls(configBaseType=configBaseType)
200 
201 def registerConfigurable(name, registry, ConfigClass=None):
202  """A decorator that adds a class as a configurable in a Registry.
203 
204  If the 'ConfigClass' argument is None, the class's ConfigClass attribute will be used.
205  """
206  def decorate(cls):
207  registry.register(name, target=cls, ConfigClass=ConfigClass)
208  return cls
209  return decorate
210 
211 def registerConfig(name, registry, target):
212  """A decorator that adds a class as a ConfigClass in a Registry, and associates it with the given
213  configurable.
214  """
215  def decorate(cls):
216  registry.register(name, target=target, ConfigClass=cls)
217  return cls
218  return decorate