build 039: complete Quotes Feed migration foundation
This commit is contained in:
110
app/src/storage/instrument_store.py
Normal file
110
app/src/storage/instrument_store.py
Normal file
@@ -0,0 +1,110 @@
|
||||
# app/src/storage/instrument_store.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
from src.market_data.acquisition.models.instrument import Instrument
|
||||
from src.storage.exceptions import InstrumentStoreError
|
||||
|
||||
|
||||
# Контракт runtime-хранилища канонического справочника инструментов.
|
||||
@runtime_checkable
|
||||
class InstrumentStoreProtocol(Protocol):
|
||||
def get(
|
||||
self,
|
||||
source_name: str,
|
||||
) -> tuple[Instrument, ...] | None:
|
||||
"""
|
||||
Вернуть сохранённый набор инструментов для источника.
|
||||
|
||||
None означает cache miss: данные для источника ещё не сохранялись.
|
||||
Пустой tuple означает успешное сохранение пустого справочника.
|
||||
"""
|
||||
|
||||
def set(
|
||||
self,
|
||||
source_name: str,
|
||||
instruments: tuple[Instrument, ...],
|
||||
) -> None:
|
||||
"""Сохранить полный immutable-набор инструментов источника."""
|
||||
|
||||
def clear(
|
||||
self,
|
||||
source_name: str | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Очистить данные одного источника или всё хранилище.
|
||||
|
||||
source_name=None очищает все сохранённые источники.
|
||||
"""
|
||||
|
||||
|
||||
# In-memory реализация runtime-хранилища справочника инструментов.
|
||||
class InMemoryInstrumentStore:
|
||||
def __init__(self) -> None:
|
||||
self._items: dict[str, tuple[Instrument, ...]] = {}
|
||||
|
||||
def get(
|
||||
self,
|
||||
source_name: str,
|
||||
) -> tuple[Instrument, ...] | None:
|
||||
normalized_source_name = self._normalize_source_name(
|
||||
source_name
|
||||
)
|
||||
|
||||
return self._items.get(normalized_source_name)
|
||||
|
||||
def set(
|
||||
self,
|
||||
source_name: str,
|
||||
instruments: tuple[Instrument, ...],
|
||||
) -> None:
|
||||
normalized_source_name = self._normalize_source_name(
|
||||
source_name
|
||||
)
|
||||
|
||||
if not isinstance(instruments, tuple):
|
||||
raise InstrumentStoreError(
|
||||
"Справочник инструментов должен быть передан как tuple."
|
||||
)
|
||||
|
||||
if not all(
|
||||
isinstance(instrument, Instrument)
|
||||
for instrument in instruments
|
||||
):
|
||||
raise InstrumentStoreError(
|
||||
"Справочник содержит объект, не являющийся Instrument."
|
||||
)
|
||||
|
||||
self._items[normalized_source_name] = instruments
|
||||
|
||||
def clear(
|
||||
self,
|
||||
source_name: str | None = None,
|
||||
) -> None:
|
||||
if source_name is None:
|
||||
self._items.clear()
|
||||
return
|
||||
|
||||
normalized_source_name = self._normalize_source_name(
|
||||
source_name
|
||||
)
|
||||
|
||||
self._items.pop(
|
||||
normalized_source_name,
|
||||
None,
|
||||
)
|
||||
|
||||
def _normalize_source_name(
|
||||
self,
|
||||
source_name: str,
|
||||
) -> str:
|
||||
normalized_source_name = str(source_name or "").strip()
|
||||
|
||||
if not normalized_source_name:
|
||||
raise InstrumentStoreError(
|
||||
"Имя источника Instrument Store не должно быть пустым."
|
||||
)
|
||||
|
||||
return normalized_source_name
|
||||
Reference in New Issue
Block a user