Next steps:

  • [ ] How does numpydoc format deprecation warnings?
  • [x] UserWarning to DeprecationWarning
  • [x] experimental until to as-of
  • [x] deprecated should also take as-of in addition to until
  • [x] force naming of kwargs for readability
  • [ ] update deprecated's wrapped_f.__name__, etc (see link from Evan)

Longer term:

  • [ ] deprecated_parameter
  • [ ] plan for deprecating full modules and subpackages

In [16]:
from skbio.util import stable, experimental, deprecated
from warnings import warn
from textwrap import wrap
import decorator

# class state_decorator(object):
    
#     _line_header = '\n        '
    
#     def _update_doc_string(self, func, state_desc):
#         doc_lines = func.__doc__.split('\n')
#         state_desc_lines = wrap('State: ' + state_desc, 79 - len(self._line_header))
#         doc_lines.insert(1, self._line_header + self._line_header.join(state_desc_lines))
#         return '\n'.join(doc_lines)
        
# class stable(state_decorator):
    
#     def __init__(self, **kwargs):
#         print kwargs
#         self.as_of = kwargs['as_of']
    
#     def __call__(self, func):
#         state_desc = 'Stable as of %s.' % self.as_of
#         func.__doc__ = self._update_doc_string(func, state_desc)
#         return func

# class experimental(state_decorator):
    
#     def __init__(self, as_of):
#         self.as_of = as_of

#     def __call__(self, func):
#         state_desc = 'Experimental as of %s.' % self.as_of
#         func.__doc__ = self._update_doc_string(func, state_desc)
#         return func

    
    
def _update_doc_string(func, state_desc):
    _line_header = '\n        '
    doc_lines = func.__doc__.split('\n')
    state_desc_lines = wrap('State: ' + state_desc, 79 - len(self._line_header))
    doc_lines.insert(1, self._line_header + self._line_header.join(state_desc_lines))
    return '\n'.join(doc_lines)

        

@decorator.decorator
def deprecated(func, *args, **kwargs):
    """ State decorator indicating deprecated functionality.

    Used to indicate that a public class or function is deprecated, meaning
    that its API will be removed in a future version of scikit-bio. Decorating
    functionality as experimental will update its doc string to indicate the
    first version of scikit-bio when the functionality was deprecated, the
    first version of scikit-bio when the functionality will no longer exist,
    and the reason for deprecation of the API. It will also cause calls to the
    API to raise a ``DeprecationWarning``.

    Parameters
    ----------
    as_of : str
        First development version where feature is considered to be deprecated.
    until : str
        First release version where feature will no longer exist.
    reason : str
        Brief description of why the API is deprecated.

    See Also
    --------
    stable
    experimental

    Examples
    --------
    >>> @deprecated(as_of='0.3.0', until='0.3.3',
    ...             reason='Users should now use skbio.g().')
    ... def f_deprecated():
    ...     \"\"\" An example deprecated function.
    ...     \"\"\"
    ...     pass
    >>> help(f_deprecated)
    Help on function f_deprecated in module skbio.util._decorator:
    <BLANKLINE>
    f_deprecated()
        An example deprecated function.
    <BLANKLINE>
        State: Deprecated as of 0.3.0 for removal in 0.3.3. Users should now
        use skbio.g().
    <BLANKLINE>

    """
    as_of = kwargs['as_of']
    until = kwargs['until']
    reason = kwargs['reason']
    
    state_desc = 'Deprecated as of %s for removal in %s. %s' %\
     (as_of, until, reason)
    func.__doc__ = _update_doc_string(func, state_desc)

    def wrapped_f(*args, **kwargs):
        warn('%s is deprecated as of scikit-bio version %s, and will be'
             ' removed in version %s. %s' %
             (func.__name__, as_of, until, reason),
             DeprecationWarning)
        return func(*args, **kwargs)

    return wrapped_f

class A(object):
    
#     @stable(as_of='0.3.1')
#     def return_x(self, x):
#         """This method returns its input.
        
#         Probably not the most useful function ever.
        
#         Parameters
#         ----------
#         x : the value to be returned
        
#         Returns
#         -------
#         whatever was passed in
#         """
#         return x

#     @experimental(as_of='0.3.4')
#     def return_y(self, y):
#         """This method returns its input.
        
#         Probably not the most useful function ever.
        
#         Parameters
#         ----------
#         y : the value to be returned
        
#         Returns
#         -------
#         whatever was passed in
#         """
#         return y

    @deprecated(as_of='0.3.1', until='0.3.4', reason='You should now be using B.return_z1.')
    def return_z1(self, z1):
        """This method returns its input.
        
        Probably not the most useful function ever.
        
        Parameters
        ----------
        z1 : the value to be returned
        
        Returns
        -------
        whatever was passed in
        """
        return z1

    @deprecated(as_of='0.3.1', until='0.3.4', reason='This method was shown to be error-prone by [Foo et al. 2015](www.pubmed.gov/12345)')
    def return_z2(self, z2):
        """This method returns its input.
        
        Probably not the most useful function ever.
        
        Parameters
        ----------
        z2 : the value to be returned
        
        Returns
        -------
        whatever was passed in
        """
        return z2


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-16-ccb79b155327> in <module>()
    107     return wrapped_f
    108 
--> 109 class A(object):
    110 
    111 #     @stable(as_of='0.3.1')

<ipython-input-16-ccb79b155327> in A()
    141 #         return y
    142 
--> 143     @deprecated(as_of='0.3.1', until='0.3.4', reason='You should now be using B.return_z1.')
    144     def return_z1(self, z1):
    145         """This method returns its input.

TypeError: deprecated() got an unexpected keyword argument 'as_of'

In [3]:
help(A().return_x)


Help on method return_x in module __main__:

return_x(self, x) method of __main__.A instance
    This method returns its input.
    
    State: Stable as of 0.3.1.
    
    Probably not the most useful function ever.
    
    Parameters
    ----------
    x : the value to be returned
    
    Returns
    -------
    whatever was passed in


In [4]:
help(A().return_y)


Help on method return_y in module __main__:

return_y(self, y) method of __main__.A instance
    This method returns its input.
    
    State: Experimental as of 0.3.4.
    
    Probably not the most useful function ever.
    
    Parameters
    ----------
    y : the value to be returned
    
    Returns
    -------
    whatever was passed in


In [5]:
print A().return_z1(42)


42

In [6]:
print A().return_z2(42)


42

In [7]:
help(A().return_z1)


Help on method wrapped_f in module skbio.util._decorator:

wrapped_f(*args, **kwargs) method of __main__.A instance
    This method returns its input.
    
    State: Deprecated as of 0.3.1 for removal in 0.3.4. You should now be
    using B.return_z1.
    
    Probably not the most useful function ever.
    
    Parameters
    ----------
    z1 : the value to be returned
    
    Returns
    -------
    whatever was passed in


In [8]:
help(A().return_z2)


Help on method wrapped_f in module skbio.util._decorator:

wrapped_f(*args, **kwargs) method of __main__.A instance
    This method returns its input.
    
    State: Deprecated as of 0.3.1 for removal in 0.3.4. This method was
    shown to be error-prone by [Foo et al. 2015](www.pubmed.gov/12345)
    
    Probably not the most useful function ever.
    
    Parameters
    ----------
    z2 : the value to be returned
    
    Returns
    -------
    whatever was passed in


In [11]:
from inspect import getargspec
getargspec(A().return_z2)


Out[11]:
ArgSpec(args=[], varargs='args', keywords='kwargs', defaults=None)

In [20]:
def d(f):
    def 
    return f

@d
def (x):
    print x

e(42)


hello
42

In [21]:
getargspec(e)


Out[21]:
ArgSpec(args=['x'], varargs=None, keywords=None, defaults=None)

In [ ]: