calculate.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import asyncio
  2. initial_state = {
  3. 'running': False,
  4. 'result': None,
  5. 'error': None
  6. }
  7. class ActionTypes(object):
  8. START = 'calculate/START'
  9. FAILURE = 'calculate/FAILURE'
  10. SUCCESS = 'calculate/SUCCESS'
  11. class ActionCreators(object):
  12. @staticmethod
  13. def calculate(value1, value2):
  14. return thunk(value1, value2)
  15. @staticmethod
  16. def start():
  17. return dict(
  18. type=ActionTypes.START
  19. )
  20. @staticmethod
  21. def success(result):
  22. return dict(
  23. type=ActionTypes.SUCCESS,
  24. result=result
  25. )
  26. @staticmethod
  27. def failure(error):
  28. return dict(
  29. type=ActionTypes.FAILURE,
  30. error=error
  31. )
  32. def reducer(state=None, action=None):
  33. if state is None:
  34. state = initial_state
  35. if not (isinstance(action, dict) and 'type' in action):
  36. return state
  37. if action['type'] == ActionTypes.START:
  38. state['running'] = True
  39. elif action['type'] == ActionTypes.SUCCESS:
  40. state['running'] = False
  41. state['result'] = action['result']
  42. state['error'] = None
  43. elif action['type'] == ActionTypes.FAILURE:
  44. state['running'] = False
  45. state['result'] = None
  46. state['error'] = action['error']
  47. return state
  48. async def calculate(value1, value2):
  49. print('dividing', value1, value2)
  50. await asyncio.sleep(3)
  51. return value1 / value2
  52. def thunk(value1, value2):
  53. async def wrapper(dispatch, get_state):
  54. dispatch(ActionCreators.start())
  55. try:
  56. result = await calculate(value1, value2)
  57. dispatch(ActionCreators.success(result))
  58. except Exception as error:
  59. dispatch(ActionCreators.failure(dict(
  60. type=error.__class__.__name__,
  61. description=str(error)
  62. )))
  63. return wrapper
  64. if __name__ == '__main__':
  65. import unittest
  66. import unittest.mock as mock
  67. import inspect
  68. loop = asyncio.get_event_loop()
  69. class TestSuite(unittest.TestCase):
  70. def test_action_creators(self):
  71. received = ActionCreators.calculate(1, 2)
  72. expected = thunk(1, 2)
  73. self.assertEqual(expected.__code__, received.__code__)
  74. self.assertEqual(ActionCreators.start(), {'type': 'calculate/START'})
  75. self.assertEqual(ActionCreators.success(4), {'type': 'calculate/SUCCESS', 'result': 4})
  76. self.assertEqual(ActionCreators.failure(ZeroDivisionError().__class__), {'type': 'calculate/FAILURE', 'error': ZeroDivisionError().__class__})
  77. def test_reducer(self):
  78. self.assertEqual(reducer(None, None), initial_state)
  79. self.assertEqual(reducer(initial_state, None), initial_state)
  80. self.assertEqual(reducer(initial_state, {'type': ActionCreators.calculate(1, 2)}), initial_state)
  81. self.assertEqual(reducer(initial_state, ActionCreators.start()), {'running': True, 'result': None, 'error': None})
  82. self.assertEqual(reducer(initial_state, ActionCreators.success(7)), {'running': False, 'result': 7, 'error': None})
  83. self.assertEqual(reducer(initial_state, ActionCreators.failure(ZeroDivisionError.__class__)), {'running': False, 'result': None, 'error': ZeroDivisionError.__class__})
  84. def test_thunk(self):
  85. dispatched_actions = []
  86. def dispatch(action):
  87. dispatched_actions.append(action)
  88. def get_state():
  89. pass
  90. wrapper = thunk(3, 7)
  91. self.assertTrue(inspect.iscoroutinefunction(wrapper))
  92. task = wrapper(dispatch, get_state)
  93. loop.run_until_complete(task)
  94. self.assertEqual(dispatched_actions, [{'type': 'calculate/START'}, {'type': 'calculate/SUCCESS', 'result': 3/7}])
  95. unittest.main()