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.

Files changed (54) hide show
  1. channel3_sdk/__init__.py +94 -38
  2. channel3_sdk/_base_client.py +1995 -0
  3. channel3_sdk/_client.py +549 -0
  4. channel3_sdk/_compat.py +219 -0
  5. channel3_sdk/_constants.py +14 -0
  6. channel3_sdk/_exceptions.py +108 -0
  7. channel3_sdk/_files.py +123 -0
  8. channel3_sdk/_models.py +829 -0
  9. channel3_sdk/_qs.py +150 -0
  10. channel3_sdk/_resource.py +43 -0
  11. channel3_sdk/_response.py +832 -0
  12. channel3_sdk/_streaming.py +333 -0
  13. channel3_sdk/_types.py +253 -0
  14. channel3_sdk/_utils/__init__.py +64 -0
  15. channel3_sdk/_utils/_compat.py +45 -0
  16. channel3_sdk/_utils/_datetime_parse.py +136 -0
  17. channel3_sdk/_utils/_logs.py +25 -0
  18. channel3_sdk/_utils/_proxy.py +65 -0
  19. channel3_sdk/_utils/_reflection.py +42 -0
  20. channel3_sdk/_utils/_resources_proxy.py +24 -0
  21. channel3_sdk/_utils/_streams.py +12 -0
  22. channel3_sdk/_utils/_sync.py +86 -0
  23. channel3_sdk/_utils/_transform.py +457 -0
  24. channel3_sdk/_utils/_typing.py +156 -0
  25. channel3_sdk/_utils/_utils.py +421 -0
  26. channel3_sdk/_version.py +4 -0
  27. channel3_sdk/lib/.keep +4 -0
  28. channel3_sdk/py.typed +0 -0
  29. channel3_sdk/resources/__init__.py +61 -0
  30. channel3_sdk/resources/brands.py +268 -0
  31. channel3_sdk/resources/enrich.py +167 -0
  32. channel3_sdk/resources/products.py +163 -0
  33. channel3_sdk/resources/search.py +227 -0
  34. channel3_sdk/types/__init__.py +15 -0
  35. channel3_sdk/types/availability_status.py +9 -0
  36. channel3_sdk/types/brand.py +17 -0
  37. channel3_sdk/types/brand_list_params.py +16 -0
  38. channel3_sdk/types/brand_list_response.py +25 -0
  39. channel3_sdk/types/enrich_enrich_url_params.py +12 -0
  40. channel3_sdk/types/enrich_enrich_url_response.py +20 -0
  41. channel3_sdk/types/price.py +18 -0
  42. channel3_sdk/types/product_retrieve_response.py +39 -0
  43. channel3_sdk/types/search_perform_params.py +61 -0
  44. channel3_sdk/types/search_perform_response.py +36 -0
  45. channel3_sdk/types/variant.py +13 -0
  46. channel3_sdk-2.0.0.dist-info/METADATA +414 -0
  47. channel3_sdk-2.0.0.dist-info/RECORD +49 -0
  48. {channel3_sdk-0.2.2.dist-info → channel3_sdk-2.0.0.dist-info}/WHEEL +1 -1
  49. channel3_sdk-2.0.0.dist-info/licenses/LICENSE +201 -0
  50. channel3_sdk/client.py +0 -574
  51. channel3_sdk/exceptions.py +0 -48
  52. channel3_sdk/models.py +0 -135
  53. channel3_sdk-0.2.2.dist-info/METADATA +0 -284
  54. 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)}")
@@ -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