/lib/sqlalchemy/util/deprecations.py

https://gitlab.com/ztane/sqlalchemy · Python · 143 lines · 114 code · 12 blank · 17 comment · 4 complexity · d96854a95c41cb6a669964e539bee950 MD5 · raw file

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