Build 060.3: add REST trade schema validation
This commit is contained in:
@@ -119,6 +119,11 @@ class CandleFeedRegistryError(MarketDataAcquisitionError):
|
||||
pass
|
||||
|
||||
|
||||
# Ошибка структуры документа Trades Feed.
|
||||
class TradeSchemaError(MarketDataAcquisitionError):
|
||||
pass
|
||||
|
||||
|
||||
# Ошибка определения типа входящего WebSocket-сообщения
|
||||
# и выбора специализированного адаптера.
|
||||
class WebSocketMessageRoutingError(MarketDataAcquisitionError):
|
||||
|
||||
@@ -11,6 +11,7 @@ from src.market_data.acquisition.exceptions import (
|
||||
CandleWebSocketSchemaError,
|
||||
InstrumentReferenceSchemaError,
|
||||
QuoteSchemaError,
|
||||
TradeSchemaError,
|
||||
)
|
||||
|
||||
|
||||
@@ -595,6 +596,94 @@ def _require_candle_mapping(
|
||||
return value
|
||||
|
||||
|
||||
# Структурно проверенное представление ответа Dzengi /api/v2/aggTrades.
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ValidatedRestAggTradesDocument:
|
||||
items: tuple[Mapping[str, object], ...]
|
||||
|
||||
|
||||
def validate_rest_agg_trades_schema(
|
||||
document: object,
|
||||
) -> ValidatedRestAggTradesDocument:
|
||||
"""
|
||||
Проверить структуру ответа Dzengi aggTrades без проверки значений.
|
||||
|
||||
Ожидается корневой JSON-массив, каждый элемент которого является
|
||||
JSON-объектом с обязательными полями:
|
||||
|
||||
- a — идентификатор агрегированной сделки;
|
||||
- p — цена;
|
||||
- q — количество;
|
||||
- T — временная метка;
|
||||
- m — признак buyer is maker.
|
||||
|
||||
Валидатор проверяет только структуру документа. Типы и допустимость
|
||||
значений должны проверяться последующими слоями acquisition pipeline.
|
||||
"""
|
||||
|
||||
if not isinstance(document, list):
|
||||
raise TradeSchemaError(
|
||||
"$ должен быть JSON-массивом, "
|
||||
f"получен {type(document).__name__}."
|
||||
)
|
||||
|
||||
validated_items: list[Mapping[str, object]] = []
|
||||
|
||||
for index, item in enumerate(document):
|
||||
path = f"$[{index}]"
|
||||
|
||||
mapping = _require_trade_mapping(
|
||||
item,
|
||||
path=path,
|
||||
)
|
||||
|
||||
_validate_rest_agg_trade_item(
|
||||
mapping,
|
||||
path=path,
|
||||
)
|
||||
|
||||
validated_items.append(
|
||||
MappingProxyType(dict(mapping))
|
||||
)
|
||||
|
||||
return ValidatedRestAggTradesDocument(
|
||||
items=tuple(validated_items),
|
||||
)
|
||||
|
||||
|
||||
def _validate_rest_agg_trade_item(
|
||||
item: Mapping[str, object],
|
||||
*,
|
||||
path: str,
|
||||
) -> None:
|
||||
for key in ("a", "p", "q", "T", "m"):
|
||||
if key not in item:
|
||||
raise TradeSchemaError(
|
||||
f"{path}.{key} отсутствует в документе aggTrades."
|
||||
)
|
||||
|
||||
|
||||
def _require_trade_mapping(
|
||||
value: object,
|
||||
*,
|
||||
path: str,
|
||||
) -> Mapping[str, object]:
|
||||
if not isinstance(value, dict):
|
||||
raise TradeSchemaError(
|
||||
f"{path} должен быть JSON-объектом, "
|
||||
f"получен {type(value).__name__}."
|
||||
)
|
||||
|
||||
for key in value:
|
||||
if not isinstance(key, str):
|
||||
raise TradeSchemaError(
|
||||
f"{path} содержит нестроковый ключ "
|
||||
f"типа {type(key).__name__}."
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
# Структурно проверенное представление события Dzengi WebSocket ohlc.event.
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ValidatedWebSocketOhlcDocument:
|
||||
|
||||
@@ -8,9 +8,11 @@ import pytest
|
||||
|
||||
from src.market_data.acquisition.exceptions import (
|
||||
InstrumentReferenceSchemaError,
|
||||
TradeSchemaError,
|
||||
)
|
||||
from src.market_data.acquisition.validation.schema import (
|
||||
validate_exchange_info_schema,
|
||||
validate_rest_agg_trades_schema,
|
||||
)
|
||||
|
||||
|
||||
@@ -266,3 +268,162 @@ def test_reject_non_object_payload_collection_item(key: str) -> None:
|
||||
match=rf"\.{key}\[0\] должен быть JSON-объектом",
|
||||
):
|
||||
validate_exchange_info_schema(document)
|
||||
|
||||
|
||||
def test_validate_rest_agg_trades_document() -> None:
|
||||
document = [
|
||||
{
|
||||
"a": 2134857062,
|
||||
"p": "64497.25",
|
||||
"q": "0.005",
|
||||
"T": 1784218066823,
|
||||
"m": False,
|
||||
},
|
||||
{
|
||||
"a": 2134857063,
|
||||
"p": "64498.10",
|
||||
"q": "0.002",
|
||||
"T": 1784218067000,
|
||||
"m": True,
|
||||
},
|
||||
]
|
||||
|
||||
validated = validate_rest_agg_trades_schema(document)
|
||||
|
||||
assert len(validated.items) == 2
|
||||
assert validated.items[0]["a"] == 2134857062
|
||||
assert validated.items[0]["p"] == "64497.25"
|
||||
assert validated.items[0]["q"] == "0.005"
|
||||
assert validated.items[0]["T"] == 1784218066823
|
||||
assert validated.items[0]["m"] is False
|
||||
assert isinstance(validated.items, tuple)
|
||||
assert isinstance(validated.items[0], MappingProxyType)
|
||||
|
||||
|
||||
def test_validate_empty_rest_agg_trades_document() -> None:
|
||||
validated = validate_rest_agg_trades_schema([])
|
||||
|
||||
assert validated.items == ()
|
||||
|
||||
|
||||
def test_rest_agg_trades_schema_preserves_unchecked_field_values() -> None:
|
||||
document = [
|
||||
{
|
||||
"a": "invalid-id",
|
||||
"p": None,
|
||||
"q": [],
|
||||
"T": False,
|
||||
"m": 123,
|
||||
}
|
||||
]
|
||||
|
||||
validated = validate_rest_agg_trades_schema(document)
|
||||
|
||||
assert validated.items[0]["a"] == "invalid-id"
|
||||
assert validated.items[0]["p"] is None
|
||||
assert validated.items[0]["q"] == []
|
||||
assert validated.items[0]["T"] is False
|
||||
assert validated.items[0]["m"] == 123
|
||||
|
||||
|
||||
def test_rest_agg_trades_schema_preserves_additional_fields() -> None:
|
||||
document = [
|
||||
{
|
||||
"a": 2134857062,
|
||||
"p": "64497.25",
|
||||
"q": "0.005",
|
||||
"T": 1784218066823,
|
||||
"m": False,
|
||||
"extra": "preserved",
|
||||
}
|
||||
]
|
||||
|
||||
validated = validate_rest_agg_trades_schema(document)
|
||||
|
||||
assert validated.items[0]["extra"] == "preserved"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"document",
|
||||
[
|
||||
None,
|
||||
{},
|
||||
"invalid",
|
||||
123,
|
||||
],
|
||||
)
|
||||
def test_reject_non_list_rest_agg_trades_root(
|
||||
document: object,
|
||||
) -> None:
|
||||
with pytest.raises(
|
||||
TradeSchemaError,
|
||||
match=r"\$ должен быть JSON-массивом",
|
||||
):
|
||||
validate_rest_agg_trades_schema(document)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"item",
|
||||
[
|
||||
None,
|
||||
[],
|
||||
"invalid",
|
||||
123,
|
||||
],
|
||||
)
|
||||
def test_reject_non_object_rest_agg_trade_item(
|
||||
item: object,
|
||||
) -> None:
|
||||
with pytest.raises(
|
||||
TradeSchemaError,
|
||||
match=r"\$\[0\] должен быть JSON-объектом",
|
||||
):
|
||||
validate_rest_agg_trades_schema([item])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"missing_key",
|
||||
[
|
||||
"a",
|
||||
"p",
|
||||
"q",
|
||||
"T",
|
||||
"m",
|
||||
],
|
||||
)
|
||||
def test_reject_rest_agg_trade_with_missing_required_field(
|
||||
missing_key: str,
|
||||
) -> None:
|
||||
item: dict[str, object] = {
|
||||
"a": 2134857062,
|
||||
"p": "64497.25",
|
||||
"q": "0.005",
|
||||
"T": 1784218066823,
|
||||
"m": False,
|
||||
}
|
||||
del item[missing_key]
|
||||
|
||||
with pytest.raises(
|
||||
TradeSchemaError,
|
||||
match=rf"\$\[0\]\.{missing_key} отсутствует",
|
||||
):
|
||||
validate_rest_agg_trades_schema([item])
|
||||
|
||||
|
||||
def test_reject_rest_agg_trade_with_non_string_key() -> None:
|
||||
document = [
|
||||
{
|
||||
"a": 2134857062,
|
||||
"p": "64497.25",
|
||||
"q": "0.005",
|
||||
"T": 1784218066823,
|
||||
"m": False,
|
||||
1: "invalid",
|
||||
}
|
||||
]
|
||||
|
||||
with pytest.raises(
|
||||
TradeSchemaError,
|
||||
match=r"\$\[0\] содержит нестроковый ключ типа int",
|
||||
):
|
||||
validate_rest_agg_trades_schema(document)
|
||||
|
||||
640
docs/migrations/build_060_3.md
Normal file
640
docs/migrations/build_060_3.md
Normal file
@@ -0,0 +1,640 @@
|
||||
# Build 060.3 — REST Trade Schema Validation
|
||||
|
||||
## Статус
|
||||
|
||||
✅ Завершён
|
||||
|
||||
---
|
||||
|
||||
# Цель Build
|
||||
|
||||
После завершения Build 060.2 в системе уже существовал transport-контракт отдельной сделки:
|
||||
|
||||
```
|
||||
REST JSON
|
||||
↓
|
||||
DzengiRestAggTrade
|
||||
```
|
||||
|
||||
Однако отсутствовал обязательный архитектурный слой Schema Validation.
|
||||
|
||||
В соответствии с архитектурой Acquisition Pipeline каждый внешний документ обязан проходить структурную проверку до начала парсинга.
|
||||
|
||||
Для REST Trades это означало необходимость добавить отдельный слой:
|
||||
|
||||
```
|
||||
REST JSON
|
||||
↓
|
||||
Schema Validation
|
||||
↓
|
||||
ValidatedRestAggTradesDocument
|
||||
↓
|
||||
REST Trade Parser
|
||||
↓
|
||||
DzengiRestAggTrade
|
||||
```
|
||||
|
||||
Именно этот слой реализован в Build 060.3.
|
||||
|
||||
---
|
||||
|
||||
# Причины появления Build
|
||||
|
||||
REST endpoint `/api/v2/aggTrades` возвращает JSON-массив объектов.
|
||||
|
||||
Пример ответа:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"a": 2134857062,
|
||||
"p": "64497.25",
|
||||
"q": "0.005",
|
||||
"T": 1784218066823,
|
||||
"m": false
|
||||
},
|
||||
{
|
||||
"a": 2134857063,
|
||||
"p": "64498.10",
|
||||
"q": "0.002",
|
||||
"T": 1784218067000,
|
||||
"m": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
До Build 060.3 никакой проверки структуры документа не существовало.
|
||||
|
||||
Parser был вынужден бы принимать произвольный объект.
|
||||
|
||||
Это нарушало принятую архитектуру Dzentra.
|
||||
|
||||
---
|
||||
|
||||
# Архитектурное решение
|
||||
|
||||
Во время проектирования рассматривались два варианта.
|
||||
|
||||
## Вариант A
|
||||
|
||||
Schema Validation получает весь REST-документ.
|
||||
|
||||
```
|
||||
REST response
|
||||
↓
|
||||
ValidatedRestAggTradesDocument
|
||||
↓
|
||||
parse_rest_agg_trades()
|
||||
↓
|
||||
tuple[DzengiRestAggTrade]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Вариант B
|
||||
|
||||
Schema Validation работает с одной сделкой.
|
||||
|
||||
```
|
||||
REST response
|
||||
↓
|
||||
trade
|
||||
↓
|
||||
ValidatedRestAggTradeDocument
|
||||
```
|
||||
|
||||
После анализа существующей архитектуры проекта был выбран вариант A.
|
||||
|
||||
Причины:
|
||||
|
||||
- ExchangeInfo валидируется целиком;
|
||||
- Candles валидируются целиком;
|
||||
- WebSocket сообщения валидируются целиком;
|
||||
- Parser всегда получает уже структурно проверенный документ.
|
||||
|
||||
Trades Feed должен следовать тем же правилам.
|
||||
|
||||
---
|
||||
|
||||
# Итоговая архитектура
|
||||
|
||||
После Build 060.3 pipeline выглядит следующим образом.
|
||||
|
||||
```
|
||||
REST JSON
|
||||
│
|
||||
▼
|
||||
validate_rest_agg_trades_schema()
|
||||
│
|
||||
▼
|
||||
ValidatedRestAggTradesDocument
|
||||
│
|
||||
▼
|
||||
parse_rest_agg_trades()
|
||||
│
|
||||
▼
|
||||
tuple[DzengiRestAggTrade]
|
||||
│
|
||||
▼
|
||||
Value Validation
|
||||
│
|
||||
▼
|
||||
Canonical Trade
|
||||
```
|
||||
|
||||
Каждый слой отвечает только за собственную область ответственности.
|
||||
|
||||
---
|
||||
|
||||
# Ответственность Schema Validation
|
||||
|
||||
Schema Validation проверяет исключительно структуру документа.
|
||||
|
||||
Проверяется:
|
||||
|
||||
- корневой JSON-массив;
|
||||
- каждый элемент массива является JSON-объектом;
|
||||
- все ключи являются строками;
|
||||
- обязательные поля присутствуют;
|
||||
- документ переводится в immutable-представление.
|
||||
|
||||
Schema Validation не занимается:
|
||||
|
||||
- проверкой типов значений;
|
||||
- преобразованием данных;
|
||||
- проверкой диапазонов;
|
||||
- проверкой бизнес-ограничений;
|
||||
- созданием transport-моделей.
|
||||
|
||||
Все перечисленные обязанности принадлежат последующим слоям Acquisition Pipeline.
|
||||
|
||||
---
|
||||
|
||||
# Новое исключение
|
||||
|
||||
В Build добавлено новое исключение.
|
||||
|
||||
```python
|
||||
class TradeSchemaError(MarketDataAcquisitionError):
|
||||
pass
|
||||
```
|
||||
|
||||
Исключение используется исключительно для ошибок структуры REST Trade документа.
|
||||
|
||||
Build сознательно не добавляет:
|
||||
|
||||
- TradeParseError;
|
||||
- TradeValueError;
|
||||
- TradeMappingError.
|
||||
|
||||
Они будут появляться только в соответствующих Build.
|
||||
|
||||
Это позволяет сохранить строгую границу ответственности каждого этапа разработки.
|
||||
|
||||
---
|
||||
|
||||
# Новый контракт Schema Layer
|
||||
|
||||
Добавлен immutable-контракт.
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ValidatedRestAggTradesDocument:
|
||||
items: tuple[Mapping[str, object], ...]
|
||||
```
|
||||
|
||||
Контракт хранит уже структурно проверенный REST-документ.
|
||||
|
||||
Каждый элемент массива:
|
||||
|
||||
- является Mapping;
|
||||
- является immutable;
|
||||
- сохраняет все поля документа;
|
||||
- ещё не преобразован в transport model.
|
||||
|
||||
Parser получает именно этот контракт.
|
||||
|
||||
# Реализация Schema Validation
|
||||
|
||||
В Build реализована новая функция.
|
||||
|
||||
```python
|
||||
validate_rest_agg_trades_schema(
|
||||
document: object,
|
||||
) -> ValidatedRestAggTradesDocument
|
||||
```
|
||||
|
||||
Функция является единственной точкой входа для проверки структуры REST Trade документа.
|
||||
|
||||
Parser больше не должен принимать произвольный JSON.
|
||||
|
||||
Он обязан получать только результат работы Schema Validation.
|
||||
|
||||
---
|
||||
|
||||
# Проверка корневого объекта
|
||||
|
||||
Первым этапом валидируется корень документа.
|
||||
|
||||
Ожидается исключительно JSON-массив.
|
||||
|
||||
Допустимый пример:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"a": 1,
|
||||
"p": "1.25",
|
||||
"q": "5",
|
||||
"T": 123,
|
||||
"m": false
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Недопустимые варианты:
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
```json
|
||||
123
|
||||
```
|
||||
|
||||
```json
|
||||
"text"
|
||||
```
|
||||
|
||||
```json
|
||||
null
|
||||
```
|
||||
|
||||
Во всех подобных случаях возбуждается
|
||||
|
||||
```python
|
||||
TradeSchemaError
|
||||
```
|
||||
|
||||
Parser никогда не увидит подобный документ.
|
||||
|
||||
---
|
||||
|
||||
# Проверка элементов массива
|
||||
|
||||
После проверки корня валидируется каждый элемент массива.
|
||||
|
||||
Каждый элемент обязан быть JSON-объектом.
|
||||
|
||||
Допустимо:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"a": 1
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Недопустимо:
|
||||
|
||||
```json
|
||||
[
|
||||
1
|
||||
]
|
||||
```
|
||||
|
||||
```json
|
||||
[
|
||||
[]
|
||||
]
|
||||
```
|
||||
|
||||
```json
|
||||
[
|
||||
null
|
||||
]
|
||||
```
|
||||
|
||||
```json
|
||||
[
|
||||
"trade"
|
||||
]
|
||||
```
|
||||
|
||||
Подобные документы отклоняются ещё до Parser.
|
||||
|
||||
---
|
||||
|
||||
# Проверка ключей
|
||||
|
||||
После проверки структуры объекта производится проверка ключей.
|
||||
|
||||
Каждый ключ обязан быть строкой.
|
||||
|
||||
Например,
|
||||
|
||||
допустимо:
|
||||
|
||||
```json
|
||||
{
|
||||
"a": 1
|
||||
}
|
||||
```
|
||||
|
||||
недопустимо:
|
||||
|
||||
```python
|
||||
{
|
||||
1: "value"
|
||||
}
|
||||
```
|
||||
|
||||
Подобная ситуация невозможна для корректного JSON, однако тест присутствует специально.
|
||||
|
||||
Причины:
|
||||
|
||||
- защита от программного формирования словаря;
|
||||
- единообразие со всеми остальными Schema Validator;
|
||||
- гарантированный контракт Parser.
|
||||
|
||||
---
|
||||
|
||||
# Проверка обязательных полей
|
||||
|
||||
Для каждого Trade объекта проверяется наличие обязательных полей.
|
||||
|
||||
Обязательными являются:
|
||||
|
||||
```
|
||||
a
|
||||
p
|
||||
q
|
||||
T
|
||||
m
|
||||
```
|
||||
|
||||
Отсутствие любого поля считается ошибкой структуры документа.
|
||||
|
||||
Например,
|
||||
|
||||
```json
|
||||
{
|
||||
"a": 1,
|
||||
"p": "10"
|
||||
}
|
||||
```
|
||||
|
||||
будет отклонён ещё на этапе Schema Validation.
|
||||
|
||||
---
|
||||
|
||||
# Дополнительные поля
|
||||
|
||||
Schema Validation не запрещает дополнительные поля.
|
||||
|
||||
Например,
|
||||
|
||||
```json
|
||||
{
|
||||
"a": 1,
|
||||
"p": "100",
|
||||
"q": "5",
|
||||
"T": 123,
|
||||
"m": false,
|
||||
"exchange": "spot",
|
||||
"custom": "value"
|
||||
}
|
||||
```
|
||||
|
||||
считается корректным документом.
|
||||
|
||||
Дополнительные поля полностью сохраняются.
|
||||
|
||||
Это решение принято намеренно.
|
||||
|
||||
Schema Layer отвечает только за минимальный контракт документа.
|
||||
|
||||
Любые дополнительные поля могут использоваться последующими Build без изменения существующей Schema Validation.
|
||||
|
||||
---
|
||||
|
||||
# Отсутствие проверки типов
|
||||
|
||||
Build принципиально не проверяет типы значений.
|
||||
|
||||
Следующий документ считается структурно корректным.
|
||||
|
||||
```python
|
||||
[
|
||||
{
|
||||
"a": "invalid",
|
||||
"p": None,
|
||||
"q": [],
|
||||
"T": False,
|
||||
"m": 123,
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Причина заключается в разделении ответственности Acquisition Pipeline.
|
||||
|
||||
Schema Validation отвечает только за структуру документа.
|
||||
|
||||
Проверка:
|
||||
|
||||
- int;
|
||||
- bool;
|
||||
- Decimal;
|
||||
- строки;
|
||||
- диапазоны значений;
|
||||
- корректность timestamp;
|
||||
|
||||
будет выполняться исключительно Parser и Value Validation.
|
||||
|
||||
Это полностью соответствует архитектуре проекта.
|
||||
|
||||
---
|
||||
|
||||
# Immutable представление
|
||||
|
||||
После успешной проверки структура документа преобразуется в immutable-контракт.
|
||||
|
||||
Каждый элемент копируется в новый словарь.
|
||||
|
||||
После этого словарь оборачивается в
|
||||
|
||||
```python
|
||||
MappingProxyType
|
||||
```
|
||||
|
||||
Коллекция объектов преобразуется в
|
||||
|
||||
```python
|
||||
tuple
|
||||
```
|
||||
|
||||
Итоговый контракт становится полностью неизменяемым.
|
||||
|
||||
Это гарантирует:
|
||||
|
||||
- невозможность случайного изменения Parser;
|
||||
- защиту между слоями Pipeline;
|
||||
- детерминированность обработки;
|
||||
- отсутствие побочных эффектов.
|
||||
|
||||
Подобная схема уже используется ExchangeInfo и Candles Schema Validation.
|
||||
|
||||
Trades Feed теперь полностью ей соответствует.
|
||||
|
||||
---
|
||||
|
||||
# Поведение при пустом ответе
|
||||
|
||||
REST endpoint может вернуть пустой массив.
|
||||
|
||||
Например,
|
||||
|
||||
```json
|
||||
[]
|
||||
```
|
||||
|
||||
Такой ответ считается полностью корректным.
|
||||
|
||||
После проверки возвращается
|
||||
|
||||
```python
|
||||
ValidatedRestAggTradesDocument(
|
||||
items=(),
|
||||
)
|
||||
```
|
||||
|
||||
Пустой документ не считается ошибкой.
|
||||
|
||||
Это позволяет корректно обрабатывать участки истории, где сделки отсутствуют.
|
||||
|
||||
---
|
||||
|
||||
# Unit-тесты
|
||||
|
||||
Для новой функциональности добавлен отдельный набор тестов.
|
||||
|
||||
Проверяются:
|
||||
|
||||
- корректный REST-документ;
|
||||
- пустой массив;
|
||||
- сохранение дополнительных полей;
|
||||
- отсутствие проверки типов значений;
|
||||
- отклонение некорректного корня документа;
|
||||
- отклонение элементов, не являющихся JSON-объектами;
|
||||
- отсутствие обязательных полей;
|
||||
- нестроковые ключи.
|
||||
|
||||
Таким образом фиксируется полный публичный контракт нового Schema Validator.
|
||||
|
||||
Любое изменение поведения в будущем будет обнаружено автоматически.
|
||||
|
||||
---
|
||||
|
||||
# Результаты Build
|
||||
|
||||
После завершения реализации были выполнены проверки проекта.
|
||||
|
||||
Schema Tests
|
||||
|
||||
```
|
||||
38 passed
|
||||
```
|
||||
|
||||
Полный набор unit-тестов
|
||||
|
||||
```
|
||||
1011 passed
|
||||
```
|
||||
|
||||
Дополнительно успешно выполнены
|
||||
|
||||
```
|
||||
python -m compileall src
|
||||
```
|
||||
|
||||
и
|
||||
|
||||
```
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Ошибок форматирования обнаружено не было.
|
||||
|
||||
---
|
||||
|
||||
# Изменённые файлы
|
||||
|
||||
В рамках Build изменены только три файла.
|
||||
|
||||
```
|
||||
src/market_data/acquisition/exceptions.py
|
||||
```
|
||||
|
||||
Добавлено исключение
|
||||
|
||||
```
|
||||
TradeSchemaError
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
src/market_data/acquisition/validation/schema.py
|
||||
```
|
||||
|
||||
Добавлены:
|
||||
|
||||
- ValidatedRestAggTradesDocument;
|
||||
- validate_rest_agg_trades_schema();
|
||||
- вспомогательные функции проверки структуры.
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
tests/unit/market_data/acquisition/validation/test_schema.py
|
||||
```
|
||||
|
||||
Добавлен полный набор unit-тестов нового Schema Validator.
|
||||
|
||||
Никакие другие части проекта не изменялись.
|
||||
|
||||
---
|
||||
|
||||
# Итог
|
||||
|
||||
Build 060.3 завершает формирование первого уровня REST Trades Acquisition Pipeline.
|
||||
|
||||
После его завершения система получила полноценный слой структурной проверки документов.
|
||||
|
||||
Pipeline теперь имеет следующий вид.
|
||||
|
||||
```
|
||||
REST JSON
|
||||
│
|
||||
▼
|
||||
Schema Validation
|
||||
│
|
||||
▼
|
||||
ValidatedRestAggTradesDocument
|
||||
│
|
||||
▼
|
||||
REST Trade Parser
|
||||
│
|
||||
▼
|
||||
DzengiRestAggTrade
|
||||
│
|
||||
▼
|
||||
Value Validation
|
||||
│
|
||||
▼
|
||||
Canonical Trade
|
||||
```
|
||||
|
||||
Следующим этапом серии станет **Build 060.4 — REST Trade Parser**, который будет отвечать за преобразование структурно проверенного документа в transport-модель `DzengiRestAggTrade`, не нарушая границ ответственности, установленных данным Build.
|
||||
|
||||
Reference in New Issue
Block a user