deprecations.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. # util/deprecations.py
  2. # Copyright (C) 2005-2017 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  7. """Helpers related to deprecation of functions, methods, classes, other
  8. functionality."""
  9. from .. import exc
  10. import warnings
  11. import re
  12. from .langhelpers import decorator
  13. def warn_deprecated(msg, stacklevel=3):
  14. warnings.warn(msg, exc.SADeprecationWarning, stacklevel=stacklevel)
  15. def warn_pending_deprecation(msg, stacklevel=3):
  16. warnings.warn(msg, exc.SAPendingDeprecationWarning, stacklevel=stacklevel)
  17. def deprecated(version, message=None, add_deprecation_to_docstring=True):
  18. """Decorates a function and issues a deprecation warning on use.
  19. :param message:
  20. If provided, issue message in the warning. A sensible default
  21. is used if not provided.
  22. :param add_deprecation_to_docstring:
  23. Default True. If False, the wrapped function's __doc__ is left
  24. as-is. If True, the 'message' is prepended to the docs if
  25. provided, or sensible default if message is omitted.
  26. """
  27. if add_deprecation_to_docstring:
  28. header = ".. deprecated:: %s %s" % \
  29. (version, (message or ''))
  30. else:
  31. header = None
  32. if message is None:
  33. message = "Call to deprecated function %(func)s"
  34. def decorate(fn):
  35. return _decorate_with_warning(
  36. fn, exc.SADeprecationWarning,
  37. message % dict(func=fn.__name__), header)
  38. return decorate
  39. def pending_deprecation(version, message=None,
  40. add_deprecation_to_docstring=True):
  41. """Decorates a function and issues a pending deprecation warning on use.
  42. :param version:
  43. An approximate future version at which point the pending deprecation
  44. will become deprecated. Not used in messaging.
  45. :param message:
  46. If provided, issue message in the warning. A sensible default
  47. is used if not provided.
  48. :param add_deprecation_to_docstring:
  49. Default True. If False, the wrapped function's __doc__ is left
  50. as-is. If True, the 'message' is prepended to the docs if
  51. provided, or sensible default if message is omitted.
  52. """
  53. if add_deprecation_to_docstring:
  54. header = ".. deprecated:: %s (pending) %s" % \
  55. (version, (message or ''))
  56. else:
  57. header = None
  58. if message is None:
  59. message = "Call to deprecated function %(func)s"
  60. def decorate(fn):
  61. return _decorate_with_warning(
  62. fn, exc.SAPendingDeprecationWarning,
  63. message % dict(func=fn.__name__), header)
  64. return decorate
  65. def _sanitize_restructured_text(text):
  66. def repl(m):
  67. type_, name = m.group(1, 2)
  68. if type_ in ("func", "meth"):
  69. name += "()"
  70. return name
  71. return re.sub(r'\:(\w+)\:`~?\.?(.+?)`', repl, text)
  72. def _decorate_with_warning(func, wtype, message, docstring_header=None):
  73. """Wrap a function with a warnings.warn and augmented docstring."""
  74. message = _sanitize_restructured_text(message)
  75. @decorator
  76. def warned(fn, *args, **kwargs):
  77. warnings.warn(message, wtype, stacklevel=3)
  78. return fn(*args, **kwargs)
  79. doc = func.__doc__ is not None and func.__doc__ or ''
  80. if docstring_header is not None:
  81. docstring_header %= dict(func=func.__name__)
  82. doc = inject_docstring_text(doc, docstring_header, 1)
  83. decorated = warned(func)
  84. decorated.__doc__ = doc
  85. return decorated
  86. import textwrap
  87. def _dedent_docstring(text):
  88. split_text = text.split("\n", 1)
  89. if len(split_text) == 1:
  90. return text
  91. else:
  92. firstline, remaining = split_text
  93. if not firstline.startswith(" "):
  94. return firstline + "\n" + textwrap.dedent(remaining)
  95. else:
  96. return textwrap.dedent(text)
  97. def inject_docstring_text(doctext, injecttext, pos):
  98. doctext = _dedent_docstring(doctext or "")
  99. lines = doctext.split('\n')
  100. injectlines = textwrap.dedent(injecttext).split("\n")
  101. if injectlines[0]:
  102. injectlines.insert(0, "")
  103. blanks = [num for num, line in enumerate(lines) if not line.strip()]
  104. blanks.insert(0, 0)
  105. inject_pos = blanks[min(pos, len(blanks) - 1)]
  106. lines = lines[0:inject_pos] + injectlines + lines[inject_pos:]
  107. return "\n".join(lines)