lsst.pex.config  13.0-2-g483026c+4
 All Classes Namespaces Files Functions Variables Properties Macros Pages
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 from builtins import object
23 
24 import collections
25 import copy
26 
27 from .config import Config, FieldValidationError, _typeStr
28 from .configChoiceField import ConfigInstanceDict, ConfigChoiceField
29 
30 __all__ = ("Registry", "makeRegistry", "RegistryField", "registerConfig", "registerConfigurable")
31 
32 
33 class ConfigurableWrapper(object):
34  """A wrapper for configurables
35 
36  Used for configurables that don't contain a ConfigClass attribute,
37  or contain one that is being overridden.
38  """
39  def __init__(self, target, ConfigClass):
40  self.ConfigClass = ConfigClass
41  self._target = target
42 
43  def __call__(self, *args, **kwargs):
44  return self._target(*args, **kwargs)
45 
46 
47 class Registry(collections.Mapping):
48  """A base class for global registries, mapping names to configurables.
49 
50  There are no hard requirements on configurable, but they typically create an algorithm
51  or are themselves the algorithm, and typical usage is as follows:
52  - configurable is a callable whose call signature is (config, ...extra arguments...)
53  - All configurables added to a particular registry will have the same call signature
54  - All configurables in a registry will typically share something important in common.
55  For example all configurables in psfMatchingRegistry return a psf matching
56  class that has a psfMatch method with a particular call signature.
57 
58  A registry acts like a read-only dictionary with an additional register method to add items.
59  The dict contains configurables and each configurable has an instance ConfigClass.
60 
61  Example:
62  registry = Registry()
63  class FooConfig(Config):
64  val = Field(dtype=int, default=3, doc="parameter for Foo")
65  class Foo(object):
66  ConfigClass = FooConfig
67  def __init__(self, config):
68  self.config = config
69  def addVal(self, num):
70  return self.config.val + num
71  registry.register("foo", Foo)
72  names = registry.keys() # returns ("foo",)
73  fooConfigurable = registry["foo"]
74  fooConfig = fooItem.ConfigClass()
75  foo = fooConfigurable(fooConfig)
76  foo.addVal(5) # returns config.val + 5
77  """
78 
79  def __init__(self, configBaseType=Config):
80  """Construct a registry of name: configurables
81 
82  @param configBaseType: base class for config classes in registry
83  """
84  if not issubclass(configBaseType, Config):
85  raise TypeError("configBaseType=%s must be a subclass of Config" % _typeStr(configBaseType,))
86  self._configBaseType = configBaseType
87  self._dict = {}
88 
89  def register(self, name, target, ConfigClass=None):
90  """Add a new item to the registry.
91 
92  @param target A callable 'object that takes a Config instance as its first argument.
93  This may be a Python type, but is not required to be.
94  @param ConfigClass A subclass of pex_config Config used to configure the configurable;
95  if None then configurable.ConfigClass is used.
96 
97  @note: If ConfigClass is provided then then 'target' is wrapped in a new object that forwards
98  function calls to it. Otherwise the original 'target' is stored.
99 
100  @raise AttributeError if ConfigClass is None and target does not have attribute ConfigClass
101  """
102  if name in self._dict:
103  raise RuntimeError("An item with name %r already exists" % name)
104  if ConfigClass is None:
105  wrapper = target
106  else:
107  wrapper = ConfigurableWrapper(target, ConfigClass)
108  if not issubclass(wrapper.ConfigClass, self._configBaseType):
109  raise TypeError("ConfigClass=%s is not a subclass of %r" %
110  (_typeStr(wrapper.ConfigClass), _typeStr(self._configBaseType)))
111  self._dict[name] = wrapper
112 
113  def __getitem__(self, key):
114  return self._dict[key]
115 
116  def __len__(self):
117  return len(self._dict)
118 
119  def __iter__(self):
120  return iter(self._dict)
121 
122  def __contains__(self, key):
123  return key in self._dict
124 
125  def makeField(self, doc, default=None, optional=False, multi=False):
126  return RegistryField(doc, self, default, optional, multi)
127 
128 
129 class RegistryAdaptor(collections.Mapping):
130  """Private class that makes a Registry behave like the thing a ConfigChoiceField expects."""
131 
132  def __init__(self, registry):
133  self.registry = registry
134 
135  def __getitem__(self, k):
136  return self.registry[k].ConfigClass
137 
138  def __iter__(self):
139  return iter(self.registry)
140 
141  def __len__(self):
142  return len(self.registry)
143 
144  def __contains__(self, k):
145  return k in self.registry
146 
147 
148 class RegistryInstanceDict(ConfigInstanceDict):
149  def __init__(self, config, field):
150  ConfigInstanceDict.__init__(self, config, field)
151  self.registry = field.registry
152 
153  def _getTarget(self):
154  if self._field.multi:
155  raise FieldValidationError(self._field, self._config,
156  "Multi-selection field has no attribute 'target'")
157  return self._field.typemap.registry[self._selection]
158  target = property(_getTarget)
159 
160  def _getTargets(self):
161  if not self._field.multi:
162  raise FieldValidationError(self._field, self._config,
163  "Single-selection field has no attribute 'targets'")
164  return [self._field.typemap.registry[c] for c in self._selection]
165  targets = property(_getTargets)
166 
167  def apply(self, *args, **kw):
168  """Call the active target(s) with the active config as a keyword arg
169 
170  If this is a multi-selection field, return a list obtained by calling
171  each active target with its corresponding active config.
172 
173  Additional arguments will be passed on to the configurable target(s)
174  """
175  if self.active is None:
176  msg = "No selection has been made. Options: %s" % \
177  (" ".join(list(self._field.typemap.registry.keys())))
178  raise FieldValidationError(self._field, self._config, msg)
179  if self._field.multi:
180  retvals = []
181  for c in self._selection:
182  retvals.append(self._field.typemap.registry[c](*args, config=self[c], **kw))
183  return retvals
184  else:
185  return self._field.typemap.registry[self.name](*args, config=self[self.name], **kw)
186 
187  def __setattr__(self, attr, value):
188  if attr == "registry":
189  object.__setattr__(self, attr, value)
190  else:
191  ConfigInstanceDict.__setattr__(self, attr, value)
192 
193 
194 class RegistryField(ConfigChoiceField):
195  instanceDictClass = RegistryInstanceDict
196 
197  def __init__(self, doc, registry, default=None, optional=False, multi=False):
198  types = RegistryAdaptor(registry)
199  self.registry = registry
200  ConfigChoiceField.__init__(self, doc, types, default, optional, multi)
201 
202  def __deepcopy__(self, memo):
203  """Customize deep-copying, want a reference to the original registry.
204  WARNING: this must be overridden by subclasses if they change the
205  constructor signature!
206  """
207  other = type(self)(doc=self.doc, registry=self.registry,
208  default=copy.deepcopy(self.default),
209  optional=self.optional, multi=self.multi)
210  other.source = self.source
211  return other
212 
213 
214 def makeRegistry(doc, configBaseType=Config):
215  """A convenience function to create a new registry.
216 
217  The returned value is an instance of a trivial subclass of Registry whose only purpose is to
218  customize its doc string and set attrList.
219  """
220  cls = type("Registry", (Registry,), {"__doc__": doc})
221  return cls(configBaseType=configBaseType)
222 
223 
224 def registerConfigurable(name, registry, ConfigClass=None):
225  """A decorator that adds a class as a configurable in a Registry.
226 
227  If the 'ConfigClass' argument is None, the class's ConfigClass attribute will be used.
228  """
229  def decorate(cls):
230  registry.register(name, target=cls, ConfigClass=ConfigClass)
231  return cls
232  return decorate
233 
234 
235 def registerConfig(name, registry, target):
236  """A decorator that adds a class as a ConfigClass in a Registry, and associates it with the given
237  configurable.
238  """
239  def decorate(cls):
240  registry.register(name, target=target, ConfigClass=cls)
241  return cls
242  return decorate