build 039: complete Quotes Feed migration foundation
This commit is contained in:
599
docs/migrations/build_025.md
Normal file
599
docs/migrations/build_025.md
Normal file
@@ -0,0 +1,599 @@
|
||||
# Build 025 — Удаление legacy `ExchangeSymbol` compatibility layer
|
||||
|
||||
**Статус:** ✅ Завершён
|
||||
**Дата:** 2026-07-12
|
||||
**Подсистема:** Instrument Reference / Exchange Integration
|
||||
**Этап:** Завершение миграции на каноническую модель `Instrument`
|
||||
|
||||
---
|
||||
|
||||
# Цель Build
|
||||
|
||||
Полностью удалить временный compatibility layer, использовавшийся во время поэтапного перехода от legacy-модели:
|
||||
|
||||
```text
|
||||
ExchangeSymbol
|
||||
```
|
||||
|
||||
к канонической модели:
|
||||
|
||||
```text
|
||||
Instrument
|
||||
```
|
||||
|
||||
После завершения Build все production-потребители работают через единый канонический контур:
|
||||
|
||||
```text
|
||||
Instrument Acquisition
|
||||
↓
|
||||
Instrument Store
|
||||
↓
|
||||
get_instruments()
|
||||
↓
|
||||
validate_symbol()
|
||||
↓
|
||||
Instrument
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Предпосылки
|
||||
|
||||
На предыдущих этапах были выполнены:
|
||||
|
||||
- создание канонической модели `Instrument`;
|
||||
- построение Instrument Acquisition pipeline;
|
||||
- создание `InstrumentStoreProtocol`;
|
||||
- реализация `InMemoryInstrumentStore`;
|
||||
- перенос кэша справочника инструментов в Storage;
|
||||
- добавление `ExchangeService.get_instruments()`;
|
||||
- перевод `validate_symbol()` на канонический справочник;
|
||||
- перевод Telegram UI и runtime-потребителей на `Instrument`;
|
||||
- удаление неиспользуемого legacy Market Handler.
|
||||
|
||||
После этого legacy-контур сохранялся только как временная проекция:
|
||||
|
||||
```text
|
||||
Instrument
|
||||
↓
|
||||
compatibility.py
|
||||
↓
|
||||
ExchangeSymbol
|
||||
↓
|
||||
get_exchange_symbols()
|
||||
```
|
||||
|
||||
Реальных production-потребителей этого контура больше не осталось.
|
||||
|
||||
---
|
||||
|
||||
# Предварительный аудит
|
||||
|
||||
Выполнен поиск production-зависимостей:
|
||||
|
||||
```bash
|
||||
grep -RIn \
|
||||
--exclude-dir="__pycache__" \
|
||||
--exclude="*.pyc" \
|
||||
-E "ExchangeSymbol|get_exchange_symbols|map_instrument_to_exchange_symbol|map_instruments_to_exchange_symbols|_exchange_symbols_projection_cache" \
|
||||
src
|
||||
```
|
||||
|
||||
Установлено, что все найденные элементы находились только внутри самого legacy-контура:
|
||||
|
||||
```text
|
||||
src/market_data/acquisition/compatibility.py
|
||||
src/integrations/exchange/models.py
|
||||
src/integrations/exchange/service.py
|
||||
```
|
||||
|
||||
Канонические production-потребители уже использовали:
|
||||
|
||||
```text
|
||||
get_instruments()
|
||||
validate_symbol()
|
||||
Instrument
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Аудит legacy parser helpers
|
||||
|
||||
Проверены методы:
|
||||
|
||||
```text
|
||||
_extract_exchange_symbols_raw()
|
||||
_parse_exchange_symbol()
|
||||
_parse_exchange_symbol_status()
|
||||
_parse_market_modes()
|
||||
_extract_filter_value()
|
||||
```
|
||||
|
||||
Выполнено:
|
||||
|
||||
```bash
|
||||
grep -RIn \
|
||||
--exclude-dir="__pycache__" \
|
||||
--exclude="*.pyc" \
|
||||
-E "_extract_exchange_symbols_raw|_parse_exchange_symbol\(|_parse_exchange_symbol_status|_parse_market_modes|_extract_filter_value" \
|
||||
src tests
|
||||
```
|
||||
|
||||
Подтверждено, что эти методы использовались только устаревшим equivalence-тестом и больше не требовались production-коду.
|
||||
|
||||
---
|
||||
|
||||
# Подготовка тестового контура
|
||||
|
||||
Перед удалением production compatibility layer были обновлены канонические тесты.
|
||||
|
||||
## Обновлён файл
|
||||
|
||||
```text
|
||||
tests/unit/integrations/exchange/test_service_instruments.py
|
||||
```
|
||||
|
||||
Из него удалены тесты legacy-проекции:
|
||||
|
||||
```text
|
||||
test_get_exchange_symbols_uses_get_instruments
|
||||
test_get_exchange_symbols_maps_canonical_instruments
|
||||
test_get_exchange_symbols_projection_cache_preserves_identity
|
||||
```
|
||||
|
||||
Сохранены все тесты канонического поведения:
|
||||
|
||||
- отключённая биржа;
|
||||
- Instrument Store hit;
|
||||
- Instrument Store miss;
|
||||
- загрузка через acquisition;
|
||||
- сохранение результата в Store;
|
||||
- повторное использование Store;
|
||||
- поддержка пустого immutable-набора;
|
||||
- общий Store между экземплярами `ExchangeService`;
|
||||
- обработка acquisition errors;
|
||||
- логирование ошибок;
|
||||
- отсутствие заполнения Store при ошибке.
|
||||
|
||||
---
|
||||
|
||||
## Обновлён файл
|
||||
|
||||
```text
|
||||
tests/unit/integrations/exchange/test_service_validate_symbol.py
|
||||
```
|
||||
|
||||
Legacy-проверка через monkeypatch метода:
|
||||
|
||||
```text
|
||||
get_exchange_symbols()
|
||||
```
|
||||
|
||||
заменена проверкой прямого использования:
|
||||
|
||||
```text
|
||||
get_instruments()
|
||||
```
|
||||
|
||||
Дополнительно добавлен отрицательный архитектурный тест:
|
||||
|
||||
```python
|
||||
def test_exchange_service_has_no_legacy_get_exchange_symbols() -> None:
|
||||
assert not hasattr(
|
||||
ExchangeService,
|
||||
"get_exchange_symbols",
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Обновлён файл
|
||||
|
||||
```text
|
||||
tests/unit/telegram/ui/test_currency_ui.py
|
||||
```
|
||||
|
||||
Legacy-проверка отсутствия вызова `get_exchange_symbols()` заменена прямой проверкой использования канонического:
|
||||
|
||||
```text
|
||||
get_instruments()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Проверка подготовительного пакета
|
||||
|
||||
Выполнена компиляция:
|
||||
|
||||
```bash
|
||||
python -m py_compile \
|
||||
tests/unit/integrations/exchange/test_service_instruments.py \
|
||||
tests/unit/integrations/exchange/test_service_validate_symbol.py \
|
||||
tests/unit/telegram/ui/test_currency_ui.py
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
Ошибок нет.
|
||||
```
|
||||
|
||||
Выполнены целевые тесты:
|
||||
|
||||
```bash
|
||||
python -m pytest \
|
||||
tests/unit/integrations/exchange/test_service_instruments.py \
|
||||
tests/unit/integrations/exchange/test_service_validate_symbol.py \
|
||||
tests/unit/telegram/ui/test_currency_ui.py \
|
||||
-q
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
49 passed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Удалённые migration-only файлы
|
||||
|
||||
Полностью удалены:
|
||||
|
||||
```text
|
||||
src/market_data/acquisition/compatibility.py
|
||||
|
||||
tests/unit/market_data/acquisition/test_compatibility.py
|
||||
tests/unit/market_data/acquisition/test_equivalence_comparator.py
|
||||
|
||||
tests/unit/integrations/exchange/test_service_exchange_symbols.py
|
||||
|
||||
tests/integration/market_data/acquisition/test_instrument_reference_equivalence.py
|
||||
tests/support/instrument_reference_equivalence.py
|
||||
```
|
||||
|
||||
Эти файлы обслуживали только временный compatibility/equivalence-контур и завершили свою миграционную задачу.
|
||||
|
||||
---
|
||||
|
||||
# Изменения в `models.py`
|
||||
|
||||
Из файла:
|
||||
|
||||
```text
|
||||
src/integrations/exchange/models.py
|
||||
```
|
||||
|
||||
полностью удалена legacy-модель:
|
||||
|
||||
```python
|
||||
@dataclass(slots=True)
|
||||
class ExchangeSymbol:
|
||||
...
|
||||
```
|
||||
|
||||
Модель:
|
||||
|
||||
```python
|
||||
SymbolValidationResult
|
||||
```
|
||||
|
||||
сохраняет канонический контракт:
|
||||
|
||||
```python
|
||||
symbol_info: Instrument | None
|
||||
```
|
||||
|
||||
Импорт `Instrument` выполняется через:
|
||||
|
||||
```python
|
||||
TYPE_CHECKING
|
||||
```
|
||||
|
||||
что исключает runtime-cycle и сохраняет корректную типизацию.
|
||||
|
||||
---
|
||||
|
||||
# Изменения в `service.py`
|
||||
|
||||
Из файла:
|
||||
|
||||
```text
|
||||
src/integrations/exchange/service.py
|
||||
```
|
||||
|
||||
удалены следующие элементы.
|
||||
|
||||
## Legacy imports
|
||||
|
||||
Удалены:
|
||||
|
||||
```python
|
||||
ExchangeSymbol
|
||||
```
|
||||
|
||||
и:
|
||||
|
||||
```python
|
||||
from src.market_data.acquisition.compatibility import (
|
||||
map_instruments_to_exchange_symbols,
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Legacy projection cache
|
||||
|
||||
Удалено поле:
|
||||
|
||||
```python
|
||||
_exchange_symbols_projection_cache
|
||||
```
|
||||
|
||||
Теперь `ExchangeService` хранит только канонический Store:
|
||||
|
||||
```python
|
||||
_instrument_store: InstrumentStoreProtocol = InMemoryInstrumentStore()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Legacy API
|
||||
|
||||
Полностью удалён метод:
|
||||
|
||||
```python
|
||||
get_exchange_symbols()
|
||||
```
|
||||
|
||||
Единственным публичным API справочника инструментов остаётся:
|
||||
|
||||
```python
|
||||
get_instruments()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Legacy parser helpers
|
||||
|
||||
Удалены методы:
|
||||
|
||||
```text
|
||||
_extract_exchange_symbols_raw()
|
||||
_parse_exchange_symbol()
|
||||
_parse_exchange_symbol_status()
|
||||
_parse_market_modes()
|
||||
_extract_filter_value()
|
||||
```
|
||||
|
||||
Их функции полностью заменены новой pipeline:
|
||||
|
||||
```text
|
||||
Dzengi REST document
|
||||
↓
|
||||
Dzengi parser
|
||||
↓
|
||||
validation
|
||||
↓
|
||||
mapper
|
||||
↓
|
||||
Instrument
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Сохранённый helper
|
||||
|
||||
Метод:
|
||||
|
||||
```python
|
||||
_safe_str()
|
||||
```
|
||||
|
||||
сохранён, так как продолжает использоваться обработкой trading fee payload.
|
||||
|
||||
---
|
||||
|
||||
# Финальный production-контур
|
||||
|
||||
После удаления compatibility layer работа со справочником инструментов выполняется так:
|
||||
|
||||
```text
|
||||
DzengiInstrumentDocumentSource
|
||||
↓
|
||||
DzengiInstrumentDocumentHandler
|
||||
↓
|
||||
InstrumentFeed
|
||||
↓
|
||||
InstrumentFeedRegistry
|
||||
↓
|
||||
InstrumentAcquisitionService
|
||||
↓
|
||||
tuple[Instrument, ...]
|
||||
↓
|
||||
Instrument Store
|
||||
↓
|
||||
ExchangeService.get_instruments()
|
||||
```
|
||||
|
||||
Проверка пользовательского символа выполняется через:
|
||||
|
||||
```text
|
||||
validate_symbol()
|
||||
↓
|
||||
SymbolValidationResult
|
||||
↓
|
||||
symbol_info: Instrument | None
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Архитектурные отрицательные тесты
|
||||
|
||||
Добавлены проверки физического отсутствия legacy API.
|
||||
|
||||
## Отсутствие legacy-метода
|
||||
|
||||
```python
|
||||
def test_exchange_service_has_no_legacy_get_exchange_symbols() -> None:
|
||||
assert not hasattr(
|
||||
ExchangeService,
|
||||
"get_exchange_symbols",
|
||||
)
|
||||
```
|
||||
|
||||
## Отсутствие legacy projection cache
|
||||
|
||||
```python
|
||||
def test_exchange_service_has_no_legacy_projection_cache() -> None:
|
||||
assert not hasattr(
|
||||
ExchangeService,
|
||||
"_exchange_symbols_projection_cache",
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Финальный аудит
|
||||
|
||||
Выполнено:
|
||||
|
||||
```bash
|
||||
grep -RIn \
|
||||
--exclude-dir="__pycache__" \
|
||||
--exclude="*.pyc" \
|
||||
-E "ExchangeSymbol|get_exchange_symbols|map_instrument_to_exchange_symbol|map_instruments_to_exchange_symbols|_exchange_symbols_projection_cache|market_data\.acquisition\.compatibility" \
|
||||
src tests
|
||||
```
|
||||
|
||||
Остались только ожидаемые упоминания в отрицательных архитектурных тестах:
|
||||
|
||||
```text
|
||||
test_exchange_service_has_no_legacy_get_exchange_symbols
|
||||
test_exchange_service_has_no_legacy_projection_cache
|
||||
```
|
||||
|
||||
Других production- или test-зависимостей не обнаружено.
|
||||
|
||||
---
|
||||
|
||||
# Проверка компиляции
|
||||
|
||||
Выполнено:
|
||||
|
||||
```bash
|
||||
python -m py_compile \
|
||||
src/integrations/exchange/models.py \
|
||||
src/integrations/exchange/service.py \
|
||||
tests/unit/integrations/exchange/test_service_instruments.py \
|
||||
tests/unit/integrations/exchange/test_service_validate_symbol.py \
|
||||
tests/unit/telegram/ui/test_currency_ui.py
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
Ошибок нет.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Целевой Regression Suite
|
||||
|
||||
Выполнено:
|
||||
|
||||
```bash
|
||||
python -m pytest \
|
||||
tests/unit/integrations/exchange/test_service_instruments.py \
|
||||
tests/unit/integrations/exchange/test_service_validate_symbol.py \
|
||||
tests/unit/integrations/exchange/test_service_symbol_runtime_status.py \
|
||||
tests/unit/integrations/exchange/test_market_stream.py \
|
||||
tests/unit/integrations/exchange/test_market_data_runner.py \
|
||||
tests/unit/telegram/ui/test_currency_ui.py \
|
||||
-q
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
94 passed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Полный Regression Suite
|
||||
|
||||
Выполнено:
|
||||
|
||||
```bash
|
||||
python -m pytest -q
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
423 passed
|
||||
```
|
||||
|
||||
Снижение общего числа тестов относительно предыдущего Build является ожидаемым, поскольку были удалены временные compatibility- и equivalence-тесты вместе с соответствующим legacy-кодом.
|
||||
|
||||
---
|
||||
|
||||
# Архитектурный итог
|
||||
|
||||
До Build 025:
|
||||
|
||||
```text
|
||||
Instrument
|
||||
├── canonical consumers
|
||||
└── compatibility mapper
|
||||
↓
|
||||
ExchangeSymbol
|
||||
↓
|
||||
legacy projection cache
|
||||
```
|
||||
|
||||
После Build 025:
|
||||
|
||||
```text
|
||||
Instrument
|
||||
↓
|
||||
Instrument Store
|
||||
↓
|
||||
canonical consumers
|
||||
```
|
||||
|
||||
В проекте больше не существует:
|
||||
|
||||
```text
|
||||
ExchangeSymbol
|
||||
compatibility.py
|
||||
get_exchange_symbols()
|
||||
_exchange_symbols_projection_cache
|
||||
Instrument → ExchangeSymbol mapping
|
||||
legacy exchangeInfo parser helpers
|
||||
migration equivalence framework
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Итог Build
|
||||
|
||||
Build 025 полностью завершён.
|
||||
|
||||
Подтверждено:
|
||||
|
||||
- все production-потребители переведены на `Instrument`;
|
||||
- удалена legacy-модель `ExchangeSymbol`;
|
||||
- удалён временный compatibility mapper;
|
||||
- удалён legacy-метод `get_exchange_symbols()`;
|
||||
- удалён projection cache;
|
||||
- удалены неиспользуемые parser helpers;
|
||||
- удалены migration-only и equivalence-тесты;
|
||||
- сохранены и усилены канонические тесты Instrument Store;
|
||||
- добавлены архитектурные тесты отсутствия legacy API;
|
||||
- компиляция проходит без ошибок;
|
||||
- целевой Regression Suite успешно пройден — **94 passed**;
|
||||
- полный Regression Suite успешно пройден — **423 passed**.
|
||||
|
||||
**Статус Build:** ✅ Завершён.
|
||||
Reference in New Issue
Block a user