LSST Applications  21.0.0-142-gef555c1e+42c9bccae2,22.0.0+052faf71bd,22.0.0+1c4650f311,22.0.0+40ce427c77,22.0.0+5b6c068b1a,22.0.0+7589c3a021,22.0.0+81ed51be6d,22.0.1-1-g7d6de66+6cae67f2c6,22.0.1-1-g87000a6+314cd8b7ea,22.0.1-1-g8760c09+052faf71bd,22.0.1-1-g8e32f31+5b6c068b1a,22.0.1-10-g779eefa+a163f08322,22.0.1-12-g3bd7ecb+bbeacc25a9,22.0.1-15-g63cc0c1+2a7037787d,22.0.1-17-ge5a99e88+3d2c1afe2e,22.0.1-19-g88addfe+6cae67f2c6,22.0.1-2-g1cb3e5b+84de06d286,22.0.1-2-g8ef0a89+6cae67f2c6,22.0.1-2-g92698f7+1c4650f311,22.0.1-2-ga9b0f51+052faf71bd,22.0.1-2-gb66926d+5b6c068b1a,22.0.1-2-gcb770ba+0723a13595,22.0.1-2-ge470956+ff9f1dc8d5,22.0.1-22-g608e23ac+2ac85e833c,22.0.1-29-g184b6e44e+8b185d4e2d,22.0.1-3-g59f966b+11ba4df19d,22.0.1-3-g8c1d971+f90df4c6d0,22.0.1-3-g997b569+d69a7aa2f8,22.0.1-3-gaaec9c0+4d194bf81c,22.0.1-4-g1930a60+283d9d2f1a,22.0.1-4-g5b7b756+c1283a92b8,22.0.1-4-g8623105+6cae67f2c6,22.0.1-7-gba73697+283d9d2f1a,22.0.1-8-g47d23f5+43acea82f3,master-g5f2689bdc5+40ce427c77,w.2021.38
LSST Data Management Base Package
Public Member Functions | List of all members
lsst.utils.wrappers.TemplateMeta Class Reference
Inheritance diagram for lsst.utils.wrappers.TemplateMeta:
lsst.afw.cameraGeom._detectorCollection.DetectorCollectionBase lsst.afw.image.exposure._exposureContinued.Exposure lsst.afw.image.image._image.DecoratedImage lsst.afw.image.image._image.Image lsst.afw.image.image._mask.Mask lsst.afw.image.maskedImage._maskedImageContinued.MaskedImage lsst.afw.table._aggregates.CovarianceMatrixKey lsst.afw.table._aggregates.PointKey lsst.afw.table._arrays.ArrayKey lsst.afw.table._base.Catalog lsst.afw.table._schema.Field lsst.afw.table._schema.Key lsst.afw.table._schema.SchemaItem lsst.afw.typehandling._GenericMap.AutoKeyMeta lsst.afw.typehandling._GenericMap.GenericMap lsst.geom._coordinates.CoordinateExpr lsst.geom._coordinates.Extent lsst.geom._coordinates.Point

Public Member Functions

def __new__ (cls, name, bases, attrs)
 
def __call__ (cls, *args, **kwds)
 
def __subclasscheck__ (cls, subclass)
 
def __instancecheck__ (cls, instance)
 
def __subclasses__ (cls)
 
def register (cls, key, subclass)
 
def alias (cls, key, subclass)
 
def __getitem__ (cls, key)
 
def __iter__ (cls)
 
def __len__ (cls)
 
def __contains__ (cls, key)
 
def keys (cls)
 
def values (cls)
 
def items (cls)
 
def get (cls, key, default=None)
 

Detailed Description

A metaclass for abstract base classes that tie together wrapped C++
template types.

C++ template classes are most easily wrapped with a separate Python class
for each template type, which results in an unnatural Python interface.
TemplateMeta provides a thin layer that connects these Python classes by
giving them a common base class and acting as a factory to construct them
in a consistent way.

To use, simply create a new class with the name of the template class, and
use ``TemplateMeta`` as its metaclass, and then call ``register`` on each
of its subclasses.  This registers the class with a "type key" - usually a
Python representation of the C++ template types.  The type key must be a
hashable object - strings, type objects, and tuples of these (for C++
classes with multiple template parameters) are good choices.  Alternate
type keys for existing classes can be added by calling ``alias``, but only
after a subclass already been registered with a "primary" type key.  For
example::

.. code-block:: python

    import numpy as np
    from ._image import ImageF, ImageD

    class Image(metaclass=TemplateMeta):
        pass

    Image.register(np.float32, ImageF)
    Image.register(np.float64, ImageD)
    Image.alias("F", ImageF)
    Image.alias("D", ImageD)

We have intentionally used ``numpy`` types as the primary keys for these
objects in this example, with strings as secondary aliases simply because
the primary key is added as a ``dtype`` attribute on the the registered
classes (so ``ImageF.dtype == numpy.float32`` in the above example).

This allows user code to construct objects directly using ``Image``, as
long as an extra ``dtype`` keyword argument is passed that matches one of
the type keys::

.. code-block:: python

    img = Image(52, 64, dtype=np.float32)

This simply forwards additional positional and keyword arguments to the
wrapped template class's constructor.

The choice of "dtype" as the name of the template parameter is also
configurable, and in fact multiple template parameters are also supported,
by setting a ``TEMPLATE_PARAMS`` class attribute on the ABC to a tuple
containing the names of the template parameters.  A ``TEMPLATE_DEFAULTS``
attribute can also be defined to a tuple of the same length containing
default values for the template parameters, allowing them to be omitted in
constructor calls.  When the length of these attributes is more than one,
the type keys passed to ``register`` and ``alias`` should be tuple of the
same length; when the length of these attributes is one, type keys should
generally not be tuples.

As an aid for those writing the Python wrappers for C++ classes,
``TemplateMeta`` also provides a way to add pure-Python methods and other
attributes to the wrapped template classes.  To add a ``sum`` method to
all registered types, for example, we can just do::

.. code-block:: python

    class Image(metaclass=TemplateMeta):

        def sum(self):
            return np.sum(self.getArray())

    Image.register(np.float32, ImageF)
    Image.register(np.float64, ImageD)

.. note::

    ``TemplateMeta`` works by overriding the ``__instancecheck__`` and
    ``__subclasscheck__`` special methods, and hence does not appear in
    its registered subclasses' method resolution order or ``__bases__``
    attributes. That means its attributes are not inherited by registered
    subclasses. Instead, attributes added to an instance of
    ``TemplateMeta`` are *copied* into the types registered with it. These
    attributes will thus *replace* existing attributes in those classes
    with the same name, and subclasses cannot delegate to base class
    implementations of these methods.

Finally, abstract base classes that use ``TemplateMeta`` define a dict-
like interface for accessing their registered subclasses, providing
something like the C++ syntax for templates::

.. code-block:: python

    Image[np.float32] -> ImageF
    Image["D"] -> ImageD

Both primary dtypes and aliases can be used as keys in this interface,
which means types with aliases will be present multiple times in the dict.
To obtain the sequence of unique subclasses, use the ``__subclasses__``
method.

.. warning::

    Python's built-in `super` function does not behave properly in classes
    that have `TemplateMeta` as their metaclass (which should be rare, as
    TemplateMeta ABCs will have base classes of their own)..

Definition at line 152 of file wrappers.py.

Member Function Documentation

◆ __call__()

def lsst.utils.wrappers.TemplateMeta.__call__ (   cls,
args,
**  kwds 
)

Reimplemented in lsst.afw.typehandling._GenericMap.AutoKeyMeta.

Definition at line 293 of file wrappers.py.

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 
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174

◆ __contains__()

def lsst.utils.wrappers.TemplateMeta.__contains__ (   cls,
  key 
)

Definition at line 458 of file wrappers.py.

458  def __contains__(cls, key):
459  return key in cls._registry
460 

◆ __getitem__()

def lsst.utils.wrappers.TemplateMeta.__getitem__ (   cls,
  key 
)

Reimplemented in lsst.afw.table._base.Catalog, and lsst.afw.cameraGeom._detectorCollection.DetectorCollectionBase.

Definition at line 449 of file wrappers.py.

449  def __getitem__(cls, key):
450  return cls._registry[key]
451 

◆ __instancecheck__()

def lsst.utils.wrappers.TemplateMeta.__instancecheck__ (   cls,
  instance 
)

Definition at line 326 of file wrappers.py.

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 
table::Key< int > type
Definition: Detector.cc:163

◆ __iter__()

def lsst.utils.wrappers.TemplateMeta.__iter__ (   cls)

Reimplemented in lsst.afw.cameraGeom._detectorCollection.DetectorCollectionBase.

Definition at line 452 of file wrappers.py.

452  def __iter__(cls):
453  return iter(cls._registry)
454 

◆ __len__()

def lsst.utils.wrappers.TemplateMeta.__len__ (   cls)

Definition at line 455 of file wrappers.py.

455  def __len__(cls):
456  return len(cls._registry)
457 

◆ __new__()

def lsst.utils.wrappers.TemplateMeta.__new__ (   cls,
  name,
  bases,
  attrs 
)

Definition at line 261 of file wrappers.py.

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 
def isAttributeSafeToTransfer(name, value)
Definition: wrappers.py:45

◆ __subclasscheck__()

def lsst.utils.wrappers.TemplateMeta.__subclasscheck__ (   cls,
  subclass 
)

Definition at line 316 of file wrappers.py.

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 

◆ __subclasses__()

def lsst.utils.wrappers.TemplateMeta.__subclasses__ (   cls)
Return a tuple of all classes that inherit from this class.

Definition at line 336 of file wrappers.py.

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 
daf::base::PropertySet * set
Definition: fits.cc:912

◆ alias()

def lsst.utils.wrappers.TemplateMeta.alias (   cls,
  key,
  subclass 
)
Add an alias that allows an existing subclass to be accessed with a
different key.

Definition at line 429 of file wrappers.py.

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 

◆ get()

def lsst.utils.wrappers.TemplateMeta.get (   cls,
  key,
  default = None 
)
Return the subclass associated with the given key (including
aliases), or ``default`` if the key is not recognized.

Definition at line 477 of file wrappers.py.

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)

◆ items()

def lsst.utils.wrappers.TemplateMeta.items (   cls)
Return an iterable of (key, subclass) pairs.

Definition at line 472 of file wrappers.py.

472  def items(cls):
473  """Return an iterable of (key, subclass) pairs.
474  """
475  return cls._registry.items()
476 
std::vector< SchemaItem< Flag > > * items

◆ keys()

def lsst.utils.wrappers.TemplateMeta.keys (   cls)
Return an iterable containing all keys (including aliases).

Definition at line 461 of file wrappers.py.

461  def keys(cls):
462  """Return an iterable containing all keys (including aliases).
463  """
464  return cls._registry.keys()
465 

◆ register()

def lsst.utils.wrappers.TemplateMeta.register (   cls,
  key,
  subclass 
)
Register a subclass of this ABC with the given key (a string,
number, type, or other hashable).

Register may only be called once for a given key or a given subclass.

Definition at line 344 of file wrappers.py.

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 

◆ values()

def lsst.utils.wrappers.TemplateMeta.values (   cls)
Return an iterable of registered subclasses, with duplicates
corresponding to any aliases.

Definition at line 466 of file wrappers.py.

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 

The documentation for this class was generated from the following file: