Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@
"Bash(curl -s -o /dev/null -w \"%{http_code}\" --max-time 10 \"https://developers.avito.ru/api-catalog/messenger/swagger.json\")",
"Bash(curl -s --max-time 10 \"https://developers.avito.ru/swagger/messenger.yaml\")",
"Bash(curl -s --max-time 10 \"https://developers.avito.ru/api-catalog/messenger/swagger.json\")",
"Bash(awk -F'|' '{print $15}')"
"Bash(awk -F'|' '{print $15}')",
"Bash(git rm *)",
"Bash(grep -E \"\\\\.json$|\\\\.lock$|todo\\\\.md|action_plan\\\\.md|usability_scorecard\\\\.md\")",
"Bash(echo \"---EXISTS:$?\")",
"Bash(git mv *)",
"Bash(make swagger-lint *)"
]
}
}
5 changes: 1 addition & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,8 @@ jobs:
- name: Install dependencies
run: poetry install --no-interaction --with docs

- name: Run strict Swagger coverage gate
run: make swagger-coverage

- name: Run quality gate
run: make check
run: make quality

- name: Run docs strict gate
run: make docs-strict
Expand Down
6 changes: 0 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,9 @@ jobs:
TAG_VERSION="${GITHUB_REF_NAME#v}"
poetry version "$TAG_VERSION"

- name: Run strict Swagger coverage gate
run: make swagger-coverage

- name: Run quality gate
run: make check

- name: Build package to PyPi
run: make build

- name: Publish package to PyPI
env:
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_API_TOKEN }}
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ pip-delete-this-directory.txt

# Unit test / coverage reports
swagger-bindings-report.json
architecture-inventory-report.json
reference-explanation-examples-report.json
htmlcov/
.tox/
.nox/
Expand Down
27 changes: 25 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ and this project adheres to Semantic Versioning.
## [Unreleased]

### Added
- Нет изменений.
- Добавлен `ClientClosedError` для вызовов после `AvitoClient.close()`.

### Deprecated
- Env alias `AVITO_SECRET` для `AVITO_CLIENT_SECRET` устарел и теперь эмитирует `DeprecationWarning`; используйте `AVITO_CLIENT_SECRET`.
- Архивные CPA-методы `CpaArchive.get_call`, `CpaArchive.get_balance_info`, `CpaArchive.get_call_by_id` и режим `CpaChat.list(version=1)` теперь эмитируют `DeprecationWarning` при первом вызове; используйте `call_tracking_call().download`, `cpa_lead().get_balance_info`, `call_tracking_call().get` и `cpa_chat().list(version=2)`.
- Архивные методы автозагрузки `AutoloadArchive.get_profile`, `AutoloadArchive.save_profile`, `AutoloadArchive.get_last_completed_report`, `AutoloadArchive.get_report` теперь эмитируют `DeprecationWarning` при первом вызове; используйте `autoload_profile().get`, `autoload_profile().save`, `autoload_report().get_last_completed` и `autoload_report().get`.

Expand All @@ -19,12 +20,34 @@ and this project adheres to Semantic Versioning.
- Убраны прямые обращения доменных клиентов к `request_json` и приватному `Transport._auth_provider`.
- Секционные клиенты переведены на `@dataclass(slots=True, frozen=True)`.
- Иерархия исключений упрощена до frozen dataclass без кастомного `__setattr__`.
- `AvitoClient.settings`, `AvitoClient.auth_provider` и `AvitoClient.transport` стали read-only свойствами; для тестов используйте `AvitoClient._from_transport(...)`.
- OAuth token flow переведен на общий `Transport` без прямого `httpx.Client().post(...)`; ошибки OAuth продолжают приходить как `AuthenticationError`.
- Публичные сигнатуры `accounts`, `ads`, `autoteka`, `cpa`, `jobs`, `messenger`, `orders`, `promotion`, `ratings` и `realty` переведены с `request`-DTO на keyword-only примитивы и коллекции.
- Swagger-bound public methods теперь принимают per-operation overrides `timeout` и `retry`; `retry="enabled"` форсирует retry, `retry="disabled"` запрещает retry для конкретного вызова.
- `Review.list`, `AutotekaMonitoring.get_monitoring_reg_actions`, `Vacancy.list`, `Vacancy.get`, `Application.get_ids` и `Resume.list` больше не принимают internal query DTO в публичных сигнатурах; передавайте `page`, `offset`, `limit`, `query`, `vacancy_id` и `updated_at_from` напрямую.
- Promotion input модели `BbipItem`, `TrxItem` и `CpaAuctionBidInput`, а также jobs-модель `ApplicationViewedItem` оформлены как публичные frozen dataclass-модели без наследования от internal `RequestModel`.
- Transport получил поддержку `Idempotency-Key`; публичные write-методы во всех доменах принимают `idempotency_key`, а dry-run/write-контракт promotion покрыт тестами.
- Во всех доменных пакетах добавлены `enums.py`; `accounts`, `ads`, `autoteka`, `jobs`, `messenger`, `orders`, `promotion`, `ratings`, `realty` и `tariffs` переведены на typed enums с fallback на `UNKNOWN` и warning-логом ровно один раз на неизвестное upstream-значение.
- `CpaCallStatusId` получил `UNKNOWN`; неизвестный upstream `statusId` больше не превращается в `None` и логирует warning один раз на процесс.
- **BREAKING:** `AccountHierarchy.list_items_by_employee(...)` теперь требует `category_id` и отправляет Swagger body `employeeId/categoryId/lastItemId`; старые `limit`/`offset` не входят в контракт `/listItemsByEmployeeIdV1`.
- **BREAKING:** статистические методы `AdStats.get_item_stats(...)`, `get_calls_stats(...)`, `get_item_analytics(...)` и `get_account_spendings(...)` теперь требуют обязательные поля периода и параметры, описанные в Swagger requestBody.
- **BREAKING:** `AdPromotion.apply_vas(...)` принимает `vas_id` для legacy v1 endpoint, а `apply_vas_direct(...)` принимает `slugs`; payload больше не использует внутренний ключ `codes`.
- **BREAKING:** CPA methods now match Swagger request bodies: complaints send `message`, balanceInfo sends JSON string `"{}"`, chats/phones/calls list methods require `limit`/`offset` or `limit` fields declared by Swagger.
- **BREAKING:** Autoteka request bodies now match Swagger: `get_leads(...)` requires `subscription_id`, catalog resolve sends `fieldsValueIds`, monitoring bucket methods send `data`, and vehicle/request identifiers use Swagger JSON types.
- **BREAKING:** Autoload profile saves now require Swagger fields (`report_email`, schedule and feed/upload URL), stock info sends `item_ids`, TrxPromo cancel sends `itemIDs`, and Autostrategy update/stop generated calls include `campaignId` and `version`.
- **BREAKING:** Jobs vacancy write methods now require Swagger billing fields, classic v1 vacancy create requires the documented required fields, `JobWebhook.update(...)` requires `secret`, and vacancy statuses send UUID string ids.
- **BREAKING:** Messenger request bodies now match Swagger for blacklist, text messages and image messages; malformed Swagger required fields absent from schema properties are ignored by the normalized schema tree.
- **BREAKING:** Special-offers request bodies now match Swagger: `create_multi(...)` sends only `itemIds`, `confirm_multi(...)` sends `dispatches`/`expiresAt`, and `get_stats(...)` requires `date_time_from`/`date_time_to`.
- Вызовы после `AvitoClient.close()` теперь поднимают `ClientClosedError` вместо `ConfigurationError`.

### Removed
- Нет изменений.
- **BREAKING:** удалены классы исключений `NotFoundError`, `ClientError`, `ServerError` из `avito.core.exceptions`. HTTP 404 и 5xx теперь маппятся на `UpstreamApiError`. Пользователям, ловившим эти типы, перейти на `UpstreamApiError` или `AvitoError` и проверять `status_code`.
- **BREAKING:** удален публичный wrapper `Application.list(...)`; используйте `application().get_ids(updated_at_from=...)` для синхронизации id и `application().get_by_ids(ids=...)` для получения данных откликов.
- **BREAKING:** internal query DTO `ApplicationIdsQuery`, `ResumeSearchQuery`, `VacanciesQuery` и `MonitoringEventsQuery` больше не re-export-ятся из доменных пакетов; публичные методы принимают primitive keyword-only параметры.
- **BREAKING:** удалены старые public input aliases `BbipItemInput`, `TrxItemInput` и `BidItemInput`; используйте `BbipItem`, `TrxItem` и `CpaAuctionBidInput`.
- Удалены legacy-модули `avito/auth/mappers.py` и `avito/auth/enums.py` (внутренние, без публичных импортов).
- Удалены инфраструктурные мета-тесты (`tests/docs/`, `tests/test_inventory_architecture.py`, `tests/test_download_avito_api_specs.py`, `tests/core/test_architecture_lint.py`, `tests/core/test_swagger_{linter,discovery,factory_map,report}.py`); архитектурные инварианты остаются под `make swagger-lint` и `make architecture-lint`.
- Удалены архитектурные тесты `tests/contracts/test_public_surface.py` и `tests/core/test_swagger.py` — публичная поверхность и метаданные `@swagger_operation` верифицируются `mypy strict` + `make swagger-lint` + `tests/contracts/test_swagger_contracts.py`.

### Fixed
- Нет изменений.
Expand Down
17 changes: 12 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,17 @@ poetry run pytest tests/test_facade.py::test_name
| Layer | Location | Responsibility |
|---|---|---|
| `AvitoClient` | `avito/client.py` | Public facade, factory methods |
| `SectionClient` | `avito/<domain>/client.py` | HTTP calls for one API section |
| `DomainObject` | `avito/<domain>/domain.py` | Explicit public methods, validation, docstrings, Swagger bindings |
| `OperationSpec` | `avito/<domain>/operations.py` or `operations/` | Internal HTTP method/path/context/retry/request/response metadata |
| `OperationExecutor` | `avito/core/operations.py` | Executes operation specs through transport |
| `Transport` | `avito/core/transport.py` | httpx, retries, error mapping, token injection |
| `AuthProvider` | `avito/auth/provider.py` | Token cache, refresh, 401 handling |
| `Mapper` | `avito/<domain>/mappers.py` | JSON → typed dataclass |
| Models | `avito/<domain>/models.py` or `models/` | dataclasses, enums, `from_payload()`, `to_payload()`, `to_params()` |
| Config | `avito/config.py`, `avito/auth/settings.py` | `AvitoSettings`, `AuthSettings` |

**Domain packages** follow a uniform structure: `__init__.py`, `domain.py` (DomainObject subclass), `client.py` (SectionClient), `models.py` (frozen dataclasses), `mappers.py`, optional `enums.py`.
**Target domain packages** follow the v2 architecture in `docs/site/explanations/domain-architecture-v2.md`: `__init__.py`, `domain.py` (DomainObject subclasses), `operations.py` or `operations/` (OperationSpec definitions), and `models.py` or `models/` (frozen dataclasses, colocated enums, payload parsing/serialization). Do not add new `client.py`, `mappers.py`, or standalone `enums.py` without an explicit architecture reason. Existing legacy modules may remain during migration; compatibility mappers should delegate to `Model.from_payload()`.

**Public models** are `@dataclass(slots=True, frozen=True)`, inherit `SerializableModel` (provides `to_dict()` / `model_dump()`), and never expose transport fields.
**Public models** are `@dataclass(slots=True, frozen=True)`, inherit `ApiModel` or another approved `SerializableModel` subclass (provides `to_dict()` / `model_dump()`), implement `from_payload()` for API JSON, and never expose transport fields. Request/query dataclasses use `to_payload()` / `to_params()` and stay out of public method signatures unless explicitly documented as public input models.

**Exceptions** live in `avito/core/exceptions.py`. `AvitoError` is the base. HTTP codes map to specific types: 401→`AuthenticationError`, 403→`AuthorizationError`, 429→`RateLimitError`, etc. These two are siblings, not parent/child.

Expand Down Expand Up @@ -65,7 +67,7 @@ Multiple Swagger bindings on one public SDK method are forbidden. If one public
When adding or changing a public method that corresponds to Avito API:

- consult `docs/avito/api/*.json` first;
- add or update the public domain method, section client call, mapper and typed public models;
- add or update the public domain method, operation spec, typed models, request/query dataclasses, and `from_payload()` / `to_payload()` / `to_params()` mapping;
- add `@swagger_operation(...)` on the public domain method;
- do not put schemas, statuses, content types, request models, response models, error models, path params, or query params into the decorator;
- add or update class-level Swagger metadata when introducing a domain class;
Expand Down Expand Up @@ -96,18 +98,23 @@ The most critical prohibitions that must never be violated:
- Using `resource_id` instead of concrete names (`item_id`, `order_id`).
- Annotating `list[T]` where `PaginatedList[T]` is returned at runtime.
- Adding or changing an Avito API public method without a `@swagger_operation(...)` binding.
- Adding or changing an Avito API public method without a matching `OperationSpec` or documented legacy adapter.
- Adding or changing an Avito API public method without a reference-ready docstring.
- Duplicating Swagger contract data inside binding decorators.
- Making `AuthenticationError` a subclass of `AuthorizationError` (or vice versa).
- Writing error messages in mixed languages (Russian only).
- Injecting methods via `setattr`/`globals()` at runtime.
- Duplicating behavior through two different public methods without deprecation.
- Leaking internal-layer request-DTOs into public signatures.
- Adding new `client.py`, `mappers.py`, or standalone `enums.py` in a new/refactored domain without an explicit architecture note.
- Adding dead code: unused imports, type aliases, TypeVars.

## Key conventions (from STYLEGUIDE.md)

- All public methods return typed SDK models, never raw `dict`.
- New/refactored domains use v2 layout: `domain.py`, `operations.py`/`operations/`, `models.py`/`models/`.
- API response parsing belongs in `Model.from_payload()`; request/query serialization belongs in dataclasses via `to_payload()` / `to_params()`.
- Enums live next to the models that use them; legacy `enums.py` modules are compatibility re-exports only.
- Field names are concrete: `item_id`, `user_id` — never `resource_id`.
- Public method arguments are primitives or domain models — internal request-DTOs must not leak out.
- Write-operations that accept `dry_run: bool = False` must build the same payload in both modes; with `dry_run=True` transport must not be called.
Expand Down
14 changes: 11 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ export
REGISTRY=10.11.0.9:5000
MKDOCS_ENV=DISABLE_MKDOCS_2_WARNING=true NO_MKDOCS_2_WARNING=1

check: swagger-update test typecheck lint swagger-lint build
check: test quality

quality: typecheck lint swagger-lint architecture-lint docstring-lint build

build: clean
poetry build
Expand Down Expand Up @@ -36,8 +38,14 @@ swagger-update:
swagger-lint: swagger-update
poetry run python scripts/lint_swagger_bindings.py --strict

architecture-lint:
poetry run python scripts/lint_architecture.py

docstring-lint:
poetry run python scripts/lint_docstrings.py

swagger-coverage: swagger-lint
poetry run pytest tests/core/test_swagger.py tests/core/test_swagger_discovery.py tests/core/test_swagger_linter.py tests/core/test_swagger_report.py tests/core/test_swagger_registry.py tests/contracts/test_swagger_contracts.py
poetry run pytest tests/core/test_swagger_registry.py tests/contracts/test_swagger_contracts.py

minor: check
poetry version minor
Expand All @@ -57,7 +65,7 @@ docs-serve:
docs-strict:
$(MKDOCS_ENV) poetry run mkdocs build --strict
poetry run python scripts/lint_swagger_bindings.py --strict
poetry run pytest tests/docs/
poetry run python scripts/lint_docstrings.py

docs-build: docs-strict

Expand Down
Loading
Loading