PageRenderTime 61ms CodeModel.GetById 35ms RepoModel.GetById 1ms app.codeStats 0ms

/pandas/util/decorators.py

http://github.com/wesm/pandas
Python | 292 lines | 260 code | 13 blank | 19 comment | 10 complexity | e05494e4fce31149c99e0c5bd85a2c3c MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  1. from pandas.compat import StringIO, callable, signature
  2. from pandas.lib import cache_readonly # noqa
  3. import sys
  4. import warnings
  5. from textwrap import dedent
  6. from functools import wraps
  7. def deprecate(name, alternative, alt_name=None):
  8. alt_name = alt_name or alternative.__name__
  9. def wrapper(*args, **kwargs):
  10. warnings.warn("%s is deprecated. Use %s instead" % (name, alt_name),
  11. FutureWarning, stacklevel=2)
  12. return alternative(*args, **kwargs)
  13. return wrapper
  14. def deprecate_kwarg(old_arg_name, new_arg_name, mapping=None, stacklevel=2):
  15. """Decorator to deprecate a keyword argument of a function
  16. Parameters
  17. ----------
  18. old_arg_name : str
  19. Name of argument in function to deprecate
  20. new_arg_name : str
  21. Name of prefered argument in function
  22. mapping : dict or callable
  23. If mapping is present, use it to translate old arguments to
  24. new arguments. A callable must do its own value checking;
  25. values not found in a dict will be forwarded unchanged.
  26. Examples
  27. --------
  28. The following deprecates 'cols', using 'columns' instead
  29. >>> @deprecate_kwarg(old_arg_name='cols', new_arg_name='columns')
  30. ... def f(columns=''):
  31. ... print(columns)
  32. ...
  33. >>> f(columns='should work ok')
  34. should work ok
  35. >>> f(cols='should raise warning')
  36. FutureWarning: cols is deprecated, use columns instead
  37. warnings.warn(msg, FutureWarning)
  38. should raise warning
  39. >>> f(cols='should error', columns="can\'t pass do both")
  40. TypeError: Can only specify 'cols' or 'columns', not both
  41. >>> @deprecate_kwarg('old', 'new', {'yes': True, 'no': False})
  42. ... def f(new=False):
  43. ... print('yes!' if new else 'no!')
  44. ...
  45. >>> f(old='yes')
  46. FutureWarning: old='yes' is deprecated, use new=True instead
  47. warnings.warn(msg, FutureWarning)
  48. yes!
  49. """
  50. if mapping is not None and not hasattr(mapping, 'get') and \
  51. not callable(mapping):
  52. raise TypeError("mapping from old to new argument values "
  53. "must be dict or callable!")
  54. def _deprecate_kwarg(func):
  55. @wraps(func)
  56. def wrapper(*args, **kwargs):
  57. old_arg_value = kwargs.pop(old_arg_name, None)
  58. if old_arg_value is not None:
  59. if mapping is not None:
  60. if hasattr(mapping, 'get'):
  61. new_arg_value = mapping.get(old_arg_value,
  62. old_arg_value)
  63. else:
  64. new_arg_value = mapping(old_arg_value)
  65. msg = "the %s=%r keyword is deprecated, " \
  66. "use %s=%r instead" % \
  67. (old_arg_name, old_arg_value,
  68. new_arg_name, new_arg_value)
  69. else:
  70. new_arg_value = old_arg_value
  71. msg = "the '%s' keyword is deprecated, " \
  72. "use '%s' instead" % (old_arg_name, new_arg_name)
  73. warnings.warn(msg, FutureWarning, stacklevel=stacklevel)
  74. if kwargs.get(new_arg_name, None) is not None:
  75. msg = ("Can only specify '%s' or '%s', not both" %
  76. (old_arg_name, new_arg_name))
  77. raise TypeError(msg)
  78. else:
  79. kwargs[new_arg_name] = new_arg_value
  80. return func(*args, **kwargs)
  81. return wrapper
  82. return _deprecate_kwarg
  83. # Substitution and Appender are derived from matplotlib.docstring (1.1.0)
  84. # module http://matplotlib.org/users/license.html
  85. class Substitution(object):
  86. """
  87. A decorator to take a function's docstring and perform string
  88. substitution on it.
  89. This decorator should be robust even if func.__doc__ is None
  90. (for example, if -OO was passed to the interpreter)
  91. Usage: construct a docstring.Substitution with a sequence or
  92. dictionary suitable for performing substitution; then
  93. decorate a suitable function with the constructed object. e.g.
  94. sub_author_name = Substitution(author='Jason')
  95. @sub_author_name
  96. def some_function(x):
  97. "%(author)s wrote this function"
  98. # note that some_function.__doc__ is now "Jason wrote this function"
  99. One can also use positional arguments.
  100. sub_first_last_names = Substitution('Edgar Allen', 'Poe')
  101. @sub_first_last_names
  102. def some_function(x):
  103. "%s %s wrote the Raven"
  104. """
  105. def __init__(self, *args, **kwargs):
  106. if (args and kwargs):
  107. raise AssertionError("Only positional or keyword args are allowed")
  108. self.params = args or kwargs
  109. def __call__(self, func):
  110. func.__doc__ = func.__doc__ and func.__doc__ % self.params
  111. return func
  112. def update(self, *args, **kwargs):
  113. "Assume self.params is a dict and update it with supplied args"
  114. self.params.update(*args, **kwargs)
  115. @classmethod
  116. def from_params(cls, params):
  117. """
  118. In the case where the params is a mutable sequence (list or dictionary)
  119. and it may change before this class is called, one may explicitly use a
  120. reference to the params rather than using *args or **kwargs which will
  121. copy the values and not reference them.
  122. """
  123. result = cls()
  124. result.params = params
  125. return result
  126. class Appender(object):
  127. """
  128. A function decorator that will append an addendum to the docstring
  129. of the target function.
  130. This decorator should be robust even if func.__doc__ is None
  131. (for example, if -OO was passed to the interpreter).
  132. Usage: construct a docstring.Appender with a string to be joined to
  133. the original docstring. An optional 'join' parameter may be supplied
  134. which will be used to join the docstring and addendum. e.g.
  135. add_copyright = Appender("Copyright (c) 2009", join='\n')
  136. @add_copyright
  137. def my_dog(has='fleas'):
  138. "This docstring will have a copyright below"
  139. pass
  140. """
  141. def __init__(self, addendum, join='', indents=0):
  142. if indents > 0:
  143. self.addendum = indent(addendum, indents=indents)
  144. else:
  145. self.addendum = addendum
  146. self.join = join
  147. def __call__(self, func):
  148. func.__doc__ = func.__doc__ if func.__doc__ else ''
  149. self.addendum = self.addendum if self.addendum else ''
  150. docitems = [func.__doc__, self.addendum]
  151. func.__doc__ = dedent(self.join.join(docitems))
  152. return func
  153. def indent(text, indents=1):
  154. if not text or not isinstance(text, str):
  155. return ''
  156. jointext = ''.join(['\n'] + [' '] * indents)
  157. return jointext.join(text.split('\n'))
  158. def suppress_stdout(f):
  159. def wrapped(*args, **kwargs):
  160. try:
  161. sys.stdout = StringIO()
  162. f(*args, **kwargs)
  163. finally:
  164. sys.stdout = sys.__stdout__
  165. return wrapped
  166. class KnownFailureTest(Exception):
  167. """Raise this exception to mark a test as a known failing test."""
  168. pass
  169. def knownfailureif(fail_condition, msg=None):
  170. """
  171. Make function raise KnownFailureTest exception if given condition is true.
  172. If the condition is a callable, it is used at runtime to dynamically
  173. make the decision. This is useful for tests that may require costly
  174. imports, to delay the cost until the test suite is actually executed.
  175. Parameters
  176. ----------
  177. fail_condition : bool or callable
  178. Flag to determine whether to mark the decorated test as a known
  179. failure (if True) or not (if False).
  180. msg : str, optional
  181. Message to give on raising a KnownFailureTest exception.
  182. Default is None.
  183. Returns
  184. -------
  185. decorator : function
  186. Decorator, which, when applied to a function, causes SkipTest
  187. to be raised when `skip_condition` is True, and the function
  188. to be called normally otherwise.
  189. Notes
  190. -----
  191. The decorator itself is decorated with the ``nose.tools.make_decorator``
  192. function in order to transmit function name, and various other metadata.
  193. """
  194. if msg is None:
  195. msg = 'Test skipped due to known failure'
  196. # Allow for both boolean or callable known failure conditions.
  197. if callable(fail_condition):
  198. fail_val = fail_condition
  199. else:
  200. fail_val = lambda: fail_condition
  201. def knownfail_decorator(f):
  202. # Local import to avoid a hard nose dependency and only incur the
  203. # import time overhead at actual test-time.
  204. import nose
  205. def knownfailer(*args, **kwargs):
  206. if fail_val():
  207. raise KnownFailureTest(msg)
  208. else:
  209. return f(*args, **kwargs)
  210. return nose.tools.make_decorator(f)(knownfailer)
  211. return knownfail_decorator
  212. def make_signature(func):
  213. """
  214. Returns a string repr of the arg list of a func call, with any defaults
  215. Examples
  216. --------
  217. >>> def f(a,b,c=2) :
  218. >>> return a*b*c
  219. >>> print(_make_signature(f))
  220. a,b,c=2
  221. """
  222. spec = signature(func)
  223. if spec.defaults is None:
  224. n_wo_defaults = len(spec.args)
  225. defaults = ('',) * n_wo_defaults
  226. else:
  227. n_wo_defaults = len(spec.args) - len(spec.defaults)
  228. defaults = ('',) * n_wo_defaults + spec.defaults
  229. args = []
  230. for i, (var, default) in enumerate(zip(spec.args, defaults)):
  231. args.append(var if default == '' else var + '=' + repr(default))
  232. if spec.varargs:
  233. args.append('*' + spec.varargs)
  234. if spec.keywords:
  235. args.append('**' + spec.keywords)
  236. return args, spec.args