Esempio n. 1
0
async def test_cycle(assert_run, event_loop):
    with event_loop.assert_cleanup():
        xs = stream.empty() | pipe.cycle() | pipe.timeout(1)
        await assert_run(xs, [], asyncio.TimeoutError())

    with event_loop.assert_cleanup():
        xs = stream.empty() | add_resource.pipe(
            1) | pipe.cycle() | pipe.timeout(1)
        await assert_run(xs, [], asyncio.TimeoutError())

    with event_loop.assert_cleanup():
        xs = stream.just(1) | add_resource.pipe(1) | pipe.cycle()
        await assert_run(xs[:5], [1] * 5)
        assert event_loop.steps == [1] * 5
Esempio n. 2
0
    async def run(self) -> None:
        """
        The main task, that reads data from the sensors and pushes it onto the event_bus.
        """
        # Generate the UUIDs of new sensors
        sensor_stream = stream.chain(
            stream.iterate(
                iterate_safely(f"{self.__topic}/get",
                               f"{self.__topic}/status_update")),
            stream.iterate(event_bus.subscribe(f"{self.__topic}/add_host")),
        ) | pipe.flatmap(
            lambda item: stream.chain(
                (stream.call(event_bus.call, f"{self.__topic}/get_config", item
                             ) | catch.pipe(TopicNotRegisteredError)),
                stream.iterate(
                    event_bus.subscribe(f"nodes/by_uuid/{item}/update")),
            )
            | pipe.until(lambda config: config is None)
            | pipe.map(lambda config: config if self._is_config_valid(
                self.__node_id, config) else None)
            | pipe.map(self._create_transport)
            | pipe.switchmap(lambda transport: stream.empty() if transport is
                             None else stream.iterate(transport.stream_data()))
            | pipe.action(lambda data: event_bus.publish("wamp/publish", data)
                          ))

        await sensor_stream
Esempio n. 3
0
    def stream_data(self,
                    config: dict[str, Any]) -> AsyncGenerator[DataEvent, None]:
        """
        Stream the data from the sensor.
        Parameters
        ----------
        config: dict
            A dictionary containing the sensor configuration.

        Returns
        -------
        AsyncGenerator
            The asynchronous stream.
        """
        data_stream = (stream.chain(
            stream.just(config),
            stream.iterate(
                event_bus.subscribe(f"nodes/by_uuid/{self.__uuid}/update")))
                       |
                       pipe.action(lambda _: logging.getLogger(__name__).info(
                           "Got new configuration for: %s", self) if config is
                                   not None else logging.getLogger(__name__).
                                   info("Removed configuration for: %s", self))
                       | pipe.map(self._parse_config)
                       | pipe.switchmap(lambda conf: stream.empty(
                       ) if conf is None or not conf["enabled"] else
                                        (self._configure_and_stream(conf))))

        return data_stream
Esempio n. 4
0
 def _configure_and_stream(
         self,
         config: dict[str, Any] | None) -> AsyncGenerator[DataEvent, None]:
     if config is None:
         return stream.empty()
     try:
         # Run all config steps in order (concat) and one at a time (task_limit=1). Drop the output. There is
         # nothing to compare them to (filter => false), then read all sensors of the bricklet and process them in
         # parallel (flatten).
         config_stream = stream.chain(
             stream.iterate(config["on_connect"])
             | pipe.starmap(lambda func, timeout: stream.just(func()) | pipe
                            .timeout(timeout))
             | pipe.concat(task_limit=1)
             | pipe.filter(lambda result: False),
             stream.iterate(config["config"].items())
             | pipe.starmap(self._parse_callback_configuration)
             | pipe.starmap(self._set_callback_configuration)
             | pipe.flatten()
             |
             pipe.map(lambda args: self._read_sensor(config["uuid"], *args))
             | pipe.flatten(),
         )
         return config_stream
     except NotConnectedError:
         # Do not log it
         raise
     except Exception:
         self._logger.exception("This should not happen.")
         raise
Esempio n. 5
0
    def _stream_data(self, transport):
        config_stream = (stream.chain(
            stream.call(
                call_safely,
                f"{self.__database_topic}/get_config",
                f"{self.__database_topic}/status_update",
                transport.uuid,
            ),
            stream.iterate(
                event_bus.subscribe(f"nodes/by_uuid/{transport.uuid}/add")),
        )
                         | pipe.map(lambda config: self._create_device(
                             transport, config))
                         | pipe.starmap(lambda config, device: stream.empty(
                         ) if device is None else device.stream_data(config))
                         | pipe.switch()
                         | context.pipe(
                             transport,
                             on_enter=lambda: logging.getLogger(__name__).info(
                                 "Connected to %s at %s (%s).", transport.name,
                                 transport.uri, transport.label),
                             on_exit=lambda: logging.getLogger(__name__).info(
                                 "Disconnected from %s at %s (%s).", transport.
                                 name, transport.uri, transport.label),
                         ))

        return config_stream
async def catch(
    source: AsyncIterable[Any],
    exc_class: Type[BaseException],
    on_exc: Callable[[BaseException],
                     Stream] = None) -> AsyncGenerator[Any, None]:
    """
    Catch an exception and then switch to the next stream `on_exc` or gracefully terminate, when no stream is given.
    Parameters
    ----------
    source: AsyncIterable
    exc_class: BaseException type
        The exception to catch
    on_exc: Callable
        A function, that takes an exception and returns a Stream.

    Yields
    -------
    Any
        The results from the source stream or the `on_exc` stream.
    """
    try:
        async with streamcontext(source) as streamer:
            async for item in streamer:
                yield item
    except exc_class as exc:
        if on_exc is not None:
            async with on_exc(exc).stream() as streamer:
                async for item in streamer:
                    yield item
        else:
            yield stream.empty()
Esempio n. 7
0
    def stream_data(self) -> AsyncGenerator[DataEvent, None]:
        """
        Generate the initial configuration of the sensor, configure it, and finally stream the data from the sensor.
        If there is a configuration update, reconfigure the sensor and start streaming again.
        Returns
        -------
        AsyncGenerator of DataEvent
            The data from the device
        """
        # Generates the first configuration
        # Query the database and if it does not have a config for the sensor, wait until there is one

        data_stream = (
            stream.chain(
                stream.call(
                    call_safely, "db_labnode_sensors/get_config", "db_labnode_sensors/status_update", self.__uuid
                )
                | pipe.takewhile(lambda config: config is not None),
                stream.iterate(event_bus.subscribe(f"nodes/by_uuid/{self.__uuid}/update")),
            )
            | pipe.action(
                lambda config: logging.getLogger(__name__).info(
                    "Got new configuration for: %s",
                    self._device,
                )
            )
            | pipe.map(self._create_config)
            | pipe.switchmap(
                lambda config: stream.empty()
                if config is None or not config["enabled"]
                else (self._configure_and_stream(config))
            )
        )

        return data_stream
Esempio n. 8
0
async def test_cycle(assert_run, event_loop):
    with event_loop.assert_cleanup():
        xs = stream.empty() | pipe.cycle() | pipe.timeout(1)
        await assert_run(xs, [], asyncio.TimeoutError())

    with event_loop.assert_cleanup():
        xs = (
            stream.empty()
            | add_resource.pipe(1)
            | pipe.cycle()
            | pipe.timeout(1)
        )
        await assert_run(xs, [], asyncio.TimeoutError())

    with event_loop.assert_cleanup():
        xs = stream.just(1) | add_resource.pipe(1) | pipe.cycle()
        await assert_run(xs[:5], [1]*5)
        assert event_loop.steps == [1]*5
Esempio n. 9
0
 async def execute(self) -> Tuple[Optional[int], Stream]:
     if self.executable_commands:
         source_action = cast(CLISource, self.executable_commands[0].action)
         count, flow = await source_action.source()
         for command in self.executable_commands[1:]:
             flow_action = cast(CLIFlow, command.action)
             flow = await flow_action.flow(flow)
         return count, flow
     else:
         return 0, stream.empty()
Esempio n. 10
0
 async def _set_callback_configuration(
         self, sid: int, unit: str, topic: str,
         config: AdvancedCallbackConfiguration):
     try:
         await self.device.set_callback_configuration(sid, *config)
     except AssertionError:
         self._logger.error(
             "Invalid configuration for %s: sid=%i, config=%s", self.device,
             sid, config)
         return stream.empty()
     remote_callback_config: AdvancedCallbackConfiguration
     remote_callback_config = await self.device.get_callback_configuration(
         sid)
     if remote_callback_config.period == 0:
         self._logger.warning(
             "Callback configuration configuration for %s: sid=%i, config=%s failed. Source disabled.",
             self.device,
             sid,
             config,
         )
         return stream.empty()
     return stream.just((sid, unit, topic, remote_callback_config))
Esempio n. 11
0
async def test_async_gen() -> None:

    async with stream.empty().stream() as empty:
        async for _ in await force_gen(empty):
            pass

    with pytest.raises(Exception):
        async with stream.throw(Exception(";)")).stream() as err:
            async for _ in await force_gen(err):
                pass

    async with stream.iterate(range(0, 100)).stream() as elems:
        assert [x async for x in await force_gen(elems)] == list(range(0, 100))
Esempio n. 12
0
    def stream_data(self) -> AsyncGenerator[DataEvent, None]:
        """
        Generate the initial configuration of the sensor, configure it, and finally stream the data from the sensor.
        If there is a configuration update, reconfigure the sensor and start streaming again.
        Returns
        -------
        AsyncGenerator of DataEvent
            The data from the device
        """
        # Generates the first configuration
        # Query the database and if it does not have a config for the sensor, wait until there is one

        data_stream = stream.chain(
            stream.just(self),
            stream.iterate(
                event_bus.subscribe(
                    f"nodes/tinkerforge/{self.device.uid}/remove"))[:1]
            | pipe.map(lambda x: None),
        ) | pipe.switchmap(
            lambda sensor: stream.empty() if sensor is None else
            (self._stream_config_updates(sensor)
             | pipe.switchmap(lambda config: stream.chain(
                 stream.just(config),
                 stream.iterate(
                     event_bus.subscribe(
                         f"nodes/by_uuid/{config['uuid']}/remove"))[:1]
                 | pipe.map(lambda x: None),
             ))
             | pipe.action(lambda config: logging.getLogger(__name__).info(
                 "Got new configuration for: %s",
                 sensor.device,
             ))
             | pipe.map(self._create_config)
             | pipe.switchmap(lambda config: stream.empty()
                              if config is None or not config["enabled"] else
                              (self._configure_and_stream(config)))))

        return data_stream
Esempio n. 13
0
async def arg_stream(ic, user, arg):
    if type(arg) in [int, str, float]:
        return stream.just({'type': type(arg).__name__, 'val': arg})

    elif isinstance(arg, pathlib.Path):
        path = misc.normpath(ic, arg)
        return read_field(ic, user, path)

    elif type(arg) is dict and 'fname' in arg:
        return display_fn(ic, arg)

    else:
        logger.warning(f"{ic.user}@{ic.path}: argument {arg}: unknow type")
        return stream.empty()
Esempio n. 14
0
    def on_error(self, exc: BaseException) -> AsyncGenerator[None, None]:
        """
        The function to call in case of an execution during streaming.

        Parameters
        ----------
        exc: BaseException
            The exception, that was raised

        Returns
        -------
        AsyncGenerator
            Am empty stream, that terminates without generating a value.
        """
        logging.getLogger(__name__).error(
            "Error while while reading %s. Terminating device. Error: %s",
            self, exc)
        return stream.empty()
Esempio n. 15
0
 def _configure_and_stream(
         self, config: dict[str, Any]) -> AsyncGenerator[DataEvent, None]:
     if config is None:
         return stream.empty()
     # Run all config steps in order (concat) and one at a time (task_limit=1). Drop the output. There is nothing to
     # compare them to (filter => false), then read the device.
     config_stream = stream.chain(
         stream.iterate(config["on_connect"])
         | pipe.starmap(lambda func, timeout: stream.just(func()) | pipe.
                        timeout(timeout))
         | pipe.concat(task_limit=1)
         | pipe.filter(lambda result: False),
         self._read_device(config)
         | pipe.map(lambda item: DataEvent(sender=config["uuid"],
                                           topic=config["topic"],
                                           value=item,
                                           sid=0,
                                           unit=config["unit"]))
         | finally_action.pipe(
             stream.call(self._clean_up, config["on_disconnect"])),
     ) | catch.pipe(TypeError, on_exc=self.on_error)
     return config_stream
Esempio n. 16
0
async def test_empty(assert_run):
    xs = stream.empty()
    await assert_run(xs, [])
Esempio n. 17
0
async def test_empty(assert_run):
    xs = stream.empty()
    await assert_run(xs, [])