channel3-sdk 0.1.1__tar.gz → 0.1.12__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.

Potentially problematic release.


This version of channel3-sdk might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: channel3-sdk
3
- Version: 0.1.1
3
+ Version: 0.1.12
4
4
  Summary: The official Python SDK for Channel3 AI Shopping API
5
5
  Home-page: https://github.com/channel3/sdk-python
6
6
  License: MIT
@@ -50,14 +50,27 @@ products = client.search(query="blue denim jacket")
50
50
  for product in products:
51
51
  print(f"Product: {product.title}")
52
52
  print(f"Brand: {product.brand_name}")
53
- print(f"Price: ${product.offers[0].price.price}")
53
+ print(f"Price: {product.price.currency} {product.price.price}")
54
+ print(f"Availability: {product.availability}")
54
55
  print("---")
55
56
 
56
57
  # Get detailed product information
57
58
  product_detail = client.get_product("prod_123456")
58
59
  print(f"Detailed info for: {product_detail.title}")
59
- print(f"Materials: {product_detail.materials}")
60
- print(f"Key features: {product_detail.key_features}")
60
+ print(f"Brand: {product_detail.brand_name}")
61
+ if product_detail.key_features:
62
+ print(f"Key features: {product_detail.key_features}")
63
+
64
+ # Get all brands
65
+ brands = client.get_brands()
66
+ for brand in brands:
67
+ print(f"Brand: {brand.name}")
68
+ if brand.description:
69
+ print(f"Description: {brand.description}")
70
+
71
+ # Get specific brand details
72
+ brand = client.get_brand("brand_123")
73
+ print(f"Brand: {brand.name}")
61
74
  ```
62
75
 
63
76
  ### Asynchronous Client
@@ -76,11 +89,16 @@ async def main():
76
89
  for product in products:
77
90
  print(f"Product: {product.title}")
78
91
  print(f"Score: {product.score}")
92
+ print(f"Price: {product.price.currency} {product.price.price}")
79
93
 
80
94
  # Get detailed product information
81
95
  if products:
82
96
  product_detail = await client.get_product(products[0].id)
83
- print(f"Gender: {product_detail.gender}")
97
+ print(f"Availability: {product_detail.availability}")
98
+
99
+ # Get brands
100
+ brands = await client.get_brands()
101
+ print(f"Found {len(brands)} brands")
84
102
 
85
103
  # Run the async function
86
104
  asyncio.run(main())
@@ -114,14 +132,14 @@ products = client.search(
114
132
  ### Search with Filters
115
133
 
116
134
  ```python
117
- from channel3_sdk import SearchFilters
135
+ from channel3_sdk import SearchFilters, AvailabilityStatus
118
136
 
119
137
  # Create search filters
120
138
  filters = SearchFilters(
121
- colors=["blue", "navy"],
122
- materials=["cotton", "denim"],
123
- min_price=50.0,
124
- max_price=200.0
139
+ brand_ids=["brand_123", "brand_456"],
140
+ gender="male",
141
+ availability=[AvailabilityStatus.IN_STOCK],
142
+ price={"min_price": 50.0, "max_price": 200.0}
125
143
  )
126
144
 
127
145
  # Search with filters
@@ -132,6 +150,21 @@ products = client.search(
132
150
  )
133
151
  ```
134
152
 
153
+ ### Brand Management
154
+
155
+ ```python
156
+ # Get all brands with pagination
157
+ brands = client.get_brands(page=1, size=50)
158
+
159
+ # Search for specific brands
160
+ nike_brands = client.get_brands(query="nike")
161
+
162
+ # Get detailed brand information
163
+ brand_detail = client.get_brand("brand_123")
164
+ print(f"Brand: {brand_detail.name}")
165
+ print(f"Logo: {brand_detail.logo_url}")
166
+ ```
167
+
135
168
  ## API Reference
136
169
 
137
170
  ### Client Classes
@@ -142,6 +175,8 @@ Synchronous client for the Channel3 API.
142
175
  **Methods:**
143
176
  - `search(query=None, image_url=None, base64_image=None, filters=None, limit=20)` → `List[Product]`
144
177
  - `get_product(product_id)` → `ProductDetail`
178
+ - `get_brands(query=None, page=1, size=100)` → `List[Brand]`
179
+ - `get_brand(brand_id)` → `Brand`
145
180
 
146
181
  #### `AsyncChannel3Client`
147
182
  Asynchronous client for the Channel3 API.
@@ -149,48 +184,62 @@ Asynchronous client for the Channel3 API.
149
184
  **Methods:**
150
185
  - `async search(query=None, image_url=None, base64_image=None, filters=None, limit=20)` → `List[Product]`
151
186
  - `async get_product(product_id)` → `ProductDetail`
187
+ - `async get_brands(query=None, page=1, size=100)` → `List[Brand]`
188
+ - `async get_brand(brand_id)` → `Brand`
152
189
 
153
190
  ### Models
154
191
 
155
192
  #### `Product`
156
193
  - `id: str` - Unique product identifier
157
194
  - `score: float` - Search relevance score
158
- - `brand_name: str` - Brand name
159
195
  - `title: str` - Product title
160
- - `description: str` - Product description
196
+ - `description: Optional[str]` - Product description
197
+ - `brand_name: str` - Brand name
161
198
  - `image_url: str` - Main product image URL
162
- - `offers: List[MerchantOffering]` - Available purchase options
163
- - `family: List[FamilyMember]` - Related products
199
+ - `price: Price` - Price information
200
+ - `availability: AvailabilityStatus` - Availability status
201
+ - `variants: List[Variant]` - Product variants
164
202
 
165
203
  #### `ProductDetail`
166
- - `brand_id: str` - Brand identifier
167
- - `brand_name: str` - Brand name
168
204
  - `title: str` - Product title
169
- - `description: str` - Product description
170
- - `image_urls: List[str]` - Product image URLs
171
- - `merchant_offerings: List[MerchantOffering]` - Purchase options
172
- - `gender: Literal["na", "men", "women"]` - Target gender
173
- - `materials: Optional[List[str]]` - Product materials
174
- - `key_features: List[str]` - Key product features
175
- - `family_members: List[FamilyMember]` - Related products
205
+ - `description: Optional[str]` - Product description
206
+ - `brand_id: Optional[str]` - Brand identifier
207
+ - `brand_name: Optional[str]` - Brand name
208
+ - `image_urls: Optional[List[str]]` - Product image URLs
209
+ - `price: Price` - Price information
210
+ - `availability: AvailabilityStatus` - Availability status
211
+ - `key_features: Optional[List[str]]` - Key product features
212
+ - `variants: List[Variant]` - Product variants
213
+
214
+ #### `Brand`
215
+ - `id: str` - Unique brand identifier
216
+ - `name: str` - Brand name
217
+ - `logo_url: Optional[str]` - Brand logo URL
218
+ - `description: Optional[str]` - Brand description
219
+
220
+ #### `Variant`
221
+ - `product_id: str` - Associated product identifier
222
+ - `title: str` - Variant title
223
+ - `image_url: str` - Variant image URL
176
224
 
177
225
  #### `SearchFilters`
178
- - `colors: Optional[List[str]]` - Color filters
179
- - `materials: Optional[List[str]]` - Material filters
226
+ - `brand_ids: Optional[List[str]]` - Brand ID filters
227
+ - `gender: Optional[Literal["male", "female", "unisex"]]` - Gender filter
228
+ - `price: Optional[SearchFilterPrice]` - Price range filter
229
+ - `availability: Optional[List[AvailabilityStatus]]` - Availability filters
230
+
231
+ #### `SearchFilterPrice`
180
232
  - `min_price: Optional[float]` - Minimum price
181
233
  - `max_price: Optional[float]` - Maximum price
182
234
 
183
- #### `MerchantOffering`
184
- - `url: str` - Purchase URL
185
- - `merchant_name: str` - Merchant name
186
- - `price: Price` - Price information
187
- - `availability: AvailabilityStatus` - Availability status
188
-
189
235
  #### `Price`
190
236
  - `price: float` - Current price
191
237
  - `compare_at_price: Optional[float]` - Original price (if discounted)
192
238
  - `currency: str` - Currency code
193
239
 
240
+ #### `AvailabilityStatus`
241
+ Enum with values: `IN_STOCK`, `OUT_OF_STOCK`, `PRE_ORDER`, `LIMITED_AVAILABILITY`, `BACK_ORDER`, `DISCONTINUED`, `SOLD_OUT`, `UNKNOWN`
242
+
194
243
  ## Error Handling
195
244
 
196
245
  The SDK provides specific exception types for different error conditions:
@@ -26,14 +26,27 @@ products = client.search(query="blue denim jacket")
26
26
  for product in products:
27
27
  print(f"Product: {product.title}")
28
28
  print(f"Brand: {product.brand_name}")
29
- print(f"Price: ${product.offers[0].price.price}")
29
+ print(f"Price: {product.price.currency} {product.price.price}")
30
+ print(f"Availability: {product.availability}")
30
31
  print("---")
31
32
 
32
33
  # Get detailed product information
33
34
  product_detail = client.get_product("prod_123456")
34
35
  print(f"Detailed info for: {product_detail.title}")
35
- print(f"Materials: {product_detail.materials}")
36
- print(f"Key features: {product_detail.key_features}")
36
+ print(f"Brand: {product_detail.brand_name}")
37
+ if product_detail.key_features:
38
+ print(f"Key features: {product_detail.key_features}")
39
+
40
+ # Get all brands
41
+ brands = client.get_brands()
42
+ for brand in brands:
43
+ print(f"Brand: {brand.name}")
44
+ if brand.description:
45
+ print(f"Description: {brand.description}")
46
+
47
+ # Get specific brand details
48
+ brand = client.get_brand("brand_123")
49
+ print(f"Brand: {brand.name}")
37
50
  ```
38
51
 
39
52
  ### Asynchronous Client
@@ -52,11 +65,16 @@ async def main():
52
65
  for product in products:
53
66
  print(f"Product: {product.title}")
54
67
  print(f"Score: {product.score}")
68
+ print(f"Price: {product.price.currency} {product.price.price}")
55
69
 
56
70
  # Get detailed product information
57
71
  if products:
58
72
  product_detail = await client.get_product(products[0].id)
59
- print(f"Gender: {product_detail.gender}")
73
+ print(f"Availability: {product_detail.availability}")
74
+
75
+ # Get brands
76
+ brands = await client.get_brands()
77
+ print(f"Found {len(brands)} brands")
60
78
 
61
79
  # Run the async function
62
80
  asyncio.run(main())
@@ -90,14 +108,14 @@ products = client.search(
90
108
  ### Search with Filters
91
109
 
92
110
  ```python
93
- from channel3_sdk import SearchFilters
111
+ from channel3_sdk import SearchFilters, AvailabilityStatus
94
112
 
95
113
  # Create search filters
96
114
  filters = SearchFilters(
97
- colors=["blue", "navy"],
98
- materials=["cotton", "denim"],
99
- min_price=50.0,
100
- max_price=200.0
115
+ brand_ids=["brand_123", "brand_456"],
116
+ gender="male",
117
+ availability=[AvailabilityStatus.IN_STOCK],
118
+ price={"min_price": 50.0, "max_price": 200.0}
101
119
  )
102
120
 
103
121
  # Search with filters
@@ -108,6 +126,21 @@ products = client.search(
108
126
  )
109
127
  ```
110
128
 
129
+ ### Brand Management
130
+
131
+ ```python
132
+ # Get all brands with pagination
133
+ brands = client.get_brands(page=1, size=50)
134
+
135
+ # Search for specific brands
136
+ nike_brands = client.get_brands(query="nike")
137
+
138
+ # Get detailed brand information
139
+ brand_detail = client.get_brand("brand_123")
140
+ print(f"Brand: {brand_detail.name}")
141
+ print(f"Logo: {brand_detail.logo_url}")
142
+ ```
143
+
111
144
  ## API Reference
112
145
 
113
146
  ### Client Classes
@@ -118,6 +151,8 @@ Synchronous client for the Channel3 API.
118
151
  **Methods:**
119
152
  - `search(query=None, image_url=None, base64_image=None, filters=None, limit=20)` → `List[Product]`
120
153
  - `get_product(product_id)` → `ProductDetail`
154
+ - `get_brands(query=None, page=1, size=100)` → `List[Brand]`
155
+ - `get_brand(brand_id)` → `Brand`
121
156
 
122
157
  #### `AsyncChannel3Client`
123
158
  Asynchronous client for the Channel3 API.
@@ -125,48 +160,62 @@ Asynchronous client for the Channel3 API.
125
160
  **Methods:**
126
161
  - `async search(query=None, image_url=None, base64_image=None, filters=None, limit=20)` → `List[Product]`
127
162
  - `async get_product(product_id)` → `ProductDetail`
163
+ - `async get_brands(query=None, page=1, size=100)` → `List[Brand]`
164
+ - `async get_brand(brand_id)` → `Brand`
128
165
 
129
166
  ### Models
130
167
 
131
168
  #### `Product`
132
169
  - `id: str` - Unique product identifier
133
170
  - `score: float` - Search relevance score
134
- - `brand_name: str` - Brand name
135
171
  - `title: str` - Product title
136
- - `description: str` - Product description
172
+ - `description: Optional[str]` - Product description
173
+ - `brand_name: str` - Brand name
137
174
  - `image_url: str` - Main product image URL
138
- - `offers: List[MerchantOffering]` - Available purchase options
139
- - `family: List[FamilyMember]` - Related products
175
+ - `price: Price` - Price information
176
+ - `availability: AvailabilityStatus` - Availability status
177
+ - `variants: List[Variant]` - Product variants
140
178
 
141
179
  #### `ProductDetail`
142
- - `brand_id: str` - Brand identifier
143
- - `brand_name: str` - Brand name
144
180
  - `title: str` - Product title
145
- - `description: str` - Product description
146
- - `image_urls: List[str]` - Product image URLs
147
- - `merchant_offerings: List[MerchantOffering]` - Purchase options
148
- - `gender: Literal["na", "men", "women"]` - Target gender
149
- - `materials: Optional[List[str]]` - Product materials
150
- - `key_features: List[str]` - Key product features
151
- - `family_members: List[FamilyMember]` - Related products
181
+ - `description: Optional[str]` - Product description
182
+ - `brand_id: Optional[str]` - Brand identifier
183
+ - `brand_name: Optional[str]` - Brand name
184
+ - `image_urls: Optional[List[str]]` - Product image URLs
185
+ - `price: Price` - Price information
186
+ - `availability: AvailabilityStatus` - Availability status
187
+ - `key_features: Optional[List[str]]` - Key product features
188
+ - `variants: List[Variant]` - Product variants
189
+
190
+ #### `Brand`
191
+ - `id: str` - Unique brand identifier
192
+ - `name: str` - Brand name
193
+ - `logo_url: Optional[str]` - Brand logo URL
194
+ - `description: Optional[str]` - Brand description
195
+
196
+ #### `Variant`
197
+ - `product_id: str` - Associated product identifier
198
+ - `title: str` - Variant title
199
+ - `image_url: str` - Variant image URL
152
200
 
153
201
  #### `SearchFilters`
154
- - `colors: Optional[List[str]]` - Color filters
155
- - `materials: Optional[List[str]]` - Material filters
202
+ - `brand_ids: Optional[List[str]]` - Brand ID filters
203
+ - `gender: Optional[Literal["male", "female", "unisex"]]` - Gender filter
204
+ - `price: Optional[SearchFilterPrice]` - Price range filter
205
+ - `availability: Optional[List[AvailabilityStatus]]` - Availability filters
206
+
207
+ #### `SearchFilterPrice`
156
208
  - `min_price: Optional[float]` - Minimum price
157
209
  - `max_price: Optional[float]` - Maximum price
158
210
 
159
- #### `MerchantOffering`
160
- - `url: str` - Purchase URL
161
- - `merchant_name: str` - Merchant name
162
- - `price: Price` - Price information
163
- - `availability: AvailabilityStatus` - Availability status
164
-
165
211
  #### `Price`
166
212
  - `price: float` - Current price
167
213
  - `compare_at_price: Optional[float]` - Original price (if discounted)
168
214
  - `currency: str` - Currency code
169
215
 
216
+ #### `AvailabilityStatus`
217
+ Enum with values: `IN_STOCK`, `OUT_OF_STOCK`, `PRE_ORDER`, `LIMITED_AVAILABILITY`, `BACK_ORDER`, `DISCONTINUED`, `SOLD_OUT`, `UNKNOWN`
218
+
170
219
  ## Error Handling
171
220
 
172
221
  The SDK provides specific exception types for different error conditions:
@@ -6,8 +6,9 @@ from .models import (
6
6
  ProductDetail,
7
7
  SearchFilters,
8
8
  SearchRequest,
9
- MerchantOffering,
10
- FamilyMember,
9
+ SearchFilterPrice,
10
+ Brand,
11
+ Variant,
11
12
  Price,
12
13
  AvailabilityStatus,
13
14
  )
@@ -20,7 +21,7 @@ from .exceptions import (
20
21
  Channel3ConnectionError,
21
22
  )
22
23
 
23
- __version__ = "0.1.0"
24
+ __version__ = "0.2.0"
24
25
  __all__ = [
25
26
  # Clients
26
27
  "Channel3Client",
@@ -30,8 +31,9 @@ __all__ = [
30
31
  "ProductDetail",
31
32
  "SearchFilters",
32
33
  "SearchRequest",
33
- "MerchantOffering",
34
- "FamilyMember",
34
+ "SearchFilterPrice",
35
+ "Brand",
36
+ "Variant",
35
37
  "Price",
36
38
  "AvailabilityStatus",
37
39
  # Exceptions
@@ -6,7 +6,7 @@ import aiohttp
6
6
  import asyncio
7
7
  from pydantic import ValidationError
8
8
 
9
- from .models import Product, ProductDetail, SearchFilters, SearchRequest
9
+ from .models import Product, ProductDetail, SearchFilters, SearchRequest, Brand
10
10
  from .exceptions import (
11
11
  Channel3Error,
12
12
  Channel3AuthenticationError,
@@ -127,7 +127,7 @@ class Channel3Client(BaseChannel3Client):
127
127
  query=query,
128
128
  image_url=image_url,
129
129
  base64_image=base64_image,
130
- filters=filters,
130
+ filters=filters or SearchFilters(),
131
131
  limit=limit,
132
132
  )
133
133
 
@@ -210,6 +210,119 @@ class Channel3Client(BaseChannel3Client):
210
210
  except ValidationError as e:
211
211
  raise Channel3Error(f"Invalid response format: {str(e)}")
212
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
+
213
326
 
214
327
  class AsyncChannel3Client(BaseChannel3Client):
215
328
  """Asynchronous Channel3 API client."""
@@ -255,7 +368,7 @@ class AsyncChannel3Client(BaseChannel3Client):
255
368
  query=query,
256
369
  image_url=image_url,
257
370
  base64_image=base64_image,
258
- filters=filters,
371
+ filters=filters or SearchFilters(),
259
372
  limit=limit,
260
373
  )
261
374
 
@@ -339,3 +452,123 @@ class AsyncChannel3Client(BaseChannel3Client):
339
452
  raise Channel3Error(f"Request failed: {str(e)}")
340
453
  except ValidationError as e:
341
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)}")
@@ -30,25 +30,21 @@ class Price(BaseModel):
30
30
  currency: str = Field(..., description="The currency code of the product.")
31
31
 
32
32
 
33
- class MerchantOffering(BaseModel):
34
- """A merchant offering a product."""
33
+ class Brand(BaseModel):
34
+ """A brand."""
35
35
 
36
- url: str = Field(
37
- default="https://buy.trychannel3.com", description="URL to purchase the product"
38
- )
39
- merchant_name: str = Field(..., description="Name of the merchant")
40
- price: Price = Field(..., description="Price information")
41
- availability: AvailabilityStatus = Field(
42
- ..., description="Product availability status"
43
- )
36
+ id: str = Field(..., description="Unique identifier for the brand")
37
+ name: str = Field(..., description="Name of the brand")
38
+ logo_url: Optional[str] = Field(None, description="Logo URL for the brand")
39
+ description: Optional[str] = Field(None, description="Description of the brand")
44
40
 
45
41
 
46
- class FamilyMember(BaseModel):
47
- """A family member product."""
42
+ class Variant(BaseModel):
43
+ """A product variant."""
48
44
 
49
- id: str = Field(..., description="Unique identifier for the family member")
50
- title: str = Field(..., description="Title of the family member product")
51
- image_url: str = Field(..., description="Image URL for the family member product")
45
+ product_id: str = Field(..., description="Unique identifier for the product")
46
+ title: str = Field(..., description="Title of the variant")
47
+ image_url: str = Field(..., description="Image URL for the variant")
52
48
 
53
49
 
54
50
  class Product(BaseModel):
@@ -56,38 +52,38 @@ class Product(BaseModel):
56
52
 
57
53
  id: str = Field(..., description="Unique identifier for the product")
58
54
  score: float = Field(..., description="Relevance score for the search query")
59
- brand_name: str = Field(..., description="Brand name of the product")
60
55
  title: str = Field(..., description="Product title")
61
- description: str = Field(..., description="Product description")
56
+ description: Optional[str] = Field(None, description="Product description")
57
+ brand_name: str = Field(..., description="Brand name of the product")
62
58
  image_url: str = Field(..., description="Main product image URL")
63
- offers: List[MerchantOffering] = Field(
64
- ..., description="List of merchant offerings"
59
+ price: Price = Field(..., description="Price information")
60
+ availability: AvailabilityStatus = Field(
61
+ ..., description="Product availability status"
65
62
  )
66
- family: List[FamilyMember] = Field(
67
- default_factory=list, description="Related family products"
63
+ variants: List[Variant] = Field(
64
+ default_factory=list, description="Product variants"
68
65
  )
69
66
 
70
67
 
71
68
  class ProductDetail(BaseModel):
72
69
  """Detailed information about a product."""
73
70
 
74
- brand_id: str = Field(..., description="Unique identifier for the brand")
75
- brand_name: str = Field(..., description="Brand name of the product")
76
71
  title: str = Field(..., description="Product title")
77
- description: str = Field(..., description="Product description")
78
- image_urls: List[str] = Field(..., description="List of product image URLs")
79
- merchant_offerings: List[MerchantOffering] = Field(
80
- ..., description="List of merchant offerings"
72
+ description: Optional[str] = Field(None, description="Product description")
73
+ brand_id: Optional[str] = Field(None, description="Unique identifier for the brand")
74
+ brand_name: Optional[str] = Field(None, description="Brand name of the product")
75
+ image_urls: Optional[List[str]] = Field(
76
+ None, description="List of product image URLs"
81
77
  )
82
- gender: Literal["na", "men", "women"] = Field(
83
- default="na", description="Target gender"
78
+ price: Price = Field(..., description="Price information")
79
+ availability: AvailabilityStatus = Field(
80
+ ..., description="Product availability status"
84
81
  )
85
- materials: Optional[List[str]] = Field(None, description="List of materials")
86
- key_features: List[str] = Field(
87
- default_factory=list, description="List of key product features"
82
+ key_features: Optional[List[str]] = Field(
83
+ None, description="List of key product features"
88
84
  )
89
- family_members: List[FamilyMember] = Field(
90
- default_factory=list, description="Related family products"
85
+ variants: List[Variant] = Field(
86
+ default_factory=list, description="Product variants"
91
87
  )
92
88
 
93
89
 
@@ -101,15 +97,17 @@ class SearchFilterPrice(BaseModel):
101
97
  class SearchFilters(BaseModel):
102
98
  """Search filters for product search."""
103
99
 
104
- brands: Optional[List[str]] = Field(None, description="List of brands to filter by")
100
+ brand_ids: Optional[List[str]] = Field(
101
+ None, description="List of brand IDs to filter by"
102
+ )
105
103
  gender: Optional[Literal["male", "female", "unisex"]] = Field(
106
104
  None, description="Gender to filter by"
107
105
  )
108
106
  price: Optional[SearchFilterPrice] = Field(
109
107
  None, description="Price range to filter by"
110
108
  )
111
- availability: Optional[AvailabilityStatus] = Field(
112
- None, description="Availability status to filter by"
109
+ availability: Optional[List[AvailabilityStatus]] = Field(
110
+ None, description="Availability statuses to filter by"
113
111
  )
114
112
 
115
113
 
@@ -121,9 +119,7 @@ class SearchRequest(BaseModel):
121
119
  base64_image: Optional[str] = Field(
122
120
  None, description="Base64-encoded image for visual search"
123
121
  )
124
- filters: SearchFilters = Field(
125
- default_factory=SearchFilters, description="Search filters"
126
- )
127
122
  limit: Optional[int] = Field(
128
123
  default=20, description="Maximum number of results to return"
129
124
  )
125
+ filters: Optional[SearchFilters] = Field(default=None, description="Search filters")
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "channel3-sdk"
3
- version = "0.1.1"
3
+ version = "0.1.12"
4
4
  description = "The official Python SDK for Channel3 AI Shopping API"
5
5
  authors = ["Channel3 <alex@trychannel3.com>"]
6
6
  readme = "README.md"