Guest User

Untitled

a guest
Nov 16th, 2018
86
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.28 KB | None | 0 0
  1. import collections
  2. import inspect
  3.  
  4. EMPTY = object()
  5.  
  6. # Goals:
  7. # - Make it easier to write tests:
  8. # - Reduce the number of variables a test needs to manage by introducing hidden state
  9. # - Automate duplicated tasks:
  10. # - Creating topologies
  11. # - Easy to control the messages sent/received:
  12. # - Simulate concurrency by interleaving state changes for differents transfers
  13. # - Simulate networks problems by delaying/replaying/dropping state changes
  14. # - Simulate attacks by producing invalid state changes (with or without valid signatures)
  15. # - Easy to test multiple nodes:
  16. # - To test both ends of a channel and simulate a network
  17. # - Easy to assert on valid states:
  18. # - Needs to take into account difference of views of both nodes:
  19. # - assertions that are valid globally
  20. # - assertions that are valid locally
  21. # - assertions that are valid after a given state change
  22.  
  23.  
  24. def get_defaults(argspec):
  25. """ Returns a dictionary with the default arguments of a function. """
  26. # This assumes the language forbids duplicate names for function arguments
  27. if argspec.kwonlydefaults is not None:
  28. args = dict(argspec.kwonlydefaults)
  29. else:
  30. args = dict()
  31.  
  32. if argspec.defaults is not None:
  33. positional_with_defaults = argspec.args[-len(argspec.defaults):]
  34. args.update(zip(positional_with_defaults, argspec.defaults))
  35.  
  36. return args
  37.  
  38.  
  39. def make_chainmap(defaults):
  40. if defaults is not None and not isinstance(defaults, collections.ChainMap):
  41. variables = collections.ChainMap(defaults)
  42. elif defaults is None:
  43. variables = collections.ChainMap()
  44. else:
  45. variables = defaults
  46.  
  47. return variables
  48.  
  49.  
  50. class Builder:
  51. def __init__(self, defaults: dict = None, factories: dict = None):
  52. variables = make_chainmap(defaults)
  53. self.variables = variables
  54. self.factories = factories or dict()
  55.  
  56. def __enter__(self):
  57. return self
  58.  
  59. def __exit__(self, *args):
  60. pass
  61.  
  62. def __getitem__(self, key):
  63. return self.variables[key]
  64.  
  65. def __setitem__(self, key, value):
  66. self.variables[key] = value
  67.  
  68. def __delitem__(self, key):
  69. del self.variables[key]
  70.  
  71. def overwrite(self, **overwrites):
  72. return Builder(self.variables.new_child(overwrites))
  73.  
  74. def builder_for(self, func):
  75. """ Createa a builder which has all the variables to call `func`. """
  76. missing = self.missing_callargs(func)
  77. return self.overwrite(**missing)
  78.  
  79. def create(self, what):
  80. """ Create an instance of `what`.
  81.  
  82. If there is a factory associated with `what`, this factory function is
  83. called, and the arguments passed to it are the ones in this builder.
  84.  
  85. Values currently in the builder always have priority. If there is no
  86. value for the given argument but `func` has a default argument, that is
  87. used instead of instantiating a new object. Otherwise a new object is
  88. created.
  89.  
  90. Note:
  91. varargs is ignored, it cannot be called by keyword
  92. """
  93. factory = self.factories.get(what, what)
  94.  
  95. if not callable(factory):
  96. raise ValueError(
  97. f'Cant create an instance of {repr(what)} because there is no '
  98. f'factory associated to it.',
  99. )
  100.  
  101. try:
  102. callargs = self.callargs_for(factory)
  103. instance = factory(**callargs)
  104. except TypeError:
  105. # builtins raise type error on inspect, assume no arguments
  106. instance = factory()
  107.  
  108. return instance
  109.  
  110. def missing_callargs(self, func):
  111. """ Return the a dictionary with the values which are missing in this
  112. builder to call func, defaults are take into account.
  113.  
  114. Raises:
  115. TypeError if func cannot be inspected.
  116. """
  117. argspec = inspect.getfullargspec(func)
  118. callargs = get_defaults(argspec)
  119. argument_names = argspec.args + argspec.kwonlyargs
  120. missing = dict()
  121.  
  122. # Do not try to force all names to have a value, otherwise this won't
  123. # work with methods (self should not be assigned)
  124. for name in argument_names:
  125. value = self.variables.get(name, EMPTY)
  126. annotation = argspec.annotations.get(name)
  127.  
  128. if value is not EMPTY:
  129. callargs[name] = value
  130. elif name not in callargs and annotation:
  131. instance = self.create(annotation)
  132. missing[name] = instance
  133.  
  134. return missing
  135.  
  136. def callargs_for(self, func):
  137. """ Return the arguments necessary to call func.
  138.  
  139. Raises:
  140. TypeError if func cannot be inspected.
  141. """
  142. argspec = inspect.getfullargspec(func)
  143. callargs = get_defaults(argspec)
  144. argument_names = argspec.args + argspec.kwonlyargs
  145.  
  146. # Do not try to force all names to have a value, otherwise this won't
  147. # work with methods (self should not be assigned)
  148. for name in argument_names:
  149. value = self.variables.get(name, EMPTY)
  150. annotation = argspec.annotations.get(name)
  151.  
  152. if value is not EMPTY:
  153. callargs[name] = value
  154. elif name not in callargs and annotation:
  155. instance = self.create(annotation)
  156. callargs[name] = instance
  157.  
  158. return callargs
Add Comment
Please, Sign In to add comment