Files
dzentra_bot/docs/migrations/build_025.md

14 KiB
Raw Permalink Blame History

Build 025 — Удаление legacy ExchangeSymbol compatibility layer

Статус: Завершён
Дата: 2026-07-12
Подсистема: Instrument Reference / Exchange Integration
Этап: Завершение миграции на каноническую модель Instrument


Цель Build

Полностью удалить временный compatibility layer, использовавшийся во время поэтапного перехода от legacy-модели:

ExchangeSymbol

к канонической модели:

Instrument

После завершения Build все production-потребители работают через единый канонический контур:

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-контур сохранялся только как временная проекция:

Instrument
    ↓
compatibility.py
    ↓
ExchangeSymbol
    ↓
get_exchange_symbols()

Реальных production-потребителей этого контура больше не осталось.


Предварительный аудит

Выполнен поиск production-зависимостей:

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-контура:

src/market_data/acquisition/compatibility.py
src/integrations/exchange/models.py
src/integrations/exchange/service.py

Канонические production-потребители уже использовали:

get_instruments()
validate_symbol()
Instrument

Аудит legacy parser helpers

Проверены методы:

_extract_exchange_symbols_raw()
_parse_exchange_symbol()
_parse_exchange_symbol_status()
_parse_market_modes()
_extract_filter_value()

Выполнено:

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 были обновлены канонические тесты.

Обновлён файл

tests/unit/integrations/exchange/test_service_instruments.py

Из него удалены тесты legacy-проекции:

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 при ошибке.

Обновлён файл

tests/unit/integrations/exchange/test_service_validate_symbol.py

Legacy-проверка через monkeypatch метода:

get_exchange_symbols()

заменена проверкой прямого использования:

get_instruments()

Дополнительно добавлен отрицательный архитектурный тест:

def test_exchange_service_has_no_legacy_get_exchange_symbols() -> None:
    assert not hasattr(
        ExchangeService,
        "get_exchange_symbols",
    )

Обновлён файл

tests/unit/telegram/ui/test_currency_ui.py

Legacy-проверка отсутствия вызова get_exchange_symbols() заменена прямой проверкой использования канонического:

get_instruments()

Проверка подготовительного пакета

Выполнена компиляция:

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

Результат:

Ошибок нет.

Выполнены целевые тесты:

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

Результат:

49 passed

Удалённые migration-only файлы

Полностью удалены:

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

Из файла:

src/integrations/exchange/models.py

полностью удалена legacy-модель:

@dataclass(slots=True)
class ExchangeSymbol:
    ...

Модель:

SymbolValidationResult

сохраняет канонический контракт:

symbol_info: Instrument | None

Импорт Instrument выполняется через:

TYPE_CHECKING

что исключает runtime-cycle и сохраняет корректную типизацию.


Изменения в service.py

Из файла:

src/integrations/exchange/service.py

удалены следующие элементы.

Legacy imports

Удалены:

ExchangeSymbol

и:

from src.market_data.acquisition.compatibility import (
    map_instruments_to_exchange_symbols,
)

Legacy projection cache

Удалено поле:

_exchange_symbols_projection_cache

Теперь ExchangeService хранит только канонический Store:

_instrument_store: InstrumentStoreProtocol = InMemoryInstrumentStore()

Legacy API

Полностью удалён метод:

get_exchange_symbols()

Единственным публичным API справочника инструментов остаётся:

get_instruments()

Legacy parser helpers

Удалены методы:

_extract_exchange_symbols_raw()
_parse_exchange_symbol()
_parse_exchange_symbol_status()
_parse_market_modes()
_extract_filter_value()

Их функции полностью заменены новой pipeline:

Dzengi REST document
        ↓
Dzengi parser
        ↓
validation
        ↓
mapper
        ↓
Instrument

Сохранённый helper

Метод:

_safe_str()

сохранён, так как продолжает использоваться обработкой trading fee payload.


Финальный production-контур

После удаления compatibility layer работа со справочником инструментов выполняется так:

DzengiInstrumentDocumentSource
        ↓
DzengiInstrumentDocumentHandler
        ↓
InstrumentFeed
        ↓
InstrumentFeedRegistry
        ↓
InstrumentAcquisitionService
        ↓
tuple[Instrument, ...]
        ↓
Instrument Store
        ↓
ExchangeService.get_instruments()

Проверка пользовательского символа выполняется через:

validate_symbol()
        ↓
SymbolValidationResult
        ↓
symbol_info: Instrument | None

Архитектурные отрицательные тесты

Добавлены проверки физического отсутствия legacy API.

Отсутствие legacy-метода

def test_exchange_service_has_no_legacy_get_exchange_symbols() -> None:
    assert not hasattr(
        ExchangeService,
        "get_exchange_symbols",
    )

Отсутствие legacy projection cache

def test_exchange_service_has_no_legacy_projection_cache() -> None:
    assert not hasattr(
        ExchangeService,
        "_exchange_symbols_projection_cache",
    )

Финальный аудит

Выполнено:

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

Остались только ожидаемые упоминания в отрицательных архитектурных тестах:

test_exchange_service_has_no_legacy_get_exchange_symbols
test_exchange_service_has_no_legacy_projection_cache

Других production- или test-зависимостей не обнаружено.


Проверка компиляции

Выполнено:

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

Результат:

Ошибок нет.

Целевой Regression Suite

Выполнено:

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

Результат:

94 passed

Полный Regression Suite

Выполнено:

python -m pytest -q

Результат:

423 passed

Снижение общего числа тестов относительно предыдущего Build является ожидаемым, поскольку были удалены временные compatibility- и equivalence-тесты вместе с соответствующим legacy-кодом.


Архитектурный итог

До Build 025:

Instrument
    ├── canonical consumers
    └── compatibility mapper
            ↓
        ExchangeSymbol
            ↓
        legacy projection cache

После Build 025:

Instrument
    ↓
Instrument Store
    ↓
canonical consumers

В проекте больше не существует:

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: Завершён.