-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #285 from scipp/integration-testing
Improve setup to facilitate integration testing and future service setup
- Loading branch information
Showing
6 changed files
with
223 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# SPDX-License-Identifier: BSD-3-Clause | ||
# Copyright (c) 2025 Scipp contributors (https://github.com/scipp) | ||
from __future__ import annotations | ||
|
||
import logging | ||
from typing import Generic, TypeVar | ||
|
||
from .core import ConfigSubscriber, HandlerRegistry, MessageSink, StreamProcessor | ||
from .core.handler import Handler | ||
from .core.service import Service | ||
from .kafka.message_adapter import AdaptingMessageSource, MessageAdapter | ||
from .kafka.source import KafkaConsumer, KafkaMessageSource | ||
|
||
Traw = TypeVar("Traw") | ||
Tin = TypeVar("Tin") | ||
Tout = TypeVar("Tout") | ||
|
||
|
||
class DataServiceBuilder(Generic[Traw, Tin, Tout]): | ||
def __init__( | ||
self, | ||
*, | ||
instrument: str, | ||
name: str, | ||
log_level: int = logging.INFO, | ||
adapter: MessageAdapter[Traw, Tin], | ||
handler_cls: type[Handler[Tin, Tout]], | ||
): | ||
self._name = f'{instrument}_{name}' | ||
self._log_level = log_level | ||
self._adapter = adapter | ||
self._handler_cls = handler_cls | ||
|
||
def build( | ||
self, | ||
control_consumer: KafkaConsumer, | ||
consumer: KafkaConsumer, | ||
sink: MessageSink[Tout], | ||
) -> Service: | ||
config_subscriber = ConfigSubscriber(consumer=control_consumer, config={}) | ||
processor = StreamProcessor( | ||
source=AdaptingMessageSource( | ||
source=KafkaMessageSource(consumer=consumer), adapter=self._adapter | ||
), | ||
sink=sink, | ||
handler_registry=HandlerRegistry( | ||
config=config_subscriber, handler_cls=self._handler_cls | ||
), | ||
) | ||
return Service( | ||
children=[config_subscriber], | ||
processor=processor, | ||
name=self._name, | ||
log_level=self._log_level, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
# SPDX-License-Identifier: BSD-3-Clause | ||
# Copyright (c) 2025 Scipp contributors (https://github.com/scipp) | ||
|
||
import time | ||
|
||
from beamlime import Handler, Message, MessageKey | ||
from beamlime.fakes import FakeMessageSink | ||
from beamlime.kafka.message_adapter import ( | ||
FakeKafkaMessage, | ||
KafkaMessage, | ||
MessageAdapter, | ||
) | ||
from beamlime.kafka.source import KafkaConsumer | ||
from beamlime.service_factory import DataServiceBuilder | ||
|
||
|
||
def fake_message_with_value(message: KafkaMessage, value: str) -> Message[str]: | ||
return Message( | ||
timestamp=1234, | ||
key=MessageKey(topic=message.topic(), source_name="dummy"), | ||
value=value, | ||
) | ||
|
||
|
||
class ForwardingAdapter(MessageAdapter[KafkaMessage, Message[int]]): | ||
def adapt(self, message: KafkaMessage) -> Message[int]: | ||
return fake_message_with_value(message, value=int(message.value().decode())) | ||
|
||
|
||
class ForwardingHandler(Handler[int, int]): | ||
def handle(self, message: Message[int]) -> list[Message[int]]: | ||
return [message] | ||
|
||
|
||
class EmptyConsumer(KafkaConsumer): | ||
def consume(self, num_messages: int, timeout: float) -> list[KafkaMessage]: | ||
return [] | ||
|
||
def close(self) -> None: | ||
pass | ||
|
||
|
||
class IntConsumer(KafkaConsumer): | ||
def __init__(self) -> None: | ||
self._values = [11, 22, 33, 44] | ||
self._index = 0 | ||
|
||
@property | ||
def at_end(self) -> bool: | ||
return self._index >= len(self._values) | ||
|
||
def consume(self, num_messages: int, timeout: float) -> list[KafkaMessage]: | ||
if self.at_end: | ||
return [] | ||
message = FakeKafkaMessage( | ||
value=str(self._values[self._index]).encode(), topic="dummy" | ||
) | ||
self._index += 1 | ||
return [message] | ||
|
||
def close(self) -> None: | ||
pass | ||
|
||
|
||
def test_basics() -> None: | ||
builder = DataServiceBuilder( | ||
instrument='instrument', | ||
name='name', | ||
adapter=ForwardingAdapter(), | ||
handler_cls=ForwardingHandler, | ||
) | ||
sink = FakeMessageSink() | ||
consumer = IntConsumer() | ||
service = builder.build( | ||
control_consumer=EmptyConsumer(), consumer=consumer, sink=sink | ||
) | ||
service.start(blocking=False) | ||
while not consumer.at_end: | ||
time.sleep(0.1) | ||
service.stop() | ||
assert len(sink.messages) == 4 | ||
values = [msg.value for msg in sink.messages] | ||
assert values == [11, 22, 33, 44] |