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