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 -[![CI](https://github.com/p141592/avito_python_api/actions/workflows/ci.yml/badge.svg)](https://github.com/p141592/avito_python_api/actions/workflows/ci.yml) -[![Coverage Status](https://coveralls.io/repos/github/p141592/avito_python_api/badge.svg?branch=main)](https://coveralls.io/github/p141592/avito_python_api?branch=main) +[![CI](https://github.com/18studio/avito_python_api/actions/workflows/ci.yml/badge.svg)](https://github.com/18studio/avito_python_api/actions/workflows/ci.yml) +[![Coverage Status](https://coveralls.io/repos/github/18studio/avito_python_api/badge.svg?branch=main)](https://coveralls.io/github/18studio/avito_python_api?branch=main) [![PyPI Downloads](https://img.shields.io/pypi/dm/avito-py.svg)](https://pypi.org/project/avito-py/) [![Docs](https://img.shields.io/badge/docs-latest-blue)](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
-[![CI](https://github.com/p141592/avito_python_api/actions/workflows/ci.yml/badge.svg)](https://github.com/p141592/avito_python_api/actions/workflows/ci.yml) -[![Coverage Status](https://coveralls.io/repos/github/p141592/avito_python_api/badge.svg?branch=main)](https://coveralls.io/github/p141592/avito_python_api?branch=main) +[![CI](https://github.com/18studio/avito_python_api/actions/workflows/ci.yml/badge.svg)](https://github.com/18studio/avito_python_api/actions/workflows/ci.yml) +[![Coverage Status](https://coveralls.io/repos/github/18studio/avito_python_api/badge.svg?branch=main)](https://coveralls.io/github/18studio/avito_python_api?branch=main) [![PyPI Downloads](https://img.shields.io/pypi/dm/avito-py.svg)](https://pypi.org/project/avito-py/) [![API coverage](https://img.shields.io/badge/API%20coverage-204%2F204-success)](reference/coverage.md) @@ -43,6 +43,14 @@ pip install avito-py [:octicons-arrow-right-24: How-to рецепты](how-to/index.md) +- :material-sync:{ .lg .middle } **Асинхронный режим** + + --- + + `AsyncAvitoClient`, `async with`, ASGI lifespan, async-пагинация и тестирование без HTTP. + + [:octicons-arrow-right-24: Async how-to](how-to/async.md) + - :material-code-tags:{ .lg .middle } **Нужен точный контракт** --- @@ -70,3 +78,5 @@ pip install avito-py | **Режим** | Tutorials | How-to | Reference | Explanations | | **Цель** | Обучение через действие | Решить конкретную задачу | Точная информация | Понять «почему» | | **Раздел** | [Tutorials](tutorials/index.md) | [How-to](how-to/index.md) | [Reference](reference/index.md) | [Explanations](explanations/index.md) | + +Для async-кода начните с рецепта [Асинхронный режим](how-to/async.md), а точный контракт смотрите в [AvitoClient и AsyncAvitoClient](reference/client.md). diff --git a/docs/site/reference/index.md b/docs/site/reference/index.md index a860974..a03dfb8 100644 --- a/docs/site/reference/index.md +++ b/docs/site/reference/index.md @@ -5,7 +5,8 @@ | Страница | Что искать | |---|---| -| [AvitoClient](client.md) | Инициализация, контекстный менеджер, фабричные методы, `debug_info()` | +| [AvitoClient и AsyncAvitoClient](client.md) | Sync/async инициализация, контекстные менеджеры, фабричные методы, `debug_info()` | +| [Асинхронный режим](../how-to/async.md) | Практический lifecycle `AsyncAvitoClient`, ASGI и async fake transport | | [Конфигурация](config.md) | `AvitoSettings`, `AuthSettings`, env-переменные, per-operation overrides | | [Покрытие API](coverage.md) | 204/204 Swagger operations из binding report | | [Методы API](operations.md) | Карта Swagger operation → публичный SDK-метод | diff --git a/mkdocs.yml b/mkdocs.yml index 9c9c82b..b7a575c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,8 +1,8 @@ site_name: avito-py site_description: Sync/async Python SDK для Avito API site_url: https://18studio.github.io/avito_python_api/ -repo_url: https://github.com/p141592/avito_python_api -repo_name: p141592/avito_python_api +repo_url: https://github.com/18studio/avito_python_api +repo_name: 18studio/avito_python_api edit_uri: edit/main/docs/site/ docs_dir: docs/site diff --git a/pyproject.toml b/pyproject.toml index f4ed994..abe128a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,8 +8,8 @@ packages=[ ] license="MIT" readme="README.md" -homepage="https://github.com/p141592/avito_python_api" -repository="https://github.com/p141592/avito_python_api" +homepage="https://github.com/18studio/avito_python_api" +repository="https://github.com/18studio/avito_python_api" documentation="https://18studio.github.io/avito_python_api/" keywords=["avito", "sdk", "python", "api"] classifiers=[ @@ -29,14 +29,14 @@ python = ">=3.12,<4.0" httpx = "^0.28.1" [tool.poetry.group.dev.dependencies] -pytest = "^8.3.5" +pytest = ">=9.0.3,<10.0.0" coverage = "^7.10.6" mypy = "^1.18.2" ruff = "^0.12.12" respx = "^0.22.0" libcst = "^1.8.6" bowler = "^0.9.0" -pytest-asyncio = "^0.24" +pytest-asyncio = ">=1.3,<2.0" [tool.poetry.group.docs.dependencies] mkdocs-material = "^9.5"