async def _call_rpc_local(self, api_name: str, name: str, kwargs: dict = frozendict()): api = self.api_registry.get(api_name) validate_event_or_rpc_name(api_name, "rpc", name) start_time = time.time() try: method = getattr(api, name) if self.config.api(api_name).cast_values: kwargs = cast_to_signature(kwargs, method) result = await run_user_provided_callable(method, args=[], kwargs=kwargs) except (asyncio.CancelledError, SuddenDeathException): raise except Exception as e: logging.exception(e) logger.warning( L( "⚡ Error while executing {}.{}. Took {}", Bold(api_name), Bold(name), human_time(time.time() - start_time), ) ) raise else: logger.info( L( "⚡ Executed {}.{} in {}", Bold(api_name), Bold(name), human_time(time.time() - start_time), ) ) return result
async def call_rpc_local(self, api_name: str, name: str, kwargs: dict = frozendict()): api = registry.get(api_name) self._validate_name(api_name, "rpc", name) start_time = time.time() try: method = getattr(api, name) if self.config.api(api_name).cast_values: kwargs = cast_to_signature(kwargs, method) result = method(**kwargs) result = await await_if_necessary(result) except (CancelledError, SuddenDeathException): raise except Exception as e: logger.warning( L( "⚡ Error while executing {}.{}. Took {}", Bold(api_name), Bold(name), human_time(time.time() - start_time), )) return e else: logger.info( L( "⚡ Executed {}.{} in {}", Bold(api_name), Bold(name), human_time(time.time() - start_time), )) return result
def test_cast_to_signature_simple(): def fn(a: int, b: str, c): pass obj = object() casted = cast_to_signature(callable=fn, parameters={"a": "1", "b": 2, "c": obj}) assert casted == {"a": 1, "b": "2", "c": obj}
def test_cast_to_signature_no_annotations(): def fn(a, b, c): pass obj = object() casted = cast_to_signature(callable=fn, parameters={"a": "1", "b": 2, "c": obj}) # Values untouched assert casted == {"a": "1", "b": 2, "c": obj}
async def _on_message(self, event_message: EventMessage, listener: Callable, options: dict, on_error: OnError): # TODO: Check events match those requested logger.info( L("📩 Received event {}.{} with ID {}".format( Bold(event_message.api_name), Bold(event_message.event_name), event_message.id))) validate_incoming(self.config, self.schema, event_message) await self.hook_registry.execute("before_event_execution", event_message=event_message) if self.config.api(event_message.api_name).cast_values: parameters = cast_to_signature(parameters=event_message.kwargs, callable=listener) else: parameters = event_message.kwargs # Call the listener. # Pass the event message as a positional argument, # thereby allowing listeners to have flexibility in the argument names. # (And therefore allowing listeners to use the `event` parameter themselves) if on_error == OnError.SHUTDOWN: # Run the callback in the queue_exception_checker(). This will # put any errors into Lightbus' error queue, and therefore # cause a shutdown await queue_exception_checker( run_user_provided_callable(listener, args=[event_message], kwargs=parameters), self.error_queue, help= (f"An error occurred while {listener} was handling an event. Lightbus will now" " shutdown. If you wish to continue you can use the on_error parameter when" " setting up your event. For example:\n\n bus.my_api.my_event.listen(fn," " listener_name='example', on_error=lightbus.OnError.ACKNOWLEDGE_AND_LOG)" ), ) elif on_error == on_error.ACKNOWLEDGE_AND_LOG: try: await listener(event_message, **parameters) except asyncio.CancelledError: raise except Exception as e: # Log here. Acknowledgement will follow in below logger.exception(e) # Acknowledge the successfully processed message await self.producer.send( AcknowledgeEventCommand(message=event_message, options=options)).wait() await self.hook_registry.execute("after_event_execution", event_message=event_message)
def test_cast_to_signature_wrapped(): def fn(a: int, b: str, c): pass @functools.wraps(fn) def wrapper(*args, **kwargs): pass obj = object() casted = cast_to_signature(callable=wrapper, parameters={"a": "1", "b": 2, "c": obj}) assert casted == {"a": 1, "b": "2", "c": obj}
def output(self, args, transport: EventTransport, message: EventMessage, bus: BusPath): """Print out the given message""" serialized = transport.serializer(message) if args.format in ("json", "pretty"): if args.format == "pretty": dumped = json.dumps(serialized, indent=4) else: dumped = json.dumps(serialized) sys.stdout.write(dumped) sys.stdout.write("\n") sys.stdout.flush() elif args.format == "human": print(Colors.BGreen, end="") print(f" {message.api_name}.{message.event_name} ".center(80, "=")) if hasattr(message, "datetime"): print(f" {message.datetime.strftime('%c')} ".center(80, " ")) print(Colors.Reset, end="") print(f"\n{Colors.BWhite}Metadata:{Colors.Reset}") for k, v in message.get_metadata().items(): print(f" {str(k).ljust(20)}: {v}") print(f"\n{Colors.BWhite}Data:{Colors.Reset}") for k, v in message.get_kwargs().items(): if isinstance(v, (dict, list)): v = json.dumps(v, indent=4) pad = " " * 24 v = "".join(pad + v for v in v.splitlines(keepends=True)).lstrip() print(f" {str(k).ljust(20)}: {v}") if args.validate or args.show_casting: print(f"\n{Colors.BWhite}Extra:{Colors.Reset}") if args.validate: try: bus.client.schema.validate_parameters( message.api_name, message.event_name, message.kwargs) except ValidationError as e: validation_message = f"{Colors.Red}{e}{Colors.Reset}" else: validation_message = f"{Colors.Green}Passed{Colors.Reset}" print(f" Validation: {validation_message}") if args.show_casting: for listener in bus.client.event_client._event_listeners: if (message.api_name, message.event_name) not in listener.events: continue hints = get_type_hints(listener.callable) casted = cast_to_signature(parameters=message.kwargs, callable=listener.callable) print( f"\n {Colors.BWhite}Casting for listener: {listener.name}{Colors.Reset}" ) for key, value in message.kwargs.items(): was = type(value) via = hints[key] now = type(casted[key]) color = Colors.Green if via == now else Colors.Red print(f" " f"{color}{str(key).ljust(20)}: " f"Received a '{was.__name__}', " f"casted to a '{via.__name__}', " f"result was a '{now.__name__}'" f"{Colors.Reset}") print("\n") else: sys.stderr.write(f"Unknown output format '{args.format}'\n") sys.exit(1)
async def listener(self, event_transport, events): """ Receive events from the transport and invoke the listener callable This is the core glue which combines the event transports' consume() method and the listener callable. The bulk of this is logging, validation, plugin hooks, and error handling. """ # event_transport.consume() returns an asynchronous generator # which will provide us with messages consumer = event_transport.consume(listen_for=events, **self.options) with self.bus_client._register_listener(events): async for event_message in consumer: # TODO: Check events match those requested # TODO: Support event name of '*', but transports should raise # TODO: an exception if it is not supported. logger.info( L("📩 Received event {}.{} with ID {}".format( Bold(event_message.api_name), Bold(event_message.event_name), event_message.id, ))) self.bus_client._validate(event_message, "incoming") await self.bus_client._plugin_hook("before_event_execution", event_message=event_message) if self.bus_client.config.api( event_message.api_name).cast_values: parameters = cast_to_signature( parameters=event_message.kwargs, callable=self.listener_callable) else: parameters = event_message.kwargs try: # Call the listener co = self.listener_callable( # Pass the event message as a positional argument, # thereby allowing listeners to have flexibility in the argument names. # (And therefore allowing listeners to use the `event` parameter themselves) event_message, **parameters, ) # Support awaitable event listeners if inspect.isawaitable(co): await co except LightbusShutdownInProgress as e: logger.info("Shutdown in progress: {}".format(e)) except Exception as e: if self.on_error == OnError.IGNORE: # We're ignore errors, so log it and move on logger.error( f"An event listener raised an exception while processing an event. Lightbus will " f"continue as normal because the on 'on_error' option is set " f"to '{OnError.IGNORE.value}'.") elif self.on_error == OnError.STOP_LISTENER: logger.error( f"An event listener raised an exception while processing an event. Lightbus will " f"stop the listener but keep on running. This is because the 'on_error' option " f"is set to '{OnError.STOP_LISTENER.value}'.") # Stop the listener by returning return else: # We're not ignoring errors, so raise it and # let the error handler callback deal with it raise # Await the consumer again, which is our way of allowing it to # acknowledge the message. This then allows us to fire the # `after_event_execution` plugin hook immediately afterwards await consumer.__anext__() await self.bus_client._plugin_hook("after_event_execution", event_message=event_message)
async def listener(self, event_transport: EventTransport, events: List[Tuple[str, str]]): """ Receive events from the transport and invoke the listener callable This is the core glue which combines the event transports' consume() method and the listener callable. The bulk of this is logging, validation, plugin hooks, and error handling. """ # event_transport.consume() returns an asynchronous generator # which will provide us with messages consumer = event_transport.consume( listen_for=events, listener_name=self.listener_name, bus_client=self, **self.options ) try: with self.bus_client._register_listener(events): async for event_messages in consumer: for event_message in event_messages: # TODO: Check events match those requested # TODO: Support event name of '*', but transports should raise # TODO: an exception if it is not supported. logger.info( L( "📩 Received event {}.{} with ID {}".format( Bold(event_message.api_name), Bold(event_message.event_name), event_message.id, ) ) ) self.bus_client._validate(event_message, "incoming") await self.bus_client._execute_hook( "before_event_execution", event_message=event_message ) if self.bus_client.config.api(event_message.api_name).cast_values: parameters = cast_to_signature( parameters=event_message.kwargs, callable=self.listener_callable ) else: parameters = event_message.kwargs try: # Call the listener. # Pass the event message as a positional argument, # thereby allowing listeners to have flexibility in the argument names. # (And therefore allowing listeners to use the `event` parameter themselves) await run_user_provided_callable( self.listener_callable, args=[event_message], kwargs=parameters, bus_client=self.bus_client, die_on_exception=False, ) except LightbusShutdownInProgress as e: logger.info("Shutdown in progress: {}".format(e)) return except Exception as e: if self.on_error == OnError.IGNORE: # We're ignore errors, so log it and move on logger.error( f"An event listener raised an exception while processing an event. Lightbus will " f"continue as normal because the on 'on_error' option is set " f"to '{OnError.IGNORE.value}'." ) elif self.on_error == OnError.STOP_LISTENER: logger.error( f"An event listener raised an exception while processing an event. Lightbus will " f"stop the listener but keep on running. This is because the 'on_error' option " f"is set to '{OnError.STOP_LISTENER.value}'." ) # Stop the listener by returning return else: # We're not ignoring errors, so raise it and # let the error handler callback deal with it raise # Acknowledge the successfully processed message await event_transport.acknowledge(event_message, bus_client=self.bus_client) await self.bus_client._execute_hook( "after_event_execution", event_message=event_message ) except CancelledError: # Close the consumer to allow it to do any cleanup try: await consumer.aclose() except StopAsyncIteration: pass