LSSTApplications  16.0-1-gce273f5+12,16.0-1-gf77f410+3,16.0-10-g230e10e+5,16.0-10-g90ce0e4+3,16.0-10-gc1446dd+3,16.0-12-g726f8f3+1,16.0-13-g4c33ca5+3,16.0-13-g5f6c0b1+3,16.0-13-gd9b1b71+3,16.0-13-gde155d7+11,16.0-16-g925333c+3,16.0-17-g6a7bfb3b+3,16.0-17-gd75be2c9,16.0-2-g0febb12+11,16.0-2-g839ba83+41,16.0-2-g9d5294e+30,16.0-3-g404ea43+3,16.0-3-gbc759ec+1,16.0-3-gcfd6c53+28,16.0-4-g03cf288+19,16.0-4-g13a27c5+5,16.0-4-g2cc461c+2,16.0-4-g5f3a788+10,16.0-4-g8a0f11a+25,16.0-4-ga3eb747,16.0-42-gaa8aebfeb+1,16.0-5-g1991253+3,16.0-5-g865efd9+3,16.0-5-gb3f8a4b+35,16.0-5-gd0f1235,16.0-6-g0a6c279+4,16.0-6-g316b399+3,16.0-6-gf0acd13+22,16.0-6-gf9cb114+4,16.0-7-g88875c5,16.0-7-gc370964+2,16.0-8-g4dec96c+16,16.0-8-gb99f628,16.0-9-gcc4efb7+15,w.2018.38
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::
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__(cls, *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  # Generate a type mapping key from input keywords. If the type returned
286  # from the keyword lookup is a numpy dtype object, fetch the underlying
287  # type of the dtype
288  key = []
289  for p, d in zip(cls.TEMPLATE_PARAMS, cls.TEMPLATE_DEFAULTS):
290  tempKey = kwds.pop(p, d)
291  if isinstance(tempKey, np.dtype):
292  tempKey = tempKey.type
293  key.append(tempKey)
294  key = tuple(key)
295 
296  # indices are only tuples if there are multiple elements
297  clz = cls._registry.get(key[0] if len(key) == 1 else key, None)
298  if clz is None:
299  d = {k: v for k, v in zip(cls.TEMPLATE_PARAMS, key)}
300  raise TypeError("No registered subclass for {}.".format(d))
301  return clz(*args, **kwds)
302 
303  def __subclasscheck__(cls, subclass):
304  # Special method hook for the issubclass built-in: we return true for
305  # any registered type or true subclass thereof.
306  if subclass in cls._registry:
307  return True
308  for v in cls._registry.values():
309  if issubclass(subclass, v):
310  return True
311  return False
312 
313  def __instancecheck__(cls, instance):
314  # Special method hook for the isinstance built-in: we return true for
315  # an instance of any registered type or true subclass thereof.
316  if type(instance) in cls._registry:
317  return True
318  for v in cls._registry.values():
319  if isinstance(instance, v):
320  return True
321  return False
322 
323  def __subclasses__(cls):
324  """Return a tuple of all classes that inherit from this class.
325  """
326  # This special method isn't defined as part of the Python data model,
327  # but it exists on builtins (including ABCMeta), and it provides useful
328  # functionality.
329  return tuple(set(cls._registry.values()))
330 
331  def register(cls, key, subclass):
332  """Register a subclass of this ABC with the given key (a string,
333  number, type, or other hashable).
334 
335  Register may only be called once for a given key or a given subclass.
336  """
337  if key is None:
338  raise ValueError("None may not be used as a key.")
339  if subclass in cls._registry.values():
340  raise ValueError(
341  "This subclass has already registered with another key; "
342  "use alias() instead."
343  )
344  if cls._registry.setdefault(key, subclass) != subclass:
345  if len(cls.TEMPLATE_PARAMS) == 1:
346  d = {cls.TEMPLATE_PARAMS[0]: key}
347  else:
348  d = {k: v for k, v in zip(cls.TEMPLATE_PARAMS, key)}
349  raise KeyError(
350  "Another subclass is already registered with {}".format(d)
351  )
352  # If the key used to register a class matches the default key,
353  # make the static methods available through the ABC
354  if cls.TEMPLATE_DEFAULTS:
355  defaults = (cls.TEMPLATE_DEFAULTS[0] if
356  len(cls.TEMPLATE_DEFAULTS) == 1 else
357  cls.TEMPLATE_DEFAULTS)
358  if key == defaults:
359  conflictStr = ("Base class has attribute {}"
360  " which is a {} method of {}."
361  " Cannot link method to base class.")
362  # In the following if statements, the explicit lookup in
363  # __dict__ must be done, as a call to getattr returns the
364  # bound method, which no longer reports as a static or class
365  # method. The static methods must be transfered to the ABC
366  # in this unbound state, so that python will still see them
367  # as static methods and not attempt to pass self. The class
368  # methods must be transfered to the ABC as a bound method
369  # so that the correct cls be called with the class method
370  for name in subclass.__dict__:
371  if name in ("__new__", "__init_subclass__"):
372  continue
373  obj = subclass.__dict__[name]
374  # copy over the static methods
375  isBuiltin = isinstance(obj, types.BuiltinFunctionType)
376  isStatic = isinstance(obj, staticmethod)
377  if isBuiltin or isStatic:
378  if hasattr(cls, name):
379  raise AttributeError(
380  conflictStr.format(name, "static", subclass))
381  setattr(cls, name, obj)
382  # copy over the class methods
383  elif isinstance(obj, classmethod):
384  if hasattr(cls, name):
385  raise AttributeError(
386  conflictStr.format(name, "class", subclass))
387  setattr(cls, name, getattr(subclass, name))
388 
389  def setattrSafe(name, value):
390  try:
391  currentValue = getattr(subclass, name)
392  if currentValue != value:
393  msg = ("subclass already has a '{}' attribute with "
394  "value {} != {}.")
395  raise ValueError(
396  msg.format(name, currentValue, value)
397  )
398  except AttributeError:
399  setattr(subclass, name, value)
400 
401  if len(cls.TEMPLATE_PARAMS) == 1:
402  setattrSafe(cls.TEMPLATE_PARAMS[0], key)
403  elif len(cls.TEMPLATE_PARAMS) == len(key):
404  for p, k in zip(cls.TEMPLATE_PARAMS, key):
405  setattrSafe(p, k)
406  else:
407  raise ValueError(
408  "key must have {} elements (one for each of {})".format(
409  len(cls.TEMPLATE_PARAMS), cls.TEMPLATE_PARAMS
410  )
411  )
412 
413  for name, attr in cls._inherited.items():
414  setattr(subclass, name, attr)
415 
416  def alias(cls, key, subclass):
417  """Add an alias that allows an existing subclass to be accessed with a
418  different key.
419  """
420  if key is None:
421  raise ValueError("None may not be used as a key.")
422  if key in cls._registry:
423  raise KeyError("Cannot multiply-register key {}".format(key))
424  primaryKey = tuple(getattr(subclass, p, None)
425  for p in cls.TEMPLATE_PARAMS)
426  if len(primaryKey) == 1:
427  # indices are only tuples if there are multiple elements
428  primaryKey = primaryKey[0]
429  if cls._registry.get(primaryKey, None) != subclass:
430  raise ValueError("Subclass is not registered with this base class.")
431  cls._registry[key] = subclass
432 
433  # Immutable mapping interface defined below. We don't use collections
434  # mixins because we don't want their comparison operators.
435 
436  def __getitem__(cls, key):
437  return cls._registry[key]
438 
439  def __iter__(cls):
440  return iter(cls._registry)
441 
442  def __len__(cls):
443  return len(cls._registry)
444 
445  def __contains__(cls, key):
446  return key in cls._registry
447 
448  def keys(cls):
449  """Return an iterable containing all keys (including aliases).
450  """
451  return cls._registry.keys()
452 
453  def values(cls):
454  """Return an iterable of registered subclasses, with duplicates
455  corresponding to any aliases.
456  """
457  return cls._registry.values()
458 
459  def items(cls):
460  """Return an iterable of (key, subclass) pairs.
461  """
462  return cls._registry.items()
463 
464  def get(cls, key, default=None):
465  """Return the subclass associated with the given key (including
466  aliases), or ``default`` if the key is not recognized.
467  """
468  return cls._registry.get(key, default)
def isAttributeSafeToTransfer(name, value)
Definition: wrappers.py:45
def __call__(cls, args, kwds)
Definition: wrappers.py:280
def __subclasscheck__(cls, subclass)
Definition: wrappers.py:303
daf::base::PropertySet * set
Definition: fits.cc:818
def continueClass(cls)
Definition: wrappers.py:59
def alias(cls, key, subclass)
Definition: wrappers.py:416
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:129
def __new__(cls, name, bases, attrs)
Definition: wrappers.py:248
def __instancecheck__(cls, instance)
Definition: wrappers.py:313
def register(cls, key, subclass)
Definition: wrappers.py:331
def get(cls, key, default=None)
Definition: wrappers.py:464
def inClass(cls, name=None)
Definition: wrappers.py:96