channel3-sdk 0.1.1__tar.gz → 0.2.0__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.
- {channel3_sdk-0.1.1 → channel3_sdk-0.2.0}/PKG-INFO +80 -31
- {channel3_sdk-0.1.1 → channel3_sdk-0.2.0}/README.md +79 -30
- {channel3_sdk-0.1.1 → channel3_sdk-0.2.0}/channel3_sdk/__init__.py +7 -5
- {channel3_sdk-0.1.1 → channel3_sdk-0.2.0}/channel3_sdk/client.py +238 -5
- {channel3_sdk-0.1.1 → channel3_sdk-0.2.0}/channel3_sdk/models.py +43 -41
- {channel3_sdk-0.1.1 → channel3_sdk-0.2.0}/pyproject.toml +1 -1
- {channel3_sdk-0.1.1 → channel3_sdk-0.2.0}/channel3_sdk/exceptions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: channel3-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
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:
|
|
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"
|
|
60
|
-
|
|
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"
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
max_price
|
|
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
|
-
- `
|
|
163
|
-
- `
|
|
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
|
-
- `
|
|
171
|
-
- `
|
|
172
|
-
- `
|
|
173
|
-
- `
|
|
174
|
-
- `
|
|
175
|
-
- `
|
|
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
|
-
- `
|
|
179
|
-
- `
|
|
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:
|
|
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"
|
|
36
|
-
|
|
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"
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
max_price
|
|
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
|
-
- `
|
|
139
|
-
- `
|
|
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
|
-
- `
|
|
147
|
-
- `
|
|
148
|
-
- `
|
|
149
|
-
- `
|
|
150
|
-
- `
|
|
151
|
-
- `
|
|
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
|
-
- `
|
|
155
|
-
- `
|
|
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
|
-
|
|
10
|
-
|
|
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.
|
|
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
|
-
"
|
|
34
|
-
"
|
|
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,
|
|
@@ -86,7 +86,7 @@ class Channel3Client(BaseChannel3Client):
|
|
|
86
86
|
query: Optional[str] = None,
|
|
87
87
|
image_url: Optional[str] = None,
|
|
88
88
|
base64_image: Optional[str] = None,
|
|
89
|
-
filters: Optional[
|
|
89
|
+
filters: Optional[SearchFilters] = None,
|
|
90
90
|
limit: int = 20,
|
|
91
91
|
) -> List[Product]:
|
|
92
92
|
"""
|
|
@@ -96,7 +96,7 @@ class Channel3Client(BaseChannel3Client):
|
|
|
96
96
|
query: Text search query
|
|
97
97
|
image_url: URL to an image to use for visual search
|
|
98
98
|
base64_image: Base64-encoded image to use for visual search
|
|
99
|
-
filters: Search filters (SearchFilters object
|
|
99
|
+
filters: Search filters (SearchFilters object)
|
|
100
100
|
limit: Maximum number of products to return (default: 20)
|
|
101
101
|
|
|
102
102
|
Returns:
|
|
@@ -118,7 +118,7 @@ class Channel3Client(BaseChannel3Client):
|
|
|
118
118
|
|
|
119
119
|
# Multimodal search with filters
|
|
120
120
|
from channel3_sdk.models import SearchFilters
|
|
121
|
-
filters = SearchFilters(
|
|
121
|
+
filters = SearchFilters(min_price=50.0, max_price=150.0)
|
|
122
122
|
products = client.search(query="denim jacket", filters=filters)
|
|
123
123
|
```
|
|
124
124
|
"""
|
|
@@ -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)}")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Pydantic models for the Channel3 API."""
|
|
2
2
|
|
|
3
3
|
from enum import Enum
|
|
4
|
-
from typing import List, Optional,
|
|
4
|
+
from typing import List, Optional, Literal
|
|
5
5
|
from pydantic import BaseModel, Field
|
|
6
6
|
|
|
7
7
|
|
|
@@ -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
|
|
34
|
-
"""A
|
|
33
|
+
class Brand(BaseModel):
|
|
34
|
+
"""A brand."""
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
)
|
|
39
|
-
|
|
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
|
|
47
|
-
"""A
|
|
42
|
+
class Variant(BaseModel):
|
|
43
|
+
"""A product variant."""
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
title: str = Field(..., description="Title of the
|
|
51
|
-
image_url: str = Field(..., description="Image URL for the
|
|
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,44 @@ 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(
|
|
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
|
-
|
|
64
|
-
|
|
59
|
+
price: Price = Field(..., description="Price information")
|
|
60
|
+
availability: AvailabilityStatus = Field(
|
|
61
|
+
..., description="Product availability status"
|
|
65
62
|
)
|
|
66
|
-
|
|
67
|
-
default_factory=list, description="
|
|
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(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
83
|
-
|
|
78
|
+
price: Price = Field(..., description="Price information")
|
|
79
|
+
availability: AvailabilityStatus = Field(
|
|
80
|
+
..., description="Product availability status"
|
|
84
81
|
)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
default_factory=list, description="List of key product features"
|
|
82
|
+
gender: Optional[Literal["unisex", "men", "women"]] = Field(
|
|
83
|
+
None, description="Gender of the product, if applicable"
|
|
88
84
|
)
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
materials: Optional[List[str]] = Field(
|
|
86
|
+
None, description="List of materials, if applicable"
|
|
87
|
+
)
|
|
88
|
+
key_features: Optional[List[str]] = Field(
|
|
89
|
+
None, description="List of key product features"
|
|
90
|
+
)
|
|
91
|
+
variants: List[Variant] = Field(
|
|
92
|
+
default_factory=list, description="Product variants"
|
|
91
93
|
)
|
|
92
94
|
|
|
93
95
|
|
|
@@ -101,15 +103,17 @@ class SearchFilterPrice(BaseModel):
|
|
|
101
103
|
class SearchFilters(BaseModel):
|
|
102
104
|
"""Search filters for product search."""
|
|
103
105
|
|
|
104
|
-
|
|
106
|
+
brand_ids: Optional[List[str]] = Field(
|
|
107
|
+
None, description="List of brand IDs to filter by"
|
|
108
|
+
)
|
|
105
109
|
gender: Optional[Literal["male", "female", "unisex"]] = Field(
|
|
106
110
|
None, description="Gender to filter by"
|
|
107
111
|
)
|
|
108
112
|
price: Optional[SearchFilterPrice] = Field(
|
|
109
113
|
None, description="Price range to filter by"
|
|
110
114
|
)
|
|
111
|
-
availability: Optional[AvailabilityStatus] = Field(
|
|
112
|
-
None, description="Availability
|
|
115
|
+
availability: Optional[List[AvailabilityStatus]] = Field(
|
|
116
|
+
None, description="Availability statuses to filter by"
|
|
113
117
|
)
|
|
114
118
|
|
|
115
119
|
|
|
@@ -121,9 +125,7 @@ class SearchRequest(BaseModel):
|
|
|
121
125
|
base64_image: Optional[str] = Field(
|
|
122
126
|
None, description="Base64-encoded image for visual search"
|
|
123
127
|
)
|
|
124
|
-
filters: SearchFilters = Field(
|
|
125
|
-
default_factory=SearchFilters, description="Search filters"
|
|
126
|
-
)
|
|
127
128
|
limit: Optional[int] = Field(
|
|
128
129
|
default=20, description="Maximum number of results to return"
|
|
129
130
|
)
|
|
131
|
+
filters: Optional[SearchFilters] = Field(default=None, description="Search filters")
|
|
File without changes
|