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 26KB

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