Skip to content

Commit 2e17c77

Browse files
committed
Добавил более метод общей информации
1 parent 462dd99 commit 2e17c77

21 files changed

Lines changed: 761 additions & 33 deletions

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ with AvitoClient.from_env() as avito:
131131
stats = avito.ad_stats(item_id=42, user_id=123).get_item_stats()
132132
```
133133

134-
`user_id` можно передать явно, задать через `AVITO_USER_ID` или оставить пустым для read-only вызовов, где SDK может определить пользователя через `account().get_self()`. Если идентификатор не удалось определить, SDK поднимает `ValidationError` с подсказкой, как вызвать метод правильно.
134+
`user_id` можно передать явно, задать через `AVITO_USER_ID` или оставить пустым для read-only вызовов, где SDK может определить пользователя через `account().get_self()`. Если идентификатор не удалось определить, SDK поднимает `ValidationError` с подсказкой, как вызвать метод правильно. Для OAuth secret поддерживаются `AVITO_CLIENT_SECRET` и alias `AVITO_SECRET`.
135135

136136
Статистические методы принимают `date`, `datetime` и ISO-строки, а в Avito API отправляют дату в формате `YYYY-MM-DD`. Модель `Listing` нормализует основные поля объявления: `title`, `price`, `status`, `description`, `url`, `category`, `city`, `published_at`, `updated_at`, `is_moderated`, `is_visible`.
137137

@@ -270,7 +270,7 @@ with AvitoClient.from_env() as avito:
270270
tariff = avito.tariff().get_tariff_info()
271271
```
272272

273-
`review().list()` по умолчанию запрашивает первую страницу отзывов (`page=1`). Для явной пагинации передайте `ReviewsQuery(page=...)`.
273+
`review().list()` по умолчанию запрашивает первую страницу отзывов (`page=1`, `limit=50`). Для явной пагинации передайте `ReviewsQuery(page=..., limit=...)`.
274274

275275
## Пагинация
276276

@@ -291,13 +291,15 @@ with AvitoClient.from_env() as avito:
291291
from avito import AvitoClient
292292

293293
with AvitoClient.from_env() as avito:
294-
items = avito.ad(user_id=123).list(status="active", limit=50)
294+
items = avito.ad(user_id=123).list(status="active", limit=50, page_size=25)
295295

296296
first = items[0]
297297
preview = items[:10]
298298
all_items = items.materialize()
299299
```
300300

301+
В `ad().list()` параметр `limit` ограничивает общий максимум элементов результата, а `page_size` задаёт размер страницы upstream API.
302+
301303
## Ошибки
302304

303305
Все исключения SDK наследуются от `AvitoError` и импортируются из `avito.core.exceptions`. HTTP-коды отображаются в конкретные типы:

avito/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,39 @@
1717
ValidationError,
1818
)
1919
from avito.core.pagination import PaginatedList
20+
from avito.summary import (
21+
AccountHealthSummary,
22+
CapabilityDiscoveryResult,
23+
CapabilityInfo,
24+
ChatSummary,
25+
ListingHealthItem,
26+
ListingHealthSummary,
27+
OrderSummary,
28+
PromotionSummary,
29+
ReviewSummary,
30+
)
2031

2132
__all__ = (
33+
"AccountHealthSummary",
2234
"AuthSettings",
2335
"AuthenticationError",
2436
"AuthorizationError",
2537
"AvitoClient",
2638
"AvitoError",
2739
"AvitoSettings",
40+
"CapabilityDiscoveryResult",
41+
"CapabilityInfo",
42+
"ChatSummary",
2843
"ConfigurationError",
2944
"ConflictError",
45+
"ListingHealthItem",
46+
"ListingHealthSummary",
47+
"OrderSummary",
3048
"PaginatedList",
49+
"PromotionSummary",
3150
"RateLimitError",
3251
"ResponseMappingError",
52+
"ReviewSummary",
3353
"TransportError",
3454
"UnsupportedOperationError",
3555
"UpstreamApiError",

avito/ads/client.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@
6767
from avito.promotion.models import PromotionActionResult
6868

6969

70+
def _bounded_total(total: int | None, max_items: int | None) -> int | None:
71+
if max_items is None:
72+
return total
73+
if total is None:
74+
return max_items
75+
return min(total, max_items)
76+
77+
7078
@dataclass(slots=True, frozen=True)
7179
class AdsClient:
7280
"""Выполняет HTTP-операции по разделу объявлений."""
@@ -90,11 +98,13 @@ def list_items(
9098
user_id: int | None = None,
9199
status: ListingStatus | str | None = None,
92100
limit: int | None = None,
101+
page_size: int | None = None,
93102
offset: int | None = None,
94103
) -> PaginatedList[Listing]:
95104
"""Получает список объявлений пользователя."""
96105

97-
start_offset = offset if offset is not None else 0 if limit is not None else None
106+
resolved_page_size = page_size or limit
107+
start_offset = offset if offset is not None else 0 if resolved_page_size is not None else None
98108
result = request_public_model(
99109
self.transport,
100110
"GET",
@@ -104,16 +114,19 @@ def list_items(
104114
params={
105115
"user_id": user_id,
106116
"status": status,
107-
"limit": limit,
117+
"limit": resolved_page_size,
108118
"offset": start_offset,
109119
},
110120
)
111-
page_size = limit if limit and limit > 0 else len(result.items)
121+
page_size = resolved_page_size if resolved_page_size and resolved_page_size > 0 else len(result.items)
122+
max_items = limit if limit is not None and limit >= 0 else None
123+
first_items = result.items[:max_items] if max_items is not None else result.items
124+
total = _bounded_total(result.total, max_items)
112125
resolved_offset = start_offset or 0
113126
start_page = resolved_offset // page_size + 1 if page_size > 0 else 1
114127
first_page = JsonPage(
115-
items=list(result.items),
116-
total=result.total,
128+
items=list(first_items),
129+
total=total,
117130
page=start_page,
118131
per_page=page_size if page_size > 0 else None,
119132
)
@@ -123,6 +136,8 @@ def list_items(
123136
user_id=user_id,
124137
status=status,
125138
page_size=page_size,
139+
max_items=max_items,
140+
base_offset=resolved_offset,
126141
)
127142
).as_list(start_page=start_page, first_page=first_page)
128143

@@ -133,11 +148,17 @@ def _fetch_ads_page(
133148
user_id: int | None,
134149
status: ListingStatus | str | None,
135150
page_size: int,
151+
max_items: int | None,
152+
base_offset: int,
136153
) -> JsonPage[Listing]:
137154
if page is None:
138155
raise ValidationError("Для операции требуется `page`.")
139156

140157
offset = (page - 1) * page_size
158+
already_requested = max(offset - base_offset, 0)
159+
remaining = max_items - already_requested if max_items is not None else None
160+
if remaining is not None and remaining <= 0:
161+
return JsonPage(items=[], total=max_items, page=page, per_page=page_size)
141162
result = request_public_model(
142163
self.transport,
143164
"GET",
@@ -147,13 +168,14 @@ def _fetch_ads_page(
147168
params={
148169
"user_id": user_id,
149170
"status": status,
150-
"limit": page_size,
171+
"limit": min(page_size, remaining) if remaining is not None else page_size,
151172
"offset": offset,
152173
},
153174
)
175+
items = result.items[:remaining] if remaining is not None else result.items
154176
return JsonPage(
155-
items=list(result.items),
156-
total=result.total,
177+
items=list(items),
178+
total=_bounded_total(result.total, max_items),
157179
page=page,
158180
per_page=page_size,
159181
)

avito/ads/domain.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ def list(
106106
*,
107107
status: ListingStatus | str | None = None,
108108
limit: int | None = None,
109+
page_size: int | None = None,
109110
offset: int | None = None,
110111
) -> PaginatedList[Listing]:
111112
"""Получает список объявлений.
@@ -117,7 +118,11 @@ def list(
117118

118119
user_id = self._resolve_user_id(self.user_id)
119120
return AdsClient(self.transport).list_items(
120-
user_id=user_id, status=status, limit=limit, offset=offset
121+
user_id=user_id,
122+
status=status,
123+
limit=limit,
124+
page_size=page_size,
125+
offset=offset,
121126
)
122127

123128
def update_price(

avito/ads/enums.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ class ListingStatus(str, Enum):
1010

1111
UNKNOWN = "__unknown__"
1212
ACTIVE = "active"
13+
REMOVED = "removed"
14+
OLD = "old"
15+
BLOCKED = "blocked"
16+
REJECTED = "rejected"
17+
NOT_FOUND = "not_found"
18+
ANOTHER_USER = "another_user"
1319

1420

1521
class AdsActionStatus(str, Enum):

avito/ads/mappers.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ def _int(payload: Payload, *keys: str) -> int | None:
107107
continue
108108
if isinstance(value, int):
109109
return value
110+
if isinstance(value, str) and value.isdigit():
111+
return int(value)
110112
return None
111113

112114

@@ -147,24 +149,25 @@ def map_ad_item(payload: object) -> Listing:
147149
"""Преобразует объявление в dataclass."""
148150

149151
data = _expect_mapping(payload)
152+
source = _mapping(data, "item", "resource", "listing", "ad") or data
150153
return Listing(
151-
item_id=_int(data, "id", "item_id", "itemId"),
152-
user_id=_int(data, "user_id", "userId"),
153-
title=_str(data, "title"),
154-
description=_str(data, "description", "descriptionHtml"),
154+
item_id=_int(source, "id", "item_id", "itemId", "itemID"),
155+
user_id=_int(source, "user_id", "userId"),
156+
title=_str(source, "title", "name"),
157+
description=_str(source, "description", "descriptionHtml"),
155158
status=map_enum_or_unknown(
156-
_nested_str(data, "status"),
159+
_nested_str(source, "status", "state"),
157160
ListingStatus,
158161
enum_name="ads.listing_status",
159162
),
160-
price=_float(data, "price"),
161-
url=_str(data, "url", "link"),
162-
category=_nested_str(data, "category", "categoryName"),
163-
city=_nested_str(data, "city", "location"),
164-
published_at=_datetime(data, "published_at", "publishedAt", "created_at", "createdAt"),
165-
updated_at=_datetime(data, "updated_at", "updatedAt"),
166-
is_moderated=_bool(data, "is_moderated", "isModerated", "moderated"),
167-
is_visible=_visibility(data),
163+
price=_float(source, "price"),
164+
url=_str(source, "url", "link", "uri"),
165+
category=_nested_str(source, "category", "categoryName"),
166+
city=_nested_str(source, "city", "location"),
167+
published_at=_datetime(source, "published_at", "publishedAt", "created_at", "createdAt"),
168+
updated_at=_datetime(source, "updated_at", "updatedAt"),
169+
is_moderated=_bool(source, "is_moderated", "isModerated", "moderated"),
170+
is_visible=_visibility(source),
168171
)
169172

170173

avito/auth/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class AuthSettings:
1616

1717
ENV_ALIASES: ClassVar[dict[str, tuple[str, ...]]] = {
1818
"client_id": ("AVITO_CLIENT_ID",),
19-
"client_secret": ("AVITO_CLIENT_SECRET",),
19+
"client_secret": ("AVITO_CLIENT_SECRET", "AVITO_SECRET"),
2020
"scope": ("AVITO_SCOPE",),
2121
"refresh_token": ("AVITO_REFRESH_TOKEN",),
2222
"token_url": ("AVITO_TOKEN_URL",),

0 commit comments

Comments
 (0)