Skip to content

Support passing function to Appender #22927

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion pandas/tests/util/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from pandas.compat import intern, PY3
import pandas.core.common as com
from pandas.util._move import move_into_mutable_buffer, BadMove, stolenbuf
from pandas.util._decorators import deprecate_kwarg, make_signature
from pandas.util._decorators import (deprecate_kwarg, make_signature,
Appender, Substitution)
from pandas.util._validators import (validate_args, validate_kwargs,
validate_args_and_kwargs,
validate_bool_kwarg)
Expand Down Expand Up @@ -531,3 +532,42 @@ def test_safe_import(monkeypatch):
monkeypatch.setitem(sys.modules, mod_name, mod)
assert not td.safe_import(mod_name, min_version="2.0")
assert td.safe_import(mod_name, min_version="1.0")


class TestAppender(object):
def test_pass_callable(self):
# GH#22927

def func():
"""foo"""
return

@Appender(func)
def wrapped():
return

assert wrapped.__doc__ == "foo"

def test_append_class(self):
# GH#22927

@Appender("bar")
class cls(object):
pass

assert cls.__doc__ == "bar"
assert cls.__name__ == "cls"
assert cls.__module__ == "pandas.tests.util.test_util"


class TestSubstitution(object):
def test_substitute_class(self):
# GH#22927

@Substitution(name="Bond, James Bond")
class cls(object):
"""%(name)s"""

assert cls.__doc__ == "Bond, James Bond"
assert cls.__name__ == "cls"
assert cls.__module__ == "pandas.tests.util.test_util"
48 changes: 42 additions & 6 deletions pandas/util/_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ def __init__(self, *args, **kwargs):
self.params = args or kwargs

def __call__(self, func):
func.__doc__ = func.__doc__ and func.__doc__ % self.params
return func
new_doc = func.__doc__ and func.__doc__ % self.params
return _set_docstring(func, new_doc)

def update(self, *args, **kwargs):
"""
Expand Down Expand Up @@ -290,18 +290,54 @@ def my_dog(has='fleas'):
"""

def __init__(self, addendum, join='', indents=0):
if callable(addendum):
# allow for passing @Appender(func) instead of
# @Appender(func.__doc__), both more succinct and helpful when
# -oo optimization strips docstrings
addendum = addendum.__doc__ or ''

if indents > 0:
self.addendum = indent(addendum, indents=indents)
else:
self.addendum = addendum
self.join = join

def __call__(self, func):
func.__doc__ = func.__doc__ if func.__doc__ else ''
doc = func.__doc__ if func.__doc__ else ''
self.addendum = self.addendum if self.addendum else ''
docitems = [func.__doc__, self.addendum]
func.__doc__ = dedent(self.join.join(docitems))
return func
docitems = [doc, self.addendum]
new_doc = dedent(self.join.join(docitems))

return _set_docstring(func, new_doc)


def _set_docstring(obj, docstring):
"""
Set the docstring for the given function or class

Parameters
----------
obj : function, method, class
docstring : str

Returns
-------
same type as obj
"""
if isinstance(obj, type):
# i.e. decorating a class, for which docstrings can not be edited

class Wrapped(obj):
__doc__ = docstring

Wrapped.__name__ = obj.__name__
Wrapped.__module__ = obj.__module__
# TODO: will this induce a perf penalty in MRO lookups?
return Wrapped

else:
obj.__doc__ = docstring
return obj


def indent(text, indents=1):
Expand Down