channel3-sdk 0.2.1__py3-none-any.whl → 1.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 (39) hide show
  1. channel3_sdk/__init__.py +23 -17
  2. channel3_sdk/_gen/.gitignore +23 -0
  3. channel3_sdk/_gen/README.md +124 -0
  4. channel3_sdk/_gen/fast_api_client/__init__.py +8 -0
  5. channel3_sdk/_gen/fast_api_client/api/__init__.py +1 -0
  6. channel3_sdk/_gen/fast_api_client/api/channel3_api/__init__.py +1 -0
  7. channel3_sdk/_gen/fast_api_client/api/channel3_api/get_brand_detail_v0_brands_brand_id_get.py +179 -0
  8. channel3_sdk/_gen/fast_api_client/api/channel3_api/get_brands_v0_brands_get.py +218 -0
  9. channel3_sdk/_gen/fast_api_client/api/channel3_api/get_product_detail_v0_products_product_id_get.py +179 -0
  10. channel3_sdk/_gen/fast_api_client/api/channel3_api/search_v0_search_post.py +193 -0
  11. channel3_sdk/_gen/fast_api_client/api/default/__init__.py +1 -0
  12. channel3_sdk/_gen/fast_api_client/api/default/root_get.py +79 -0
  13. channel3_sdk/_gen/fast_api_client/client.py +268 -0
  14. channel3_sdk/_gen/fast_api_client/errors.py +16 -0
  15. channel3_sdk/_gen/fast_api_client/models/__init__.py +35 -0
  16. channel3_sdk/_gen/fast_api_client/models/availability_status.py +15 -0
  17. channel3_sdk/_gen/fast_api_client/models/brand.py +109 -0
  18. channel3_sdk/_gen/fast_api_client/models/error_response.py +59 -0
  19. channel3_sdk/_gen/fast_api_client/models/paginated_response_brand.py +83 -0
  20. channel3_sdk/_gen/fast_api_client/models/pagination_meta.py +84 -0
  21. channel3_sdk/_gen/fast_api_client/models/price.py +89 -0
  22. channel3_sdk/_gen/fast_api_client/models/product.py +166 -0
  23. channel3_sdk/_gen/fast_api_client/models/product_detail.py +306 -0
  24. channel3_sdk/_gen/fast_api_client/models/product_detail_gender_type_0.py +10 -0
  25. channel3_sdk/_gen/fast_api_client/models/search_config.py +69 -0
  26. channel3_sdk/_gen/fast_api_client/models/search_filter_price.py +92 -0
  27. channel3_sdk/_gen/fast_api_client/models/search_filters.py +191 -0
  28. channel3_sdk/_gen/fast_api_client/models/search_filters_gender_type_0.py +10 -0
  29. channel3_sdk/_gen/fast_api_client/models/search_request.py +191 -0
  30. channel3_sdk/_gen/fast_api_client/models/variant.py +75 -0
  31. channel3_sdk/_gen/fast_api_client/py.typed +1 -0
  32. channel3_sdk/_gen/fast_api_client/types.py +54 -0
  33. channel3_sdk/_gen/pyproject.toml +26 -0
  34. channel3_sdk/client.py +266 -479
  35. {channel3_sdk-0.2.1.dist-info → channel3_sdk-1.0.0.dist-info}/METADATA +83 -26
  36. channel3_sdk-1.0.0.dist-info/RECORD +38 -0
  37. {channel3_sdk-0.2.1.dist-info → channel3_sdk-1.0.0.dist-info}/WHEEL +1 -1
  38. channel3_sdk/models.py +0 -135
  39. channel3_sdk-0.2.1.dist-info/RECORD +0 -7
channel3_sdk/client.py CHANGED
@@ -1,36 +1,154 @@
1
1
  # channel3_sdk/client.py
2
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
3
+ from typing import Any, Dict, List, Optional, Union
8
4
 
9
- from .models import Product, ProductDetail, SearchFilters, SearchRequest, Brand
5
+ from ._gen.fast_api_client.api.channel3_api.get_brand_detail_v0_brands_brand_id_get import (
6
+ asyncio_detailed as get_brand_asyncio_detailed,
7
+ )
8
+ from ._gen.fast_api_client.api.channel3_api.get_brand_detail_v0_brands_brand_id_get import (
9
+ sync_detailed as get_brand_sync_detailed,
10
+ )
11
+ from ._gen.fast_api_client.api.channel3_api.get_brands_v0_brands_get import (
12
+ asyncio_detailed as get_brands_asyncio_detailed,
13
+ )
14
+ from ._gen.fast_api_client.api.channel3_api.get_brands_v0_brands_get import (
15
+ sync_detailed as get_brands_sync_detailed,
16
+ )
17
+ from ._gen.fast_api_client.api.channel3_api.get_product_detail_v0_products_product_id_get import (
18
+ asyncio_detailed as get_product_asyncio_detailed,
19
+ )
20
+ from ._gen.fast_api_client.api.channel3_api.get_product_detail_v0_products_product_id_get import (
21
+ sync_detailed as get_product_sync_detailed,
22
+ )
23
+ from ._gen.fast_api_client.api.channel3_api.search_v0_search_post import (
24
+ asyncio_detailed as search_asyncio_detailed,
25
+ )
26
+ from ._gen.fast_api_client.api.channel3_api.search_v0_search_post import (
27
+ sync_detailed as search_sync_detailed,
28
+ )
29
+
30
+ # Generated client imports
31
+ from ._gen.fast_api_client.client import AuthenticatedClient
32
+ from ._gen.fast_api_client.models.error_response import (
33
+ ErrorResponse as GenErrorResponse,
34
+ )
35
+ from ._gen.fast_api_client.models.paginated_response_brand import (
36
+ PaginatedResponseBrand as GenPaginatedResponseBrand,
37
+ )
38
+ from ._gen.fast_api_client.models.product import Product as GenProduct
39
+ from ._gen.fast_api_client.models.product_detail import (
40
+ ProductDetail as GenProductDetail,
41
+ )
42
+ from ._gen.fast_api_client.models.search_config import (
43
+ SearchConfig as GenSearchConfig,
44
+ )
45
+ from ._gen.fast_api_client.models.search_filter_price import (
46
+ SearchFilterPrice as GenSearchFilterPrice,
47
+ )
48
+ from ._gen.fast_api_client.models.search_filters import (
49
+ SearchFilters as GenSearchFilters,
50
+ )
51
+ from ._gen.fast_api_client.models.search_request import (
52
+ SearchRequest as GenSearchRequest,
53
+ )
54
+ from ._gen.fast_api_client.types import UNSET
55
+ from ._gen.fast_api_client.types import Response as GenResponse
10
56
  from .exceptions import (
11
- Channel3Error,
12
57
  Channel3AuthenticationError,
13
- Channel3ValidationError,
58
+ Channel3ConnectionError,
59
+ Channel3Error,
14
60
  Channel3NotFoundError,
15
61
  Channel3ServerError,
16
- Channel3ConnectionError,
62
+ Channel3ValidationError,
17
63
  )
18
64
 
19
65
 
66
+ def _strip_v0_suffix(base_url: str) -> str:
67
+ if base_url.endswith("/v0"):
68
+ return base_url[:-3]
69
+ return base_url
70
+
71
+
72
+ def _convert_filters_to_generated(
73
+ filters: Optional[GenSearchFilters | Dict[str, Any]],
74
+ ) -> Optional[GenSearchFilters]:
75
+ if filters is None:
76
+ return None
77
+ if isinstance(filters, GenSearchFilters):
78
+ return filters
79
+ # dict → generated model
80
+ price = None
81
+ price_dict = filters.get("price") if isinstance(filters, dict) else None
82
+ if isinstance(price_dict, dict):
83
+ price = GenSearchFilterPrice(
84
+ min_price=price_dict.get("min_price"),
85
+ max_price=price_dict.get("max_price"),
86
+ )
87
+ return GenSearchFilters(
88
+ brand_ids=filters.get("brand_ids"), # type: ignore[arg-type]
89
+ gender=filters.get("gender"), # type: ignore[arg-type]
90
+ price=price,
91
+ availability=filters.get("availability"), # type: ignore[arg-type]
92
+ )
93
+
94
+
95
+ def _convert_config_to_generated(
96
+ config: Optional[Union[GenSearchConfig, Dict[str, Any]]],
97
+ ) -> Optional[GenSearchConfig]:
98
+ if config is None:
99
+ return None
100
+ if isinstance(config, GenSearchConfig):
101
+ return config
102
+ # dict → generated model
103
+ return GenSearchConfig(
104
+ enrich_query=config.get("enrich_query", True),
105
+ semantic_search=config.get("semantic_search", True),
106
+ )
107
+
108
+
109
+ def _raise_for_status(url: str, response: GenResponse[Any]) -> None:
110
+ status_code = int(response.status_code)
111
+ data: Dict[str, Any] = {}
112
+ if response.parsed is not None and isinstance(response.parsed, GenErrorResponse):
113
+ detail = response.parsed.detail
114
+ if isinstance(detail, dict):
115
+ data = detail
116
+ else:
117
+ data = {"detail": detail}
118
+ error_message = data.get("detail") if isinstance(data.get("detail"), str) else None
119
+ if status_code == 200:
120
+ return
121
+ if status_code == 401:
122
+ raise Channel3AuthenticationError(
123
+ "Invalid or missing API key", status_code=status_code, response_data=data
124
+ )
125
+ if status_code == 404:
126
+ raise Channel3NotFoundError(
127
+ error_message or "Resource not found",
128
+ status_code=status_code,
129
+ response_data=data,
130
+ )
131
+ if status_code == 422:
132
+ raise Channel3ValidationError(
133
+ f"Validation error: {error_message or 'Unprocessable Entity'}",
134
+ status_code=status_code,
135
+ response_data=data,
136
+ )
137
+ if status_code == 500:
138
+ raise Channel3ServerError(
139
+ "Internal server error", status_code=status_code, response_data=data
140
+ )
141
+ raise Channel3Error(
142
+ f"Request to {url} failed: {error_message or f'Status {status_code}'}",
143
+ status_code=status_code,
144
+ response_data=data,
145
+ )
146
+
147
+
20
148
  class BaseChannel3Client:
21
149
  """Base client with common functionality."""
22
150
 
23
151
  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
152
  self.api_key = api_key or os.getenv("CHANNEL3_API_KEY")
35
153
  if not self.api_key:
36
154
  raise ValueError(
@@ -40,535 +158,204 @@ class BaseChannel3Client:
40
158
  self.base_url = base_url or "https://api.trychannel3.com/v0"
41
159
  self.headers = {"x-api-key": self.api_key, "Content-Type": "application/json"}
42
160
 
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}"
161
+ def _build_generated_client(self) -> AuthenticatedClient:
162
+ gen_base_url = _strip_v0_suffix(self.base_url)
163
+ client = AuthenticatedClient(
164
+ base_url=gen_base_url,
165
+ token=self.api_key,
166
+ prefix="",
167
+ auth_header_name="x-api-key",
49
168
  )
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
- )
169
+ client = client.with_headers({"Content-Type": "application/json"})
170
+ return client
79
171
 
80
172
 
81
173
  class Channel3Client(BaseChannel3Client):
82
- """Synchronous Channel3 API client."""
174
+ """Synchronous Channel3 API client (returns generated models)."""
83
175
 
84
176
  def search(
85
177
  self,
86
178
  query: Optional[str] = None,
87
179
  image_url: Optional[str] = None,
88
180
  base64_image: Optional[str] = None,
89
- filters: Optional[SearchFilters] = None,
181
+ filters: Optional[Union[GenSearchFilters, Dict[str, Any]]] = None,
90
182
  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(
183
+ config: Optional[Union[GenSearchConfig, Dict[str, Any]]] = None,
184
+ context: Optional[str] = None,
185
+ ) -> List[GenProduct]:
186
+ gen_client = self._build_generated_client()
187
+ request_body = GenSearchRequest(
127
188
  query=query,
128
189
  image_url=image_url,
129
190
  base64_image=base64_image,
130
- filters=filters,
191
+ filters=_convert_filters_to_generated(filters)
192
+ if filters is not None
193
+ else UNSET, # type: ignore[arg-type]
131
194
  limit=limit,
195
+ config=_convert_config_to_generated(config)
196
+ if config is not None
197
+ else UNSET, # type: ignore[arg-type]
198
+ context=context,
132
199
  )
133
200
 
134
- url = f"{self.base_url}/search"
135
-
136
201
  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]
202
+ response = search_sync_detailed(client=gen_client, body=request_body)
203
+ self._raise_and_validate_search(response)
204
+ return response.parsed # type: ignore[return-value]
205
+ except Exception as e:
206
+ if isinstance(e, Channel3Error):
207
+ raise
208
+ raise Channel3ConnectionError(f"Request failed: {str(e)}")
209
+
210
+ def _raise_and_validate_search(
211
+ self, response: GenResponse[Union[GenErrorResponse, List[Any]]]
212
+ ) -> None:
213
+ url = f"{_strip_v0_suffix(self.base_url)}/v0/search"
214
+ _raise_for_status(url, response)
215
+ if not isinstance(response.parsed, list):
216
+ raise Channel3Error("Invalid response format: expected list of products")
151
217
 
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
- """
218
+ def get_product(self, product_id: str) -> GenProductDetail:
187
219
  if not product_id or not product_id.strip():
188
220
  raise ValueError("product_id cannot be empty")
189
221
 
190
- url = f"{self.base_url}/products/{product_id}"
191
-
222
+ gen_client = self._build_generated_client()
192
223
  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)}"
224
+ response = get_product_sync_detailed(
225
+ product_id=product_id, client=gen_client
205
226
  )
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)}")
227
+ url = f"{_strip_v0_suffix(self.base_url)}/v0/products/{product_id}"
228
+ _raise_for_status(url, response)
229
+ return response.parsed # type: ignore[return-value]
230
+ except Exception as e:
231
+ if isinstance(e, Channel3Error):
232
+ raise
233
+ raise Channel3ConnectionError(f"Request failed: {str(e)}")
212
234
 
213
235
  def get_brands(
214
236
  self,
215
237
  query: Optional[str] = None,
216
238
  page: int = 1,
217
239
  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
-
240
+ ) -> GenPaginatedResponseBrand:
241
+ gen_client = self._build_generated_client()
253
242
  try:
254
- response = requests.get(
255
- url, headers=self.headers, params=params, timeout=30
243
+ response = get_brands_sync_detailed(
244
+ client=gen_client, query=query, page=page, size=size
256
245
  )
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
- """
246
+ url = f"{_strip_v0_suffix(self.base_url)}/v0/brands"
247
+ _raise_for_status(url, response)
248
+ return response.parsed # type: ignore[return-value]
249
+ except Exception as e:
250
+ if isinstance(e, Channel3Error):
251
+ raise
252
+ raise Channel3ConnectionError(f"Request failed: {str(e)}")
253
+
254
+ def get_brand(self, brand_id: str) -> Any:
300
255
  if not brand_id or not brand_id.strip():
301
256
  raise ValueError("brand_id cannot be empty")
302
257
 
303
- url = f"{self.base_url}/brands/{brand_id}"
304
-
258
+ gen_client = self._build_generated_client()
305
259
  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)}")
260
+ response = get_brand_sync_detailed(brand_id=brand_id, client=gen_client)
261
+ url = f"{_strip_v0_suffix(self.base_url)}/v0/brands/{brand_id}"
262
+ _raise_for_status(url, response)
263
+ return response.parsed
264
+ except Exception as e:
265
+ if isinstance(e, Channel3Error):
266
+ raise
267
+ raise Channel3ConnectionError(f"Request failed: {str(e)}")
325
268
 
326
269
 
327
270
  class AsyncChannel3Client(BaseChannel3Client):
328
- """Asynchronous Channel3 API client."""
271
+ """Asynchronous Channel3 API client (returns generated models)."""
329
272
 
330
273
  async def search(
331
274
  self,
332
275
  query: Optional[str] = None,
333
276
  image_url: Optional[str] = None,
334
277
  base64_image: Optional[str] = None,
335
- filters: Optional[Union[SearchFilters, Dict[str, Any]]] = None,
278
+ filters: Optional[Union[GenSearchFilters, Dict[str, Any]]] = None,
336
279
  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(
280
+ config: Optional[Union[GenSearchConfig, Dict[str, Any]]] = None,
281
+ context: Optional[str] = None,
282
+ ) -> List[GenProduct]:
283
+ gen_client = self._build_generated_client()
284
+ request_body = GenSearchRequest(
368
285
  query=query,
369
286
  image_url=image_url,
370
287
  base64_image=base64_image,
371
- filters=filters or SearchFilters(),
288
+ filters=_convert_filters_to_generated(filters)
289
+ if filters is not None
290
+ else UNSET, # type: ignore[arg-type]
372
291
  limit=limit,
292
+ config=_convert_config_to_generated(config)
293
+ if config is not None
294
+ else UNSET, # type: ignore[arg-type]
295
+ context=context,
373
296
  )
374
297
 
375
- url = f"{self.base_url}/search"
376
-
377
298
  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)}"
299
+ response = await search_asyncio_detailed(
300
+ client=gen_client, body=request_body
396
301
  )
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
- """
302
+ url = f"{_strip_v0_suffix(self.base_url)}/v0/search"
303
+ _raise_for_status(url, response)
304
+ return response.parsed # type: ignore[return-value]
305
+ except Exception as e:
306
+ if isinstance(e, Channel3Error):
307
+ raise
308
+ raise Channel3ConnectionError(f"Request failed: {str(e)}")
309
+
310
+ async def get_product(self, product_id: str) -> GenProductDetail:
427
311
  if not product_id or not product_id.strip():
428
312
  raise ValueError("product_id cannot be empty")
429
313
 
430
- url = f"{self.base_url}/products/{product_id}"
431
-
314
+ gen_client = self._build_generated_client()
432
315
  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)}"
316
+ response = await get_product_asyncio_detailed(
317
+ product_id=product_id, client=gen_client
448
318
  )
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)}")
319
+ url = f"{_strip_v0_suffix(self.base_url)}/v0/products/{product_id}"
320
+ _raise_for_status(url, response)
321
+ return response.parsed # type: ignore[return-value]
322
+ except Exception as e:
323
+ if isinstance(e, Channel3Error):
324
+ raise
325
+ raise Channel3ConnectionError(f"Request failed: {str(e)}")
455
326
 
456
327
  async def get_brands(
457
328
  self,
458
329
  query: Optional[str] = None,
459
330
  page: int = 1,
460
331
  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
-
332
+ ) -> GenPaginatedResponseBrand:
333
+ gen_client = self._build_generated_client()
496
334
  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)}"
335
+ response = await get_brands_asyncio_detailed(
336
+ client=gen_client, query=query, page=page, size=size
515
337
  )
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
- """
338
+ url = f"{_strip_v0_suffix(self.base_url)}/v0/brands"
339
+ _raise_for_status(url, response)
340
+ return response.parsed # type: ignore[return-value]
341
+ except Exception as e:
342
+ if isinstance(e, Channel3Error):
343
+ raise
344
+ raise Channel3ConnectionError(f"Request failed: {str(e)}")
345
+
346
+ async def get_brand(self, brand_id: str) -> Any:
547
347
  if not brand_id or not brand_id.strip():
548
348
  raise ValueError("brand_id cannot be empty")
549
349
 
550
- url = f"{self.base_url}/brands/{brand_id}"
551
-
350
+ gen_client = self._build_generated_client()
552
351
  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)}"
352
+ response = await get_brand_asyncio_detailed(
353
+ brand_id=brand_id, client=gen_client
568
354
  )
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)}")
355
+ url = f"{_strip_v0_suffix(self.base_url)}/v0/brands/{brand_id}"
356
+ _raise_for_status(url, response)
357
+ return response.parsed # type: ignore[return-value]
358
+ except Exception as e:
359
+ if isinstance(e, Channel3Error):
360
+ raise
361
+ raise Channel3ConnectionError(f"Request failed: {str(e)}")