Source code for Cauldron.utils.helpers
# -*- coding: utf-8 -*-
import abc
import textwrap
import re
import inspect
from ..exc import CauldronAPINotImplemented, CauldronAPINotImplementedWarning
try:
from astropy.utils.decorators import wraps
except ImportError:
from functools import wraps
__all__ = ['api_not_implemented', 'api_not_required', 'api_required']
def _inherited_docstring(*clses):
"""Make a class inherit docstrings."""
for cls in clses:
for bcls in inspect.getmro(cls):
if ('__doc__' in bcls.__dict__ and
bcls.__dict__['__doc__'] is not None and
bcls.__dict__['__doc__'].strip() != ''):
return bcls.__dict__['__doc__']
else:
raise ValueError("Can't find an inheritable docstring for {0!r}".format(cls))
def _docstring_left_indent(docstring):
"""Compute the left indent from a docstring."""
lines = docstring.expandtabs().splitlines()
if len(lines) > 1:
matches = [ re.search('(\S)', line) for line in lines[1:] ]
try:
left_indent = min(match.start() for match in matches if match)
except ValueError:
raise ValueError("Malformed docstring '{0:s}'".format(docstring))
else:
left_indent = 0
return left_indent
def _append_to_docstring(docstring, text):
"""Append `text` to `docstring` preserving indentation rules."""
left_indent = _docstring_left_indent(docstring)
newlines = [''] + text.splitlines()
newlines = [ (' ' * left_indent) + newline for newline in newlines ]
docstring += "\n" + "\n".join(newlines)
return docstring
def _prepend_to_docstring(docstring, text):
"""Prepend `text` to `docstring` preserving indentation rules."""
left_indent = _docstring_left_indent(docstring) * ' '
newlines = [''] + text.splitlines()
newlines = [ left_indent + newline for newline in newlines ]
docstring = "\n" + "\n".join(newlines) + ("\n" + left_indent) * 2 + docstring
return docstring
[docs]def api_not_implemented(func):
"""A decorator to correctly set the API not implemented."""
func.__doc__ = _append_to_docstring(func.__doc__, textwrap.dedent("""
.. warning:: `{0}` is not implemented in the Cauldron version of the KTL API.
""".format(func.__name__)))
@wraps(func)
def api_not_implemented(*args, **kwargs):
"""Sub-function to handle API not implmeneted."""
raise CauldronAPINotImplemented("The Cauldron API does not support '{0}'.".format(func.__name__))
return api_not_implemented
[docs]def api_not_required(func):
"""A decorator to mark a function as implementing a not-required API"""
func.__doc__ = _append_to_docstring(func.__doc__, textwrap.dedent("""
.. note:: Cauldron backends are not required to implement this function. If the do not, it will raise an :exc:`CauldronAPINotImplemented` error.
"""))
@wraps(func)
def api_not_required(*args, **kwargs):
"""Sub-function to handle API not implmeneted."""
raise CauldronAPINotImplemented("The Cauldron API does require support of '{0}'.".format(func.__name__))
return api_not_required
[docs]def api_required(func):
"""A decorator to mark a function as abstract and requiring a backend implementation."""
func.__doc__ = _append_to_docstring(func.__doc__, textwrap.dedent("""
*This is an abstract method. Backends must implement this method*
"""))
return abc.abstractmethod(func)
def api_override(func):
"""A decorator to mark a function as something subclasses can override."""
func.__doc__ = _append_to_docstring(func.__doc__, textwrap.dedent("""
*This method can be overridden to provide specific behavior in user subclasses.*
"""))
return func
class _Setting(object):
"""A settings object, which can be passed around by value."""
def __init__(self, name, value, lock=None):
super(_Setting, self).__init__()
self.name = name
self.__value = value
self._lock = lock
@property
def value(self):
"""Setting's boolean value"""
return self.__value
@value.setter
def value(self, new_value):
"""Set the new value."""
if self._lock is not None and self._lock:
raise RuntimeError("Setting '{0}' is locked by '{1}', can't change value.".format(self.name, self._lock.name))
self.__value = bool(new_value)
@property
def inverse(self):
"""A setting which is always the inverse of this setting."""
return _SettingInverse(self)
def __invert__(self):
"""Handle inversion."""
return _SettingInverse(self)
def __repr__(self):
"""Represent this value"""
return "<Setting {0}={1}>".format(self.name, self.value)
def __bool__(self):
"""Cast this setting to it's own boolean value."""
return bool(self.value)
def __nonzero__(self):
"""Cast setting to a boolean value (Python 2)"""
return self.__bool__()
def on(self):
"""Turn this setting on."""
self.value = True
def off(self):
"""Turn this setting off."""
self.value = False
class _SettingInverse(_Setting):
"""The inverse of a setting object."""
def __init__(self, setting):
self.__setting = setting
super(_SettingInverse, self).__init__(name = "~{0}".format(setting.name), value = not setting, lock=setting._lock)
@property
def value(self):
"""Get this setting's value."""
return not self.__setting.value
@value.setter
def value(self, new_value):
"""Set this setting's value."""
self.__setting.value = not new_value