avito-ads 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. avito_ads-0.1.0/LICENSE +21 -0
  2. avito_ads-0.1.0/PKG-INFO +513 -0
  3. avito_ads-0.1.0/README.md +473 -0
  4. avito_ads-0.1.0/avito_ads/__init__.py +126 -0
  5. avito_ads-0.1.0/avito_ads/_async/__init__.py +7 -0
  6. avito_ads-0.1.0/avito_ads/_async/client.py +103 -0
  7. avito_ads-0.1.0/avito_ads/_async/resources.py +327 -0
  8. avito_ads-0.1.0/avito_ads/_async/token_provider.py +79 -0
  9. avito_ads-0.1.0/avito_ads/_async/transport.py +114 -0
  10. avito_ads-0.1.0/avito_ads/_core/__init__.py +12 -0
  11. avito_ads-0.1.0/avito_ads/_core/operations.py +399 -0
  12. avito_ads-0.1.0/avito_ads/_core/response.py +20 -0
  13. avito_ads-0.1.0/avito_ads/_core/transport.py +179 -0
  14. avito_ads-0.1.0/avito_ads/_sync/__init__.py +7 -0
  15. avito_ads-0.1.0/avito_ads/_sync/client.py +108 -0
  16. avito_ads-0.1.0/avito_ads/_sync/resources.py +304 -0
  17. avito_ads-0.1.0/avito_ads/_sync/token_provider.py +79 -0
  18. avito_ads-0.1.0/avito_ads/_sync/transport.py +111 -0
  19. avito_ads-0.1.0/avito_ads/auth/__init__.py +24 -0
  20. avito_ads-0.1.0/avito_ads/auth/credentials.py +23 -0
  21. avito_ads-0.1.0/avito_ads/auth/storage.py +87 -0
  22. avito_ads-0.1.0/avito_ads/auth/token.py +70 -0
  23. avito_ads-0.1.0/avito_ads/configuration.py +128 -0
  24. avito_ads-0.1.0/avito_ads/contract_builder.py +169 -0
  25. avito_ads-0.1.0/avito_ads/enums.py +161 -0
  26. avito_ads-0.1.0/avito_ads/exceptions.py +93 -0
  27. avito_ads-0.1.0/avito_ads/filters.py +193 -0
  28. avito_ads-0.1.0/avito_ads/integrations/__init__.py +13 -0
  29. avito_ads-0.1.0/avito_ads/integrations/_common.py +44 -0
  30. avito_ads-0.1.0/avito_ads/integrations/django.py +78 -0
  31. avito_ads-0.1.0/avito_ads/integrations/fastapi.py +69 -0
  32. avito_ads-0.1.0/avito_ads/integrations/flask.py +76 -0
  33. avito_ads-0.1.0/avito_ads/models/__init__.py +40 -0
  34. avito_ads-0.1.0/avito_ads/models/account.py +59 -0
  35. avito_ads-0.1.0/avito_ads/models/advertiser.py +55 -0
  36. avito_ads-0.1.0/avito_ads/models/base.py +65 -0
  37. avito_ads-0.1.0/avito_ads/models/campaign.py +75 -0
  38. avito_ads-0.1.0/avito_ads/models/child_account.py +33 -0
  39. avito_ads-0.1.0/avito_ads/models/contract.py +60 -0
  40. avito_ads-0.1.0/avito_ads/models/created.py +47 -0
  41. avito_ads-0.1.0/avito_ads/models/creative.py +71 -0
  42. avito_ads-0.1.0/avito_ads/models/group.py +67 -0
  43. avito_ads-0.1.0/avito_ads/models/statistics.py +123 -0
  44. avito_ads-0.1.0/avito_ads/models/user.py +23 -0
  45. avito_ads-0.1.0/avito_ads/pagination.py +126 -0
  46. avito_ads-0.1.0/avito_ads/py.typed +0 -0
  47. avito_ads-0.1.0/avito_ads.egg-info/PKG-INFO +513 -0
  48. avito_ads-0.1.0/avito_ads.egg-info/SOURCES.txt +51 -0
  49. avito_ads-0.1.0/avito_ads.egg-info/dependency_links.txt +1 -0
  50. avito_ads-0.1.0/avito_ads.egg-info/requires.txt +16 -0
  51. avito_ads-0.1.0/avito_ads.egg-info/top_level.txt +1 -0
  52. avito_ads-0.1.0/pyproject.toml +76 -0
  53. avito_ads-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Avito Ads Python SDK Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,513 @@
1
+ Metadata-Version: 2.4
2
+ Name: avito-ads
3
+ Version: 0.1.0
4
+ Summary: Python SDK для публичного API кабинета Авито Реклама (Avito Ads). Синхронный и асинхронный режимы (httpx), Python 3.8+, интеграции с Django, Flask и FastAPI.
5
+ Author-email: asbezrukov <asbezrukov@avito.ru>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/avito-tech/avito-ads-sdk-python3
8
+ Project-URL: Repository, https://github.com/avito-tech/avito-ads-sdk-python3
9
+ Project-URL: Changelog, https://github.com/avito-tech/avito-ads-sdk-python3/blob/main/CHANGELOG.md
10
+ Project-URL: Issues, https://github.com/avito-tech/avito-ads-sdk-python3/issues
11
+ Keywords: avito,avito-ads,реклама,api,client,sdk,async,httpx
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: httpx<1.0,>=0.24
28
+ Provides-Extra: django
29
+ Requires-Dist: django>=3.2; extra == "django"
30
+ Provides-Extra: flask
31
+ Requires-Dist: flask>=2.0; extra == "flask"
32
+ Provides-Extra: fastapi
33
+ Requires-Dist: fastapi>=0.95; extra == "fastapi"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=7.0; extra == "dev"
36
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
37
+ Requires-Dist: ruff>=0.1; extra == "dev"
38
+ Requires-Dist: mypy>=1.0; extra == "dev"
39
+ Dynamic: license-file
40
+
41
+ # Avito Ads Python SDK
42
+
43
+ [![CI](https://github.com/avito-tech/avito-ads-sdk-python3/actions/workflows/ci.yml/badge.svg)](https://github.com/avito-tech/avito-ads-sdk-python3/actions/workflows/ci.yml)
44
+ [![Python](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://www.python.org/)
45
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
46
+
47
+ Python SDK для API **Авито Реклама** (Avito Ads API).
48
+
49
+ - [Об Авито Реклама](http://avito.ru/ads)
50
+ - [Возможности API Авито Реклама](https://ads-help.avito.com/external/api)
51
+ - [Техническая документация API](https://developers.avito.ru/api-catalog/ads/documentation)
52
+
53
+ Библиотека закрывает все основные методы API: аккаунт и баланс, дочерние аккаунты и переводы средств, рекламодателей, договоры, кампании, группы объявлений, креативы, статистику и управление пользователями. Поддерживаются **два режима работы — синхронный и асинхронный** — с одинаковым набором методов. Транспорт построен на [httpx](https://www.python-httpx.org/), авторизация — OAuth2 `client_credentials` с автоматическим обновлением и кэшированием токена, а также повторными попытками при ошибках `429` и `5xx`.
54
+
55
+ ## Возможности
56
+
57
+ - Поддержка **Python 3.8+**.
58
+ - **Синхронный (`Client`) и асинхронный (`AsyncClient`) режимы** с идентичным API — разница только в `await` / `async for` / `async with`.
59
+ - Установка в любой проект; интеграции с **Django**, **Flask**, **FastAPI** (асинхронная).
60
+ - OAuth2 `client_credentials`: автоматическое получение, кэширование и обновление токена.
61
+ - Раздельные окружения: **production** и **sandbox** (песочница).
62
+ - Автоматические повторы при `429`/`5xx` с экспоненциальной задержкой и учётом заголовка `Retry-After`; однократное обновление токена и повтор при `401`.
63
+ - Типизированные модели ответов и типизированные исключения по кодам ошибок.
64
+ - Постраничная выборка с удобным перебором всех страниц через генератор (`for`) или async-генератор (`async for`).
65
+ - Чтение остатка лимита из заголовка `Api-Point-Balance`.
66
+ - Полная типизация (`py.typed`, проверка `mypy --strict`).
67
+ - Документация и docstrings на русском языке.
68
+
69
+ ## Требования
70
+
71
+ - Python >= 3.8
72
+ - `httpx >= 0.24, < 1.0`
73
+
74
+ ## Установка
75
+
76
+ ```bash
77
+ pip install avito-ads
78
+ ```
79
+
80
+ С интеграциями:
81
+
82
+ ```bash
83
+ pip install "avito-ads[flask]" # или [django], [fastapi]
84
+ ```
85
+
86
+ ## Быстрый старт
87
+
88
+ ```python
89
+ from avito_ads import Client, Configuration
90
+
91
+ config = Configuration.create(
92
+ "ВАШ_CLIENT_ID",
93
+ "ВАШ_CLIENT_SECRET",
94
+ 123456789, # accountID — идентификатор рекламного аккаунта
95
+ ).production() # .sandbox() для песочницы
96
+
97
+ with Client(config) as client:
98
+ # Баланс аккаунта (в рублях)
99
+ balance = client.account.get_balance()
100
+ print(balance.balance, "₽, бонусы:", balance.bonus_balance)
101
+ ```
102
+
103
+ `accountID` — это идентификатор аккаунта, к которому привязан токен; он задаётся один раз в конфигурации и подставляется во все запросы автоматически.
104
+
105
+ ## Синхронный и асинхронный режимы
106
+
107
+ Библиотека предоставляет два клиента с одинаковым набором ресурсов, методов и аргументов: синхронный `Client` и асинхронный `AsyncClient`. Оба создаются из одной и той же `Configuration`.
108
+
109
+ Синхронный режим:
110
+
111
+ ```python
112
+ from avito_ads import Client, Configuration
113
+
114
+ config = Configuration.create("ID", "SECRET", 123456789).sandbox()
115
+ with Client(config) as client:
116
+ balance = client.account.get_balance()
117
+ for campaign in client.campaigns.iterate():
118
+ print(campaign.id, campaign.name)
119
+ ```
120
+
121
+ Асинхронный режим — те же вызовы с `await`, перебор через `async for`, клиент как `async with`:
122
+
123
+ ```python
124
+ import asyncio
125
+ from avito_ads import AsyncClient, Configuration
126
+
127
+ async def main() -> None:
128
+ config = Configuration.create("ID", "SECRET", 123456789).sandbox()
129
+ async with AsyncClient(config) as client:
130
+ balance = await client.account.get_balance()
131
+ async for campaign in client.campaigns.iterate():
132
+ print(campaign.id, campaign.name)
133
+
134
+ asyncio.run(main())
135
+ ```
136
+
137
+ Оба клиента можно создавать и без менеджера контекста; в этом случае закрывайте их вручную: `client.close()` для синхронного и `await client.aclose()` для асинхронного.
138
+
139
+ ## Конфигурация
140
+
141
+ Объект `Configuration` неизменяемый: все методы `with_*` и `production`/`sandbox` возвращают новый экземпляр.
142
+
143
+ ```python
144
+ config = (
145
+ Configuration.create(client_id, client_secret, account_id)
146
+ .sandbox() # или .production()
147
+ .with_timeout(30.0) # таймаут запроса, сек
148
+ .with_max_retries(4) # макс. число повторов при 429/5xx
149
+ .with_retry_base_delay(1000) # базовая задержка повтора, мс
150
+ .with_token_leeway(60) # запас на досрочное обновление токена, сек
151
+ .with_user_agent("my-app/1.0") # значение заголовка User-Agent
152
+ )
153
+ ```
154
+
155
+ При необходимости можно передать готовый HTTP-клиент httpx (например, с прокси, лимитами пула или HTTP/2):
156
+
157
+ ```python
158
+ import httpx
159
+
160
+ # для синхронного Client
161
+ config = config.with_http_client(httpx.Client(http2=True))
162
+ # для асинхронного AsyncClient
163
+ config = config.with_async_client(httpx.AsyncClient(http2=True))
164
+ ```
165
+
166
+ ### Окружения
167
+
168
+ | Окружение | Базовый адрес API |
169
+ |---|---|
170
+ | `production()` | `https://api.avito.ru/ads/` |
171
+ | `sandbox()` | `https://api.avito.ru/ads-sandbox/` |
172
+
173
+ Эндпоинт получения токена общий для обоих окружений: `https://api.avito.ru/token`.
174
+
175
+ ## Хранение и обновление токена
176
+
177
+ Токен запрашивается автоматически при первом обращении и переиспользуется до истечения срока. По умолчанию он хранится в памяти процесса. Чтобы переживать перезапуски, реализуйте свой класс хранилища и передайте его:
178
+
179
+ ```python
180
+ # синхронный клиент — протокол avito_ads.auth.storage.TokenStorage (get/set/delete)
181
+ config = config.with_token_storage(my_storage)
182
+
183
+ # асинхронный клиент — протокол avito_ads.auth.storage.AsyncTokenStorage
184
+ # (async get/set/delete, удобно для асинхронного Redis)
185
+ config = config.with_async_token_storage(my_async_storage)
186
+ ```
187
+
188
+ ## Работа с методами API
189
+
190
+ Все группы методов доступны как свойства клиента.
191
+
192
+ ### Аккаунт
193
+
194
+ ```python
195
+ account = client.account.get()
196
+ print(account.short_name, "/ ИНН", account.inn)
197
+
198
+ balance = client.account.get_balance()
199
+ ```
200
+
201
+ ### Дочерние аккаунты и переводы
202
+
203
+ ```python
204
+ for child in client.child_accounts.list():
205
+ print(child.id, child.short_name)
206
+
207
+ # Создать дочерний аккаунт-непокупатель
208
+ created = client.child_accounts.create_nonpayer_child("ООО Дочка", True)
209
+
210
+ # Перевести средства / бонусы (сумма не менее 1)
211
+ client.child_accounts.transfer_funds(account_id_to=987654321, amount=5000)
212
+ client.child_accounts.transfer_bonus(account_id_to=987654321, amount=100)
213
+ ```
214
+
215
+ ### Рекламодатели
216
+
217
+ ```python
218
+ from avito_ads.enums import LegalType, LegalRole
219
+
220
+ created = client.advertisers.create(
221
+ inn="7712345678",
222
+ short_name="ООО Реклама",
223
+ long_name="Общество с ограниченной ответственностью «Реклама»",
224
+ ogrn="1177746123456",
225
+ legal_address="г. Москва, ул. Примерная, д. 1",
226
+ actual_address="г. Москва, ул. Примерная, д. 1",
227
+ legal_role=LegalRole.ADVERTISER,
228
+ legal_type=LegalType.UL,
229
+ kpp="771701001",
230
+ )
231
+
232
+ advertisers = client.advertisers.list()
233
+ ```
234
+
235
+ ### Договоры
236
+
237
+ Для создания договора удобно использовать `ContractBuilder` — он проверяет обязательные поля по типу договора и бросает понятное исключение валидации ещё до запроса.
238
+
239
+ ```python
240
+ from avito_ads import ContractBuilder
241
+ from avito_ads.enums import ContractCounterpartyType
242
+
243
+ builder = (
244
+ ContractBuilder.intermediary_contract()
245
+ .advertiser(987654321)
246
+ .counterparty_type(ContractCounterpartyType.DIRECT_WITH_ADVERTISER)
247
+ .subject("mediation")
248
+ .object("commercial")
249
+ .reporting_required(True)
250
+ .funds_allocation_to_principal(False)
251
+ .date("2025-01-15")
252
+ .number("ДА-2025/01")
253
+ .intermediary({
254
+ "shortName": "ООО Реклама",
255
+ "longName": "Общество с ограниченной ответственностью «Реклама»",
256
+ "inn": "7712345678",
257
+ "ogrn": "1177746123456",
258
+ "kpp": "771701001",
259
+ "legalAddress": "г. Москва, ул. Примерная, д. 1",
260
+ "actualAddress": "г. Москва, ул. Примерная, д. 1",
261
+ "legalType": "ul",
262
+ })
263
+ )
264
+
265
+ created = client.contracts.create(builder)
266
+ print("ID договора:", created.id)
267
+ ```
268
+
269
+ Доступны быстрые конструкторы `ContractBuilder.service()`, `ContractBuilder.intermediary_contract()` и `ContractBuilder.external("CID")`. Дополнительное соглашение создаётся вызовом `.parent_id(...)` (поле `intermediary` при этом передавать нельзя).
270
+
271
+ В `create()` можно передать и готовый словарь тела запроса — тогда клиентская валидация не выполняется.
272
+
273
+ ```python
274
+ contracts = client.contracts.list()
275
+ ```
276
+
277
+ ### Кампании, группы, креативы
278
+
279
+ ```python
280
+ for campaign in client.campaigns.list():
281
+ print(campaign.id, campaign.name, f"[{campaign.status}]")
282
+
283
+ groups = client.groups.list()
284
+ creatives = client.creatives.list()
285
+
286
+ # Изменение бюджета и ставки группы (только ручное управление ставкой; значение не менее 1)
287
+ client.groups.change_budget(group_id=555, budget=100000)
288
+ client.groups.change_price(group_id=555, price=25)
289
+ ```
290
+
291
+ ### Статистика
292
+
293
+ Период не может превышать 100 дней; даты — в формате `YYYY-MM-DD`. Эти ограничения проверяются на стороне клиента.
294
+
295
+ ```python
296
+ stats = client.statistics.campaign(campaign_id=555, date_from="2025-01-01", date_to="2025-01-31")
297
+ total = stats.campaign.total_data
298
+ print("Показы:", total.views, "клики:", total.clicks)
299
+
300
+ by_groups = client.statistics.groups(555, "2025-01-01", "2025-01-31", [101, 102])
301
+ by_creatives = client.statistics.creatives(555, "2025-01-01", "2025-01-31", [201, 202])
302
+ ```
303
+
304
+ ### Пользователи
305
+
306
+ ```python
307
+ from avito_ads.enums import UserRole
308
+
309
+ users = client.users.list()
310
+ client.users.add(user_id=42, role=UserRole.ADMIN)
311
+ client.users.set_role(user_id=42, role=UserRole.VIEWER)
312
+ client.users.delete(user_id=42)
313
+ ```
314
+
315
+ ## Постраничная выборка
316
+
317
+ Методы `list()` возвращают `PaginatedResult` — по нему можно итерироваться и брать `len()`.
318
+
319
+ ```python
320
+ result = client.campaigns.list(limit=50, page=1)
321
+
322
+ print("Всего:", result.total)
323
+ print("Остаток лимита API:", result.api_point_balance)
324
+
325
+ for campaign in result:
326
+ ...
327
+
328
+ if result.has_next_page():
329
+ next_page = client.campaigns.list(limit=50, page=2)
330
+ ```
331
+
332
+ Чтобы обойти все страницы автоматически, используйте `iterate()` — он возвращает генератор и подгружает страницы по мере необходимости:
333
+
334
+ ```python
335
+ for campaign in client.campaigns.iterate():
336
+ print(campaign.name)
337
+ ```
338
+
339
+ В асинхронном режиме `iterate()` возвращает async-генератор:
340
+
341
+ ```python
342
+ async for campaign in async_client.campaigns.iterate():
343
+ print(campaign.name)
344
+ ```
345
+
346
+ ## Фильтры
347
+
348
+ Фильтры можно задавать словарём или объектом-фильтром с проверкой имён полей.
349
+
350
+ ```python
351
+ from avito_ads import CampaignsFilter, DateRange
352
+ from avito_ads.enums import CampaignStatus
353
+
354
+ f = (
355
+ CampaignsFilter()
356
+ .statuses([CampaignStatus.ACTIVE.value])
357
+ .contract_ids([10, 20])
358
+ .created_at(DateRange("2025-01-01", "2025-01-31"))
359
+ )
360
+
361
+ campaigns = client.campaigns.list(f)
362
+ ```
363
+
364
+ Пустой фильтр корректно сериализуется в JSON-объект `{}`, как того требует API.
365
+
366
+ ## Обработка ошибок
367
+
368
+ Все исключения наследуются от `avito_ads.exceptions.AvitoAdsError`.
369
+
370
+ | HTTP | Исключение |
371
+ |---|---|
372
+ | 400 | `BadRequestError` |
373
+ | 401 | `AuthenticationError` |
374
+ | 403 | `AccessDeniedError` |
375
+ | 404 | `NotFoundError` |
376
+ | 429 | `RateLimitError` (атрибут `retry_after`) |
377
+ | 5xx | `ServerError` |
378
+ | сеть/таймаут | `NetworkError` |
379
+
380
+ ```python
381
+ from avito_ads.exceptions import ApiError, RateLimitError
382
+
383
+ try:
384
+ client.account.get_balance()
385
+ except RateLimitError as exc:
386
+ retry_after = exc.retry_after
387
+ except ApiError as exc:
388
+ print(exc.status_code, exc.error_code, str(exc))
389
+ ```
390
+
391
+ Ошибки валидации на стороне клиента (неверный период статистики, сумма перевода меньше 1, неизвестная роль и т. п.) бросают `avito_ads.exceptions.ValidationError`.
392
+
393
+ ## Лимиты и повторы
394
+
395
+ API ограничивает частоту запросов (порядка 500 в минуту). Клиент автоматически повторяет запросы при `429` и `5xx` с экспоненциальной задержкой и учитывает заголовок `Retry-After`. Остаток лимита доступен из заголовка `Api-Point-Balance` (например, `PaginatedResult.api_point_balance`).
396
+
397
+ ## Интеграция с Django
398
+
399
+ В `settings.py`:
400
+
401
+ ```python
402
+ AVITO_ADS = {
403
+ "client_id": "...",
404
+ "client_secret": "...",
405
+ "account_id": 123456789,
406
+ "environment": "production",
407
+ }
408
+ ```
409
+
410
+ Использование:
411
+
412
+ ```python
413
+ from avito_ads.integrations.django import get_client
414
+
415
+ balance = get_client().account.get_balance()
416
+ ```
417
+
418
+ Опционально добавьте `"avito_ads.integrations.django"` в `INSTALLED_APPS`, чтобы конфигурация проверялась при старте приложения.
419
+
420
+ ## Интеграция с Flask
421
+
422
+ ```python
423
+ from flask import Flask
424
+ from avito_ads.integrations.flask import AvitoAds
425
+
426
+ app = Flask(__name__)
427
+ app.config.update(
428
+ AVITO_ADS_CLIENT_ID="...",
429
+ AVITO_ADS_CLIENT_SECRET="...",
430
+ AVITO_ADS_ACCOUNT_ID=123456789,
431
+ AVITO_ADS_ENVIRONMENT="production",
432
+ )
433
+
434
+ avito = AvitoAds(app)
435
+
436
+ @app.route("/balance")
437
+ def balance():
438
+ return {"balance": avito.client.account.get_balance().balance}
439
+ ```
440
+
441
+ Поддерживается и фабричный паттерн через `AvitoAds().init_app(app)`.
442
+
443
+ ## Интеграция с FastAPI
444
+
445
+ FastAPI — асинхронный фреймворк, поэтому интеграция отдаёт асинхронный `AsyncClient`:
446
+
447
+ ```python
448
+ from contextlib import asynccontextmanager
449
+ from fastapi import Depends, FastAPI
450
+ from avito_ads import AsyncClient
451
+ from avito_ads.integrations.fastapi import AvitoAdsProvider
452
+
453
+ avito = AvitoAdsProvider({
454
+ "client_id": "...",
455
+ "client_secret": "...",
456
+ "account_id": 123456789,
457
+ "environment": "production",
458
+ })
459
+
460
+ @asynccontextmanager
461
+ async def lifespan(app: FastAPI):
462
+ yield
463
+ await avito.aclose()
464
+
465
+ app = FastAPI(lifespan=lifespan)
466
+
467
+ @app.get("/balance")
468
+ async def balance(client: AsyncClient = Depends(avito)):
469
+ result = await client.account.get_balance()
470
+ return {"balance": result.balance}
471
+ ```
472
+
473
+ ## Песочница
474
+
475
+ Создание тестового аккаунта доступно только в песочнице:
476
+
477
+ ```python
478
+ config = Configuration.create(client_id, client_secret, account_id).sandbox()
479
+
480
+ with Client(config) as client:
481
+ created = client.account.create_sandbox_account(
482
+ inn="7712345678",
483
+ short_name="Тестовый аккаунт",
484
+ long_name="Полное наименование",
485
+ ogrn="1177746123456",
486
+ legal_address="г. Москва, ул. Примерная, д. 1",
487
+ actual_address="г. Москва, ул. Примерная, д. 1",
488
+ contact={"name": "Иван Иванов", "phone": "+79001234567"},
489
+ )
490
+ ```
491
+
492
+ ## Разработка и тестирование
493
+
494
+ ```bash
495
+ pip install -e ".[dev]"
496
+ pytest # модульные тесты
497
+ ruff check . # линтер
498
+ mypy avito_ads # проверка типов (strict)
499
+ ```
500
+
501
+ Интеграционные тесты против реальной песочницы по умолчанию пропускаются. Чтобы запустить их:
502
+
503
+ ```bash
504
+ AVITO_ADS_RUN_INTEGRATION=1 \
505
+ AVITO_ADS_CLIENT_ID=... \
506
+ AVITO_ADS_CLIENT_SECRET=... \
507
+ AVITO_ADS_ACCOUNT_ID=... \
508
+ pytest -m integration
509
+ ```
510
+
511
+ ## Лицензия
512
+
513
+ [MIT](LICENSE).