В SDK должен быть единый машинно-проверяемый способ связать публичный SDK-метод с операцией из Swagger-спецификации.
Swagger-файлы в docs/avito/api/*.json являются единственным источником истины по API-контракту:
- HTTP method;
- path;
- path/query/header parameters;
- request body;
- content-type;
- response statuses;
- response schemas;
- error schemas;
- deprecated state.
Декоратор и class-level metadata не должны дублировать API-контракт. Они описывают только:
какой SDK method соответствует какой Swagger operation
как contract-test runner должен вызвать этот SDK method
Перед внедрением binding-ов нужно обновить локальные Swagger/OpenAPI спецификации из публичного каталога Авито:
poetry run python scripts/download_avito_api_specs.pyСценарий работ:
- Запустить
poetry run python scripts/download_avito_api_specs.py, чтобыdocs/avito/api/*.jsonотражали актуальный источник API-контрактов. - Проверить diff Swagger-файлов и зафиксировать, если изменилось число операций,
operation_id,deprecated, paths, параметры или схемы. - Реализовать
avito/core/swagger.pyсSwaggerOperationBindingи@swagger_operation(...). - Расставить class-level metadata и decorators на публичных domain methods без дублирования Swagger-контракта.
- Реализовать
scripts/lint_swagger_bindings.pyиmake swagger-lint. - Добавить
make swagger-lintв общий quality gate. - Добавить unit-тесты декоратора, линтера и contract tests через
SwaggerFakeTransport. - Завершить проверкой:
make check
make swagger-lintМодуль:
avito/core/swagger.py
Основной декоратор:
@swagger_operation(
method: str,
path: str,
*,
spec: str | None = None,
operation_id: str | None = None,
factory: str | None = None,
factory_args: Mapping[str, str] | None = None,
method_args: Mapping[str, str] | None = None,
deprecated: bool = False,
legacy: bool = False,
)Пример:
class Chat:
__swagger_domain__ = "messenger"
__swagger_spec__ = "Мессенджер.json"
__sdk_factory__ = "chat"
@swagger_operation(
"GET",
"/messenger/v1/accounts/{user_id}/chats/{chat_id}",
factory_args={
"user_id": "path.user_id",
"chat_id": "path.chat_id",
},
)
def get(self) -> ChatInfo:
...Публичные domain objects и section clients могут объявлять служебные поля:
__swagger_domain__: str
__swagger_spec__: str
__sdk_factory__: str
__sdk_factory_args__: Mapping[str, str]Назначение:
__swagger_domain__
Логический домен SDK: ads, messenger, orders, promotion, accounts и т.д.
Используется для группировки contract tests и отчетов линтера.
__swagger_spec__
Имя Swagger-файла из docs/avito/api/.
Используется как default spec для всех decorated методов класса.
__sdk_factory__
Имя factory method на AvitoClient.
Например: "chat" означает client.chat(...).
__sdk_factory_args__
Default mapping аргументов factory.
Используется, если method-level factory_args не указан.
Приоритет значений:
1. Значения из @swagger_operation(...)
2. Значения из class-level metadata
3. Auto-resolve через Swagger registry, если это безопасно и однозначно
Декоратор должен записывать metadata в атрибут функции:
func.__swagger_binding__Тип:
@dataclass(frozen=True, slots=True)
class SwaggerOperationBinding:
method: str
path: str
spec: str | None
operation_id: str | None
factory: str | None
factory_args: Mapping[str, str]
method_args: Mapping[str, str]
deprecated: bool
legacy: boolТребования:
methodнормализуется в uppercase.pathхранится в Swagger-формате:/path/{param}.factory_argsиmethod_argsвнутри модели должны быть immutable mapping.- Декоратор не должен менять поведение метода.
- Декоратор не должен выполнять загрузку Swagger-файлов на import time.
В декораторе запрещены любые поля, дублирующие Swagger-контракт:
response_model=...
request_model=...
request_schema=...
response_schema=...
success_statuses=...
error_statuses=...
content_type=...
required_fields=...
query_params=...
path_params=...Причина: это создает второй источник истины и допускает расхождение со Swagger.
factory_args и method_args описывают, как contract-test runner строит SDK-вызов из Swagger-generated request data.
Разрешенные выражения:
path.<name> path parameter
query.<name> query parameter
header.<name> header parameter
body весь request body
body.<field> поле request body
constant.<name> тестовая константа из controlled test registry
Примеры:
factory_args={
"user_id": "path.user_id",
"item_id": "path.item_id",
}method_args={
"request": "body",
}method_args={
"limit": "query.limit",
"offset": "query.offset",
}Ограничения:
factory_argsиmethod_argsне должны содержать Python expressions.- Запрещены произвольные callables.
- Запрещены dotted paths, которые не относятся к Swagger request.
constant.*разрешается только для заранее зарегистрированных тестовых констант.
Операция определяется ключом:
spec + method + normalized_path
Если spec не указан, operation может быть найдена по:
method + normalized_path
только если совпадение среди всех Swagger-файлов ровно одно.
operation_id, если указан, является дополнительной проверкой, а не основным источником истины.
Нужен отдельный CLI-линтер:
poetry run python scripts/lint_swagger_bindings.pyИ make target:
make swagger-lintЛинтер должен запускаться вместе с общей проверкой качества проекта.
Линтер загружает все файлы:
docs/avito/api/*.json
Проверяет:
- JSON валиден;
- Swagger/OpenAPI структура поддерживается;
- все paths и operations извлекаются;
- каждая operation имеет стабильный ключ;
- нет дублей
spec + method + path; - path parameters в path совпадают с parameters/request definition.
Линтер импортирует пакет avito и находит все функции/методы с:
__swagger_binding__Для каждого binding определяет:
- module;
- class name;
- method name;
- effective
spec; - effective
factory; - effective
factory_args; - effective
method_args; - class-level metadata.
Обязательные проверки:
1. Каждая Swagger operation имеет ровно один SDK binding.
2. Каждый SDK binding указывает на существующую Swagger operation.
3. Две SDK methods не могут ссылаться на одну Swagger operation.
4. Один SDK method не может иметь несколько bindings, кроме явно разрешенной политики.
5. spec из binding/class metadata должен существовать в docs/avito/api/.
6. method/path должны совпадать с operation из Swagger.
7. operation_id, если указан, должен совпадать со Swagger.
Проверки:
1. Если Swagger operation deprecated=true, binding должен иметь deprecated=True.
2. Если binding deprecated=True, Swagger operation тоже должна быть deprecated.
3. Если политика SDK требует legacy domain для deprecated operations, binding должен иметь legacy=True.
4. Non-deprecated operation не может иметь legacy=True без явного исключения.
Исключения, если они понадобятся, должны быть описаны в отдельном allowlist-файле с причиной и датой удаления. По умолчанию allowlist запрещен.
Для каждого binding:
1. factory должен существовать на AvitoClient.
2. Если factory не указан в decorator, должен быть __sdk_factory__ на классе.
3. factory_args должны соответствовать сигнатуре factory.
4. method_args должны соответствовать сигнатуре decorated SDK method.
5. Required параметры factory/method должны быть покрыты mapping-ом.
6. В mapping не должно быть лишних аргументов.
Для каждого выражения в factory_args и method_args:
path.<name>
<name> должен существовать среди path parameters Swagger operation.
query.<name>
<name> должен существовать среди query parameters Swagger operation.
header.<name>
<name> должен существовать среди header parameters Swagger operation.
body
Swagger operation должна иметь request body.
body.<field>
Swagger operation должна иметь request body schema, где поле существует.
constant.<name>
<name> должен существовать в test constants registry.
Линтер должен падать, если в SwaggerOperationBinding или decorator call появляются запрещенные поля:
- statuses;
- schemas;
- content types;
- response models;
- request models;
- error models.
Это можно проверять через сигнатуру декоратора и unit-тесты самого декоратора.
Ошибка должна быть точной и actionable.
Пример:
[SWAGGER_BINDING_NOT_FOUND]
Swagger operation has no SDK binding:
spec=Мессенджер.json
method=GET
path=/messenger/v1/accounts/{user_id}/chats/{chat_id}
[SWAGGER_BINDING_DUPLICATE]
Multiple SDK methods bind the same Swagger operation:
spec=Объявления.json
method=GET
path=/core/v1/items/{item_id}
methods:
- avito.ads.domain.Ad.get
- avito.ads.client.AdsClient.get_item
[SWAGGER_ARG_UNKNOWN_QUERY_PARAM]
Binding references unknown query parameter:
method=avito.messenger.domain.Chat.list
expression=query.page
swagger_operation=GET /messenger/v1/accounts/{user_id}/chats
known_query_params=[limit, offset]
Декоратор не должен:
- генерировать SDK-код;
- валидировать реальные payload на runtime;
- выполнять HTTP;
- знать response statuses;
- знать schemas;
- заменять Swagger;
- заменять typed models.
Runtime/request/response проверки делает SwaggerFakeTransport и contract tests, используя Swagger operation, найденную через binding.
Swagger operation
↔ exactly one @swagger_operation SDK method
→ SwaggerFakeTransport validates actual HTTP request/response
→ contract tests validate all statuses and errors from Swagger
Декоратор дает строгую адресацию. Линтер гарантирует, что адресация полная и валидная. Swagger остается единственным источником API-контракта.