diff --git a/usability_scorecard.md b/.ai/usability_scorecard.md similarity index 100% rename from usability_scorecard.md rename to .ai/usability_scorecard.md diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0f7eb33..695da25 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -57,7 +57,7 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} with: - args: --root-dir "${{ github.workspace }}/site" --exclude "avito\.ru" --exclude "^https://p141592\.github\.io/avito_python_api/" --retry-wait-time 5 --max-retries 3 --timeout 30 site/ + args: --root-dir "${{ github.workspace }}/site" --exclude "avito\.ru" --exclude "^https://18studio\.github\.io/avito_python_api/" --retry-wait-time 5 --max-retries 3 --timeout 30 site/ deploy: runs-on: ubuntu-latest diff --git a/Makefile b/Makefile index 0c65698..289f33b 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ docs-report: docs-check: docs-strict ln -sfn . site/avito_python_api - lychee --root-dir "$(PWD)/site" --exclude "avito\.ru" --exclude "^https://p141592\.github\.io/avito_python_api/" --retry-wait-time 5 --max-retries 3 --timeout 30 site/ + lychee --root-dir "$(PWD)/site" --exclude "avito\.ru" --exclude "^https://18studio\.github\.io/avito_python_api/" --retry-wait-time 5 --max-retries 3 --timeout 30 site/ qa-docs: poetry run pydocstyle \ diff --git a/README.md b/README.md index afc2ae5..9675e09 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # SDK для Avito -[](https://github.com/p141592/avito_python_api/actions/workflows/ci.yml) -[](https://coveralls.io/github/p141592/avito_python_api?branch=main) +[](https://github.com/18studio/avito_python_api/actions/workflows/ci.yml) +[](https://coveralls.io/github/18studio/avito_python_api?branch=main) [](https://pypi.org/project/avito-py/) [](https://18studio.github.io/avito_python_api/) @@ -121,6 +121,71 @@ async with AsyncAvitoClient.from_env() as avito: - `AvitoSettings.from_env()` и `AvitoClient.from_env()` детерминированно читают `.env` из текущей рабочей директории или из переданного `env_file`; - при отсутствии `AVITO_CLIENT_ID` или `AVITO_CLIENT_SECRET` SDK поднимает `ConfigurationError` при создании клиента, до первого HTTP-запроса. +## Асинхронный режим + +Для async-кода используйте `AsyncAvitoClient`. Он повторяет доменную поверхность +`AvitoClient`: фабрики (`account()`, `ad()`, `chat()`, `order()` и другие), +аргументы методов и возвращаемые SDK-модели остаются теми же, но сетевые вызовы +выполняются через `await`. + +`AsyncAvitoClient` обязательно открывается через `async with`: в этот момент SDK +создаёт loop-bound `httpx.AsyncClient`, async locks и transport. Для ручного +закрытия есть `await avito.aclose()`, но для application-кода предпочтителен +контекстный менеджер. + +```python +from avito import AsyncAvitoClient + + +async def load_active_ads() -> list[str]: + async with AsyncAvitoClient.from_env() as avito: + profile = await avito.account().get_self() + ads = await avito.ad(user_id=profile.id).list(status="active", limit=20) + items = await ads.materialize() + return [item.title for item in items] +``` + +Async-пагинация возвращает `AsyncPaginatedList[T]`, а не обычный `list`. +Читайте страницы через `async for` или явно материализуйте результат: + +```python +from avito import AsyncAvitoClient + + +async def print_ads() -> None: + async with AsyncAvitoClient.from_env() as avito: + ads = await avito.ad(user_id=123).list(status="active", limit=100) + + async for item in ads: + print(item.title) +``` + +Для ASGI-приложений создавайте один `AsyncAvitoClient` в lifespan приложения и +закрывайте его на shutdown. Один экземпляр клиента нельзя переносить между event +loop. + +```python +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager + +from fastapi import FastAPI + +from avito import AsyncAvitoClient + + +@asynccontextmanager +async def lifespan(app: FastAPI) -> AsyncIterator[None]: + async with AsyncAvitoClient.from_env() as avito: + app.state.avito = avito + yield + + +app = FastAPI(lifespan=lifespan) +``` + +Подробнее: [Асинхронный режим](https://18studio.github.io/avito_python_api/how-to/async/) +и справочник [AvitoClient и AsyncAvitoClient](https://18studio.github.io/avito_python_api/reference/client/). + ## Примеры по доменам ### Аккаунт и объявления diff --git a/docs/site/assets/_gen_reference.py b/docs/site/assets/_gen_reference.py index c3f6ca7..5ebee82 100644 --- a/docs/site/assets/_gen_reference.py +++ b/docs/site/assets/_gen_reference.py @@ -16,7 +16,7 @@ EXCLUDED_PACKAGES = {"auth", "core", "testing"} PACKAGE_ROOT = Path("avito") -GITHUB_API_URL = "https://github.com/p141592/avito_python_api/blob/main/docs/avito/api" +GITHUB_API_URL = "https://github.com/18studio/avito_python_api/blob/main/docs/avito/api" def public_domain_packages() -> list[str]: diff --git a/docs/site/explanations/.pages b/docs/site/explanations/.pages index 7e3f9b8..d6df08d 100644 --- a/docs/site/explanations/.pages +++ b/docs/site/explanations/.pages @@ -7,6 +7,7 @@ nav: - pagination-semantics.md - dry-run-and-idempotency.md - testing-strategy.md + - async-domain-template.md - api-coverage-and-deprecations.md - swagger-binding-subsystem.md - config-resolution.md diff --git a/docs/site/explanations/index.md b/docs/site/explanations/index.md index 9b4c814..3ea8f97 100644 --- a/docs/site/explanations/index.md +++ b/docs/site/explanations/index.md @@ -12,6 +12,7 @@ Explanations описывают причины архитектурных реш | [Семантика пагинации](pagination-semantics.md) | Почему `PaginatedList` ленивый и когда загружаются страницы | | [Dry-run и идемпотентность](dry-run-and-idempotency.md) | Как write-операции проверяются без сетевого вызова | | [Стратегия тестирования](testing-strategy.md) | Как `FakeTransport`, contract-тесты и docs-harness проверяют SDK | +| [Шаблон async-домена](async-domain-template.md) | Как портировать доменный объект в `async_domain.py` без нарушения Swagger bindings | | [Покрытие API и deprecation](api-coverage-and-deprecations.md) | Как specs, reference и runtime warnings связаны между собой | | [Swagger binding subsystem](swagger-binding-subsystem.md) | Как Swagger specs, bindings, strict lint, JSON report и contract runner сохраняют coverage-контекст | | [Resolution конфигурации](config-resolution.md) | Как env, `.env` и defaults превращаются в `AvitoSettings` | diff --git a/docs/site/how-to/async.md b/docs/site/how-to/async.md index c2c1a01..9241c6e 100644 --- a/docs/site/how-to/async.md +++ b/docs/site/how-to/async.md @@ -1,4 +1,4 @@ -# Async API +# Асинхронный режим `AsyncAvitoClient` повторяет доменную поверхность `AvitoClient`, но все сетевые методы вызываются через `await`. Клиент обязательно открывается через `async with`: diff --git a/docs/site/how-to/index.md b/docs/site/how-to/index.md index 7c1a595..5fbe0ab 100644 --- a/docs/site/how-to/index.md +++ b/docs/site/how-to/index.md @@ -5,7 +5,7 @@ How-to раздел собирает рецепты для конкретных | Рецепт | Задача | |---|---| | [Авторизация и конфигурация](auth-and-config.md) | Создать клиент через env, явные ключи или `AvitoSettings` | -| [Async API](async.md) | Использовать `AsyncAvitoClient`, ASGI lifespan и async fake transport | +| [Асинхронный режим](async.md) | Использовать `AsyncAvitoClient`, ASGI lifespan и async fake transport | | [Профиль, баланс и иерархия аккаунта](account-profile.md) | Получить профиль, баланс, историю операций и данные сотрудников | | [Объявления, статистика и продвижение](ad-listing-and-stats.md) | Найти объявления, открыть карточку, прочитать статистику и подготовить VAS | | [Продвижение с dry-run](promotion-dry-run.md) | Проверить payload write-операции без сетевого вызова | diff --git a/docs/site/index.md b/docs/site/index.md index c9bfcb9..5dc1b75 100644 --- a/docs/site/index.md +++ b/docs/site/index.md @@ -16,8 +16,8 @@ pip install avito-py