async def test_send_result(redis_result_transport: RedisResultTransport, redis_client): await redis_result_transport.send_result( rpc_message=RpcMessage( id="123abc", api_name="my.api", procedure_name="my_proc", kwargs={"field": "value"}, return_path="abc", ), result_message=ResultMessage(id="345", rpc_message_id="123abc", result="All done! 😎"), return_path= "redis+key://my.api.my_proc:result:e1821498-e57c-11e7-af9d-7831c1c3936e", bus_client=None, ) assert await redis_client.keys("*") == [ b"my.api.my_proc:result:e1821498-e57c-11e7-af9d-7831c1c3936e" ] result = await redis_client.lpop( "my.api.my_proc:result:e1821498-e57c-11e7-af9d-7831c1c3936e") assert json.loads(result) == { "metadata": { "error": False, "rpc_message_id": "123abc", "id": "345" }, "kwargs": { "result": "All done! 😎" }, }
async def _consume_rpcs_with_transport(self, rpc_transport: RpcTransport, apis: List[Api] = None): rpc_messages = await rpc_transport.consume_rpcs(apis) for rpc_message in rpc_messages: self._validate(rpc_message, "incoming") await self._plugin_hook("before_rpc_execution", rpc_message=rpc_message) try: result = await self.call_rpc_local( api_name=rpc_message.api_name, name=rpc_message.procedure_name, kwargs=rpc_message.kwargs, ) except SuddenDeathException: # Used to simulate message failure for testing pass else: result = deform_to_bus(result) result_message = ResultMessage(result=result, rpc_message_id=rpc_message.id) await self._plugin_hook("after_rpc_execution", rpc_message=rpc_message, result_message=result_message) self._validate( result_message, "outgoing", api_name=rpc_message.api_name, procedure_name=rpc_message.procedure_name, ) await self.send_result(rpc_message=rpc_message, result_message=result_message)
async def test_send_result(redis_result_transport: RedisResultTransport, redis_client): await redis_result_transport.send_result( rpc_message=RpcMessage( rpc_id='123abc', api_name='my.api', procedure_name='my_proc', kwargs={'field': 'value'}, return_path='abc', ), result_message=ResultMessage( rpc_id='123abc', result='All done! 😎', ), return_path= 'redis+key://my.api.my_proc:result:e1821498-e57c-11e7-af9d-7831c1c3936e', ) assert await redis_client.keys('*') == [ b'my.api.my_proc:result:e1821498-e57c-11e7-af9d-7831c1c3936e' ] result = await redis_client.lpop( 'my.api.my_proc:result:e1821498-e57c-11e7-af9d-7831c1c3936e') assert json.loads(result) == { 'error': False, 'rpc_id': '123abc', 'result': 'All done! 😎', }
async def receive_result( self, rpc_message: RpcMessage, return_path: str, options: dict ) -> ResultMessage: logger.info("⌛ Faking listening for results. Will issue fake result in 0.5 seconds...") await asyncio.sleep(0.1) # This is relied upon in testing logger.debug("Faking received result") return ResultMessage(result="Fake result", rpc_message_id=rpc_message.id)
async def call_rpc(self, rpc_message: RpcMessage, options: dict, bus_client: "BusClient"): # Direct RPC transport calls API method immediately logger.debug("Directly executing RPC call for message {}".format(rpc_message)) api = registry.get(rpc_message.api_name) result = await getattr(api, rpc_message.procedure_name)(**rpc_message.kwargs) logger.debug("Sending result for message {}".format(rpc_message)) await self.result_transport.send_result( rpc_message=rpc_message, result_message=ResultMessage(result=result, rpc_message_id=rpc_message.id), return_path=rpc_message.return_path, ) logger.info( "⚡️ Directly executed RPC call & sent result for message {}.".format(rpc_message) )
async def receive_result(self, rpc_message: RpcMessage, return_path: str, options: dict) -> ResultMessage: logger.info(L("⌛ Awaiting Redis result for RPC message: {}", Bold(rpc_message))) redis_key = self._parse_return_path(return_path) pool = await self.get_redis_pool() with await pool as redis: start_time = time.time() # TODO: Make timeout configurable _, result = await redis.blpop(redis_key, timeout=5) result_dictionary = redis_decode(result) logger.info(L( "⬅ Received Redis result in {} for RPC message {}: {}", human_time(time.time() - start_time), rpc_message, Bold(result) )) return ResultMessage.from_dict(result_dictionary)
async def _consume_rpcs_with_transport( self, rpc_transport: RpcTransport, apis: List[Api] = None ): while True: try: rpc_messages = await rpc_transport.consume_rpcs(apis, bus_client=self) except TransportIsClosed: return for rpc_message in rpc_messages: self._validate(rpc_message, "incoming") await self._execute_hook("before_rpc_execution", rpc_message=rpc_message) try: result = await self.call_rpc_local( api_name=rpc_message.api_name, name=rpc_message.procedure_name, kwargs=rpc_message.kwargs, ) except SuddenDeathException: # Used to simulate message failure for testing return except CancelledError: raise except Exception as e: result = e else: result = deform_to_bus(result) result_message = ResultMessage(result=result, rpc_message_id=rpc_message.id) await self._execute_hook( "after_rpc_execution", rpc_message=rpc_message, result_message=result_message ) self._validate( result_message, "outgoing", api_name=rpc_message.api_name, procedure_name=rpc_message.procedure_name, ) await self.send_result(rpc_message=rpc_message, result_message=result_message)
async def send_result(self, rpc_message: RpcMessage, result_message: ResultMessage, return_path: str): logger.debug(L( "Sending result {} into Redis using return path {}", Bold(result_message), Bold(return_path) )) redis_key = self._parse_return_path(return_path) pool = await self.get_redis_pool() with await pool as redis: start_time = time.time() p = redis.pipeline() p.lpush(redis_key, redis_encode(result_message.to_dict())) # TODO: Make result expiry configurable p.expire(redis_key, timeout=60) await p.execute() logger.debug(L( "➡ Sent result {} into Redis in {} using return path {}", Bold(result_message), human_time(time.time() - start_time), Bold(return_path) ))
async def consume_rpcs(self, apis=None): if apis is None: apis = registry.all() while True: rpc_messages = await self.rpc_transport.consume_rpcs(apis) for rpc_message in rpc_messages: await plugin_hook('before_rpc_execution', rpc_message=rpc_message, bus_client=self) try: result = await self.call_rpc_local( api_name=rpc_message.api_name, name=rpc_message.procedure_name, kwargs=rpc_message.kwargs ) except SuddenDeathException: # Used to simulate message failure for testing pass else: result_message = ResultMessage(result=result, rpc_id=rpc_message.rpc_id) await plugin_hook('after_rpc_execution', rpc_message=rpc_message, result_message=result_message, bus_client=self) await self.send_result(rpc_message=rpc_message, result_message=result_message)
async def handle_execute_rpc(self, command: commands.ExecuteRpcCommand): await self.schema.ensure_loaded_from_bus() validate_incoming(self.config, self.schema, command.message) await self.hook_registry.execute("before_rpc_execution", rpc_message=command.message) try: result = await self._call_rpc_local( api_name=command.message.api_name, name=command.message.procedure_name, kwargs=command.message.kwargs, ) except SuddenDeathException: # Used to simulate message failure for testing return except asyncio.CancelledError: raise except Exception as e: result = e else: result = deform_to_bus(result) result_message = ResultMessage( result=result, rpc_message_id=command.message.id, api_name=command.message.api_name, procedure_name=command.message.procedure_name, ) await self.hook_registry.execute( "after_rpc_execution", rpc_message=command.message, result_message=result_message ) if not result_message.error: validate_outgoing(self.config, self.schema, result_message) await self.producer.send( commands.SendResultCommand(message=result_message, rpc_message=command.message) ).wait()