Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

deprecate.py 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814
  1. # -*- test-case-name: twisted.python.test.test_deprecate -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Deprecation framework for Twisted.
  6. To mark a method, function, or class as being deprecated do this::
  7. from incremental import Version
  8. from twisted.python.deprecate import deprecated
  9. @deprecated(Version("Twisted", 22, 10, 0))
  10. def badAPI(self, first, second):
  11. '''
  12. Docstring for badAPI.
  13. '''
  14. ...
  15. @deprecated(Version("Twisted", 22, 10, 0))
  16. class BadClass:
  17. '''
  18. Docstring for BadClass.
  19. '''
  20. The newly-decorated badAPI will issue a warning when called, and BadClass will
  21. issue a warning when instantiated. Both will also have a deprecation notice
  22. appended to their docstring.
  23. To deprecate properties you can use::
  24. from incremental import Version
  25. from twisted.python.deprecate import deprecatedProperty
  26. class OtherwiseUndeprecatedClass:
  27. @deprecatedProperty(Version("Twisted", 22, 10, 0))
  28. def badProperty(self):
  29. '''
  30. Docstring for badProperty.
  31. '''
  32. @badProperty.setter
  33. def badProperty(self, value):
  34. '''
  35. Setter sill also raise the deprecation warning.
  36. '''
  37. To mark module-level attributes as being deprecated you can use::
  38. badAttribute = "someValue"
  39. ...
  40. deprecatedModuleAttribute(
  41. Version("Twisted", 22, 10, 0),
  42. "Use goodAttribute instead.",
  43. "your.full.module.name",
  44. "badAttribute")
  45. The deprecated attributes will issue a warning whenever they are accessed. If
  46. the attributes being deprecated are in the same module as the
  47. L{deprecatedModuleAttribute} call is being made from, the C{__name__} global
  48. can be used as the C{moduleName} parameter.
  49. To mark an optional, keyword parameter of a function or method as deprecated
  50. without deprecating the function itself, you can use::
  51. @deprecatedKeywordParameter(Version("Twisted", 22, 10, 0), "baz")
  52. def someFunction(foo, bar=0, baz=None):
  53. ...
  54. See also L{incremental.Version}.
  55. @type DEPRECATION_WARNING_FORMAT: C{str}
  56. @var DEPRECATION_WARNING_FORMAT: The default deprecation warning string format
  57. to use when one is not provided by the user.
  58. """
  59. __all__ = [
  60. "deprecated",
  61. "deprecatedProperty",
  62. "getDeprecationWarningString",
  63. "getWarningMethod",
  64. "setWarningMethod",
  65. "deprecatedModuleAttribute",
  66. "deprecatedKeywordParameter",
  67. ]
  68. import inspect
  69. import sys
  70. from dis import findlinestarts
  71. from functools import wraps
  72. from types import ModuleType
  73. from typing import Any, Callable, Dict, Optional, TypeVar, cast
  74. from warnings import warn, warn_explicit
  75. from incremental import Version, getVersionString
  76. DEPRECATION_WARNING_FORMAT = "%(fqpn)s was deprecated in %(version)s"
  77. # Notionally, part of twisted.python.reflect, but defining it there causes a
  78. # cyclic dependency between this module and that module. Define it here,
  79. # instead, and let reflect import it to re-expose to the public.
  80. def _fullyQualifiedName(obj):
  81. """
  82. Return the fully qualified name of a module, class, method or function.
  83. Classes and functions need to be module level ones to be correctly
  84. qualified.
  85. @rtype: C{str}.
  86. """
  87. try:
  88. name = obj.__qualname__
  89. except AttributeError:
  90. name = obj.__name__
  91. if inspect.isclass(obj) or inspect.isfunction(obj):
  92. moduleName = obj.__module__
  93. return f"{moduleName}.{name}"
  94. elif inspect.ismethod(obj):
  95. return f"{obj.__module__}.{obj.__qualname__}"
  96. return name
  97. # Try to keep it looking like something in twisted.python.reflect.
  98. _fullyQualifiedName.__module__ = "twisted.python.reflect"
  99. _fullyQualifiedName.__name__ = "fullyQualifiedName"
  100. _fullyQualifiedName.__qualname__ = "fullyQualifiedName"
  101. def _getReplacementString(replacement):
  102. """
  103. Surround a replacement for a deprecated API with some polite text exhorting
  104. the user to consider it as an alternative.
  105. @type replacement: C{str} or callable
  106. @return: a string like "please use twisted.python.modules.getModule
  107. instead".
  108. """
  109. if callable(replacement):
  110. replacement = _fullyQualifiedName(replacement)
  111. return f"please use {replacement} instead"
  112. def _getDeprecationDocstring(version, replacement=None):
  113. """
  114. Generate an addition to a deprecated object's docstring that explains its
  115. deprecation.
  116. @param version: the version it was deprecated.
  117. @type version: L{incremental.Version}
  118. @param replacement: The replacement, if specified.
  119. @type replacement: C{str} or callable
  120. @return: a string like "Deprecated in Twisted 27.2.0; please use
  121. twisted.timestream.tachyon.flux instead."
  122. """
  123. doc = f"Deprecated in {getVersionString(version)}"
  124. if replacement:
  125. doc = f"{doc}; {_getReplacementString(replacement)}"
  126. return doc + "."
  127. def _getDeprecationWarningString(fqpn, version, format=None, replacement=None):
  128. """
  129. Return a string indicating that the Python name was deprecated in the given
  130. version.
  131. @param fqpn: Fully qualified Python name of the thing being deprecated
  132. @type fqpn: C{str}
  133. @param version: Version that C{fqpn} was deprecated in.
  134. @type version: L{incremental.Version}
  135. @param format: A user-provided format to interpolate warning values into, or
  136. L{DEPRECATION_WARNING_FORMAT
  137. <twisted.python.deprecate.DEPRECATION_WARNING_FORMAT>} if L{None} is
  138. given.
  139. @type format: C{str}
  140. @param replacement: what should be used in place of C{fqpn}. Either pass in
  141. a string, which will be inserted into the warning message, or a
  142. callable, which will be expanded to its full import path.
  143. @type replacement: C{str} or callable
  144. @return: A textual description of the deprecation
  145. @rtype: C{str}
  146. """
  147. if format is None:
  148. format = DEPRECATION_WARNING_FORMAT
  149. warningString = format % {"fqpn": fqpn, "version": getVersionString(version)}
  150. if replacement:
  151. warningString = "{}; {}".format(
  152. warningString, _getReplacementString(replacement)
  153. )
  154. return warningString
  155. def getDeprecationWarningString(callableThing, version, format=None, replacement=None):
  156. """
  157. Return a string indicating that the callable was deprecated in the given
  158. version.
  159. @type callableThing: C{callable}
  160. @param callableThing: Callable object to be deprecated
  161. @type version: L{incremental.Version}
  162. @param version: Version that C{callableThing} was deprecated in.
  163. @type format: C{str}
  164. @param format: A user-provided format to interpolate warning values into,
  165. or L{DEPRECATION_WARNING_FORMAT
  166. <twisted.python.deprecate.DEPRECATION_WARNING_FORMAT>} if L{None} is
  167. given
  168. @param replacement: what should be used in place of the callable. Either
  169. pass in a string, which will be inserted into the warning message,
  170. or a callable, which will be expanded to its full import path.
  171. @type replacement: C{str} or callable
  172. @return: A string describing the deprecation.
  173. @rtype: C{str}
  174. """
  175. return _getDeprecationWarningString(
  176. _fullyQualifiedName(callableThing), version, format, replacement
  177. )
  178. def _appendToDocstring(thingWithDoc, textToAppend):
  179. """
  180. Append the given text to the docstring of C{thingWithDoc}.
  181. If C{thingWithDoc} has no docstring, then the text just replaces the
  182. docstring. If it has a single-line docstring then it appends a blank line
  183. and the message text. If it has a multi-line docstring, then in appends a
  184. blank line a the message text, and also does the indentation correctly.
  185. """
  186. if thingWithDoc.__doc__:
  187. docstringLines = thingWithDoc.__doc__.splitlines()
  188. else:
  189. docstringLines = []
  190. if len(docstringLines) == 0:
  191. docstringLines.append(textToAppend)
  192. elif len(docstringLines) == 1:
  193. docstringLines.extend(["", textToAppend, ""])
  194. else:
  195. spaces = docstringLines.pop()
  196. docstringLines.extend(["", spaces + textToAppend, spaces])
  197. thingWithDoc.__doc__ = "\n".join(docstringLines)
  198. def deprecated(version, replacement=None):
  199. """
  200. Return a decorator that marks callables as deprecated. To deprecate a
  201. property, see L{deprecatedProperty}.
  202. @type version: L{incremental.Version}
  203. @param version: The version in which the callable will be marked as
  204. having been deprecated. The decorated function will be annotated
  205. with this version, having it set as its C{deprecatedVersion}
  206. attribute.
  207. @param replacement: what should be used in place of the callable. Either
  208. pass in a string, which will be inserted into the warning message,
  209. or a callable, which will be expanded to its full import path.
  210. @type replacement: C{str} or callable
  211. """
  212. def deprecationDecorator(function):
  213. """
  214. Decorator that marks C{function} as deprecated.
  215. """
  216. warningString = getDeprecationWarningString(
  217. function, version, None, replacement
  218. )
  219. @wraps(function)
  220. def deprecatedFunction(*args, **kwargs):
  221. warn(warningString, DeprecationWarning, stacklevel=2)
  222. return function(*args, **kwargs)
  223. _appendToDocstring(
  224. deprecatedFunction, _getDeprecationDocstring(version, replacement)
  225. )
  226. deprecatedFunction.deprecatedVersion = version # type: ignore[attr-defined]
  227. return deprecatedFunction
  228. return deprecationDecorator
  229. def deprecatedProperty(version, replacement=None):
  230. """
  231. Return a decorator that marks a property as deprecated. To deprecate a
  232. regular callable or class, see L{deprecated}.
  233. @type version: L{incremental.Version}
  234. @param version: The version in which the callable will be marked as
  235. having been deprecated. The decorated function will be annotated
  236. with this version, having it set as its C{deprecatedVersion}
  237. attribute.
  238. @param replacement: what should be used in place of the callable.
  239. Either pass in a string, which will be inserted into the warning
  240. message, or a callable, which will be expanded to its full import
  241. path.
  242. @type replacement: C{str} or callable
  243. @return: A new property with deprecated setter and getter.
  244. @rtype: C{property}
  245. @since: 16.1.0
  246. """
  247. class _DeprecatedProperty(property):
  248. """
  249. Extension of the build-in property to allow deprecated setters.
  250. """
  251. def _deprecatedWrapper(self, function):
  252. @wraps(function)
  253. def deprecatedFunction(*args, **kwargs):
  254. warn(
  255. self.warningString, # type: ignore[attr-defined]
  256. DeprecationWarning,
  257. stacklevel=2,
  258. )
  259. return function(*args, **kwargs)
  260. return deprecatedFunction
  261. def setter(self, function):
  262. return property.setter(self, self._deprecatedWrapper(function))
  263. def deprecationDecorator(function):
  264. warningString = getDeprecationWarningString(
  265. function, version, None, replacement
  266. )
  267. @wraps(function)
  268. def deprecatedFunction(*args, **kwargs):
  269. warn(warningString, DeprecationWarning, stacklevel=2)
  270. return function(*args, **kwargs)
  271. _appendToDocstring(
  272. deprecatedFunction, _getDeprecationDocstring(version, replacement)
  273. )
  274. deprecatedFunction.deprecatedVersion = version # type: ignore[attr-defined]
  275. result = _DeprecatedProperty(deprecatedFunction)
  276. result.warningString = warningString # type: ignore[attr-defined]
  277. return result
  278. return deprecationDecorator
  279. def getWarningMethod():
  280. """
  281. Return the warning method currently used to record deprecation warnings.
  282. """
  283. return warn
  284. def setWarningMethod(newMethod):
  285. """
  286. Set the warning method to use to record deprecation warnings.
  287. The callable should take message, category and stacklevel. The return
  288. value is ignored.
  289. """
  290. global warn
  291. warn = newMethod
  292. class _InternalState:
  293. """
  294. An L{_InternalState} is a helper object for a L{_ModuleProxy}, so that it
  295. can easily access its own attributes, bypassing its logic for delegating to
  296. another object that it's proxying for.
  297. @ivar proxy: a L{_ModuleProxy}
  298. """
  299. def __init__(self, proxy):
  300. object.__setattr__(self, "proxy", proxy)
  301. def __getattribute__(self, name):
  302. return object.__getattribute__(object.__getattribute__(self, "proxy"), name)
  303. def __setattr__(self, name, value):
  304. return object.__setattr__(object.__getattribute__(self, "proxy"), name, value)
  305. class _ModuleProxy:
  306. """
  307. Python module wrapper to hook module-level attribute access.
  308. Access to deprecated attributes first checks
  309. L{_ModuleProxy._deprecatedAttributes}, if the attribute does not appear
  310. there then access falls through to L{_ModuleProxy._module}, the wrapped
  311. module object.
  312. @ivar _module: Module on which to hook attribute access.
  313. @type _module: C{module}
  314. @ivar _deprecatedAttributes: Mapping of attribute names to objects that
  315. retrieve the module attribute's original value.
  316. @type _deprecatedAttributes: C{dict} mapping C{str} to
  317. L{_DeprecatedAttribute}
  318. @ivar _lastWasPath: Heuristic guess as to whether warnings about this
  319. package should be ignored for the next call. If the last attribute
  320. access of this module was a C{getattr} of C{__path__}, we will assume
  321. that it was the import system doing it and we won't emit a warning for
  322. the next access, even if it is to a deprecated attribute. The CPython
  323. import system always tries to access C{__path__}, then the attribute
  324. itself, then the attribute itself again, in both successful and failed
  325. cases.
  326. @type _lastWasPath: C{bool}
  327. """
  328. def __init__(self, module):
  329. state = _InternalState(self)
  330. state._module = module
  331. state._deprecatedAttributes = {}
  332. state._lastWasPath = False
  333. def __repr__(self) -> str:
  334. """
  335. Get a string containing the type of the module proxy and a
  336. representation of the wrapped module object.
  337. """
  338. state = _InternalState(self)
  339. return f"<{type(self).__name__} module={state._module!r}>"
  340. def __setattr__(self, name, value):
  341. """
  342. Set an attribute on the wrapped module object.
  343. """
  344. state = _InternalState(self)
  345. state._lastWasPath = False
  346. setattr(state._module, name, value)
  347. def __getattribute__(self, name):
  348. """
  349. Get an attribute from the module object, possibly emitting a warning.
  350. If the specified name has been deprecated, then a warning is issued.
  351. (Unless certain obscure conditions are met; see
  352. L{_ModuleProxy._lastWasPath} for more information about what might quash
  353. such a warning.)
  354. """
  355. state = _InternalState(self)
  356. if state._lastWasPath:
  357. deprecatedAttribute = None
  358. else:
  359. deprecatedAttribute = state._deprecatedAttributes.get(name)
  360. if deprecatedAttribute is not None:
  361. # If we have a _DeprecatedAttribute object from the earlier lookup,
  362. # allow it to issue the warning.
  363. value = deprecatedAttribute.get()
  364. else:
  365. # Otherwise, just retrieve the underlying value directly; it's not
  366. # deprecated, there's no warning to issue.
  367. value = getattr(state._module, name)
  368. if name == "__path__":
  369. state._lastWasPath = True
  370. else:
  371. state._lastWasPath = False
  372. return value
  373. class _DeprecatedAttribute:
  374. """
  375. Wrapper for deprecated attributes.
  376. This is intended to be used by L{_ModuleProxy}. Calling
  377. L{_DeprecatedAttribute.get} will issue a warning and retrieve the
  378. underlying attribute's value.
  379. @type module: C{module}
  380. @ivar module: The original module instance containing this attribute
  381. @type fqpn: C{str}
  382. @ivar fqpn: Fully qualified Python name for the deprecated attribute
  383. @type version: L{incremental.Version}
  384. @ivar version: Version that the attribute was deprecated in
  385. @type message: C{str}
  386. @ivar message: Deprecation message
  387. """
  388. def __init__(self, module, name, version, message):
  389. """
  390. Initialise a deprecated name wrapper.
  391. """
  392. self.module = module
  393. self.__name__ = name
  394. self.fqpn = module.__name__ + "." + name
  395. self.version = version
  396. self.message = message
  397. def get(self):
  398. """
  399. Get the underlying attribute value and issue a deprecation warning.
  400. """
  401. # This might fail if the deprecated thing is a module inside a package.
  402. # In that case, don't emit the warning this time. The import system
  403. # will come back again when it's not an AttributeError and we can emit
  404. # the warning then.
  405. result = getattr(self.module, self.__name__)
  406. message = _getDeprecationWarningString(
  407. self.fqpn, self.version, DEPRECATION_WARNING_FORMAT + ": " + self.message
  408. )
  409. warn(message, DeprecationWarning, stacklevel=3)
  410. return result
  411. def _deprecateAttribute(proxy, name, version, message):
  412. """
  413. Mark a module-level attribute as being deprecated.
  414. @type proxy: L{_ModuleProxy}
  415. @param proxy: The module proxy instance proxying the deprecated attributes
  416. @type name: C{str}
  417. @param name: Attribute name
  418. @type version: L{incremental.Version}
  419. @param version: Version that the attribute was deprecated in
  420. @type message: C{str}
  421. @param message: Deprecation message
  422. """
  423. _module = object.__getattribute__(proxy, "_module")
  424. attr = _DeprecatedAttribute(_module, name, version, message)
  425. # Add a deprecated attribute marker for this module's attribute. When this
  426. # attribute is accessed via _ModuleProxy a warning is emitted.
  427. _deprecatedAttributes = object.__getattribute__(proxy, "_deprecatedAttributes")
  428. _deprecatedAttributes[name] = attr
  429. def deprecatedModuleAttribute(version, message, moduleName, name):
  430. """
  431. Declare a module-level attribute as being deprecated.
  432. @type version: L{incremental.Version}
  433. @param version: Version that the attribute was deprecated in
  434. @type message: C{str}
  435. @param message: Deprecation message
  436. @type moduleName: C{str}
  437. @param moduleName: Fully-qualified Python name of the module containing
  438. the deprecated attribute; if called from the same module as the
  439. attributes are being deprecated in, using the C{__name__} global can
  440. be helpful
  441. @type name: C{str}
  442. @param name: Attribute name to deprecate
  443. """
  444. module = sys.modules[moduleName]
  445. if not isinstance(module, _ModuleProxy):
  446. module = cast(ModuleType, _ModuleProxy(module))
  447. sys.modules[moduleName] = module
  448. _deprecateAttribute(module, name, version, message)
  449. def warnAboutFunction(offender, warningString):
  450. """
  451. Issue a warning string, identifying C{offender} as the responsible code.
  452. This function is used to deprecate some behavior of a function. It differs
  453. from L{warnings.warn} in that it is not limited to deprecating the behavior
  454. of a function currently on the call stack.
  455. @param offender: The function that is being deprecated.
  456. @param warningString: The string that should be emitted by this warning.
  457. @type warningString: C{str}
  458. @since: 11.0
  459. """
  460. # inspect.getmodule() is attractive, but somewhat
  461. # broken in Python < 2.6. See Python bug 4845.
  462. offenderModule = sys.modules[offender.__module__]
  463. warn_explicit(
  464. warningString,
  465. category=DeprecationWarning,
  466. filename=inspect.getabsfile(offenderModule),
  467. lineno=max(lineNumber for _, lineNumber in findlinestarts(offender.__code__)),
  468. module=offenderModule.__name__,
  469. registry=offender.__globals__.setdefault("__warningregistry__", {}),
  470. module_globals=None,
  471. )
  472. def _passedArgSpec(argspec, positional, keyword):
  473. """
  474. Take an I{inspect.ArgSpec}, a tuple of positional arguments, and a dict of
  475. keyword arguments, and return a mapping of arguments that were actually
  476. passed to their passed values.
  477. @param argspec: The argument specification for the function to inspect.
  478. @type argspec: I{inspect.ArgSpec}
  479. @param positional: The positional arguments that were passed.
  480. @type positional: L{tuple}
  481. @param keyword: The keyword arguments that were passed.
  482. @type keyword: L{dict}
  483. @return: A dictionary mapping argument names (those declared in C{argspec})
  484. to values that were passed explicitly by the user.
  485. @rtype: L{dict} mapping L{str} to L{object}
  486. """
  487. result: Dict[str, object] = {}
  488. unpassed = len(argspec.args) - len(positional)
  489. if argspec.keywords is not None:
  490. kwargs = result[argspec.keywords] = {}
  491. if unpassed < 0:
  492. if argspec.varargs is None:
  493. raise TypeError("Too many arguments.")
  494. else:
  495. result[argspec.varargs] = positional[len(argspec.args) :]
  496. for name, value in zip(argspec.args, positional):
  497. result[name] = value
  498. for name, value in keyword.items():
  499. if name in argspec.args:
  500. if name in result:
  501. raise TypeError("Already passed.")
  502. result[name] = value
  503. elif argspec.keywords is not None:
  504. kwargs[name] = value
  505. else:
  506. raise TypeError("no such param")
  507. return result
  508. def _passedSignature(signature, positional, keyword):
  509. """
  510. Take an L{inspect.Signature}, a tuple of positional arguments, and a dict of
  511. keyword arguments, and return a mapping of arguments that were actually
  512. passed to their passed values.
  513. @param signature: The signature of the function to inspect.
  514. @type signature: L{inspect.Signature}
  515. @param positional: The positional arguments that were passed.
  516. @type positional: L{tuple}
  517. @param keyword: The keyword arguments that were passed.
  518. @type keyword: L{dict}
  519. @return: A dictionary mapping argument names (those declared in
  520. C{signature}) to values that were passed explicitly by the user.
  521. @rtype: L{dict} mapping L{str} to L{object}
  522. """
  523. result = {}
  524. kwargs = None
  525. numPositional = 0
  526. for (n, (name, param)) in enumerate(signature.parameters.items()):
  527. if param.kind == inspect.Parameter.VAR_POSITIONAL:
  528. # Varargs, for example: *args
  529. result[name] = positional[n:]
  530. numPositional = len(result[name]) + 1
  531. elif param.kind == inspect.Parameter.VAR_KEYWORD:
  532. # Variable keyword args, for example: **my_kwargs
  533. kwargs = result[name] = {}
  534. elif param.kind in (
  535. inspect.Parameter.POSITIONAL_OR_KEYWORD,
  536. inspect.Parameter.POSITIONAL_ONLY,
  537. ):
  538. if n < len(positional):
  539. result[name] = positional[n]
  540. numPositional += 1
  541. elif param.kind == inspect.Parameter.KEYWORD_ONLY:
  542. if name not in keyword:
  543. if param.default == inspect.Parameter.empty:
  544. raise TypeError(f"missing keyword arg {name}")
  545. else:
  546. result[name] = param.default
  547. else:
  548. raise TypeError(f"'{name}' parameter is invalid kind: {param.kind}")
  549. if len(positional) > numPositional:
  550. raise TypeError("Too many arguments.")
  551. for name, value in keyword.items():
  552. if name in signature.parameters.keys():
  553. if name in result:
  554. raise TypeError("Already passed.")
  555. result[name] = value
  556. elif kwargs is not None:
  557. kwargs[name] = value
  558. else:
  559. raise TypeError("no such param")
  560. return result
  561. def _mutuallyExclusiveArguments(argumentPairs):
  562. """
  563. Decorator which causes its decoratee to raise a L{TypeError} if two of the
  564. given arguments are passed at the same time.
  565. @param argumentPairs: pairs of argument identifiers, each pair indicating
  566. an argument that may not be passed in conjunction with another.
  567. @type argumentPairs: sequence of 2-sequences of L{str}
  568. @return: A decorator, used like so::
  569. @_mutuallyExclusiveArguments([["tweedledum", "tweedledee"]])
  570. def function(tweedledum=1, tweedledee=2):
  571. "Don't pass tweedledum and tweedledee at the same time."
  572. @rtype: 1-argument callable taking a callable and returning a callable.
  573. """
  574. def wrapper(wrappee):
  575. spec = inspect.signature(wrappee)
  576. _passed = _passedSignature
  577. @wraps(wrappee)
  578. def wrapped(*args, **kwargs):
  579. arguments = _passed(spec, args, kwargs)
  580. for this, that in argumentPairs:
  581. if this in arguments and that in arguments:
  582. raise TypeError(
  583. ("The %r and %r arguments to %s " "are mutually exclusive.")
  584. % (this, that, _fullyQualifiedName(wrappee))
  585. )
  586. return wrappee(*args, **kwargs)
  587. return wrapped
  588. return wrapper
  589. _Tc = TypeVar("_Tc", bound=Callable[..., Any])
  590. def deprecatedKeywordParameter(
  591. version: Version, name: str, replacement: Optional[str] = None
  592. ) -> Callable[[_Tc], _Tc]:
  593. """
  594. Return a decorator that marks a keyword parameter of a callable
  595. as deprecated. A warning will be emitted if a caller supplies
  596. a value for the parameter, whether the caller uses a keyword or
  597. positional syntax.
  598. @type version: L{incremental.Version}
  599. @param version: The version in which the parameter will be marked as
  600. having been deprecated.
  601. @type name: L{str}
  602. @param name: The name of the deprecated parameter.
  603. @type replacement: L{str}
  604. @param replacement: Optional text indicating what should be used in
  605. place of the deprecated parameter.
  606. @since: Twisted 21.2.0
  607. """
  608. def wrapper(wrappee: _Tc) -> _Tc:
  609. warningString = _getDeprecationWarningString(
  610. f"The {name!r} parameter to {_fullyQualifiedName(wrappee)}",
  611. version,
  612. replacement=replacement,
  613. )
  614. doc = "The {!r} parameter was deprecated in {}".format(
  615. name,
  616. getVersionString(version),
  617. )
  618. if replacement:
  619. doc = doc + "; " + _getReplacementString(replacement)
  620. doc += "."
  621. params = inspect.signature(wrappee).parameters
  622. if (
  623. name in params
  624. and params[name].kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
  625. ):
  626. parameterIndex = list(params).index(name)
  627. def checkDeprecatedParameter(*args, **kwargs):
  628. if len(args) > parameterIndex or name in kwargs:
  629. warn(warningString, DeprecationWarning, stacklevel=2)
  630. return wrappee(*args, **kwargs)
  631. else:
  632. def checkDeprecatedParameter(*args, **kwargs):
  633. if name in kwargs:
  634. warn(warningString, DeprecationWarning, stacklevel=2)
  635. return wrappee(*args, **kwargs)
  636. decorated = cast(_Tc, wraps(wrappee)(checkDeprecatedParameter))
  637. _appendToDocstring(decorated, doc)
  638. return decorated
  639. return wrapper