__all__ = ["docstring", "combomethod"]
import functools
[docs]def docstring(documentation, prepend=False, join=""):
r"""Prepend or append a string to the current documentation of the function.
This decorator should be robust even if ``func.__doc__`` is None
(for example, if -OO was passed to the interpreter).
Usage::
@docstring('Appended this line')
def func():
"This docstring will have a line below."
pass
>>> print(func.__doc__)
This docstring will have a line below.
Appended this line
Args:
documentation (str): Documentation string that should be added,
appended or prepended to the current documentation string.
prepend (bool): Prepend the documentation string to the current
documentation if ``True`` else append. default=``False``
join (str): String used to separate docstrings. default='\n'
"""
def decorator(func):
current = (func.__doc__ if func.__doc__ else "").strip()
doc = documentation.strip()
new = "\n".join(
[doc, join, current] if prepend else [current, join, doc]
)
lines = len(new.strip().splitlines())
if lines == 1:
# If it's a one liner keep it that way and strip whitespace
func.__doc__ = new.strip()
else:
# Else strip whitespace from the beginning and add a newline
# at the end
func.__doc__ = new.strip() + "\n"
return func
return decorator
class ComboMethodError(Exception):
"""Inappropriate initialization of the combo method descriptor."""
pass
class ComboMethod(object):
"""Combo method descriptor.
Descriptor is initializes eater by passing in the staticmethod or the
class method. After that an instance method can be added using the instance
decorator.
This class should never be used. Use the ``combomethod`` function instead.
"""
def __init__(self, staticmethod=None, classmethod=None):
if staticmethod is None and classmethod is None:
raise ComboMethodError(
"Either static method or class method has to be provided"
)
self.staticmethod = staticmethod
self.classmethod = classmethod
self.instancemethod = None
def instance(self, instancemethod):
self.instancemethod = instancemethod
return self
def __get__(self, obj, type=None):
if obj is None:
if self.staticmethod is not None:
return self.staticmethod
else:
@functools.wraps(self.classmethod)
def wrapper(*args, **kwargs):
return self.classmethod(type, *args, **kwargs)
return wrapper
else:
if self.instancemethod is None:
raise ComboMethodError("Instance method is not provided")
@functools.wraps(self.instancemethod)
def wrapper(*args, **kwargs):
return self.instancemethod(obj, *args, **kwargs)
return wrapper
[docs]def combomethod(method=None, static=False):
"""Create a class method or static method.
It will be used when you call it on the class but can be overridden by an
instance method of the same name that will be called when the method is
called on the instance.
Usage::
class Foo(object):
class_variable = 2
def __init__(self):
self.instance_variable = 3
# Override class variable for test case
self.class_variable = 4
@combomethod(static=True)
def static_and_instance(x):
return x + 1
@static_and_instance.instance
def static_and_instance(self, x):
return x + self.instance_variable
@combomethod
def class_and_instance(cls, x):
return x + cls.class_variable
@class_and_instance.instance
def class_and_instance(self, x):
return x + self.instance_variable
>>> Foo.static_and_instance(100)
101
>>> Foo.class_and_instance(100)
102
>>> f = Foo()
>>> f.static_and_instance(100)
103
>>> f.class_and_instance(100)
103
"""
return ComboMethod if static else ComboMethod(None, method)