Пример #1
0
    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
Пример #2
0
    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
Пример #3
0
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}
Пример #4
0
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}
Пример #5
0
    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)
Пример #6
0
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}
Пример #7
0
    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)
Пример #8
0
    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)
Пример #9
0
    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