channel3-sdk 0.2.2__py3-none-any.whl → 2.0.0__py3-none-any.whl
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.
Potentially problematic release.
This version of channel3-sdk might be problematic. Click here for more details.
- channel3_sdk/__init__.py +94 -38
- channel3_sdk/_base_client.py +1995 -0
- channel3_sdk/_client.py +549 -0
- channel3_sdk/_compat.py +219 -0
- channel3_sdk/_constants.py +14 -0
- channel3_sdk/_exceptions.py +108 -0
- channel3_sdk/_files.py +123 -0
- channel3_sdk/_models.py +829 -0
- channel3_sdk/_qs.py +150 -0
- channel3_sdk/_resource.py +43 -0
- channel3_sdk/_response.py +832 -0
- channel3_sdk/_streaming.py +333 -0
- channel3_sdk/_types.py +253 -0
- channel3_sdk/_utils/__init__.py +64 -0
- channel3_sdk/_utils/_compat.py +45 -0
- channel3_sdk/_utils/_datetime_parse.py +136 -0
- channel3_sdk/_utils/_logs.py +25 -0
- channel3_sdk/_utils/_proxy.py +65 -0
- channel3_sdk/_utils/_reflection.py +42 -0
- channel3_sdk/_utils/_resources_proxy.py +24 -0
- channel3_sdk/_utils/_streams.py +12 -0
- channel3_sdk/_utils/_sync.py +86 -0
- channel3_sdk/_utils/_transform.py +457 -0
- channel3_sdk/_utils/_typing.py +156 -0
- channel3_sdk/_utils/_utils.py +421 -0
- channel3_sdk/_version.py +4 -0
- channel3_sdk/lib/.keep +4 -0
- channel3_sdk/py.typed +0 -0
- channel3_sdk/resources/__init__.py +61 -0
- channel3_sdk/resources/brands.py +268 -0
- channel3_sdk/resources/enrich.py +167 -0
- channel3_sdk/resources/products.py +163 -0
- channel3_sdk/resources/search.py +227 -0
- channel3_sdk/types/__init__.py +15 -0
- channel3_sdk/types/availability_status.py +9 -0
- channel3_sdk/types/brand.py +17 -0
- channel3_sdk/types/brand_list_params.py +16 -0
- channel3_sdk/types/brand_list_response.py +25 -0
- channel3_sdk/types/enrich_enrich_url_params.py +12 -0
- channel3_sdk/types/enrich_enrich_url_response.py +20 -0
- channel3_sdk/types/price.py +18 -0
- channel3_sdk/types/product_retrieve_response.py +39 -0
- channel3_sdk/types/search_perform_params.py +61 -0
- channel3_sdk/types/search_perform_response.py +36 -0
- channel3_sdk/types/variant.py +13 -0
- channel3_sdk-2.0.0.dist-info/METADATA +414 -0
- channel3_sdk-2.0.0.dist-info/RECORD +49 -0
- {channel3_sdk-0.2.2.dist-info → channel3_sdk-2.0.0.dist-info}/WHEEL +1 -1
- channel3_sdk-2.0.0.dist-info/licenses/LICENSE +201 -0
- channel3_sdk/client.py +0 -574
- channel3_sdk/exceptions.py +0 -48
- channel3_sdk/models.py +0 -135
- channel3_sdk-0.2.2.dist-info/METADATA +0 -284
- channel3_sdk-0.2.2.dist-info/RECORD +0 -7
channel3_sdk/client.py
DELETED
|
@@ -1,574 +0,0 @@
|
|
|
1
|
-
# channel3_sdk/client.py
|
|
2
|
-
import os
|
|
3
|
-
import requests
|
|
4
|
-
from typing import List, Optional, Dict, Any, Union
|
|
5
|
-
import aiohttp
|
|
6
|
-
import asyncio
|
|
7
|
-
from pydantic import ValidationError
|
|
8
|
-
|
|
9
|
-
from .models import Product, ProductDetail, SearchFilters, SearchRequest, Brand
|
|
10
|
-
from .exceptions import (
|
|
11
|
-
Channel3Error,
|
|
12
|
-
Channel3AuthenticationError,
|
|
13
|
-
Channel3ValidationError,
|
|
14
|
-
Channel3NotFoundError,
|
|
15
|
-
Channel3ServerError,
|
|
16
|
-
Channel3ConnectionError,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class BaseChannel3Client:
|
|
21
|
-
"""Base client with common functionality."""
|
|
22
|
-
|
|
23
|
-
def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
|
|
24
|
-
"""
|
|
25
|
-
Initialize the base Channel3 client.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
api_key: Your Channel3 API key. If not provided, will look for CHANNEL3_API_KEY in environment.
|
|
29
|
-
base_url: Base URL for the API. Defaults to https://api.trychannel3.com/v0
|
|
30
|
-
|
|
31
|
-
Raises:
|
|
32
|
-
ValueError: If no API key is provided and none is found in environment variables.
|
|
33
|
-
"""
|
|
34
|
-
self.api_key = api_key or os.getenv("CHANNEL3_API_KEY")
|
|
35
|
-
if not self.api_key:
|
|
36
|
-
raise ValueError(
|
|
37
|
-
"No API key provided. Set CHANNEL3_API_KEY environment variable or pass api_key parameter."
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
self.base_url = base_url or "https://api.trychannel3.com/v0"
|
|
41
|
-
self.headers = {"x-api-key": self.api_key, "Content-Type": "application/json"}
|
|
42
|
-
|
|
43
|
-
def _handle_error_response(
|
|
44
|
-
self, status_code: int, response_data: Dict[str, Any], url: str
|
|
45
|
-
) -> None:
|
|
46
|
-
"""Handle error responses and raise appropriate exceptions."""
|
|
47
|
-
error_message = response_data.get(
|
|
48
|
-
"detail", f"Request failed with status {status_code}"
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
if status_code == 401:
|
|
52
|
-
raise Channel3AuthenticationError(
|
|
53
|
-
"Invalid or missing API key",
|
|
54
|
-
status_code=status_code,
|
|
55
|
-
response_data=response_data,
|
|
56
|
-
)
|
|
57
|
-
elif status_code == 404:
|
|
58
|
-
raise Channel3NotFoundError(
|
|
59
|
-
error_message, status_code=status_code, response_data=response_data
|
|
60
|
-
)
|
|
61
|
-
elif status_code == 422:
|
|
62
|
-
raise Channel3ValidationError(
|
|
63
|
-
f"Validation error: {error_message}",
|
|
64
|
-
status_code=status_code,
|
|
65
|
-
response_data=response_data,
|
|
66
|
-
)
|
|
67
|
-
elif status_code == 500:
|
|
68
|
-
raise Channel3ServerError(
|
|
69
|
-
"Internal server error",
|
|
70
|
-
status_code=status_code,
|
|
71
|
-
response_data=response_data,
|
|
72
|
-
)
|
|
73
|
-
else:
|
|
74
|
-
raise Channel3Error(
|
|
75
|
-
f"Request to {url} failed: {error_message}",
|
|
76
|
-
status_code=status_code,
|
|
77
|
-
response_data=response_data,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
class Channel3Client(BaseChannel3Client):
|
|
82
|
-
"""Synchronous Channel3 API client."""
|
|
83
|
-
|
|
84
|
-
def search(
|
|
85
|
-
self,
|
|
86
|
-
query: Optional[str] = None,
|
|
87
|
-
image_url: Optional[str] = None,
|
|
88
|
-
base64_image: Optional[str] = None,
|
|
89
|
-
filters: Optional[SearchFilters] = None,
|
|
90
|
-
limit: int = 20,
|
|
91
|
-
) -> List[Product]:
|
|
92
|
-
"""
|
|
93
|
-
Search for products using text query, image, or both with optional filters.
|
|
94
|
-
|
|
95
|
-
Args:
|
|
96
|
-
query: Text search query
|
|
97
|
-
image_url: URL to an image to use for visual search
|
|
98
|
-
base64_image: Base64-encoded image to use for visual search
|
|
99
|
-
filters: Search filters (SearchFilters object)
|
|
100
|
-
limit: Maximum number of products to return (default: 20)
|
|
101
|
-
|
|
102
|
-
Returns:
|
|
103
|
-
List of Product objects
|
|
104
|
-
|
|
105
|
-
Raises:
|
|
106
|
-
Channel3AuthenticationError: If API key is invalid
|
|
107
|
-
Channel3ValidationError: If request parameters are invalid
|
|
108
|
-
Channel3ServerError: If server encounters an error
|
|
109
|
-
Channel3ConnectionError: If there are connection issues
|
|
110
|
-
|
|
111
|
-
Examples:
|
|
112
|
-
```python
|
|
113
|
-
# Text search
|
|
114
|
-
products = client.search(query="blue denim jacket")
|
|
115
|
-
|
|
116
|
-
# Image search
|
|
117
|
-
products = client.search(image_url="https://example.com/image.jpg")
|
|
118
|
-
|
|
119
|
-
# Multimodal search with filters
|
|
120
|
-
from channel3_sdk.models import SearchFilters
|
|
121
|
-
filters = SearchFilters(min_price=50.0, max_price=150.0)
|
|
122
|
-
products = client.search(query="denim jacket", filters=filters)
|
|
123
|
-
```
|
|
124
|
-
"""
|
|
125
|
-
# Build request payload
|
|
126
|
-
search_request = SearchRequest(
|
|
127
|
-
query=query,
|
|
128
|
-
image_url=image_url,
|
|
129
|
-
base64_image=base64_image,
|
|
130
|
-
filters=filters,
|
|
131
|
-
limit=limit,
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
url = f"{self.base_url}/search"
|
|
135
|
-
|
|
136
|
-
try:
|
|
137
|
-
response = requests.post(
|
|
138
|
-
url,
|
|
139
|
-
json=search_request.model_dump(exclude_none=True),
|
|
140
|
-
headers=self.headers,
|
|
141
|
-
timeout=30,
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
response_data = response.json()
|
|
145
|
-
|
|
146
|
-
if response.status_code != 200:
|
|
147
|
-
self._handle_error_response(response.status_code, response_data, url)
|
|
148
|
-
|
|
149
|
-
# Parse and validate response
|
|
150
|
-
return [Product(**item) for item in response_data]
|
|
151
|
-
|
|
152
|
-
except requests.exceptions.ConnectionError as e:
|
|
153
|
-
raise Channel3ConnectionError(
|
|
154
|
-
f"Failed to connect to Channel3 API: {str(e)}"
|
|
155
|
-
)
|
|
156
|
-
except requests.exceptions.Timeout as e:
|
|
157
|
-
raise Channel3ConnectionError(f"Request timed out: {str(e)}")
|
|
158
|
-
except requests.exceptions.RequestException as e:
|
|
159
|
-
raise Channel3Error(f"Request failed: {str(e)}")
|
|
160
|
-
except ValidationError as e:
|
|
161
|
-
raise Channel3Error(f"Invalid response format: {str(e)}")
|
|
162
|
-
|
|
163
|
-
def get_product(self, product_id: str) -> ProductDetail:
|
|
164
|
-
"""
|
|
165
|
-
Get detailed information about a specific product by its ID.
|
|
166
|
-
|
|
167
|
-
Args:
|
|
168
|
-
product_id: The unique identifier of the product
|
|
169
|
-
|
|
170
|
-
Returns:
|
|
171
|
-
ProductDetail object with detailed product information
|
|
172
|
-
|
|
173
|
-
Raises:
|
|
174
|
-
Channel3AuthenticationError: If API key is invalid
|
|
175
|
-
Channel3NotFoundError: If product is not found
|
|
176
|
-
Channel3ValidationError: If product_id is invalid
|
|
177
|
-
Channel3ServerError: If server encounters an error
|
|
178
|
-
Channel3ConnectionError: If there are connection issues
|
|
179
|
-
|
|
180
|
-
Example:
|
|
181
|
-
```python
|
|
182
|
-
product_detail = client.get_product("prod_123456")
|
|
183
|
-
print(f"Product: {product_detail.title}")
|
|
184
|
-
print(f"Brand: {product_detail.brand_name}")
|
|
185
|
-
```
|
|
186
|
-
"""
|
|
187
|
-
if not product_id or not product_id.strip():
|
|
188
|
-
raise ValueError("product_id cannot be empty")
|
|
189
|
-
|
|
190
|
-
url = f"{self.base_url}/products/{product_id}"
|
|
191
|
-
|
|
192
|
-
try:
|
|
193
|
-
response = requests.get(url, headers=self.headers, timeout=30)
|
|
194
|
-
response_data = response.json()
|
|
195
|
-
|
|
196
|
-
if response.status_code != 200:
|
|
197
|
-
self._handle_error_response(response.status_code, response_data, url)
|
|
198
|
-
|
|
199
|
-
# Parse and validate response
|
|
200
|
-
return ProductDetail(**response_data)
|
|
201
|
-
|
|
202
|
-
except requests.exceptions.ConnectionError as e:
|
|
203
|
-
raise Channel3ConnectionError(
|
|
204
|
-
f"Failed to connect to Channel3 API: {str(e)}"
|
|
205
|
-
)
|
|
206
|
-
except requests.exceptions.Timeout as e:
|
|
207
|
-
raise Channel3ConnectionError(f"Request timed out: {str(e)}")
|
|
208
|
-
except requests.exceptions.RequestException as e:
|
|
209
|
-
raise Channel3Error(f"Request failed: {str(e)}")
|
|
210
|
-
except ValidationError as e:
|
|
211
|
-
raise Channel3Error(f"Invalid response format: {str(e)}")
|
|
212
|
-
|
|
213
|
-
def get_brands(
|
|
214
|
-
self,
|
|
215
|
-
query: Optional[str] = None,
|
|
216
|
-
page: int = 1,
|
|
217
|
-
size: int = 100,
|
|
218
|
-
) -> List[Brand]:
|
|
219
|
-
"""
|
|
220
|
-
Get all brands that the vendor currently sells.
|
|
221
|
-
|
|
222
|
-
Args:
|
|
223
|
-
query: Optional text query to filter brands
|
|
224
|
-
page: Page number for pagination (default: 1)
|
|
225
|
-
size: Number of brands per page (default: 100)
|
|
226
|
-
|
|
227
|
-
Returns:
|
|
228
|
-
List of Brand objects
|
|
229
|
-
|
|
230
|
-
Raises:
|
|
231
|
-
Channel3AuthenticationError: If API key is invalid
|
|
232
|
-
Channel3ValidationError: If request parameters are invalid
|
|
233
|
-
Channel3ServerError: If server encounters an error
|
|
234
|
-
Channel3ConnectionError: If there are connection issues
|
|
235
|
-
|
|
236
|
-
Example:
|
|
237
|
-
```python
|
|
238
|
-
brands = client.get_brands()
|
|
239
|
-
for brand in brands:
|
|
240
|
-
print(f"Brand: {brand.name}")
|
|
241
|
-
```
|
|
242
|
-
"""
|
|
243
|
-
url = f"{self.base_url}/brands"
|
|
244
|
-
params = {}
|
|
245
|
-
|
|
246
|
-
if query is not None:
|
|
247
|
-
params["query"] = query
|
|
248
|
-
if page != 1:
|
|
249
|
-
params["page"] = page
|
|
250
|
-
if size != 100:
|
|
251
|
-
params["size"] = size
|
|
252
|
-
|
|
253
|
-
try:
|
|
254
|
-
response = requests.get(
|
|
255
|
-
url, headers=self.headers, params=params, timeout=30
|
|
256
|
-
)
|
|
257
|
-
response_data = response.json()
|
|
258
|
-
|
|
259
|
-
if response.status_code != 200:
|
|
260
|
-
self._handle_error_response(response.status_code, response_data, url)
|
|
261
|
-
|
|
262
|
-
# Parse and validate response
|
|
263
|
-
return [Brand(**item) for item in response_data]
|
|
264
|
-
|
|
265
|
-
except requests.exceptions.ConnectionError as e:
|
|
266
|
-
raise Channel3ConnectionError(
|
|
267
|
-
f"Failed to connect to Channel3 API: {str(e)}"
|
|
268
|
-
)
|
|
269
|
-
except requests.exceptions.Timeout as e:
|
|
270
|
-
raise Channel3ConnectionError(f"Request timed out: {str(e)}")
|
|
271
|
-
except requests.exceptions.RequestException as e:
|
|
272
|
-
raise Channel3Error(f"Request failed: {str(e)}")
|
|
273
|
-
except ValidationError as e:
|
|
274
|
-
raise Channel3Error(f"Invalid response format: {str(e)}")
|
|
275
|
-
|
|
276
|
-
def get_brand(self, brand_id: str) -> Brand:
|
|
277
|
-
"""
|
|
278
|
-
Get detailed information for a specific brand by its ID.
|
|
279
|
-
|
|
280
|
-
Args:
|
|
281
|
-
brand_id: The unique identifier of the brand
|
|
282
|
-
|
|
283
|
-
Returns:
|
|
284
|
-
Brand object with detailed brand information
|
|
285
|
-
|
|
286
|
-
Raises:
|
|
287
|
-
Channel3AuthenticationError: If API key is invalid
|
|
288
|
-
Channel3NotFoundError: If brand is not found
|
|
289
|
-
Channel3ValidationError: If brand_id is invalid
|
|
290
|
-
Channel3ServerError: If server encounters an error
|
|
291
|
-
Channel3ConnectionError: If there are connection issues
|
|
292
|
-
|
|
293
|
-
Example:
|
|
294
|
-
```python
|
|
295
|
-
brand = client.get_brand("brand_123456")
|
|
296
|
-
print(f"Brand: {brand.name}")
|
|
297
|
-
print(f"Description: {brand.description}")
|
|
298
|
-
```
|
|
299
|
-
"""
|
|
300
|
-
if not brand_id or not brand_id.strip():
|
|
301
|
-
raise ValueError("brand_id cannot be empty")
|
|
302
|
-
|
|
303
|
-
url = f"{self.base_url}/brands/{brand_id}"
|
|
304
|
-
|
|
305
|
-
try:
|
|
306
|
-
response = requests.get(url, headers=self.headers, timeout=30)
|
|
307
|
-
response_data = response.json()
|
|
308
|
-
|
|
309
|
-
if response.status_code != 200:
|
|
310
|
-
self._handle_error_response(response.status_code, response_data, url)
|
|
311
|
-
|
|
312
|
-
# Parse and validate response
|
|
313
|
-
return Brand(**response_data)
|
|
314
|
-
|
|
315
|
-
except requests.exceptions.ConnectionError as e:
|
|
316
|
-
raise Channel3ConnectionError(
|
|
317
|
-
f"Failed to connect to Channel3 API: {str(e)}"
|
|
318
|
-
)
|
|
319
|
-
except requests.exceptions.Timeout as e:
|
|
320
|
-
raise Channel3ConnectionError(f"Request timed out: {str(e)}")
|
|
321
|
-
except requests.exceptions.RequestException as e:
|
|
322
|
-
raise Channel3Error(f"Request failed: {str(e)}")
|
|
323
|
-
except ValidationError as e:
|
|
324
|
-
raise Channel3Error(f"Invalid response format: {str(e)}")
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
class AsyncChannel3Client(BaseChannel3Client):
|
|
328
|
-
"""Asynchronous Channel3 API client."""
|
|
329
|
-
|
|
330
|
-
async def search(
|
|
331
|
-
self,
|
|
332
|
-
query: Optional[str] = None,
|
|
333
|
-
image_url: Optional[str] = None,
|
|
334
|
-
base64_image: Optional[str] = None,
|
|
335
|
-
filters: Optional[Union[SearchFilters, Dict[str, Any]]] = None,
|
|
336
|
-
limit: int = 20,
|
|
337
|
-
) -> List[Product]:
|
|
338
|
-
"""
|
|
339
|
-
Search for products using text query, image, or both with optional filters.
|
|
340
|
-
|
|
341
|
-
Args:
|
|
342
|
-
query: Text search query
|
|
343
|
-
image_url: URL to an image to use for visual search
|
|
344
|
-
base64_image: Base64-encoded image to use for visual search
|
|
345
|
-
filters: Search filters (SearchFilters object or dict)
|
|
346
|
-
limit: Maximum number of products to return (default: 20)
|
|
347
|
-
|
|
348
|
-
Returns:
|
|
349
|
-
List of Product objects
|
|
350
|
-
|
|
351
|
-
Raises:
|
|
352
|
-
Channel3AuthenticationError: If API key is invalid
|
|
353
|
-
Channel3ValidationError: If request parameters are invalid
|
|
354
|
-
Channel3ServerError: If server encounters an error
|
|
355
|
-
Channel3ConnectionError: If there are connection issues
|
|
356
|
-
|
|
357
|
-
Examples:
|
|
358
|
-
```python
|
|
359
|
-
# Text search
|
|
360
|
-
products = await async_client.search(query="blue denim jacket")
|
|
361
|
-
|
|
362
|
-
# Image search
|
|
363
|
-
products = await async_client.search(image_url="https://example.com/image.jpg")
|
|
364
|
-
```
|
|
365
|
-
"""
|
|
366
|
-
# Build request payload
|
|
367
|
-
search_request = SearchRequest(
|
|
368
|
-
query=query,
|
|
369
|
-
image_url=image_url,
|
|
370
|
-
base64_image=base64_image,
|
|
371
|
-
filters=filters or SearchFilters(),
|
|
372
|
-
limit=limit,
|
|
373
|
-
)
|
|
374
|
-
|
|
375
|
-
url = f"{self.base_url}/search"
|
|
376
|
-
|
|
377
|
-
try:
|
|
378
|
-
async with aiohttp.ClientSession() as session:
|
|
379
|
-
async with session.post(
|
|
380
|
-
url,
|
|
381
|
-
json=search_request.model_dump(exclude_none=True),
|
|
382
|
-
headers=self.headers,
|
|
383
|
-
timeout=aiohttp.ClientTimeout(total=30),
|
|
384
|
-
) as response:
|
|
385
|
-
response_data = await response.json()
|
|
386
|
-
|
|
387
|
-
if response.status != 200:
|
|
388
|
-
self._handle_error_response(response.status, response_data, url)
|
|
389
|
-
|
|
390
|
-
# Parse and validate response
|
|
391
|
-
return [Product(**item) for item in response_data]
|
|
392
|
-
|
|
393
|
-
except aiohttp.ClientConnectionError as e:
|
|
394
|
-
raise Channel3ConnectionError(
|
|
395
|
-
f"Failed to connect to Channel3 API: {str(e)}"
|
|
396
|
-
)
|
|
397
|
-
except asyncio.TimeoutError as e:
|
|
398
|
-
raise Channel3ConnectionError(f"Request timed out: {str(e)}")
|
|
399
|
-
except aiohttp.ClientError as e:
|
|
400
|
-
raise Channel3Error(f"Request failed: {str(e)}")
|
|
401
|
-
except ValidationError as e:
|
|
402
|
-
raise Channel3Error(f"Invalid response format: {str(e)}")
|
|
403
|
-
|
|
404
|
-
async def get_product(self, product_id: str) -> ProductDetail:
|
|
405
|
-
"""
|
|
406
|
-
Get detailed information about a specific product by its ID.
|
|
407
|
-
|
|
408
|
-
Args:
|
|
409
|
-
product_id: The unique identifier of the product
|
|
410
|
-
|
|
411
|
-
Returns:
|
|
412
|
-
ProductDetail object with detailed product information
|
|
413
|
-
|
|
414
|
-
Raises:
|
|
415
|
-
Channel3AuthenticationError: If API key is invalid
|
|
416
|
-
Channel3NotFoundError: If product is not found
|
|
417
|
-
Channel3ValidationError: If product_id is invalid
|
|
418
|
-
Channel3ServerError: If server encounters an error
|
|
419
|
-
Channel3ConnectionError: If there are connection issues
|
|
420
|
-
|
|
421
|
-
Example:
|
|
422
|
-
```python
|
|
423
|
-
product_detail = await async_client.get_product("prod_123456")
|
|
424
|
-
print(f"Product: {product_detail.title}")
|
|
425
|
-
```
|
|
426
|
-
"""
|
|
427
|
-
if not product_id or not product_id.strip():
|
|
428
|
-
raise ValueError("product_id cannot be empty")
|
|
429
|
-
|
|
430
|
-
url = f"{self.base_url}/products/{product_id}"
|
|
431
|
-
|
|
432
|
-
try:
|
|
433
|
-
async with aiohttp.ClientSession() as session:
|
|
434
|
-
async with session.get(
|
|
435
|
-
url, headers=self.headers, timeout=aiohttp.ClientTimeout(total=30)
|
|
436
|
-
) as response:
|
|
437
|
-
response_data = await response.json()
|
|
438
|
-
|
|
439
|
-
if response.status != 200:
|
|
440
|
-
self._handle_error_response(response.status, response_data, url)
|
|
441
|
-
|
|
442
|
-
# Parse and validate response
|
|
443
|
-
return ProductDetail(**response_data)
|
|
444
|
-
|
|
445
|
-
except aiohttp.ClientConnectionError as e:
|
|
446
|
-
raise Channel3ConnectionError(
|
|
447
|
-
f"Failed to connect to Channel3 API: {str(e)}"
|
|
448
|
-
)
|
|
449
|
-
except asyncio.TimeoutError as e:
|
|
450
|
-
raise Channel3ConnectionError(f"Request timed out: {str(e)}")
|
|
451
|
-
except aiohttp.ClientError as e:
|
|
452
|
-
raise Channel3Error(f"Request failed: {str(e)}")
|
|
453
|
-
except ValidationError as e:
|
|
454
|
-
raise Channel3Error(f"Invalid response format: {str(e)}")
|
|
455
|
-
|
|
456
|
-
async def get_brands(
|
|
457
|
-
self,
|
|
458
|
-
query: Optional[str] = None,
|
|
459
|
-
page: int = 1,
|
|
460
|
-
size: int = 100,
|
|
461
|
-
) -> List[Brand]:
|
|
462
|
-
"""
|
|
463
|
-
Get all brands that the vendor currently sells.
|
|
464
|
-
|
|
465
|
-
Args:
|
|
466
|
-
query: Optional text query to filter brands
|
|
467
|
-
page: Page number for pagination (default: 1)
|
|
468
|
-
size: Number of brands per page (default: 100)
|
|
469
|
-
|
|
470
|
-
Returns:
|
|
471
|
-
List of Brand objects
|
|
472
|
-
|
|
473
|
-
Raises:
|
|
474
|
-
Channel3AuthenticationError: If API key is invalid
|
|
475
|
-
Channel3ValidationError: If request parameters are invalid
|
|
476
|
-
Channel3ServerError: If server encounters an error
|
|
477
|
-
Channel3ConnectionError: If there are connection issues
|
|
478
|
-
|
|
479
|
-
Example:
|
|
480
|
-
```python
|
|
481
|
-
brands = await async_client.get_brands()
|
|
482
|
-
for brand in brands:
|
|
483
|
-
print(f"Brand: {brand.name}")
|
|
484
|
-
```
|
|
485
|
-
"""
|
|
486
|
-
url = f"{self.base_url}/brands"
|
|
487
|
-
params = {}
|
|
488
|
-
|
|
489
|
-
if query is not None:
|
|
490
|
-
params["query"] = query
|
|
491
|
-
if page != 1:
|
|
492
|
-
params["page"] = page
|
|
493
|
-
if size != 100:
|
|
494
|
-
params["size"] = size
|
|
495
|
-
|
|
496
|
-
try:
|
|
497
|
-
async with aiohttp.ClientSession() as session:
|
|
498
|
-
async with session.get(
|
|
499
|
-
url,
|
|
500
|
-
headers=self.headers,
|
|
501
|
-
params=params,
|
|
502
|
-
timeout=aiohttp.ClientTimeout(total=30),
|
|
503
|
-
) as response:
|
|
504
|
-
response_data = await response.json()
|
|
505
|
-
|
|
506
|
-
if response.status != 200:
|
|
507
|
-
self._handle_error_response(response.status, response_data, url)
|
|
508
|
-
|
|
509
|
-
# Parse and validate response
|
|
510
|
-
return [Brand(**item) for item in response_data]
|
|
511
|
-
|
|
512
|
-
except aiohttp.ClientConnectionError as e:
|
|
513
|
-
raise Channel3ConnectionError(
|
|
514
|
-
f"Failed to connect to Channel3 API: {str(e)}"
|
|
515
|
-
)
|
|
516
|
-
except asyncio.TimeoutError as e:
|
|
517
|
-
raise Channel3ConnectionError(f"Request timed out: {str(e)}")
|
|
518
|
-
except aiohttp.ClientError as e:
|
|
519
|
-
raise Channel3Error(f"Request failed: {str(e)}")
|
|
520
|
-
except ValidationError as e:
|
|
521
|
-
raise Channel3Error(f"Invalid response format: {str(e)}")
|
|
522
|
-
|
|
523
|
-
async def get_brand(self, brand_id: str) -> Brand:
|
|
524
|
-
"""
|
|
525
|
-
Get detailed information for a specific brand by its ID.
|
|
526
|
-
|
|
527
|
-
Args:
|
|
528
|
-
brand_id: The unique identifier of the brand
|
|
529
|
-
|
|
530
|
-
Returns:
|
|
531
|
-
Brand object with detailed brand information
|
|
532
|
-
|
|
533
|
-
Raises:
|
|
534
|
-
Channel3AuthenticationError: If API key is invalid
|
|
535
|
-
Channel3NotFoundError: If brand is not found
|
|
536
|
-
Channel3ValidationError: If brand_id is invalid
|
|
537
|
-
Channel3ServerError: If server encounters an error
|
|
538
|
-
Channel3ConnectionError: If there are connection issues
|
|
539
|
-
|
|
540
|
-
Example:
|
|
541
|
-
```python
|
|
542
|
-
brand = await async_client.get_brand("brand_123456")
|
|
543
|
-
print(f"Brand: {brand.name}")
|
|
544
|
-
print(f"Description: {brand.description}")
|
|
545
|
-
```
|
|
546
|
-
"""
|
|
547
|
-
if not brand_id or not brand_id.strip():
|
|
548
|
-
raise ValueError("brand_id cannot be empty")
|
|
549
|
-
|
|
550
|
-
url = f"{self.base_url}/brands/{brand_id}"
|
|
551
|
-
|
|
552
|
-
try:
|
|
553
|
-
async with aiohttp.ClientSession() as session:
|
|
554
|
-
async with session.get(
|
|
555
|
-
url, headers=self.headers, timeout=aiohttp.ClientTimeout(total=30)
|
|
556
|
-
) as response:
|
|
557
|
-
response_data = await response.json()
|
|
558
|
-
|
|
559
|
-
if response.status != 200:
|
|
560
|
-
self._handle_error_response(response.status, response_data, url)
|
|
561
|
-
|
|
562
|
-
# Parse and validate response
|
|
563
|
-
return Brand(**response_data)
|
|
564
|
-
|
|
565
|
-
except aiohttp.ClientConnectionError as e:
|
|
566
|
-
raise Channel3ConnectionError(
|
|
567
|
-
f"Failed to connect to Channel3 API: {str(e)}"
|
|
568
|
-
)
|
|
569
|
-
except asyncio.TimeoutError as e:
|
|
570
|
-
raise Channel3ConnectionError(f"Request timed out: {str(e)}")
|
|
571
|
-
except aiohttp.ClientError as e:
|
|
572
|
-
raise Channel3Error(f"Request failed: {str(e)}")
|
|
573
|
-
except ValidationError as e:
|
|
574
|
-
raise Channel3Error(f"Invalid response format: {str(e)}")
|
channel3_sdk/exceptions.py
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
"""Custom exceptions for the Channel3 SDK."""
|
|
2
|
-
|
|
3
|
-
from typing import Optional, Dict, Any
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Channel3Error(Exception):
|
|
7
|
-
"""Base exception for all Channel3 SDK errors."""
|
|
8
|
-
|
|
9
|
-
def __init__(
|
|
10
|
-
self,
|
|
11
|
-
message: str,
|
|
12
|
-
status_code: Optional[int] = None,
|
|
13
|
-
response_data: Optional[Dict[str, Any]] = None,
|
|
14
|
-
):
|
|
15
|
-
super().__init__(message)
|
|
16
|
-
self.message = message
|
|
17
|
-
self.status_code = status_code
|
|
18
|
-
self.response_data = response_data or {}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class Channel3AuthenticationError(Channel3Error):
|
|
22
|
-
"""Raised when authentication fails (401)."""
|
|
23
|
-
|
|
24
|
-
pass
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class Channel3ValidationError(Channel3Error):
|
|
28
|
-
"""Raised when request validation fails (422)."""
|
|
29
|
-
|
|
30
|
-
pass
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class Channel3NotFoundError(Channel3Error):
|
|
34
|
-
"""Raised when a resource is not found (404)."""
|
|
35
|
-
|
|
36
|
-
pass
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class Channel3ServerError(Channel3Error):
|
|
40
|
-
"""Raised when the server encounters an error (500)."""
|
|
41
|
-
|
|
42
|
-
pass
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class Channel3ConnectionError(Channel3Error):
|
|
46
|
-
"""Raised when there are connection issues."""
|
|
47
|
-
|
|
48
|
-
pass
|