LSSTApplications  16.0-10-g0ee56ad+4,16.0-11-ga33d1f2+4,16.0-12-g3ef5c14+2,16.0-12-g71e5ef5+17,16.0-12-gbdf3636+2,16.0-13-g118c103+2,16.0-13-g8f68b0a+2,16.0-15-gbf5c1cb+3,16.0-16-gfd17674+2,16.0-17-g7c01f5c+2,16.0-18-g0a50484,16.0-20-ga20f992+7,16.0-21-g0e05fd4+5,16.0-21-g15e2d33+3,16.0-22-g62d8060+3,16.0-22-g847a80f+3,16.0-25-gf00d9b8,16.0-28-g3990c221+3,16.0-3-gf928089+2,16.0-32-g88a4f23+4,16.0-34-gd7987ad+2,16.0-37-gc7333cb+1,16.0-4-g10fc685+1,16.0-4-g18f3627+25,16.0-4-g5f3a788+25,16.0-5-gaf5c3d7+3,16.0-5-gcc1f4bb,16.0-6-g3b92700+3,16.0-6-g4412fcd+2,16.0-6-g7235603+3,16.0-69-g2562ce1b+1,16.0-7-g0913a87,16.0-8-g14ebd58+3,16.0-8-g2df868b,16.0-8-g4cec79c+5,16.0-8-gadf6c7a,16.0-82-g59ec2a54a,16.0-9-g5400cdc+1,16.0-9-ge6233d7+4,master-g2880f2d8cf+2,v17.0.rc1
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  .. warning::
83 
84  Python's built-in `super` function does not behave properly in classes
85  decorated with `continueClass`. Base class methods must be invoked
86  directly using their explicit types instead.
87 
88  """
89  orig = getattr(sys.modules[cls.__module__], cls.__name__)
90  for name in dir(cls):
91  # Common descriptors like classmethod and staticmethod can only be
92  # accessed without invoking their magic if we use __dict__; if we use
93  # getattr on those we'll get e.g. a bound method instance on the dummy
94  # class rather than a classmethod instance we can put on the target
95  # class.
96  attr = cls.__dict__.get(name, None) or getattr(cls, name)
97  if isAttributeSafeToTransfer(name, attr):
98  setattr(orig, name, attr)
99  return orig
100 
101 
102 def inClass(cls, name=None):
103  """Add the decorated function to the given class as a method.
104 
105  For example::
106 
107  .. code-block:: python
108 
109  class Foo:
110  pass
111 
112  @inClass(Foo)
113  def run(self):
114  return None
115 
116  is equivalent to::
117 
118  .. code-block:: python
119 
120  class Foo:
121  def run(self):
122  return None
123 
124  Standard decorators like ``classmethod``, ``staticmethod``, and
125  ``property`` may be used *after* this decorator. Custom decorators
126  may only be used if they return an object with a ``__name__`` attribute
127  or the ``name`` optional argument is provided.
128  """
129  def decorate(func):
130  # Using 'name' instead of 'name1' breaks the closure because
131  # assignment signals a strictly local variable.
132  name1 = name
133  if name1 is None:
134  if hasattr(func, "__name__"):
135  name1 = func.__name__
136  else:
137  if hasattr(func, "__func__"):
138  # classmethod and staticmethod have __func__ but no __name__
139  name1 = func.__func__.__name__
140  elif hasattr(func, "fget"):
141  # property has fget but no __name__
142  name1 = func.fget.__name__
143  else:
144  raise ValueError(
145  "Could not guess attribute name for '{}'.".format(func)
146  )
147  setattr(cls, name1, func)
148  return func
149  return decorate
150 
151 
153  """A metaclass for abstract base classes that tie together wrapped C++
154  template types.
155 
156  C++ template classes are most easily wrapped with a separate Python class
157  for each template type, which results in an unnatural Python interface.
158  TemplateMeta provides a thin layer that connects these Python classes by
159  giving them a common base class and acting as a factory to construct them
160  in a consistent way.
161 
162  To use, simply create a new class with the name of the template class, and
163  use ``TemplateMeta`` as its metaclass, and then call ``register`` on each
164  of its subclasses. This registers the class with a "type key" - usually a
165  Python representation of the C++ template types. The type key must be a
166  hashable object - strings, type objects, and tuples of these (for C++
167  classes with multiple template parameters) are good choices. Alternate
168  type keys for existing classes can be added by calling ``alias``, but only
169  after a subclass already been registered with a "primary" type key. For
170  example::
171 
172  .. code-block:: python
173 
174  import numpy as np
175  from ._image import ImageF, ImageD
176 
177  class Image(metaclass=TemplateMeta):
178  pass
179 
180  Image.register(np.float32, ImageF)
181  Image.register(np.float64, ImageD)
182  Image.alias("F", ImageF)
183  Image.alias("D", ImageD)
184 
185  We have intentionally used ``numpy`` types as the primary keys for these
186  objects in this example, with strings as secondary aliases simply because
187  the primary key is added as a ``dtype`` attribute on the the registered
188  classes (so ``ImageF.dtype == numpy.float32`` in the above example).
189 
190  This allows user code to construct objects directly using ``Image``, as
191  long as an extra ``dtype`` keyword argument is passed that matches one of
192  the type keys::
193 
194  .. code-block:: python
195 
196  img = Image(52, 64, dtype=np.float32)
197 
198  This simply forwards additional positional and keyword arguments to the
199  wrapped template class's constructor.
200 
201  The choice of "dtype" as the name of the template parameter is also
202  configurable, and in fact multiple template parameters are also supported,
203  by setting a ``TEMPLATE_PARAMS`` class attribute on the ABC to a tuple
204  containing the names of the template parameters. A ``TEMPLATE_DEFAULTS``
205  attribute can also be defined to a tuple of the same length containing
206  default values for the template parameters, allowing them to be omitted in
207  constructor calls. When the length of these attributes is more than one,
208  the type keys passed to ``register`` and ``alias`` should be tuple of the
209  same length; when the length of these attributes is one, type keys should
210  generally not be tuples.
211 
212  As an aid for those writing the Python wrappers for C++ classes,
213  ``TemplateMeta`` also provides a way to add pure-Python methods and other
214  attributes to the wrapped template classes. To add a ``sum`` method to
215  all registered types, for example, we can just do::
216 
217  .. code-block:: python
218 
219  class Image(metaclass=TemplateMeta):
220 
221  def sum(self):
222  return np.sum(self.getArray())
223 
224  Image.register(np.float32, ImageF)
225  Image.register(np.float64, ImageD)
226 
227  .. note::
228 
229  ``TemplateMeta`` works by overriding the ``__instancecheck__`` and
230  ``__subclasscheck__`` special methods, and hence does not appear in
231  its registered subclasses' method resolution order or ``__bases__``
232  attributes. That means its attributes are not inherited by registered
233  subclasses. Instead, attributes added to an instance of
234  ``TemplateMeta`` are *copied* into the types registered with it. These
235  attributes will thus *replace* existing attributes in those classes
236  with the same name, and subclasses cannot delegate to base class
237  implementations of these methods.
238 
239  Finally, abstract base classes that use ``TemplateMeta`` define a dict-
240  like interface for accessing their registered subclasses, providing
241  something like the C++ syntax for templates::
242 
243  .. code-block:: python
244 
245  Image[np.float32] -> ImageF
246  Image["D"] -> ImageD
247 
248  Both primary dtypes and aliases can be used as keys in this interface,
249  which means types with aliases will be present multiple times in the dict.
250  To obtain the sequence of unique subclasses, use the ``__subclasses__``
251  method.
252 
253  .. warning::
254 
255  Python's built-in `super` function does not behave properly in classes
256  that have `TemplateMeta` as their metaclass (which should be rare, as
257  TemplateMeta ABCs will have base classes of their own)..
258 
259  """
260 
261  def __new__(cls, name, bases, attrs):
262  # __new__ is invoked when the abstract base class is defined (via a
263  # class statement). We save a dict of class attributes (including
264  # methods) that were defined in the class body so we can copy them
265  # to registered subclasses later.
266  # We also initialize an empty dict to store the registered subclasses.
267  attrs["_inherited"] = {k: v for k, v in attrs.items()
268  if isAttributeSafeToTransfer(k, v)}
269  # The special "TEMPLATE_PARAMS" class attribute, if defined, contains
270  # names of the template parameters, which we use to set those
271  # attributes on registered subclasses and intercept arguments to the
272  # constructor. This line removes it from the dict of things that
273  # should be inherited while setting a default of 'dtype' if it's not
274  # defined.
275  attrs["TEMPLATE_PARAMS"] = \
276  attrs["_inherited"].pop("TEMPLATE_PARAMS", ("dtype",))
277  attrs["TEMPLATE_DEFAULTS"] = \
278  attrs["_inherited"].pop("TEMPLATE_DEFAULTS",
279  (None,)*len(attrs["TEMPLATE_PARAMS"]))
280  attrs["_registry"] = dict()
281  self = type.__new__(cls, name, bases, attrs)
282 
283  if len(self.TEMPLATE_PARAMS) == 0:
284  raise ValueError(
285  "TEMPLATE_PARAMS must be a tuple with at least one element."
286  )
287  if len(self.TEMPLATE_DEFAULTS) != len(self.TEMPLATE_PARAMS):
288  raise ValueError(
289  "TEMPLATE_PARAMS and TEMPLATE_DEFAULTS must have same length."
290  )
291  return self
292 
293  def __call__(cls, *args, **kwds):
294  # __call__ is invoked when someone tries to construct an instance of
295  # the abstract base class.
296  # If the ABC defines a "TEMPLATE_PARAMS" attribute, we use those strings
297  # as the kwargs we should intercept to find the right type.
298  # Generate a type mapping key from input keywords. If the type returned
299  # from the keyword lookup is a numpy dtype object, fetch the underlying
300  # type of the dtype
301  key = []
302  for p, d in zip(cls.TEMPLATE_PARAMS, cls.TEMPLATE_DEFAULTS):
303  tempKey = kwds.pop(p, d)
304  if isinstance(tempKey, np.dtype):
305  tempKey = tempKey.type
306  key.append(tempKey)
307  key = tuple(key)
308 
309  # indices are only tuples if there are multiple elements
310  clz = cls._registry.get(key[0] if len(key) == 1 else key, None)
311  if clz is None:
312  d = {k: v for k, v in zip(cls.TEMPLATE_PARAMS, key)}
313  raise TypeError("No registered subclass for {}.".format(d))
314  return clz(*args, **kwds)
315 
316  def __subclasscheck__(cls, subclass):
317  # Special method hook for the issubclass built-in: we return true for
318  # any registered type or true subclass thereof.
319  if subclass in cls._registry:
320  return True
321  for v in cls._registry.values():
322  if issubclass(subclass, v):
323  return True
324  return False
325 
326  def __instancecheck__(cls, instance):
327  # Special method hook for the isinstance built-in: we return true for
328  # an instance of any registered type or true subclass thereof.
329  if type(instance) in cls._registry:
330  return True
331  for v in cls._registry.values():
332  if isinstance(instance, v):
333  return True
334  return False
335 
336  def __subclasses__(cls):
337  """Return a tuple of all classes that inherit from this class.
338  """
339  # This special method isn't defined as part of the Python data model,
340  # but it exists on builtins (including ABCMeta), and it provides useful
341  # functionality.
342  return tuple(set(cls._registry.values()))
343 
344  def register(cls, key, subclass):
345  """Register a subclass of this ABC with the given key (a string,
346  number, type, or other hashable).
347 
348  Register may only be called once for a given key or a given subclass.
349  """
350  if key is None:
351  raise ValueError("None may not be used as a key.")
352  if subclass in cls._registry.values():
353  raise ValueError(
354  "This subclass has already registered with another key; "
355  "use alias() instead."
356  )
357  if cls._registry.setdefault(key, subclass) != subclass:
358  if len(cls.TEMPLATE_PARAMS) == 1:
359  d = {cls.TEMPLATE_PARAMS[0]: key}
360  else:
361  d = {k: v for k, v in zip(cls.TEMPLATE_PARAMS, key)}
362  raise KeyError(
363  "Another subclass is already registered with {}".format(d)
364  )
365  # If the key used to register a class matches the default key,
366  # make the static methods available through the ABC
367  if cls.TEMPLATE_DEFAULTS:
368  defaults = (cls.TEMPLATE_DEFAULTS[0] if
369  len(cls.TEMPLATE_DEFAULTS) == 1 else
370  cls.TEMPLATE_DEFAULTS)
371  if key == defaults:
372  conflictStr = ("Base class has attribute {}"
373  " which is a {} method of {}."
374  " Cannot link method to base class.")
375  # In the following if statements, the explicit lookup in
376  # __dict__ must be done, as a call to getattr returns the
377  # bound method, which no longer reports as a static or class
378  # method. The static methods must be transfered to the ABC
379  # in this unbound state, so that python will still see them
380  # as static methods and not attempt to pass self. The class
381  # methods must be transfered to the ABC as a bound method
382  # so that the correct cls be called with the class method
383  for name in subclass.__dict__:
384  if name in ("__new__", "__init_subclass__"):
385  continue
386  obj = subclass.__dict__[name]
387  # copy over the static methods
388  isBuiltin = isinstance(obj, types.BuiltinFunctionType)
389  isStatic = isinstance(obj, staticmethod)
390  if isBuiltin or isStatic:
391  if hasattr(cls, name):
392  raise AttributeError(
393  conflictStr.format(name, "static", subclass))
394  setattr(cls, name, obj)
395  # copy over the class methods
396  elif isinstance(obj, classmethod):
397  if hasattr(cls, name):
398  raise AttributeError(
399  conflictStr.format(name, "class", subclass))
400  setattr(cls, name, getattr(subclass, name))
401 
402  def setattrSafe(name, value):
403  try:
404  currentValue = getattr(subclass, name)
405  if currentValue != value:
406  msg = ("subclass already has a '{}' attribute with "
407  "value {} != {}.")
408  raise ValueError(
409  msg.format(name, currentValue, value)
410  )
411  except AttributeError:
412  setattr(subclass, name, value)
413 
414  if len(cls.TEMPLATE_PARAMS) == 1:
415  setattrSafe(cls.TEMPLATE_PARAMS[0], key)
416  elif len(cls.TEMPLATE_PARAMS) == len(key):
417  for p, k in zip(cls.TEMPLATE_PARAMS, key):
418  setattrSafe(p, k)
419  else:
420  raise ValueError(
421  "key must have {} elements (one for each of {})".format(
422  len(cls.TEMPLATE_PARAMS), cls.TEMPLATE_PARAMS
423  )
424  )
425 
426  for name, attr in cls._inherited.items():
427  setattr(subclass, name, attr)
428 
429  def alias(cls, key, subclass):
430  """Add an alias that allows an existing subclass to be accessed with a
431  different key.
432  """
433  if key is None:
434  raise ValueError("None may not be used as a key.")
435  if key in cls._registry:
436  raise KeyError("Cannot multiply-register key {}".format(key))
437  primaryKey = tuple(getattr(subclass, p, None)
438  for p in cls.TEMPLATE_PARAMS)
439  if len(primaryKey) == 1:
440  # indices are only tuples if there are multiple elements
441  primaryKey = primaryKey[0]
442  if cls._registry.get(primaryKey, None) != subclass:
443  raise ValueError("Subclass is not registered with this base class.")
444  cls._registry[key] = subclass
445 
446  # Immutable mapping interface defined below. We don't use collections
447  # mixins because we don't want their comparison operators.
448 
449  def __getitem__(cls, key):
450  return cls._registry[key]
451 
452  def __iter__(cls):
453  return iter(cls._registry)
454 
455  def __len__(cls):
456  return len(cls._registry)
457 
458  def __contains__(cls, key):
459  return key in cls._registry
460 
461  def keys(cls):
462  """Return an iterable containing all keys (including aliases).
463  """
464  return cls._registry.keys()
465 
466  def values(cls):
467  """Return an iterable of registered subclasses, with duplicates
468  corresponding to any aliases.
469  """
470  return cls._registry.values()
471 
472  def items(cls):
473  """Return an iterable of (key, subclass) pairs.
474  """
475  return cls._registry.items()
476 
477  def get(cls, key, default=None):
478  """Return the subclass associated with the given key (including
479  aliases), or ``default`` if the key is not recognized.
480  """
481  return cls._registry.get(key, default)
def isAttributeSafeToTransfer(name, value)
Definition: wrappers.py:45
def __call__(cls, args, kwds)
Definition: wrappers.py:293
def __subclasscheck__(cls, subclass)
Definition: wrappers.py:316
daf::base::PropertySet * set
Definition: fits.cc:832
def continueClass(cls)
Definition: wrappers.py:59
def alias(cls, key, subclass)
Definition: wrappers.py:429
table::Key< int > type
Definition: Detector.cc:164
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:168
def __new__(cls, name, bases, attrs)
Definition: wrappers.py:261
def __instancecheck__(cls, instance)
Definition: wrappers.py:326
def register(cls, key, subclass)
Definition: wrappers.py:344
def get(cls, key, default=None)
Definition: wrappers.py:477
def inClass(cls, name=None)
Definition: wrappers.py:102