Development of an internal social media platform with personalised dashboards for students
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.

decorators.py 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. # Copyright (c) 2015-2016 Cara Vinson <ceridwenv@gmail.com>
  2. # Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
  3. # Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com>
  4. # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
  5. # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
  6. """ A few useful function/method decorators."""
  7. import functools
  8. import wrapt
  9. from astroid import context as contextmod
  10. from astroid import exceptions
  11. from astroid import util
  12. @wrapt.decorator
  13. def cached(func, instance, args, kwargs):
  14. """Simple decorator to cache result of method calls without args."""
  15. cache = getattr(instance, '__cache', None)
  16. if cache is None:
  17. instance.__cache = cache = {}
  18. try:
  19. return cache[func]
  20. except KeyError:
  21. cache[func] = result = func(*args, **kwargs)
  22. return result
  23. class cachedproperty(object):
  24. """ Provides a cached property equivalent to the stacking of
  25. @cached and @property, but more efficient.
  26. After first usage, the <property_name> becomes part of the object's
  27. __dict__. Doing:
  28. del obj.<property_name> empties the cache.
  29. Idea taken from the pyramid_ framework and the mercurial_ project.
  30. .. _pyramid: http://pypi.python.org/pypi/pyramid
  31. .. _mercurial: http://pypi.python.org/pypi/Mercurial
  32. """
  33. __slots__ = ('wrapped',)
  34. def __init__(self, wrapped):
  35. try:
  36. wrapped.__name__
  37. except AttributeError:
  38. util.reraise(TypeError('%s must have a __name__ attribute'
  39. % wrapped))
  40. self.wrapped = wrapped
  41. @property
  42. def __doc__(self):
  43. doc = getattr(self.wrapped, '__doc__', None)
  44. return ('<wrapped by the cachedproperty decorator>%s'
  45. % ('\n%s' % doc if doc else ''))
  46. def __get__(self, inst, objtype=None):
  47. if inst is None:
  48. return self
  49. val = self.wrapped(inst)
  50. setattr(inst, self.wrapped.__name__, val)
  51. return val
  52. def path_wrapper(func):
  53. """return the given infer function wrapped to handle the path
  54. Used to stop inference if the node has already been looked
  55. at for a given `InferenceContext` to prevent infinite recursion
  56. """
  57. # TODO: switch this to wrapt after the monkey-patching is fixed (ceridwen)
  58. @functools.wraps(func)
  59. def wrapped(node, context=None, _func=func, **kwargs):
  60. """wrapper function handling context"""
  61. if context is None:
  62. context = contextmod.InferenceContext()
  63. if context.push(node):
  64. return
  65. yielded = set()
  66. generator = _func(node, context, **kwargs)
  67. try:
  68. while True:
  69. res = next(generator)
  70. # unproxy only true instance, not const, tuple, dict...
  71. if res.__class__.__name__ == 'Instance':
  72. ares = res._proxied
  73. else:
  74. ares = res
  75. if ares not in yielded:
  76. yield res
  77. yielded.add(ares)
  78. except StopIteration as error:
  79. # Explicit StopIteration to return error information, see
  80. # comment in raise_if_nothing_inferred.
  81. if error.args:
  82. raise StopIteration(error.args[0])
  83. else:
  84. raise StopIteration
  85. return wrapped
  86. @wrapt.decorator
  87. def yes_if_nothing_inferred(func, instance, args, kwargs):
  88. inferred = False
  89. for node in func(*args, **kwargs):
  90. inferred = True
  91. yield node
  92. if not inferred:
  93. yield util.Uninferable
  94. @wrapt.decorator
  95. def raise_if_nothing_inferred(func, instance, args, kwargs):
  96. '''All generators wrapped with raise_if_nothing_inferred *must*
  97. explicitly raise StopIteration with information to create an
  98. appropriate structured InferenceError.
  99. '''
  100. # TODO: Explicitly raising StopIteration in a generator will cause
  101. # a RuntimeError in Python >=3.7, as per
  102. # http://legacy.python.org/dev/peps/pep-0479/ . Before 3.7 is
  103. # released, this code will need to use one of four possible
  104. # solutions: a decorator that restores the current behavior as
  105. # described in
  106. # http://legacy.python.org/dev/peps/pep-0479/#sub-proposal-decorator-to-explicitly-request-current-behaviour
  107. # , dynamic imports or exec to generate different code for
  108. # different versions, drop support for all Python versions <3.3,
  109. # or refactoring to change how these decorators work. In any
  110. # event, after dropping support for Python <3.3 this code should
  111. # be refactored to use `yield from`.
  112. inferred = False
  113. try:
  114. generator = func(*args, **kwargs)
  115. while True:
  116. yield next(generator)
  117. inferred = True
  118. except StopIteration as error:
  119. if not inferred:
  120. if error.args:
  121. # pylint: disable=not-a-mapping
  122. raise exceptions.InferenceError(**error.args[0])
  123. else:
  124. raise exceptions.InferenceError(
  125. 'StopIteration raised without any error information.')