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.

context.py 3.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. # Copyright (c) 2015-2016 Cara Vinson <ceridwenv@gmail.com>
  2. # Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com>
  3. # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
  4. # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
  5. """Various context related utilities, including inference and call contexts."""
  6. import contextlib
  7. import copy
  8. import pprint
  9. class InferenceContext(object):
  10. """Provide context for inference
  11. Store already inferred nodes to save time
  12. Account for already visited nodes to infinite stop infinite recursion
  13. """
  14. __slots__ = ('path', 'lookupname', 'callcontext', 'boundnode', 'inferred')
  15. def __init__(self, path=None, inferred=None):
  16. self.path = path or set()
  17. """Path of visited nodes and their lookupname
  18. :type: set(tuple(NodeNG, optional(str)))"""
  19. self.lookupname = None
  20. self.callcontext = None
  21. self.boundnode = None
  22. self.inferred = inferred or {}
  23. """
  24. :type: dict(seq, seq)
  25. Inferred node contexts to their mapped results
  26. Currently the key is (node, lookupname, callcontext, boundnode)
  27. and the value is tuple of the inferred results
  28. """
  29. def push(self, node):
  30. """Push node into inference path
  31. :return: True if node is already in context path else False
  32. :rtype: bool
  33. Allows one to see if the given node has already
  34. been looked at for this inference context"""
  35. name = self.lookupname
  36. if (node, name) in self.path:
  37. return True
  38. self.path.add((node, name))
  39. return False
  40. def clone(self):
  41. """Clone inference path
  42. For example, each side of a binary operation (BinOp)
  43. starts with the same context but diverge as each side is inferred
  44. so the InferenceContext will need be cloned"""
  45. # XXX copy lookupname/callcontext ?
  46. clone = InferenceContext(copy.copy(self.path), inferred=self.inferred)
  47. clone.callcontext = self.callcontext
  48. clone.boundnode = self.boundnode
  49. return clone
  50. def cache_generator(self, key, generator):
  51. """Cache result of generator into dictionary
  52. Used to cache inference results"""
  53. results = []
  54. for result in generator:
  55. results.append(result)
  56. yield result
  57. self.inferred[key] = tuple(results)
  58. @contextlib.contextmanager
  59. def restore_path(self):
  60. path = set(self.path)
  61. yield
  62. self.path = path
  63. def __str__(self):
  64. state = ('%s=%s' % (field, pprint.pformat(getattr(self, field),
  65. width=80 - len(field)))
  66. for field in self.__slots__)
  67. return '%s(%s)' % (type(self).__name__, ',\n '.join(state))
  68. class CallContext(object):
  69. """Holds information for a call site."""
  70. __slots__ = ('args', 'keywords')
  71. def __init__(self, args, keywords=None):
  72. self.args = args
  73. if keywords:
  74. keywords = [(arg.arg, arg.value) for arg in keywords]
  75. else:
  76. keywords = []
  77. self.keywords = keywords
  78. def copy_context(context):
  79. if context is not None:
  80. return context.clone()
  81. return InferenceContext()