legacy.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. # event/legacy.py
  2. # Copyright (C) 2005-2022 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: https://www.opensource.org/licenses/mit-license.php
  7. """Routines to handle adaption of legacy call signatures,
  8. generation of deprecation notes and docstrings.
  9. """
  10. from .. import util
  11. def _legacy_signature(since, argnames, converter=None):
  12. def leg(fn):
  13. if not hasattr(fn, "_legacy_signatures"):
  14. fn._legacy_signatures = []
  15. fn._legacy_signatures.append((since, argnames, converter))
  16. return fn
  17. return leg
  18. def _wrap_fn_for_legacy(dispatch_collection, fn, argspec):
  19. for since, argnames, conv in dispatch_collection.legacy_signatures:
  20. if argnames[-1] == "**kw":
  21. has_kw = True
  22. argnames = argnames[0:-1]
  23. else:
  24. has_kw = False
  25. if len(argnames) == len(argspec.args) and has_kw is bool(
  26. argspec.varkw
  27. ):
  28. formatted_def = "def %s(%s%s)" % (
  29. dispatch_collection.name,
  30. ", ".join(dispatch_collection.arg_names),
  31. ", **kw" if has_kw else "",
  32. )
  33. warning_txt = (
  34. 'The argument signature for the "%s.%s" event listener '
  35. "has changed as of version %s, and conversion for "
  36. "the old argument signature will be removed in a "
  37. 'future release. The new signature is "%s"'
  38. % (
  39. dispatch_collection.clsname,
  40. dispatch_collection.name,
  41. since,
  42. formatted_def,
  43. )
  44. )
  45. if conv:
  46. assert not has_kw
  47. def wrap_leg(*args):
  48. util.warn_deprecated(warning_txt, version=since)
  49. return fn(*conv(*args))
  50. else:
  51. def wrap_leg(*args, **kw):
  52. util.warn_deprecated(warning_txt, version=since)
  53. argdict = dict(zip(dispatch_collection.arg_names, args))
  54. args = [argdict[name] for name in argnames]
  55. if has_kw:
  56. return fn(*args, **kw)
  57. else:
  58. return fn(*args)
  59. return wrap_leg
  60. else:
  61. return fn
  62. def _indent(text, indent):
  63. return "\n".join(indent + line for line in text.split("\n"))
  64. def _standard_listen_example(dispatch_collection, sample_target, fn):
  65. example_kw_arg = _indent(
  66. "\n".join(
  67. "%(arg)s = kw['%(arg)s']" % {"arg": arg}
  68. for arg in dispatch_collection.arg_names[0:2]
  69. ),
  70. " ",
  71. )
  72. if dispatch_collection.legacy_signatures:
  73. current_since = max(
  74. since
  75. for since, args, conv in dispatch_collection.legacy_signatures
  76. )
  77. else:
  78. current_since = None
  79. text = (
  80. "from sqlalchemy import event\n\n\n"
  81. "@event.listens_for(%(sample_target)s, '%(event_name)s')\n"
  82. "def receive_%(event_name)s("
  83. "%(named_event_arguments)s%(has_kw_arguments)s):\n"
  84. " \"listen for the '%(event_name)s' event\"\n"
  85. "\n # ... (event handling logic) ...\n"
  86. )
  87. text %= {
  88. "current_since": " (arguments as of %s)" % current_since
  89. if current_since
  90. else "",
  91. "event_name": fn.__name__,
  92. "has_kw_arguments": ", **kw" if dispatch_collection.has_kw else "",
  93. "named_event_arguments": ", ".join(dispatch_collection.arg_names),
  94. "example_kw_arg": example_kw_arg,
  95. "sample_target": sample_target,
  96. }
  97. return text
  98. def _legacy_listen_examples(dispatch_collection, sample_target, fn):
  99. text = ""
  100. for since, args, conv in dispatch_collection.legacy_signatures:
  101. text += (
  102. "\n# DEPRECATED calling style (pre-%(since)s, "
  103. "will be removed in a future release)\n"
  104. "@event.listens_for(%(sample_target)s, '%(event_name)s')\n"
  105. "def receive_%(event_name)s("
  106. "%(named_event_arguments)s%(has_kw_arguments)s):\n"
  107. " \"listen for the '%(event_name)s' event\"\n"
  108. "\n # ... (event handling logic) ...\n"
  109. % {
  110. "since": since,
  111. "event_name": fn.__name__,
  112. "has_kw_arguments": " **kw"
  113. if dispatch_collection.has_kw
  114. else "",
  115. "named_event_arguments": ", ".join(args),
  116. "sample_target": sample_target,
  117. }
  118. )
  119. return text
  120. def _version_signature_changes(parent_dispatch_cls, dispatch_collection):
  121. since, args, conv = dispatch_collection.legacy_signatures[0]
  122. return (
  123. "\n.. deprecated:: %(since)s\n"
  124. " The :class:`.%(clsname)s.%(event_name)s` event now accepts the \n"
  125. " arguments ``%(named_event_arguments)s%(has_kw_arguments)s``.\n"
  126. " Support for listener functions which accept the previous \n"
  127. ' argument signature(s) listed above as "deprecated" will be \n'
  128. " removed in a future release."
  129. % {
  130. "since": since,
  131. "clsname": parent_dispatch_cls.__name__,
  132. "event_name": dispatch_collection.name,
  133. "named_event_arguments": ", ".join(dispatch_collection.arg_names),
  134. "has_kw_arguments": ", **kw" if dispatch_collection.has_kw else "",
  135. }
  136. )
  137. def _augment_fn_docs(dispatch_collection, parent_dispatch_cls, fn):
  138. header = (
  139. ".. container:: event_signatures\n\n"
  140. " Example argument forms::\n"
  141. "\n"
  142. )
  143. sample_target = getattr(parent_dispatch_cls, "_target_class_doc", "obj")
  144. text = header + _indent(
  145. _standard_listen_example(dispatch_collection, sample_target, fn),
  146. " " * 8,
  147. )
  148. if dispatch_collection.legacy_signatures:
  149. text += _indent(
  150. _legacy_listen_examples(dispatch_collection, sample_target, fn),
  151. " " * 8,
  152. )
  153. text += _version_signature_changes(
  154. parent_dispatch_cls, dispatch_collection
  155. )
  156. return util.inject_docstring_text(fn.__doc__, text, 1)