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.
- channel3_sdk/__init__.py +23 -17
- channel3_sdk/_gen/.gitignore +23 -0
- channel3_sdk/_gen/README.md +124 -0
- channel3_sdk/_gen/fast_api_client/__init__.py +8 -0
- channel3_sdk/_gen/fast_api_client/api/__init__.py +1 -0
- channel3_sdk/_gen/fast_api_client/api/channel3_api/__init__.py +1 -0
- channel3_sdk/_gen/fast_api_client/api/channel3_api/get_brand_detail_v0_brands_brand_id_get.py +179 -0
- channel3_sdk/_gen/fast_api_client/api/channel3_api/get_brands_v0_brands_get.py +218 -0
- channel3_sdk/_gen/fast_api_client/api/channel3_api/get_product_detail_v0_products_product_id_get.py +179 -0
- channel3_sdk/_gen/fast_api_client/api/channel3_api/search_v0_search_post.py +193 -0
- channel3_sdk/_gen/fast_api_client/api/default/__init__.py +1 -0
- channel3_sdk/_gen/fast_api_client/api/default/root_get.py +79 -0
- channel3_sdk/_gen/fast_api_client/client.py +268 -0
- channel3_sdk/_gen/fast_api_client/errors.py +16 -0
- channel3_sdk/_gen/fast_api_client/models/__init__.py +35 -0
- channel3_sdk/_gen/fast_api_client/models/availability_status.py +15 -0
- channel3_sdk/_gen/fast_api_client/models/brand.py +109 -0
- channel3_sdk/_gen/fast_api_client/models/error_response.py +59 -0
- channel3_sdk/_gen/fast_api_client/models/paginated_response_brand.py +83 -0
- channel3_sdk/_gen/fast_api_client/models/pagination_meta.py +84 -0
- channel3_sdk/_gen/fast_api_client/models/price.py +89 -0
- channel3_sdk/_gen/fast_api_client/models/product.py +166 -0
- channel3_sdk/_gen/fast_api_client/models/product_detail.py +306 -0
- channel3_sdk/_gen/fast_api_client/models/product_detail_gender_type_0.py +10 -0
- channel3_sdk/_gen/fast_api_client/models/search_config.py +69 -0
- channel3_sdk/_gen/fast_api_client/models/search_filter_price.py +92 -0
- channel3_sdk/_gen/fast_api_client/models/search_filters.py +191 -0
- channel3_sdk/_gen/fast_api_client/models/search_filters_gender_type_0.py +10 -0
- channel3_sdk/_gen/fast_api_client/models/search_request.py +191 -0
- channel3_sdk/_gen/fast_api_client/models/variant.py +75 -0
- channel3_sdk/_gen/fast_api_client/py.typed +1 -0
- channel3_sdk/_gen/fast_api_client/types.py +54 -0
- channel3_sdk/_gen/pyproject.toml +26 -0
- channel3_sdk/client.py +266 -479
- {channel3_sdk-0.2.1.dist-info → channel3_sdk-1.0.0.dist-info}/METADATA +83 -26
- channel3_sdk-1.0.0.dist-info/RECORD +38 -0
- {channel3_sdk-0.2.1.dist-info → channel3_sdk-1.0.0.dist-info}/WHEEL +1 -1
- channel3_sdk/models.py +0 -135
- 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
|
|
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 .
|
|
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
|
-
|
|
58
|
+
Channel3ConnectionError,
|
|
59
|
+
Channel3Error,
|
|
14
60
|
Channel3NotFoundError,
|
|
15
61
|
Channel3ServerError,
|
|
16
|
-
|
|
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
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
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
|
-
|
|
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[
|
|
181
|
+
filters: Optional[Union[GenSearchFilters, Dict[str, Any]]] = None,
|
|
90
182
|
limit: int = 20,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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 =
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
|
|
191
|
-
|
|
222
|
+
gen_client = self._build_generated_client()
|
|
192
223
|
try:
|
|
193
|
-
response =
|
|
194
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
) ->
|
|
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 =
|
|
255
|
-
|
|
243
|
+
response = get_brands_sync_detailed(
|
|
244
|
+
client=gen_client, query=query, page=page, size=size
|
|
256
245
|
)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
304
|
-
|
|
258
|
+
gen_client = self._build_generated_client()
|
|
305
259
|
try:
|
|
306
|
-
response =
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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[
|
|
278
|
+
filters: Optional[Union[GenSearchFilters, Dict[str, Any]]] = None,
|
|
336
279
|
limit: int = 20,
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
|
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
|
-
|
|
379
|
-
|
|
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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
431
|
-
|
|
314
|
+
gen_client = self._build_generated_client()
|
|
432
315
|
try:
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
) ->
|
|
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
|
-
|
|
498
|
-
|
|
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
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
-
|
|
551
|
-
|
|
350
|
+
gen_client = self._build_generated_client()
|
|
552
351
|
try:
|
|
553
|
-
|
|
554
|
-
|
|
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
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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)}")
|