LSSTApplications  16.0+42,16.0-1-gce273f5+8,16.0-10-g230e10e+1,16.0-11-g9fe0e56+17,16.0-11-gce733cf+17,16.0-12-g5ad1ebf+9,16.0-12-gc85596e+2,16.0-13-gde155d7+2,16.0-14-g9428de4d,16.0-14-gc1cf4a94+2,16.0-15-g8e16a51+14,16.0-2-g0febb12+7,16.0-2-g839ba83+32,16.0-2-g9d5294e+22,16.0-2-gab3db49+7,16.0-2-gf41ba6b+6,16.0-2-gf4e7cdd+5,16.0-3-g6923fb6+15,16.0-3-g8e51203+2,16.0-3-g9645794+6,16.0-3-gcfd6c53+20,16.0-35-g34c7dfe62+1,16.0-4-g03cf288+11,16.0-4-g32d12de,16.0-4-g5f3a788+7,16.0-4-g7690030+30,16.0-4-g8a0f11a+16,16.0-4-ga5d8928+16,16.0-5-g0da18be+7,16.0-5-g4940a70,16.0-5-g563880a+2,16.0-5-g7742071+2,16.0-5-gb3f8a4b+26,16.0-6-g3610b4f+5,16.0-6-gf0acd13+14,16.0-8-g4dec96c+7,16.0-8-gc315727+16,16.0-9-g1de645c+7,16.0-9-gcc4efb7+6,w.2018.36
LSSTDataManagementBasePackage
wrappers.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 #
4 # Copyright 2008-2017 AURA/LSST.
5 #
6 # This product includes software developed by the
7 # LSST Project (http://www.lsst.org/).
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the LSST License Statement and
20 # the GNU General Public License along with this program. If not,
21 # see <https://www.lsstcorp.org/LegalNotices/>.
22 #
23 
24 import sys
25 import types
26 
27 import numpy as np
28 
29 __all__ = ("continueClass", "inClass", "TemplateMeta")
30 
31 
32 INTRINSIC_SPECIAL_ATTRIBUTES = frozenset((
33  "__qualname__",
34  "__module__",
35  "__metaclass__",
36  "__dict__",
37  "__weakref__",
38  "__class__",
39  "__subclasshook__",
40  "__name__",
41  "__doc__",
42 ))
43 
44 
45 def isAttributeSafeToTransfer(name, value):
46  """Return True if an attribute is safe to monkeypatch-transfer to another
47  class.
48 
49  This rejects special methods that are defined automatically for all
50  classes, leaving only those explicitly defined in a class decorated by
51  `continueClass` or registered with an instance of `TemplateMeta`.
52  """
53  if name.startswith("__") and (value is getattr(object, name, None) or
54  name in INTRINSIC_SPECIAL_ATTRIBUTES):
55  return False
56  return True
57 
58 
59 def continueClass(cls):
60  """Re-open the decorated class, adding any new definitions into the original.
61 
62  For example::
63 
64  .. code-block:: python
65 
66  class Foo:
67  pass
68 
69  @continueClass
70  class Foo:
71  def run(self):
72  return None
73 
74  is equivalent to::
75 
76  .. code-block:: python
77 
78  class Foo:
79  def run(self):
80  return None
81 
82  """
83  orig = getattr(sys.modules[cls.__module__], cls.__name__)
84  for name in dir(cls):
85  # Common descriptors like classmethod and staticmethod can only be
86  # accessed without invoking their magic if we use __dict__; if we use
87  # getattr on those we'll get e.g. a bound method instance on the dummy
88  # class rather than a classmethod instance we can put on the target
89  # class.
90  attr = cls.__dict__.get(name, None) or getattr(cls, name)
91  if isAttributeSafeToTransfer(name, attr):
92  setattr(orig, name, attr)
93  return orig
94 
95 
96 def inClass(cls, name=None):
97  """Add the decorated function to the given class as a method.
98 
99  For example::
100 
101  .. code-block:: python
102 
103  class Foo:
104  pass
105 
106  @inClass(Foo)
107  def run(self):
108  return None
109 
110  is equivalent to::
111 
112  .. code-block:: python
113 
114  class Foo:
115  def run(self):
116  return None
117 
118  Standard decorators like ``classmethod``, ``staticmethod``, and
119  ``property`` may be used *after* this decorator. Custom decorators
120  may only be used if they return an object with a ``__name__`` attribute
121  or the ``name`` optional argument is provided.
122  """
123  def decorate(func):
124  # Using 'name' instead of 'name1' breaks the closure because
125  # assignment signals a strictly local variable.
126  name1 = name
127  if name1 is None:
128  if hasattr(func, "__name__"):
129  name1 = func.__name__
130  else:
131  if hasattr(func, "__func__"):
132  # classmethod and staticmethod have __func__ but no __name__
133  name1 = func.__func__.__name__
134  elif hasattr(func, "fget"):
135  # property has fget but no __name__
136  name1 = func.fget.__name__
137  else:
138  raise ValueError(
139  "Could not guess attribute name for '{}'.".format(func)
140  )
141  setattr(cls, name1, func)
142  return func
143  return decorate
144 
145 
147  """A metaclass for abstract base classes that tie together wrapped C++
148  template types.
149 
150  C++ template classes are most easily wrapped with a separate Python class
151  for each template type, which results in an unnatural Python interface.
152  TemplateMeta provides a thin layer that connects these Python classes by
153  giving them a common base class and acting as a factory to construct them
154  in a consistent way.
155 
156  To use, simply create a new class with the name of the template class, and
157  use ``TemplateMeta`` as its metaclass, and then call ``register`` on each
158  of its subclasses. This registers the class with a "type key" - usually a
159  Python representation of the C++ template types. The type key must be a
160  hashable object - strings, type objects, and tuples of these (for C++
161  classes with multiple template parameters) are good choices. Alternate
162  type keys for existing classes can be added by calling ``alias``, but only
163  after a subclass already been registered with a "primary" type key. For
164  example (using Python 3 metaclass syntax)::
165 
166  .. code-block:: python
167 
168  import numpy as np
169  from ._image import ImageF, ImageD
170 
171  class Image(metaclass=TemplateMeta):
172  pass
173 
174  Image.register(np.float32, ImageF)
175  Image.register(np.float64, ImageD)
176  Image.alias("F", ImageF)
177  Image.alias("D", ImageD)
178 
179  We have intentionally used ``numpy`` types as the primary keys for these
180  objects in this example, with strings as secondary aliases simply because
181  the primary key is added as a ``dtype`` attribute on the the registered
182  classes (so ``ImageF.dtype == numpy.float32`` in the above example).
183 
184  This allows user code to construct objects directly using ``Image``, as
185  long as an extra ``dtype`` keyword argument is passed that matches one of
186  the type keys::
187 
188  .. code-block:: python
189 
190  img = Image(52, 64, dtype=np.float32)
191 
192  This simply forwards additional positional and keyword arguments to the
193  wrapped template class's constructor.
194 
195  The choice of "dtype" as the name of the template parameter is also
196  configurable, and in fact multiple template parameters are also supported,
197  by setting a ``TEMPLATE_PARAMS`` class attribute on the ABC to a tuple
198  containing the names of the template parameters. A ``TEMPLATE_DEFAULTS``
199  attribute can also be defined to a tuple of the same length containing
200  default values for the template parameters, allowing them to be omitted in
201  constructor calls. When the length of these attributes is more than one,
202  the type keys passed to ``register`` and ``alias`` should be tuple of the
203  same length; when the length of these attributes is one, type keys should
204  generally not be tuples.
205 
206  As an aid for those writing the Python wrappers for C++ classes,
207  ``TemplateMeta`` also provides a way to add pure-Python methods and other
208  attributes to the wrapped template classes. To add a ``sum`` method to
209  all registered types, for example, we can just do::
210 
211  .. code-block:: python
212 
213  class Image(metaclass=TemplateMeta):
214 
215  def sum(self):
216  return np.sum(self.getArray())
217 
218  Image.register(np.float32, ImageF)
219  Image.register(np.float64, ImageD)
220 
221  .. note::
222 
223  ``TemplateMeta`` works by overriding the ``__instancecheck__`` and
224  ``__subclasscheck__`` special methods, and hence does not appear in
225  its registered subclasses' method resolution order or ``__bases__``
226  attributes. That means its attributes are not inherited by registered
227  subclasses. Instead, attributes added to an instance of
228  ``TemplateMeta`` are *copied* into the types registered with it. These
229  attributes will thus *replace* existing attributes in those classes
230  with the same name, and subclasses cannot delegate to base class
231  implementations of these methods.
232 
233  Finally, abstract base classes that use ``TemplateMeta`` define a dict-
234  like interface for accessing their registered subclasses, providing
235  something like the C++ syntax for templates::
236 
237  .. code-block:: python
238 
239  Image[np.float32] -> ImageF
240  Image["D"] -> ImageD
241 
242  Both primary dtypes and aliases can be used as keys in this interface,
243  which means types with aliases will be present multiple times in the dict.
244  To obtain the sequence of unique subclasses, use the ``__subclasses__``
245  method.
246  """
247 
248  def __new__(cls, name, bases, attrs):
249  # __new__ is invoked when the abstract base class is defined (via a
250  # class statement). We save a dict of class attributes (including
251  # methods) that were defined in the class body so we can copy them
252  # to registered subclasses later.
253  # We also initialize an empty dict to store the registered subclasses.
254  attrs["_inherited"] = {k: v for k, v in attrs.items()
255  if isAttributeSafeToTransfer(k, v)}
256  # The special "TEMPLATE_PARAMS" class attribute, if defined, contains
257  # names of the template parameters, which we use to set those
258  # attributes on registered subclasses and intercept arguments to the
259  # constructor. This line removes it from the dict of things that
260  # should be inherited while setting a default of 'dtype' if it's not
261  # defined.
262  attrs["TEMPLATE_PARAMS"] = \
263  attrs["_inherited"].pop("TEMPLATE_PARAMS", ("dtype",))
264  attrs["TEMPLATE_DEFAULTS"] = \
265  attrs["_inherited"].pop("TEMPLATE_DEFAULTS",
266  (None,)*len(attrs["TEMPLATE_PARAMS"]))
267  attrs["_registry"] = dict()
268  self = type.__new__(cls, name, bases, attrs)
269 
270  if len(self.TEMPLATE_PARAMS) == 0:
271  raise ValueError(
272  "TEMPLATE_PARAMS must be a tuple with at least one element."
273  )
274  if len(self.TEMPLATE_DEFAULTS) != len(self.TEMPLATE_PARAMS):
275  raise ValueError(
276  "TEMPLATE_PARAMS and TEMPLATE_DEFAULTS must have same length."
277  )
278  return self
279 
280  def __call__(self, *args, **kwds):
281  # __call__ is invoked when someone tries to construct an instance of
282  # the abstract base class.
283  # If the ABC defines a "TEMPLATE_PARAMS" attribute, we use those strings
284  # as the kwargs we should intercept to find the right type.
285 
286  # Generate a type mapping key from input keywords. If the type returned
287  # from the keyword lookup is a numpy dtype object, fetch the underlying
288  # type of the dtype
289  key = []
290  for p, d in zip(self.TEMPLATE_PARAMS, self.TEMPLATE_DEFAULTS):
291  tempKey = kwds.pop(p, d)
292  if isinstance(tempKey, np.dtype):
293  tempKey = tempKey.type
294  key.append(tempKey)
295  key = tuple(key)
296 
297  # indices are only tuples if there are multiple elements
298  cls = self._registry.get(key[0] if len(key) == 1 else key, None)
299  if cls is None:
300  d = {k: v for k, v in zip(self.TEMPLATE_PARAMS, key)}
301  raise TypeError("No registered subclass for {}.".format(d))
302  return cls(*args, **kwds)
303 
304  def __subclasscheck__(self, subclass):
305  # Special method hook for the issubclass built-in: we return true for
306  # any registered type or true subclass thereof.
307  if subclass in self._registry:
308  return True
309  for v in self._registry.values():
310  if issubclass(subclass, v):
311  return True
312  return False
313 
314  def __instancecheck__(self, instance):
315  # Special method hook for the isinstance built-in: we return true for
316  # an instance of any registered type or true subclass thereof.
317  if type(instance) in self._registry:
318  return True
319  for v in self._registry.values():
320  if isinstance(instance, v):
321  return True
322  return False
323 
324  def __subclasses__(self):
325  """Return a tuple of all classes that inherit from this class.
326  """
327  # This special method isn't defined as part of the Python data model,
328  # but it exists on builtins (including ABCMeta), and it provides useful
329  # functionality.
330  return tuple(set(self._registry.values()))
331 
332  def register(self, key, subclass):
333  """Register a subclass of this ABC with the given key (a string,
334  number, type, or other hashable).
335 
336  Register may only be called once for a given key or a given subclass.
337  """
338  if key is None:
339  raise ValueError("None may not be used as a key.")
340  if subclass in self._registry.values():
341  raise ValueError(
342  "This subclass has already registered with another key; "
343  "use alias() instead."
344  )
345  if self._registry.setdefault(key, subclass) != subclass:
346  if len(self.TEMPLATE_PARAMS) == 1:
347  d = {self.TEMPLATE_PARAMS[0]: key}
348  else:
349  d = {k: v for k, v in zip(self.TEMPLATE_PARAMS, key)}
350  raise KeyError(
351  "Another subclass is already registered with {}".format(d)
352  )
353  # If the key used to register a class matches the default key,
354  # make the static methods available through the ABC
355  if self.TEMPLATE_DEFAULTS:
356  defaults = (self.TEMPLATE_DEFAULTS[0] if
357  len(self.TEMPLATE_DEFAULTS) == 1 else
358  self.TEMPLATE_DEFAULTS)
359  if key == defaults:
360  conflictStr = ("Base Class has an attribute with the same"
361  "name as a {} method in the default subclass"
362  ". Cannot link {} method to base class")
363  # In the following if statements, the explicit lookup in
364  # __dict__ must be done, as a call to getattr returns the
365  # bound method, which no longer reports as a static or class
366  # method. The static methods must be transfered to the ABC
367  # in this unbound state, so that python will still see them
368  # as static methods and not attempt to pass self. The class
369  # methods must be transfered to the ABC as a bound method
370  # so that the correct cls be called with the class method
371  for name in subclass.__dict__:
372  if name == "__new__":
373  continue
374  obj = subclass.__dict__[name]
375  # copy over the static methods
376  isBuiltin = isinstance(obj, types.BuiltinFunctionType)
377  isStatic = isinstance(obj, staticmethod)
378  if isBuiltin or isStatic:
379  if hasattr(self, name):
380  raise AttributeError(conflictStr.format("static"))
381  setattr(self, name, obj)
382  # copy over the class methods
383  elif isinstance(obj, classmethod):
384  if hasattr(self, name):
385  raise AttributeError(conflictStr.format("class"))
386  setattr(self, name, getattr(subclass, name))
387 
388  def setattrSafe(name, value):
389  try:
390  currentValue = getattr(subclass, name)
391  if currentValue != value:
392  msg = ("subclass already has a '{}' attribute with "
393  "value {} != {}.")
394  raise ValueError(
395  msg.format(name, currentValue, value)
396  )
397  except AttributeError:
398  setattr(subclass, name, value)
399 
400  if len(self.TEMPLATE_PARAMS) == 1:
401  setattrSafe(self.TEMPLATE_PARAMS[0], key)
402  elif len(self.TEMPLATE_PARAMS) == len(key):
403  for p, k in zip(self.TEMPLATE_PARAMS, key):
404  setattrSafe(p, k)
405  else:
406  raise ValueError(
407  "key must have {} elements (one for each of {})".format(
408  len(self.TEMPLATE_PARAMS), self.TEMPLATE_PARAMS
409  )
410  )
411 
412  for name, attr in self._inherited.items():
413  setattr(subclass, name, attr)
414 
415  def alias(self, key, subclass):
416  """Add an alias that allows an existing subclass to be accessed with a
417  different key.
418  """
419  if key is None:
420  raise ValueError("None may not be used as a key.")
421  if key in self._registry:
422  raise KeyError("Cannot multiply-register key {}".format(key))
423  primaryKey = tuple(getattr(subclass, p, None)
424  for p in self.TEMPLATE_PARAMS)
425  if len(primaryKey) == 1:
426  # indices are only tuples if there are multiple elements
427  primaryKey = primaryKey[0]
428  if self._registry.get(primaryKey, None) != subclass:
429  raise ValueError("Subclass is not registered with this base class.")
430  self._registry[key] = subclass
431 
432  # Immutable mapping interface defined below. We don't use collections
433  # mixins because we don't want their comparison operators.
434 
435  def __getitem__(self, key):
436  return self._registry[key]
437 
438  def __iter__(self):
439  return iter(self._registry)
440 
441  def __len__(self):
442  return len(self._registry)
443 
444  def __contains__(self, key):
445  return key in self._registry
446 
447  def keys(self):
448  """Return an iterable containing all keys (including aliases).
449  """
450  return self._registry.keys()
451 
452  def values(self):
453  """Return an iterable of registered subclasses, with duplicates
454  corresponding to any aliases.
455  """
456  return self._registry.values()
457 
458  def items(self):
459  """Return an iterable of (key, subclass) pairs.
460  """
461  return self._registry.items()
462 
463  def get(self, key, default=None):
464  """Return the subclass associated with the given key (including
465  aliases), or ``default`` if the key is not recognized.
466  """
467  return self._registry.get(key, default)
def isAttributeSafeToTransfer(name, value)
Definition: wrappers.py:45
def __call__(self, args, kwds)
Definition: wrappers.py:280
daf::base::PropertySet * set
Definition: fits.cc:818
def continueClass(cls)
Definition: wrappers.py:59
def alias(self, key, subclass)
Definition: wrappers.py:415
def __instancecheck__(self, instance)
Definition: wrappers.py:314
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:129
def __subclasscheck__(self, subclass)
Definition: wrappers.py:304
def __new__(cls, name, bases, attrs)
Definition: wrappers.py:248
def get(self, key, default=None)
Definition: wrappers.py:463
def inClass(cls, name=None)
Definition: wrappers.py:96
def register(self, key, subclass)
Definition: wrappers.py:332