600 lines
14 KiB
Markdown
600 lines
14 KiB
Markdown
# 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:** ✅ Завершён.
|