Repository: sunpy/sunpy

Issue:
Decorator to be able to prepend and/or append to doc strings
Once #2369 is merged in we have under `sunpy/sun/sun.py`, a decorator (called `add_parameter_docs`) that we use to append to the functions under `sun.py` as they all take the same parameter input.

We would like this to moved under to `sunpy/util/decorators.py` where we can import this function more generally as well as let it take arguments allowing us to pass in a prepend and/or append argument to the decorator that would add it to a decorated function's docstring. Maybe `.format` on the docstring if a dictionary is provided as an argument to the decorator.

Inspiration can come from https://github.com/pandas-dev/pandas/blob/master/pandas/core/generic.py#L477 


Discussion:

Resolving patch:
diff --git a/sunpy/sun/sun.py b/sunpy/sun/sun.py
--- a/sunpy/sun/sun.py
+++ b/sunpy/sun/sun.py
@@ -17,6 +17,8 @@
 from sunpy.sun import constants
 from sunpy.util.decorators import deprecated
 from sunpy.extern import six
+from sunpy.util.decorators import add_common_docstring
+
 
 __all__ = [
     "print_params", "heliographic_solar_center", "solar_north", "apparent_declination",
@@ -58,13 +60,7 @@
 """
 
 
-def add_parameter_docs(f):
-    if isinstance(f.__doc__, six.string_types):
-        f.__doc__ += PARAMETER_DOCS
-    return f
-
-
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def solar_cycle_number(t='now'):
     """
     Return the solar cycle number.
@@ -75,7 +71,7 @@ def solar_cycle_number(t='now'):
     return result
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def solar_semidiameter_angular_size(t='now'):
     r"""
     Return the angular size of the semi-diameter of the Sun as
@@ -98,7 +94,7 @@ def solar_semidiameter_angular_size(t='now'):
     return Angle(solar_semidiameter_rad.to(u.arcsec, equivalencies=u.dimensionless_angles()))
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def position(t='now'):
     """
     Returns the position of the Sun (right ascension and declination)
@@ -110,7 +106,7 @@ def position(t='now'):
     return ra, dec
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def eccentricity_SunEarth_orbit(t='now'):
     """
     Returns the eccentricity of the Sun Earth Orbit.
@@ -121,7 +117,7 @@ def eccentricity_SunEarth_orbit(t='now'):
     return result
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def mean_ecliptic_longitude(t='now'):
     """
     Returns the mean ecliptic longitude.
@@ -133,13 +129,13 @@ def mean_ecliptic_longitude(t='now'):
     return Longitude(result)
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def longitude_Sun_perigee(t='now'):  # pylint: disable=W0613
     # T = julian_centuries(t)
     return 1
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def mean_anomaly(t='now'):
     """
     Returns the mean anomaly (the angle through which the Sun has moved
@@ -152,7 +148,7 @@ def mean_anomaly(t='now'):
     return Longitude(result)
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def carrington_rotation_number(t='now'):
     """
     Return the Carrington Rotation number
@@ -163,7 +159,7 @@ def carrington_rotation_number(t='now'):
     return result
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def geometric_mean_longitude(t='now'):
     """
     Returns the geometric mean longitude (in degrees).
@@ -175,7 +171,7 @@ def geometric_mean_longitude(t='now'):
     return Longitude(result)
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def equation_of_center(t='now'):
     """
     Returns the Sun's equation of center (in degrees).
@@ -189,7 +185,7 @@ def equation_of_center(t='now'):
     return Angle(result)
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def true_longitude(t='now'):
     """
     Returns the Sun's true geometric longitude (in degrees).
@@ -201,7 +197,7 @@ def true_longitude(t='now'):
     return Longitude(result)
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def true_anomaly(t='now'):
     """
     Returns the Sun's true anomaly (in degrees).
@@ -212,7 +208,7 @@ def true_anomaly(t='now'):
 
 
 @deprecated('0.8', 'Use sunpy.coordinates.get_sunearth_distance() for higher accuracy')
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def sunearth_distance(t='now'):
     """
     Returns the Sun Earth distance (AU). There are a set of higher
@@ -225,7 +221,7 @@ def sunearth_distance(t='now'):
     return result * u.AU
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def apparent_longitude(t='now'):
     """
     Returns the apparent longitude of the Sun.
@@ -238,7 +234,7 @@ def apparent_longitude(t='now'):
     return Longitude(result)
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def true_latitude(t='now'):  # pylint: disable=W0613
     """
     Returns the true latitude. Never more than 1.2 arcsec from 0,
@@ -248,7 +244,7 @@ def true_latitude(t='now'):  # pylint: disable=W0613
     return Latitude(0.0 * u.deg)
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def apparent_latitude(t='now'):  # pylint: disable=W0613
     """
     Returns the true latitude. Set to 0 here.
@@ -257,7 +253,7 @@ def apparent_latitude(t='now'):  # pylint: disable=W0613
     return Latitude(0.0 * u.deg)
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def true_obliquity_of_ecliptic(t='now'):
     """
     Returns the true obliquity of the ecliptic.
@@ -268,7 +264,7 @@ def true_obliquity_of_ecliptic(t='now'):
     return Angle(result, u.deg)
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def true_rightascension(t='now'):
     """
     Return the true right ascension.
@@ -280,7 +276,7 @@ def true_rightascension(t='now'):
     return Longitude(true_ra.to(u.hourangle))
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def true_declination(t='now'):
     """
     Return the true declination.
@@ -290,7 +286,7 @@ def true_declination(t='now'):
     return Latitude(result.to(u.deg))
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def apparent_obliquity_of_ecliptic(t='now'):
     """
     Return the apparent obliquity of the ecliptic.
@@ -301,7 +297,7 @@ def apparent_obliquity_of_ecliptic(t='now'):
     return result
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def apparent_rightascension(t='now'):
     """
     Returns the apparent right ascension of the Sun.
@@ -313,7 +309,7 @@ def apparent_rightascension(t='now'):
     return Longitude(app_ra.to(u.hourangle))
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def apparent_declination(t='now'):
     """
     Returns the apparent declination of the Sun.
@@ -326,7 +322,7 @@ def apparent_declination(t='now'):
 
 
 @deprecated('0.8', 'Use sunpy.coordinates.get_sun_P() for higher accuracy')
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def solar_north(t='now'):
     """
     Returns the position of the Solar north pole in degrees.
@@ -348,7 +344,7 @@ def solar_north(t='now'):
 
 
 @deprecated('0.8', 'Use sunpy.coordinates.get_sun_L0() and .get_sun_B0() for higher accuracy')
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def heliographic_solar_center(t='now'):
     """
     Returns the position of the solar center in heliographic coordinates.
@@ -372,7 +368,7 @@ def heliographic_solar_center(t='now'):
     return Longitude(he_lon.to(u.deg)), Latitude(he_lat.to(u.deg))
 
 
-@add_parameter_docs
+@add_common_docstring(append=PARAMETER_DOCS)
 def print_params(t='now'):
     """
     Print out a summary of Solar ephemeris.
diff --git a/sunpy/util/decorators.py b/sunpy/util/decorators.py
--- a/sunpy/util/decorators.py
+++ b/sunpy/util/decorators.py
@@ -12,6 +12,7 @@
 import warnings
 
 from sunpy.util.exceptions import SunpyDeprecationWarning
+from sunpy.extern import six
 
 __all__ = ['deprecated']
 
@@ -95,7 +96,7 @@ def deprecate_function(func, message):
         func = get_function(func)
 
         def deprecated_func(*args, **kwargs):
-            
+
             category = SunpyDeprecationWarning
 
             warnings.warn(message, category, stacklevel=2)
@@ -162,7 +163,7 @@ def deprecate(obj, message=message, name=name, alternative=alternative):
         altmessage = ''
         if not message or type(message) is type(deprecate):
             message = ('The {func} {obj_type} is deprecated and may '
-                           'be removed in a future version.')
+                       'be removed in a future version.')
             if alternative:
                 altmessage = '\n        Use {} instead.'.format(alternative)
 
@@ -182,3 +183,39 @@ def deprecate(obj, message=message, name=name, alternative=alternative):
         return deprecate(message)
 
     return deprecate
+
+
+class add_common_docstring(object):
+    """
+    A function decorator that will append and/or prepend an addendum
+    to the docstring of the target function.
+
+
+    Parameters
+    ----------
+    append : `str`, optional
+        A string to append to the end of the functions docstring.
+
+    prepend : `str`, optional
+        A string to prepend to the start of the functions docstring.
+
+    **kwargs : `dict`, optional
+        A dictionary to format append and prepend strings.
+    """
+
+    def __init__(self, append=None, prepend=None, **kwargs):
+        if kwargs:
+            append = append.format(**kwargs)
+            prepend = prepend.format(**kwargs)
+        self.append = append
+        self.prepend = prepend
+
+    def __call__(self, func):
+        func.__doc__ = func.__doc__ if func.__doc__ else ''
+        self.append = self.append if self.append else ''
+        self.prepend = self.prepend if self.prepend else ''
+        if self.append and isinstance(func.__doc__, six.string_types):
+            func.__doc__ += self.append
+        if self.prepend and isinstance(func.__doc__, six.string_types):
+            func.__doc__ = self.prepend + func.__doc__
+        return func


Test patch: