Build 060.22: implement Acquisition Runtime Service
This commit is contained in:
@@ -0,0 +1,136 @@
|
|||||||
|
# app/src/market_data/acquisition/runtime/acquisition_runtime_service.py
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
"""
|
||||||
|
Сервис исполнения команд Acquisition Runtime.
|
||||||
|
|
||||||
|
Build 060.22 вводит первую производственную реализацию
|
||||||
|
сервисного слоя Runtime подсистемы Market Data Acquisition.
|
||||||
|
|
||||||
|
Сервис принимает типизированные Runtime-команды и делегирует
|
||||||
|
их выполнение соответствующим инфраструктурным зависимостям.
|
||||||
|
|
||||||
|
Сервис не содержит знаний о Trades Feed, Consistency или Recovery.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from src.market_data.acquisition.runtime.acquisition_runtime_service_protocol import (
|
||||||
|
AcquisitionRuntimeServiceProtocol,
|
||||||
|
)
|
||||||
|
from src.market_data.acquisition.runtime.runtime_commands import (
|
||||||
|
ConnectCommand,
|
||||||
|
DisconnectCommand,
|
||||||
|
SendBinaryCommand,
|
||||||
|
SendTextCommand,
|
||||||
|
SubscribeCommand,
|
||||||
|
UnsubscribeCommand,
|
||||||
|
)
|
||||||
|
from src.market_data.acquisition.runtime.websocket_protocol import (
|
||||||
|
AcquisitionRuntimeCommand,
|
||||||
|
AcquisitionRuntimeCommandDispatcherProtocol,
|
||||||
|
AcquisitionRuntimeEventPublisherProtocol,
|
||||||
|
WebSocketSessionProtocol,
|
||||||
|
WebSocketSubscriptionManagerProtocol,
|
||||||
|
WebSocketTransportProtocol,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AcquisitionRuntimeService(
|
||||||
|
AcquisitionRuntimeServiceProtocol,
|
||||||
|
AcquisitionRuntimeCommandDispatcherProtocol,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Сервис маршрутизации Acquisition Runtime Commands.
|
||||||
|
|
||||||
|
Каждая команда делегируется ровно одной инфраструктурной
|
||||||
|
зависимости.
|
||||||
|
|
||||||
|
Сервис не управляет reconnect, heartbeat, scheduler,
|
||||||
|
Recovery или обработкой рыночных данных.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
session: WebSocketSessionProtocol,
|
||||||
|
transport: WebSocketTransportProtocol,
|
||||||
|
subscription_manager: WebSocketSubscriptionManagerProtocol,
|
||||||
|
event_publisher: AcquisitionRuntimeEventPublisherProtocol,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Создать Acquisition Runtime Service.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
session:
|
||||||
|
Компонент жизненного цикла WebSocket-сессии.
|
||||||
|
|
||||||
|
transport:
|
||||||
|
Низкоуровневый WebSocket-транспорт.
|
||||||
|
|
||||||
|
subscription_manager:
|
||||||
|
Компонент управления активными подписками.
|
||||||
|
|
||||||
|
event_publisher:
|
||||||
|
Компонент публикации инфраструктурных Runtime-событий.
|
||||||
|
|
||||||
|
В Build 060.22 зависимость сохраняется как часть
|
||||||
|
утверждённой архитектуры, но публикация событий
|
||||||
|
будет подключена в последующих Build.
|
||||||
|
"""
|
||||||
|
self._session = session
|
||||||
|
self._transport = transport
|
||||||
|
self._subscription_manager = subscription_manager
|
||||||
|
self._event_publisher = event_publisher
|
||||||
|
|
||||||
|
async def dispatch(
|
||||||
|
self,
|
||||||
|
command: AcquisitionRuntimeCommand,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Выполнить одну инфраструктурную команду Acquisition Runtime.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command:
|
||||||
|
Типизированная команда Runtime Layer.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TypeError:
|
||||||
|
Если передан неподдерживаемый тип команды.
|
||||||
|
|
||||||
|
Exception:
|
||||||
|
Любое исключение инфраструктурной зависимости
|
||||||
|
распространяется вызывающему компоненту без изменения.
|
||||||
|
"""
|
||||||
|
if isinstance(command, ConnectCommand):
|
||||||
|
await self._session.start()
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(command, DisconnectCommand):
|
||||||
|
await self._session.stop()
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(command, SubscribeCommand):
|
||||||
|
await self._subscription_manager.subscribe(
|
||||||
|
command.subscription_key,
|
||||||
|
command.message,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(command, UnsubscribeCommand):
|
||||||
|
await self._subscription_manager.unsubscribe(
|
||||||
|
command.subscription_key,
|
||||||
|
command.message,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(command, SendTextCommand):
|
||||||
|
await self._transport.send(command.message.payload)
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(command, SendBinaryCommand):
|
||||||
|
await self._transport.send(command.message.payload)
|
||||||
|
return
|
||||||
|
|
||||||
|
raise TypeError(
|
||||||
|
"Неподдерживаемый тип Acquisition Runtime Command: "
|
||||||
|
f"{type(command).__name__}."
|
||||||
|
)
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# app/src/market_data/acquisition/runtime/acquisition_runtime_service_protocol.py
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
"""
|
||||||
|
Публичный контракт Acquisition Runtime Service.
|
||||||
|
|
||||||
|
Build 060.22 вводит сервисный уровень исполнения типизированных
|
||||||
|
команд Runtime подсистемы Market Data Acquisition.
|
||||||
|
|
||||||
|
Контракт не определяет устройство WebSocket Session, Transport,
|
||||||
|
Subscription Manager или механизма публикации событий.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Protocol, runtime_checkable
|
||||||
|
|
||||||
|
from src.market_data.acquisition.runtime.websocket_protocol import (
|
||||||
|
AcquisitionRuntimeCommand,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class AcquisitionRuntimeServiceProtocol(Protocol):
|
||||||
|
"""
|
||||||
|
Контракт сервиса исполнения Acquisition Runtime Commands.
|
||||||
|
|
||||||
|
Реализация принимает одну типизированную команду и передаёт её
|
||||||
|
соответствующей инфраструктурной зависимости.
|
||||||
|
|
||||||
|
Сервис не содержит знаний о Trades Feed, Consistency и Recovery.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def dispatch(
|
||||||
|
self,
|
||||||
|
command: AcquisitionRuntimeCommand,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Выполнить одну инфраструктурную команду Acquisition Runtime.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command:
|
||||||
|
Типизированная команда Runtime Layer.
|
||||||
|
"""
|
||||||
|
...
|
||||||
@@ -23,6 +23,10 @@ from src.market_data.acquisition.runtime.runtime_events import (
|
|||||||
ReconnectFailedEvent,
|
ReconnectFailedEvent,
|
||||||
ReconnectStartedEvent,
|
ReconnectStartedEvent,
|
||||||
)
|
)
|
||||||
|
from src.market_data.acquisition.runtime.transport_messages import (
|
||||||
|
TransportBinaryMessage,
|
||||||
|
TransportTextMessage,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
AcquisitionRuntimeCommand = (
|
AcquisitionRuntimeCommand = (
|
||||||
@@ -46,6 +50,11 @@ AcquisitionRuntimeEvent = (
|
|||||||
| HeartbeatTimeoutEvent
|
| HeartbeatTimeoutEvent
|
||||||
)
|
)
|
||||||
|
|
||||||
|
AcquisitionSubscriptionMessage = (
|
||||||
|
TransportTextMessage
|
||||||
|
| TransportBinaryMessage
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@runtime_checkable
|
@runtime_checkable
|
||||||
class WebSocketTransportProtocol(Protocol):
|
class WebSocketTransportProtocol(Protocol):
|
||||||
@@ -108,10 +117,50 @@ class WebSocketSubscriptionManagerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
Контракт управления активными WebSocket-подписками.
|
Контракт управления активными WebSocket-подписками.
|
||||||
|
|
||||||
Конкретные модели подписок и формат сообщений будут добавлены
|
Subscription Manager отвечает за регистрацию и удаление
|
||||||
в последующих Build'ах.
|
активных подписок, а также за восстановление зарегистрированных
|
||||||
|
подписок после повторного подключения.
|
||||||
|
|
||||||
|
Менеджер работает только с универсальными транспортными
|
||||||
|
сообщениями и не содержит exchange-specific логики.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
async def subscribe(
|
||||||
|
self,
|
||||||
|
subscription_key: str,
|
||||||
|
message: AcquisitionSubscriptionMessage,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Зарегистрировать и выполнить WebSocket-подписку.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
subscription_key:
|
||||||
|
Уникальный инфраструктурный ключ подписки.
|
||||||
|
|
||||||
|
message:
|
||||||
|
Полностью сформированное транспортное сообщение
|
||||||
|
подписки.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
async def unsubscribe(
|
||||||
|
self,
|
||||||
|
subscription_key: str,
|
||||||
|
message: AcquisitionSubscriptionMessage,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Отменить и удалить существующую WebSocket-подписку.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
subscription_key:
|
||||||
|
Уникальный инфраструктурный ключ подписки.
|
||||||
|
|
||||||
|
message:
|
||||||
|
Полностью сформированное транспортное сообщение
|
||||||
|
отмены подписки.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
async def restore_subscriptions(self) -> None:
|
async def restore_subscriptions(self) -> None:
|
||||||
"""Восстановить активные подписки после подключения."""
|
"""Восстановить активные подписки после подключения."""
|
||||||
...
|
...
|
||||||
|
|||||||
@@ -0,0 +1,323 @@
|
|||||||
|
# app/tests/unit/market_data/acquisition/runtime/test_acquisition_runtime_service.py
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from src.market_data.acquisition.runtime.acquisition_runtime_service import (
|
||||||
|
AcquisitionRuntimeService,
|
||||||
|
)
|
||||||
|
from src.market_data.acquisition.runtime.acquisition_runtime_service_protocol import (
|
||||||
|
AcquisitionRuntimeServiceProtocol,
|
||||||
|
)
|
||||||
|
from src.market_data.acquisition.runtime.runtime_commands import (
|
||||||
|
ConnectCommand,
|
||||||
|
DisconnectCommand,
|
||||||
|
SendBinaryCommand,
|
||||||
|
SendTextCommand,
|
||||||
|
SubscribeCommand,
|
||||||
|
UnsubscribeCommand,
|
||||||
|
)
|
||||||
|
from src.market_data.acquisition.runtime.transport_messages import (
|
||||||
|
TransportBinaryMessage,
|
||||||
|
TransportTextMessage,
|
||||||
|
)
|
||||||
|
from src.market_data.acquisition.runtime.websocket_protocol import (
|
||||||
|
AcquisitionRuntimeCommandDispatcherProtocol,
|
||||||
|
AcquisitionRuntimeEvent,
|
||||||
|
AcquisitionSubscriptionMessage,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FakeSession:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.started = 0
|
||||||
|
self.stopped = 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_connected(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def start(self) -> None:
|
||||||
|
self.started += 1
|
||||||
|
|
||||||
|
async def stop(self) -> None:
|
||||||
|
self.stopped += 1
|
||||||
|
|
||||||
|
|
||||||
|
class FakeTransport:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.messages: list[str | bytes] = []
|
||||||
|
|
||||||
|
async def connect(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def disconnect(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def send(
|
||||||
|
self,
|
||||||
|
message: str | bytes,
|
||||||
|
) -> None:
|
||||||
|
self.messages.append(message)
|
||||||
|
|
||||||
|
async def receive(self) -> str | bytes:
|
||||||
|
raise AssertionError("receive() should not be called.")
|
||||||
|
|
||||||
|
|
||||||
|
class FakeSubscriptionManager:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.subscriptions: list[
|
||||||
|
tuple[str, AcquisitionSubscriptionMessage]
|
||||||
|
] = []
|
||||||
|
self.unsubscriptions: list[
|
||||||
|
tuple[str, AcquisitionSubscriptionMessage]
|
||||||
|
] = []
|
||||||
|
|
||||||
|
async def subscribe(
|
||||||
|
self,
|
||||||
|
subscription_key: str,
|
||||||
|
message: AcquisitionSubscriptionMessage,
|
||||||
|
) -> None:
|
||||||
|
self.subscriptions.append(
|
||||||
|
(
|
||||||
|
subscription_key,
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def unsubscribe(
|
||||||
|
self,
|
||||||
|
subscription_key: str,
|
||||||
|
message: AcquisitionSubscriptionMessage,
|
||||||
|
) -> None:
|
||||||
|
self.unsubscriptions.append(
|
||||||
|
(
|
||||||
|
subscription_key,
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def restore_subscriptions(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def clear_subscriptions(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class FakeEventPublisher:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.events: list[AcquisitionRuntimeEvent] = []
|
||||||
|
|
||||||
|
async def publish(
|
||||||
|
self,
|
||||||
|
event: AcquisitionRuntimeEvent,
|
||||||
|
) -> None:
|
||||||
|
self.events.append(event)
|
||||||
|
|
||||||
|
|
||||||
|
def create_service() -> tuple[
|
||||||
|
AcquisitionRuntimeService,
|
||||||
|
FakeSession,
|
||||||
|
FakeTransport,
|
||||||
|
FakeSubscriptionManager,
|
||||||
|
FakeEventPublisher,
|
||||||
|
]:
|
||||||
|
session = FakeSession()
|
||||||
|
transport = FakeTransport()
|
||||||
|
subscriptions = FakeSubscriptionManager()
|
||||||
|
publisher = FakeEventPublisher()
|
||||||
|
|
||||||
|
service = AcquisitionRuntimeService(
|
||||||
|
session=session,
|
||||||
|
transport=transport,
|
||||||
|
subscription_manager=subscriptions,
|
||||||
|
event_publisher=publisher,
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
service,
|
||||||
|
session,
|
||||||
|
transport,
|
||||||
|
subscriptions,
|
||||||
|
publisher,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_implements_protocol() -> None:
|
||||||
|
service, *_ = create_service()
|
||||||
|
|
||||||
|
assert isinstance(
|
||||||
|
service,
|
||||||
|
AcquisitionRuntimeServiceProtocol,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_is_command_dispatcher() -> None:
|
||||||
|
service, *_ = create_service()
|
||||||
|
|
||||||
|
assert isinstance(
|
||||||
|
service,
|
||||||
|
AcquisitionRuntimeCommandDispatcherProtocol,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_dispatch_connect_command() -> None:
|
||||||
|
service, session, *_ = create_service()
|
||||||
|
|
||||||
|
asyncio.run(
|
||||||
|
service.dispatch(
|
||||||
|
ConnectCommand(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert session.started == 1
|
||||||
|
assert session.stopped == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_dispatch_disconnect_command() -> None:
|
||||||
|
service, session, *_ = create_service()
|
||||||
|
|
||||||
|
asyncio.run(
|
||||||
|
service.dispatch(
|
||||||
|
DisconnectCommand(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert session.started == 0
|
||||||
|
assert session.stopped == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_dispatch_subscribe_command() -> None:
|
||||||
|
service, _, _, subscriptions, _ = create_service()
|
||||||
|
|
||||||
|
message = TransportTextMessage(
|
||||||
|
payload='{"subscribe":true}',
|
||||||
|
)
|
||||||
|
|
||||||
|
asyncio.run(
|
||||||
|
service.dispatch(
|
||||||
|
SubscribeCommand(
|
||||||
|
subscription_key="BTCUSDT",
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert subscriptions.subscriptions == [
|
||||||
|
(
|
||||||
|
"BTCUSDT",
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_dispatch_unsubscribe_command() -> None:
|
||||||
|
service, _, _, subscriptions, _ = create_service()
|
||||||
|
|
||||||
|
message = TransportTextMessage(
|
||||||
|
payload='{"unsubscribe":true}',
|
||||||
|
)
|
||||||
|
|
||||||
|
asyncio.run(
|
||||||
|
service.dispatch(
|
||||||
|
UnsubscribeCommand(
|
||||||
|
subscription_key="BTCUSDT",
|
||||||
|
message=message,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert subscriptions.unsubscriptions == [
|
||||||
|
(
|
||||||
|
"BTCUSDT",
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_dispatch_send_text_command() -> None:
|
||||||
|
service, _, transport, _, _ = create_service()
|
||||||
|
|
||||||
|
asyncio.run(
|
||||||
|
service.dispatch(
|
||||||
|
SendTextCommand(
|
||||||
|
message=TransportTextMessage(
|
||||||
|
payload="hello",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert transport.messages == [
|
||||||
|
"hello",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_dispatch_send_binary_command() -> None:
|
||||||
|
service, _, transport, _, _ = create_service()
|
||||||
|
|
||||||
|
asyncio.run(
|
||||||
|
service.dispatch(
|
||||||
|
SendBinaryCommand(
|
||||||
|
message=TransportBinaryMessage(
|
||||||
|
payload=b"\x01\x02",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert transport.messages == [
|
||||||
|
b"\x01\x02",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_event_publisher_is_not_used_yet() -> None:
|
||||||
|
service, _, _, _, publisher = create_service()
|
||||||
|
|
||||||
|
asyncio.run(
|
||||||
|
service.dispatch(
|
||||||
|
ConnectCommand(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert publisher.events == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_unsupported_command_raises_type_error() -> None:
|
||||||
|
class UnsupportedCommand:
|
||||||
|
pass
|
||||||
|
|
||||||
|
service, *_ = create_service()
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
TypeError,
|
||||||
|
match="Неподдерживаемый тип Acquisition Runtime Command",
|
||||||
|
):
|
||||||
|
asyncio.run(
|
||||||
|
service.dispatch(
|
||||||
|
UnsupportedCommand(), # type: ignore[arg-type]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_session_exception_is_propagated() -> None:
|
||||||
|
class BrokenSession(FakeSession):
|
||||||
|
async def start(self) -> None:
|
||||||
|
raise RuntimeError("boom")
|
||||||
|
|
||||||
|
service = AcquisitionRuntimeService(
|
||||||
|
session=BrokenSession(),
|
||||||
|
transport=FakeTransport(),
|
||||||
|
subscription_manager=FakeSubscriptionManager(),
|
||||||
|
event_publisher=FakeEventPublisher(),
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError, match="boom"):
|
||||||
|
asyncio.run(
|
||||||
|
service.dispatch(
|
||||||
|
ConnectCommand(),
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -7,6 +7,7 @@ from src.market_data.acquisition.runtime.websocket_protocol import (
|
|||||||
AcquisitionRuntimeCommandDispatcherProtocol,
|
AcquisitionRuntimeCommandDispatcherProtocol,
|
||||||
AcquisitionRuntimeEvent,
|
AcquisitionRuntimeEvent,
|
||||||
AcquisitionRuntimeEventPublisherProtocol,
|
AcquisitionRuntimeEventPublisherProtocol,
|
||||||
|
AcquisitionSubscriptionMessage,
|
||||||
WebSocketSessionProtocol,
|
WebSocketSessionProtocol,
|
||||||
WebSocketSubscriptionManagerProtocol,
|
WebSocketSubscriptionManagerProtocol,
|
||||||
WebSocketTransportProtocol,
|
WebSocketTransportProtocol,
|
||||||
@@ -40,6 +41,20 @@ class FakeWebSocketSession:
|
|||||||
|
|
||||||
|
|
||||||
class FakeWebSocketSubscriptionManager:
|
class FakeWebSocketSubscriptionManager:
|
||||||
|
async def subscribe(
|
||||||
|
self,
|
||||||
|
subscription_key: str,
|
||||||
|
message: AcquisitionSubscriptionMessage,
|
||||||
|
) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def unsubscribe(
|
||||||
|
self,
|
||||||
|
subscription_key: str,
|
||||||
|
message: AcquisitionSubscriptionMessage,
|
||||||
|
) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
async def restore_subscriptions(self) -> None:
|
async def restore_subscriptions(self) -> None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -100,6 +115,20 @@ def test_incomplete_transport_does_not_satisfy_protocol() -> None:
|
|||||||
assert not isinstance(IncompleteTransport(), WebSocketTransportProtocol)
|
assert not isinstance(IncompleteTransport(), WebSocketTransportProtocol)
|
||||||
|
|
||||||
|
|
||||||
|
def test_incomplete_subscription_manager_does_not_satisfy_protocol() -> None:
|
||||||
|
class IncompleteSubscriptionManager:
|
||||||
|
async def restore_subscriptions(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def clear_subscriptions(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
assert not isinstance(
|
||||||
|
IncompleteSubscriptionManager(),
|
||||||
|
WebSocketSubscriptionManagerProtocol,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_incomplete_command_dispatcher_does_not_satisfy_protocol() -> None:
|
def test_incomplete_command_dispatcher_does_not_satisfy_protocol() -> None:
|
||||||
class IncompleteCommandDispatcher:
|
class IncompleteCommandDispatcher:
|
||||||
pass
|
pass
|
||||||
|
|||||||
1224
docs/migrations/build_060_22.md
Normal file
1224
docs/migrations/build_060_22.md
Normal file
File diff suppressed because it is too large
Load Diff
1424
docs/migrations/build_060_22_architecture.md
Normal file
1424
docs/migrations/build_060_22_architecture.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user