channel3-sdk 0.0.1__tar.gz → 0.1.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.0/PKG-INFO +235 -0
- channel3_sdk-0.1.0/README.md +210 -0
- channel3_sdk-0.1.0/channel3_sdk/__init__.py +44 -0
- channel3_sdk-0.1.0/channel3_sdk/client.py +341 -0
- channel3_sdk-0.1.0/channel3_sdk/exceptions.py +48 -0
- channel3_sdk-0.1.0/channel3_sdk/models.py +116 -0
- channel3_sdk-0.1.0/pyproject.toml +40 -0
- channel3_sdk-0.0.1/PKG-INFO +0 -18
- channel3_sdk-0.0.1/README.md +0 -0
- channel3_sdk-0.0.1/channel3_sdk/__init__.py +0 -1
- channel3_sdk-0.0.1/channel3_sdk/client.py +0 -134
- channel3_sdk-0.0.1/pyproject.toml +0 -17
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: channel3-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The official Python SDK for Channel3 AI Shopping API
|
|
5
|
+
Home-page: https://github.com/channel3/sdk-python
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: ai,shopping,ecommerce,search,api
|
|
8
|
+
Author: Channel3
|
|
9
|
+
Author-email: alex@trychannel3.com
|
|
10
|
+
Requires-Python: >=3.8,<4.0
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Requires-Dist: aiohttp (>=3.9.0,<4.0.0)
|
|
20
|
+
Requires-Dist: pydantic (>=2.5.0,<3.0.0)
|
|
21
|
+
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
22
|
+
Project-URL: Repository, https://github.com/channel3/sdk-python
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Channel3 Python SDK
|
|
26
|
+
|
|
27
|
+
The official Python SDK for the [Channel3](https://trychannel3.com) AI Shopping API.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install channel3-sdk
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### Synchronous Client
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import os
|
|
41
|
+
from channel3_sdk import Channel3Client
|
|
42
|
+
|
|
43
|
+
# Initialize the client
|
|
44
|
+
client = Channel3Client(api_key="your_api_key_here")
|
|
45
|
+
# Or use environment variable: CHANNEL3_API_KEY
|
|
46
|
+
|
|
47
|
+
# Search for products
|
|
48
|
+
products = client.search(query="blue denim jacket")
|
|
49
|
+
|
|
50
|
+
for product in products:
|
|
51
|
+
print(f"Product: {product.title}")
|
|
52
|
+
print(f"Brand: {product.brand_name}")
|
|
53
|
+
print(f"Price: ${product.offers[0].price.price}")
|
|
54
|
+
print("---")
|
|
55
|
+
|
|
56
|
+
# Get detailed product information
|
|
57
|
+
product_detail = client.get_product("prod_123456")
|
|
58
|
+
print(f"Detailed info for: {product_detail.title}")
|
|
59
|
+
print(f"Materials: {product_detail.materials}")
|
|
60
|
+
print(f"Key features: {product_detail.key_features}")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Asynchronous Client
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import asyncio
|
|
67
|
+
from channel3_sdk import AsyncChannel3Client
|
|
68
|
+
|
|
69
|
+
async def main():
|
|
70
|
+
# Initialize the async client
|
|
71
|
+
client = AsyncChannel3Client(api_key="your_api_key_here")
|
|
72
|
+
|
|
73
|
+
# Search for products
|
|
74
|
+
products = await client.search(query="running shoes")
|
|
75
|
+
|
|
76
|
+
for product in products:
|
|
77
|
+
print(f"Product: {product.title}")
|
|
78
|
+
print(f"Score: {product.score}")
|
|
79
|
+
|
|
80
|
+
# Get detailed product information
|
|
81
|
+
if products:
|
|
82
|
+
product_detail = await client.get_product(products[0].id)
|
|
83
|
+
print(f"Gender: {product_detail.gender}")
|
|
84
|
+
|
|
85
|
+
# Run the async function
|
|
86
|
+
asyncio.run(main())
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Advanced Usage
|
|
90
|
+
|
|
91
|
+
### Visual Search
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
# Search by image URL
|
|
95
|
+
products = client.search(image_url="https://example.com/image.jpg")
|
|
96
|
+
|
|
97
|
+
# Search by base64 image
|
|
98
|
+
with open("image.jpg", "rb") as f:
|
|
99
|
+
import base64
|
|
100
|
+
base64_image = base64.b64encode(f.read()).decode()
|
|
101
|
+
products = client.search(base64_image=base64_image)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Multimodal Search
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# Combine text and image search
|
|
108
|
+
products = client.search(
|
|
109
|
+
query="blue denim jacket",
|
|
110
|
+
image_url="https://example.com/jacket.jpg"
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Search with Filters
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from channel3_sdk import SearchFilters
|
|
118
|
+
|
|
119
|
+
# Create search filters
|
|
120
|
+
filters = SearchFilters(
|
|
121
|
+
colors=["blue", "navy"],
|
|
122
|
+
materials=["cotton", "denim"],
|
|
123
|
+
min_price=50.0,
|
|
124
|
+
max_price=200.0
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Search with filters
|
|
128
|
+
products = client.search(
|
|
129
|
+
query="jacket",
|
|
130
|
+
filters=filters,
|
|
131
|
+
limit=10
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## API Reference
|
|
136
|
+
|
|
137
|
+
### Client Classes
|
|
138
|
+
|
|
139
|
+
#### `Channel3Client`
|
|
140
|
+
Synchronous client for the Channel3 API.
|
|
141
|
+
|
|
142
|
+
**Methods:**
|
|
143
|
+
- `search(query=None, image_url=None, base64_image=None, filters=None, limit=20)` → `List[Product]`
|
|
144
|
+
- `get_product(product_id)` → `ProductDetail`
|
|
145
|
+
|
|
146
|
+
#### `AsyncChannel3Client`
|
|
147
|
+
Asynchronous client for the Channel3 API.
|
|
148
|
+
|
|
149
|
+
**Methods:**
|
|
150
|
+
- `async search(query=None, image_url=None, base64_image=None, filters=None, limit=20)` → `List[Product]`
|
|
151
|
+
- `async get_product(product_id)` → `ProductDetail`
|
|
152
|
+
|
|
153
|
+
### Models
|
|
154
|
+
|
|
155
|
+
#### `Product`
|
|
156
|
+
- `id: str` - Unique product identifier
|
|
157
|
+
- `score: float` - Search relevance score
|
|
158
|
+
- `brand_name: str` - Brand name
|
|
159
|
+
- `title: str` - Product title
|
|
160
|
+
- `description: str` - Product description
|
|
161
|
+
- `image_url: str` - Main product image URL
|
|
162
|
+
- `offers: List[MerchantOffering]` - Available purchase options
|
|
163
|
+
- `family: List[FamilyMember]` - Related products
|
|
164
|
+
|
|
165
|
+
#### `ProductDetail`
|
|
166
|
+
- `brand_id: str` - Brand identifier
|
|
167
|
+
- `brand_name: str` - Brand name
|
|
168
|
+
- `title: str` - Product title
|
|
169
|
+
- `description: str` - Product description
|
|
170
|
+
- `image_urls: List[str]` - Product image URLs
|
|
171
|
+
- `merchant_offerings: List[MerchantOffering]` - Purchase options
|
|
172
|
+
- `gender: Literal["na", "men", "women"]` - Target gender
|
|
173
|
+
- `materials: Optional[List[str]]` - Product materials
|
|
174
|
+
- `key_features: List[str]` - Key product features
|
|
175
|
+
- `family_members: List[FamilyMember]` - Related products
|
|
176
|
+
|
|
177
|
+
#### `SearchFilters`
|
|
178
|
+
- `colors: Optional[List[str]]` - Color filters
|
|
179
|
+
- `materials: Optional[List[str]]` - Material filters
|
|
180
|
+
- `min_price: Optional[float]` - Minimum price
|
|
181
|
+
- `max_price: Optional[float]` - Maximum price
|
|
182
|
+
|
|
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
|
+
#### `Price`
|
|
190
|
+
- `price: float` - Current price
|
|
191
|
+
- `compare_at_price: Optional[float]` - Original price (if discounted)
|
|
192
|
+
- `currency: str` - Currency code
|
|
193
|
+
|
|
194
|
+
## Error Handling
|
|
195
|
+
|
|
196
|
+
The SDK provides specific exception types for different error conditions:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from channel3_sdk import (
|
|
200
|
+
Channel3AuthenticationError,
|
|
201
|
+
Channel3ValidationError,
|
|
202
|
+
Channel3NotFoundError,
|
|
203
|
+
Channel3ServerError,
|
|
204
|
+
Channel3ConnectionError
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
products = client.search(query="shoes")
|
|
209
|
+
except Channel3AuthenticationError:
|
|
210
|
+
print("Invalid API key")
|
|
211
|
+
except Channel3ValidationError as e:
|
|
212
|
+
print(f"Invalid request: {e.message}")
|
|
213
|
+
except Channel3NotFoundError:
|
|
214
|
+
print("Resource not found")
|
|
215
|
+
except Channel3ServerError:
|
|
216
|
+
print("Server error - please try again later")
|
|
217
|
+
except Channel3ConnectionError:
|
|
218
|
+
print("Connection error - check your internet connection")
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Environment Variables
|
|
222
|
+
|
|
223
|
+
- `CHANNEL3_API_KEY` - Your Channel3 API key
|
|
224
|
+
|
|
225
|
+
## Requirements
|
|
226
|
+
|
|
227
|
+
- Python 3.8+
|
|
228
|
+
- requests
|
|
229
|
+
- aiohttp
|
|
230
|
+
- pydantic
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT License
|
|
235
|
+
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Channel3 Python SDK
|
|
2
|
+
|
|
3
|
+
The official Python SDK for the [Channel3](https://trychannel3.com) AI Shopping API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install channel3-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
### Synchronous Client
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import os
|
|
17
|
+
from channel3_sdk import Channel3Client
|
|
18
|
+
|
|
19
|
+
# Initialize the client
|
|
20
|
+
client = Channel3Client(api_key="your_api_key_here")
|
|
21
|
+
# Or use environment variable: CHANNEL3_API_KEY
|
|
22
|
+
|
|
23
|
+
# Search for products
|
|
24
|
+
products = client.search(query="blue denim jacket")
|
|
25
|
+
|
|
26
|
+
for product in products:
|
|
27
|
+
print(f"Product: {product.title}")
|
|
28
|
+
print(f"Brand: {product.brand_name}")
|
|
29
|
+
print(f"Price: ${product.offers[0].price.price}")
|
|
30
|
+
print("---")
|
|
31
|
+
|
|
32
|
+
# Get detailed product information
|
|
33
|
+
product_detail = client.get_product("prod_123456")
|
|
34
|
+
print(f"Detailed info for: {product_detail.title}")
|
|
35
|
+
print(f"Materials: {product_detail.materials}")
|
|
36
|
+
print(f"Key features: {product_detail.key_features}")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Asynchronous Client
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import asyncio
|
|
43
|
+
from channel3_sdk import AsyncChannel3Client
|
|
44
|
+
|
|
45
|
+
async def main():
|
|
46
|
+
# Initialize the async client
|
|
47
|
+
client = AsyncChannel3Client(api_key="your_api_key_here")
|
|
48
|
+
|
|
49
|
+
# Search for products
|
|
50
|
+
products = await client.search(query="running shoes")
|
|
51
|
+
|
|
52
|
+
for product in products:
|
|
53
|
+
print(f"Product: {product.title}")
|
|
54
|
+
print(f"Score: {product.score}")
|
|
55
|
+
|
|
56
|
+
# Get detailed product information
|
|
57
|
+
if products:
|
|
58
|
+
product_detail = await client.get_product(products[0].id)
|
|
59
|
+
print(f"Gender: {product_detail.gender}")
|
|
60
|
+
|
|
61
|
+
# Run the async function
|
|
62
|
+
asyncio.run(main())
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Advanced Usage
|
|
66
|
+
|
|
67
|
+
### Visual Search
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
# Search by image URL
|
|
71
|
+
products = client.search(image_url="https://example.com/image.jpg")
|
|
72
|
+
|
|
73
|
+
# Search by base64 image
|
|
74
|
+
with open("image.jpg", "rb") as f:
|
|
75
|
+
import base64
|
|
76
|
+
base64_image = base64.b64encode(f.read()).decode()
|
|
77
|
+
products = client.search(base64_image=base64_image)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Multimodal Search
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
# Combine text and image search
|
|
84
|
+
products = client.search(
|
|
85
|
+
query="blue denim jacket",
|
|
86
|
+
image_url="https://example.com/jacket.jpg"
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Search with Filters
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from channel3_sdk import SearchFilters
|
|
94
|
+
|
|
95
|
+
# Create search filters
|
|
96
|
+
filters = SearchFilters(
|
|
97
|
+
colors=["blue", "navy"],
|
|
98
|
+
materials=["cotton", "denim"],
|
|
99
|
+
min_price=50.0,
|
|
100
|
+
max_price=200.0
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Search with filters
|
|
104
|
+
products = client.search(
|
|
105
|
+
query="jacket",
|
|
106
|
+
filters=filters,
|
|
107
|
+
limit=10
|
|
108
|
+
)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## API Reference
|
|
112
|
+
|
|
113
|
+
### Client Classes
|
|
114
|
+
|
|
115
|
+
#### `Channel3Client`
|
|
116
|
+
Synchronous client for the Channel3 API.
|
|
117
|
+
|
|
118
|
+
**Methods:**
|
|
119
|
+
- `search(query=None, image_url=None, base64_image=None, filters=None, limit=20)` → `List[Product]`
|
|
120
|
+
- `get_product(product_id)` → `ProductDetail`
|
|
121
|
+
|
|
122
|
+
#### `AsyncChannel3Client`
|
|
123
|
+
Asynchronous client for the Channel3 API.
|
|
124
|
+
|
|
125
|
+
**Methods:**
|
|
126
|
+
- `async search(query=None, image_url=None, base64_image=None, filters=None, limit=20)` → `List[Product]`
|
|
127
|
+
- `async get_product(product_id)` → `ProductDetail`
|
|
128
|
+
|
|
129
|
+
### Models
|
|
130
|
+
|
|
131
|
+
#### `Product`
|
|
132
|
+
- `id: str` - Unique product identifier
|
|
133
|
+
- `score: float` - Search relevance score
|
|
134
|
+
- `brand_name: str` - Brand name
|
|
135
|
+
- `title: str` - Product title
|
|
136
|
+
- `description: str` - Product description
|
|
137
|
+
- `image_url: str` - Main product image URL
|
|
138
|
+
- `offers: List[MerchantOffering]` - Available purchase options
|
|
139
|
+
- `family: List[FamilyMember]` - Related products
|
|
140
|
+
|
|
141
|
+
#### `ProductDetail`
|
|
142
|
+
- `brand_id: str` - Brand identifier
|
|
143
|
+
- `brand_name: str` - Brand name
|
|
144
|
+
- `title: str` - Product title
|
|
145
|
+
- `description: str` - Product description
|
|
146
|
+
- `image_urls: List[str]` - Product image URLs
|
|
147
|
+
- `merchant_offerings: List[MerchantOffering]` - Purchase options
|
|
148
|
+
- `gender: Literal["na", "men", "women"]` - Target gender
|
|
149
|
+
- `materials: Optional[List[str]]` - Product materials
|
|
150
|
+
- `key_features: List[str]` - Key product features
|
|
151
|
+
- `family_members: List[FamilyMember]` - Related products
|
|
152
|
+
|
|
153
|
+
#### `SearchFilters`
|
|
154
|
+
- `colors: Optional[List[str]]` - Color filters
|
|
155
|
+
- `materials: Optional[List[str]]` - Material filters
|
|
156
|
+
- `min_price: Optional[float]` - Minimum price
|
|
157
|
+
- `max_price: Optional[float]` - Maximum price
|
|
158
|
+
|
|
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
|
+
#### `Price`
|
|
166
|
+
- `price: float` - Current price
|
|
167
|
+
- `compare_at_price: Optional[float]` - Original price (if discounted)
|
|
168
|
+
- `currency: str` - Currency code
|
|
169
|
+
|
|
170
|
+
## Error Handling
|
|
171
|
+
|
|
172
|
+
The SDK provides specific exception types for different error conditions:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from channel3_sdk import (
|
|
176
|
+
Channel3AuthenticationError,
|
|
177
|
+
Channel3ValidationError,
|
|
178
|
+
Channel3NotFoundError,
|
|
179
|
+
Channel3ServerError,
|
|
180
|
+
Channel3ConnectionError
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
products = client.search(query="shoes")
|
|
185
|
+
except Channel3AuthenticationError:
|
|
186
|
+
print("Invalid API key")
|
|
187
|
+
except Channel3ValidationError as e:
|
|
188
|
+
print(f"Invalid request: {e.message}")
|
|
189
|
+
except Channel3NotFoundError:
|
|
190
|
+
print("Resource not found")
|
|
191
|
+
except Channel3ServerError:
|
|
192
|
+
print("Server error - please try again later")
|
|
193
|
+
except Channel3ConnectionError:
|
|
194
|
+
print("Connection error - check your internet connection")
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Environment Variables
|
|
198
|
+
|
|
199
|
+
- `CHANNEL3_API_KEY` - Your Channel3 API key
|
|
200
|
+
|
|
201
|
+
## Requirements
|
|
202
|
+
|
|
203
|
+
- Python 3.8+
|
|
204
|
+
- requests
|
|
205
|
+
- aiohttp
|
|
206
|
+
- pydantic
|
|
207
|
+
|
|
208
|
+
## License
|
|
209
|
+
|
|
210
|
+
MIT License
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Channel3 SDK for Python - Official SDK for the Channel3 AI Shopping API."""
|
|
2
|
+
|
|
3
|
+
from .client import Channel3Client, AsyncChannel3Client
|
|
4
|
+
from .models import (
|
|
5
|
+
Product,
|
|
6
|
+
ProductDetail,
|
|
7
|
+
SearchFilters,
|
|
8
|
+
SearchRequest,
|
|
9
|
+
MerchantOffering,
|
|
10
|
+
FamilyMember,
|
|
11
|
+
Price,
|
|
12
|
+
AvailabilityStatus,
|
|
13
|
+
)
|
|
14
|
+
from .exceptions import (
|
|
15
|
+
Channel3Error,
|
|
16
|
+
Channel3AuthenticationError,
|
|
17
|
+
Channel3ValidationError,
|
|
18
|
+
Channel3NotFoundError,
|
|
19
|
+
Channel3ServerError,
|
|
20
|
+
Channel3ConnectionError,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__version__ = "0.1.0"
|
|
24
|
+
__all__ = [
|
|
25
|
+
# Clients
|
|
26
|
+
"Channel3Client",
|
|
27
|
+
"AsyncChannel3Client",
|
|
28
|
+
# Models
|
|
29
|
+
"Product",
|
|
30
|
+
"ProductDetail",
|
|
31
|
+
"SearchFilters",
|
|
32
|
+
"SearchRequest",
|
|
33
|
+
"MerchantOffering",
|
|
34
|
+
"FamilyMember",
|
|
35
|
+
"Price",
|
|
36
|
+
"AvailabilityStatus",
|
|
37
|
+
# Exceptions
|
|
38
|
+
"Channel3Error",
|
|
39
|
+
"Channel3AuthenticationError",
|
|
40
|
+
"Channel3ValidationError",
|
|
41
|
+
"Channel3NotFoundError",
|
|
42
|
+
"Channel3ServerError",
|
|
43
|
+
"Channel3ConnectionError",
|
|
44
|
+
]
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# channel3_sdk/client.py
|
|
2
|
+
import os
|
|
3
|
+
import requests
|
|
4
|
+
from typing import List, Optional, Dict, Any, Union
|
|
5
|
+
import aiohttp
|
|
6
|
+
import asyncio
|
|
7
|
+
from pydantic import ValidationError
|
|
8
|
+
|
|
9
|
+
from .models import Product, ProductDetail, SearchFilters, SearchRequest
|
|
10
|
+
from .exceptions import (
|
|
11
|
+
Channel3Error,
|
|
12
|
+
Channel3AuthenticationError,
|
|
13
|
+
Channel3ValidationError,
|
|
14
|
+
Channel3NotFoundError,
|
|
15
|
+
Channel3ServerError,
|
|
16
|
+
Channel3ConnectionError,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BaseChannel3Client:
|
|
21
|
+
"""Base client with common functionality."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
|
|
24
|
+
"""
|
|
25
|
+
Initialize the base Channel3 client.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
api_key: Your Channel3 API key. If not provided, will look for CHANNEL3_API_KEY in environment.
|
|
29
|
+
base_url: Base URL for the API. Defaults to https://api.trychannel3.com/v0
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
ValueError: If no API key is provided and none is found in environment variables.
|
|
33
|
+
"""
|
|
34
|
+
self.api_key = api_key or os.getenv("CHANNEL3_API_KEY")
|
|
35
|
+
if not self.api_key:
|
|
36
|
+
raise ValueError(
|
|
37
|
+
"No API key provided. Set CHANNEL3_API_KEY environment variable or pass api_key parameter."
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
self.base_url = base_url or "https://api.trychannel3.com/v0"
|
|
41
|
+
self.headers = {"x-api-key": self.api_key, "Content-Type": "application/json"}
|
|
42
|
+
|
|
43
|
+
def _handle_error_response(
|
|
44
|
+
self, status_code: int, response_data: Dict[str, Any], url: str
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Handle error responses and raise appropriate exceptions."""
|
|
47
|
+
error_message = response_data.get(
|
|
48
|
+
"detail", f"Request failed with status {status_code}"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if status_code == 401:
|
|
52
|
+
raise Channel3AuthenticationError(
|
|
53
|
+
"Invalid or missing API key",
|
|
54
|
+
status_code=status_code,
|
|
55
|
+
response_data=response_data,
|
|
56
|
+
)
|
|
57
|
+
elif status_code == 404:
|
|
58
|
+
raise Channel3NotFoundError(
|
|
59
|
+
error_message, status_code=status_code, response_data=response_data
|
|
60
|
+
)
|
|
61
|
+
elif status_code == 422:
|
|
62
|
+
raise Channel3ValidationError(
|
|
63
|
+
f"Validation error: {error_message}",
|
|
64
|
+
status_code=status_code,
|
|
65
|
+
response_data=response_data,
|
|
66
|
+
)
|
|
67
|
+
elif status_code == 500:
|
|
68
|
+
raise Channel3ServerError(
|
|
69
|
+
"Internal server error",
|
|
70
|
+
status_code=status_code,
|
|
71
|
+
response_data=response_data,
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
raise Channel3Error(
|
|
75
|
+
f"Request to {url} failed: {error_message}",
|
|
76
|
+
status_code=status_code,
|
|
77
|
+
response_data=response_data,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class Channel3Client(BaseChannel3Client):
|
|
82
|
+
"""Synchronous Channel3 API client."""
|
|
83
|
+
|
|
84
|
+
def search(
|
|
85
|
+
self,
|
|
86
|
+
query: Optional[str] = None,
|
|
87
|
+
image_url: Optional[str] = None,
|
|
88
|
+
base64_image: Optional[str] = None,
|
|
89
|
+
filters: Optional[Union[SearchFilters, Dict[str, Any]]] = None,
|
|
90
|
+
limit: int = 20,
|
|
91
|
+
) -> List[Product]:
|
|
92
|
+
"""
|
|
93
|
+
Search for products using text query, image, or both with optional filters.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
query: Text search query
|
|
97
|
+
image_url: URL to an image to use for visual search
|
|
98
|
+
base64_image: Base64-encoded image to use for visual search
|
|
99
|
+
filters: Search filters (SearchFilters object or dict)
|
|
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(colors=["blue"], min_price=50.0, max_price=150.0)
|
|
122
|
+
products = client.search(query="denim jacket", filters=filters)
|
|
123
|
+
```
|
|
124
|
+
"""
|
|
125
|
+
# Build request payload
|
|
126
|
+
search_request = SearchRequest(
|
|
127
|
+
query=query,
|
|
128
|
+
image_url=image_url,
|
|
129
|
+
base64_image=base64_image,
|
|
130
|
+
filters=filters,
|
|
131
|
+
limit=limit,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
url = f"{self.base_url}/search"
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
response = requests.post(
|
|
138
|
+
url,
|
|
139
|
+
json=search_request.model_dump(exclude_none=True),
|
|
140
|
+
headers=self.headers,
|
|
141
|
+
timeout=30,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
response_data = response.json()
|
|
145
|
+
|
|
146
|
+
if response.status_code != 200:
|
|
147
|
+
self._handle_error_response(response.status_code, response_data, url)
|
|
148
|
+
|
|
149
|
+
# Parse and validate response
|
|
150
|
+
return [Product(**item) for item in response_data]
|
|
151
|
+
|
|
152
|
+
except requests.exceptions.ConnectionError as e:
|
|
153
|
+
raise Channel3ConnectionError(
|
|
154
|
+
f"Failed to connect to Channel3 API: {str(e)}"
|
|
155
|
+
)
|
|
156
|
+
except requests.exceptions.Timeout as e:
|
|
157
|
+
raise Channel3ConnectionError(f"Request timed out: {str(e)}")
|
|
158
|
+
except requests.exceptions.RequestException as e:
|
|
159
|
+
raise Channel3Error(f"Request failed: {str(e)}")
|
|
160
|
+
except ValidationError as e:
|
|
161
|
+
raise Channel3Error(f"Invalid response format: {str(e)}")
|
|
162
|
+
|
|
163
|
+
def get_product(self, product_id: str) -> ProductDetail:
|
|
164
|
+
"""
|
|
165
|
+
Get detailed information about a specific product by its ID.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
product_id: The unique identifier of the product
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
ProductDetail object with detailed product information
|
|
172
|
+
|
|
173
|
+
Raises:
|
|
174
|
+
Channel3AuthenticationError: If API key is invalid
|
|
175
|
+
Channel3NotFoundError: If product is not found
|
|
176
|
+
Channel3ValidationError: If product_id is invalid
|
|
177
|
+
Channel3ServerError: If server encounters an error
|
|
178
|
+
Channel3ConnectionError: If there are connection issues
|
|
179
|
+
|
|
180
|
+
Example:
|
|
181
|
+
```python
|
|
182
|
+
product_detail = client.get_product("prod_123456")
|
|
183
|
+
print(f"Product: {product_detail.title}")
|
|
184
|
+
print(f"Brand: {product_detail.brand_name}")
|
|
185
|
+
```
|
|
186
|
+
"""
|
|
187
|
+
if not product_id or not product_id.strip():
|
|
188
|
+
raise ValueError("product_id cannot be empty")
|
|
189
|
+
|
|
190
|
+
url = f"{self.base_url}/products/{product_id}"
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
response = requests.get(url, headers=self.headers, timeout=30)
|
|
194
|
+
response_data = response.json()
|
|
195
|
+
|
|
196
|
+
if response.status_code != 200:
|
|
197
|
+
self._handle_error_response(response.status_code, response_data, url)
|
|
198
|
+
|
|
199
|
+
# Parse and validate response
|
|
200
|
+
return ProductDetail(**response_data)
|
|
201
|
+
|
|
202
|
+
except requests.exceptions.ConnectionError as e:
|
|
203
|
+
raise Channel3ConnectionError(
|
|
204
|
+
f"Failed to connect to Channel3 API: {str(e)}"
|
|
205
|
+
)
|
|
206
|
+
except requests.exceptions.Timeout as e:
|
|
207
|
+
raise Channel3ConnectionError(f"Request timed out: {str(e)}")
|
|
208
|
+
except requests.exceptions.RequestException as e:
|
|
209
|
+
raise Channel3Error(f"Request failed: {str(e)}")
|
|
210
|
+
except ValidationError as e:
|
|
211
|
+
raise Channel3Error(f"Invalid response format: {str(e)}")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class AsyncChannel3Client(BaseChannel3Client):
|
|
215
|
+
"""Asynchronous Channel3 API client."""
|
|
216
|
+
|
|
217
|
+
async def search(
|
|
218
|
+
self,
|
|
219
|
+
query: Optional[str] = None,
|
|
220
|
+
image_url: Optional[str] = None,
|
|
221
|
+
base64_image: Optional[str] = None,
|
|
222
|
+
filters: Optional[Union[SearchFilters, Dict[str, Any]]] = None,
|
|
223
|
+
limit: int = 20,
|
|
224
|
+
) -> List[Product]:
|
|
225
|
+
"""
|
|
226
|
+
Search for products using text query, image, or both with optional filters.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
query: Text search query
|
|
230
|
+
image_url: URL to an image to use for visual search
|
|
231
|
+
base64_image: Base64-encoded image to use for visual search
|
|
232
|
+
filters: Search filters (SearchFilters object or dict)
|
|
233
|
+
limit: Maximum number of products to return (default: 20)
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
List of Product objects
|
|
237
|
+
|
|
238
|
+
Raises:
|
|
239
|
+
Channel3AuthenticationError: If API key is invalid
|
|
240
|
+
Channel3ValidationError: If request parameters are invalid
|
|
241
|
+
Channel3ServerError: If server encounters an error
|
|
242
|
+
Channel3ConnectionError: If there are connection issues
|
|
243
|
+
|
|
244
|
+
Examples:
|
|
245
|
+
```python
|
|
246
|
+
# Text search
|
|
247
|
+
products = await async_client.search(query="blue denim jacket")
|
|
248
|
+
|
|
249
|
+
# Image search
|
|
250
|
+
products = await async_client.search(image_url="https://example.com/image.jpg")
|
|
251
|
+
```
|
|
252
|
+
"""
|
|
253
|
+
# Build request payload
|
|
254
|
+
search_request = SearchRequest(
|
|
255
|
+
query=query,
|
|
256
|
+
image_url=image_url,
|
|
257
|
+
base64_image=base64_image,
|
|
258
|
+
filters=filters,
|
|
259
|
+
limit=limit,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
url = f"{self.base_url}/search"
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
async with aiohttp.ClientSession() as session:
|
|
266
|
+
async with session.post(
|
|
267
|
+
url,
|
|
268
|
+
json=search_request.model_dump(exclude_none=True),
|
|
269
|
+
headers=self.headers,
|
|
270
|
+
timeout=aiohttp.ClientTimeout(total=30),
|
|
271
|
+
) as response:
|
|
272
|
+
response_data = await response.json()
|
|
273
|
+
|
|
274
|
+
if response.status != 200:
|
|
275
|
+
self._handle_error_response(response.status, response_data, url)
|
|
276
|
+
|
|
277
|
+
# Parse and validate response
|
|
278
|
+
return [Product(**item) for item in response_data]
|
|
279
|
+
|
|
280
|
+
except aiohttp.ClientConnectionError as e:
|
|
281
|
+
raise Channel3ConnectionError(
|
|
282
|
+
f"Failed to connect to Channel3 API: {str(e)}"
|
|
283
|
+
)
|
|
284
|
+
except asyncio.TimeoutError as e:
|
|
285
|
+
raise Channel3ConnectionError(f"Request timed out: {str(e)}")
|
|
286
|
+
except aiohttp.ClientError as e:
|
|
287
|
+
raise Channel3Error(f"Request failed: {str(e)}")
|
|
288
|
+
except ValidationError as e:
|
|
289
|
+
raise Channel3Error(f"Invalid response format: {str(e)}")
|
|
290
|
+
|
|
291
|
+
async def get_product(self, product_id: str) -> ProductDetail:
|
|
292
|
+
"""
|
|
293
|
+
Get detailed information about a specific product by its ID.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
product_id: The unique identifier of the product
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
ProductDetail object with detailed product information
|
|
300
|
+
|
|
301
|
+
Raises:
|
|
302
|
+
Channel3AuthenticationError: If API key is invalid
|
|
303
|
+
Channel3NotFoundError: If product is not found
|
|
304
|
+
Channel3ValidationError: If product_id is invalid
|
|
305
|
+
Channel3ServerError: If server encounters an error
|
|
306
|
+
Channel3ConnectionError: If there are connection issues
|
|
307
|
+
|
|
308
|
+
Example:
|
|
309
|
+
```python
|
|
310
|
+
product_detail = await async_client.get_product("prod_123456")
|
|
311
|
+
print(f"Product: {product_detail.title}")
|
|
312
|
+
```
|
|
313
|
+
"""
|
|
314
|
+
if not product_id or not product_id.strip():
|
|
315
|
+
raise ValueError("product_id cannot be empty")
|
|
316
|
+
|
|
317
|
+
url = f"{self.base_url}/products/{product_id}"
|
|
318
|
+
|
|
319
|
+
try:
|
|
320
|
+
async with aiohttp.ClientSession() as session:
|
|
321
|
+
async with session.get(
|
|
322
|
+
url, headers=self.headers, timeout=aiohttp.ClientTimeout(total=30)
|
|
323
|
+
) as response:
|
|
324
|
+
response_data = await response.json()
|
|
325
|
+
|
|
326
|
+
if response.status != 200:
|
|
327
|
+
self._handle_error_response(response.status, response_data, url)
|
|
328
|
+
|
|
329
|
+
# Parse and validate response
|
|
330
|
+
return ProductDetail(**response_data)
|
|
331
|
+
|
|
332
|
+
except aiohttp.ClientConnectionError as e:
|
|
333
|
+
raise Channel3ConnectionError(
|
|
334
|
+
f"Failed to connect to Channel3 API: {str(e)}"
|
|
335
|
+
)
|
|
336
|
+
except asyncio.TimeoutError as e:
|
|
337
|
+
raise Channel3ConnectionError(f"Request timed out: {str(e)}")
|
|
338
|
+
except aiohttp.ClientError as e:
|
|
339
|
+
raise Channel3Error(f"Request failed: {str(e)}")
|
|
340
|
+
except ValidationError as e:
|
|
341
|
+
raise Channel3Error(f"Invalid response format: {str(e)}")
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Custom exceptions for the Channel3 SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Dict, Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Channel3Error(Exception):
|
|
7
|
+
"""Base exception for all Channel3 SDK errors."""
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
message: str,
|
|
12
|
+
status_code: Optional[int] = None,
|
|
13
|
+
response_data: Optional[Dict[str, Any]] = None,
|
|
14
|
+
):
|
|
15
|
+
super().__init__(message)
|
|
16
|
+
self.message = message
|
|
17
|
+
self.status_code = status_code
|
|
18
|
+
self.response_data = response_data or {}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Channel3AuthenticationError(Channel3Error):
|
|
22
|
+
"""Raised when authentication fails (401)."""
|
|
23
|
+
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Channel3ValidationError(Channel3Error):
|
|
28
|
+
"""Raised when request validation fails (422)."""
|
|
29
|
+
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Channel3NotFoundError(Channel3Error):
|
|
34
|
+
"""Raised when a resource is not found (404)."""
|
|
35
|
+
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Channel3ServerError(Channel3Error):
|
|
40
|
+
"""Raised when the server encounters an error (500)."""
|
|
41
|
+
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Channel3ConnectionError(Channel3Error):
|
|
46
|
+
"""Raised when there are connection issues."""
|
|
47
|
+
|
|
48
|
+
pass
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Pydantic models for the Channel3 API."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import List, Optional, Union, Literal
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AvailabilityStatus(str, Enum):
|
|
9
|
+
"""Availability status of a product."""
|
|
10
|
+
|
|
11
|
+
IN_STOCK = "InStock"
|
|
12
|
+
OUT_OF_STOCK = "OutOfStock"
|
|
13
|
+
PRE_ORDER = "PreOrder"
|
|
14
|
+
LIMITED_AVAILABILITY = "LimitedAvailability"
|
|
15
|
+
BACK_ORDER = "BackOrder"
|
|
16
|
+
DISCONTINUED = "Discontinued"
|
|
17
|
+
SOLD_OUT = "SoldOut"
|
|
18
|
+
UNKNOWN = "Unknown"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Price(BaseModel):
|
|
22
|
+
"""Price information for a product."""
|
|
23
|
+
|
|
24
|
+
price: float = Field(
|
|
25
|
+
..., description="The current price of the product, including any discounts."
|
|
26
|
+
)
|
|
27
|
+
compare_at_price: Optional[float] = Field(
|
|
28
|
+
None, description="The original price of the product before any discounts."
|
|
29
|
+
)
|
|
30
|
+
currency: str = Field(..., description="The currency code of the product.")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class MerchantOffering(BaseModel):
|
|
34
|
+
"""A merchant offering a product."""
|
|
35
|
+
|
|
36
|
+
url: str = Field(
|
|
37
|
+
default="https://buy.trychannel3.com", description="URL to purchase the product"
|
|
38
|
+
)
|
|
39
|
+
merchant_name: str = Field(..., description="Name of the merchant")
|
|
40
|
+
price: Price = Field(..., description="Price information")
|
|
41
|
+
availability: AvailabilityStatus = Field(
|
|
42
|
+
..., description="Product availability status"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class FamilyMember(BaseModel):
|
|
47
|
+
"""A family member product."""
|
|
48
|
+
|
|
49
|
+
id: str = Field(..., description="Unique identifier for the family member")
|
|
50
|
+
title: str = Field(..., description="Title of the family member product")
|
|
51
|
+
image_url: str = Field(..., description="Image URL for the family member product")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Product(BaseModel):
|
|
55
|
+
"""A product returned from search."""
|
|
56
|
+
|
|
57
|
+
id: str = Field(..., description="Unique identifier for the product")
|
|
58
|
+
score: float = Field(..., description="Relevance score for the search query")
|
|
59
|
+
brand_name: str = Field(..., description="Brand name of the product")
|
|
60
|
+
title: str = Field(..., description="Product title")
|
|
61
|
+
description: str = Field(..., description="Product description")
|
|
62
|
+
image_url: str = Field(..., description="Main product image URL")
|
|
63
|
+
offers: List[MerchantOffering] = Field(
|
|
64
|
+
..., description="List of merchant offerings"
|
|
65
|
+
)
|
|
66
|
+
family: List[FamilyMember] = Field(
|
|
67
|
+
default_factory=list, description="Related family products"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ProductDetail(BaseModel):
|
|
72
|
+
"""Detailed information about a product."""
|
|
73
|
+
|
|
74
|
+
brand_id: str = Field(..., description="Unique identifier for the brand")
|
|
75
|
+
brand_name: str = Field(..., description="Brand name of the product")
|
|
76
|
+
title: str = Field(..., description="Product title")
|
|
77
|
+
description: str = Field(..., description="Product description")
|
|
78
|
+
image_urls: List[str] = Field(..., description="List of product image URLs")
|
|
79
|
+
merchant_offerings: List[MerchantOffering] = Field(
|
|
80
|
+
..., description="List of merchant offerings"
|
|
81
|
+
)
|
|
82
|
+
gender: Literal["na", "men", "women"] = Field(
|
|
83
|
+
default="na", description="Target gender"
|
|
84
|
+
)
|
|
85
|
+
materials: Optional[List[str]] = Field(None, description="List of materials")
|
|
86
|
+
key_features: List[str] = Field(
|
|
87
|
+
default_factory=list, description="List of key product features"
|
|
88
|
+
)
|
|
89
|
+
family_members: List[FamilyMember] = Field(
|
|
90
|
+
default_factory=list, description="Related family products"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class SearchFilters(BaseModel):
|
|
95
|
+
"""Search filters for product search."""
|
|
96
|
+
|
|
97
|
+
colors: Optional[List[str]] = Field(None, description="List of colors to filter by")
|
|
98
|
+
materials: Optional[List[str]] = Field(
|
|
99
|
+
None, description="List of materials to filter by"
|
|
100
|
+
)
|
|
101
|
+
min_price: Optional[float] = Field(None, description="Minimum price filter")
|
|
102
|
+
max_price: Optional[float] = Field(None, description="Maximum price filter")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class SearchRequest(BaseModel):
|
|
106
|
+
"""Request model for product search."""
|
|
107
|
+
|
|
108
|
+
query: Optional[str] = Field(None, description="Text search query")
|
|
109
|
+
image_url: Optional[str] = Field(None, description="URL of image for visual search")
|
|
110
|
+
base64_image: Optional[str] = Field(
|
|
111
|
+
None, description="Base64-encoded image for visual search"
|
|
112
|
+
)
|
|
113
|
+
filters: Optional[SearchFilters] = Field(None, description="Search filters")
|
|
114
|
+
limit: Optional[int] = Field(
|
|
115
|
+
default=20, description="Maximum number of results to return"
|
|
116
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "channel3-sdk"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "The official Python SDK for Channel3 AI Shopping API"
|
|
5
|
+
authors = ["Channel3 <alex@trychannel3.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
packages = [{ include = "channel3_sdk" }]
|
|
9
|
+
repository = "https://github.com/channel3/sdk-python"
|
|
10
|
+
keywords = ["ai", "shopping", "ecommerce", "search", "api"]
|
|
11
|
+
|
|
12
|
+
[tool.poetry.dependencies]
|
|
13
|
+
python = "^3.8"
|
|
14
|
+
requests = "^2.31.0"
|
|
15
|
+
aiohttp = "^3.9.0"
|
|
16
|
+
pydantic = "^2.5.0"
|
|
17
|
+
|
|
18
|
+
[tool.poetry.group.dev.dependencies]
|
|
19
|
+
pytest = "^7.4.0"
|
|
20
|
+
pytest-asyncio = "^0.21.0"
|
|
21
|
+
black = "^23.0.0"
|
|
22
|
+
isort = "^5.12.0"
|
|
23
|
+
mypy = "^1.7.0"
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["poetry-core"]
|
|
27
|
+
build-backend = "poetry.core.masonry.api"
|
|
28
|
+
|
|
29
|
+
[tool.black]
|
|
30
|
+
line-length = 88
|
|
31
|
+
target-version = ['py38']
|
|
32
|
+
|
|
33
|
+
[tool.isort]
|
|
34
|
+
profile = "black"
|
|
35
|
+
|
|
36
|
+
[tool.mypy]
|
|
37
|
+
python_version = "3.8"
|
|
38
|
+
warn_return_any = true
|
|
39
|
+
warn_unused_configs = true
|
|
40
|
+
disallow_untyped_defs = true
|
channel3_sdk-0.0.1/PKG-INFO
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: channel3-sdk
|
|
3
|
-
Version: 0.0.1
|
|
4
|
-
Summary: The API for AI Shopping
|
|
5
|
-
License: MIT
|
|
6
|
-
Author: Alex
|
|
7
|
-
Author-email: alex@trychannel3.com
|
|
8
|
-
Requires-Python: >=3.10,<4.0
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
-
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
|
|
18
|
-
|
channel3_sdk-0.0.1/README.md
DELETED
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .client import Channel3Client
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
# channel3_sdk/client.py
|
|
2
|
-
import os
|
|
3
|
-
import requests
|
|
4
|
-
from typing import List, Optional, Dict, Any, Union
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class Channel3Client:
|
|
8
|
-
def __init__(self, api_key: str = None):
|
|
9
|
-
"""
|
|
10
|
-
Initialize a Channel3 API client.
|
|
11
|
-
|
|
12
|
-
The client provides methods to interact with Channel3's product search and retrieval API.
|
|
13
|
-
|
|
14
|
-
Args:
|
|
15
|
-
api_key: Your Channel3 API key. If not provided, will look for CHANNEL3_API_KEY in environment.
|
|
16
|
-
|
|
17
|
-
Raises:
|
|
18
|
-
ValueError: If no API key is provided and none is found in environment variables.
|
|
19
|
-
|
|
20
|
-
Example:
|
|
21
|
-
```python
|
|
22
|
-
# Initialize with explicit API key
|
|
23
|
-
client = Channel3Client(api_key="your_api_key")
|
|
24
|
-
|
|
25
|
-
# Or use environment variable
|
|
26
|
-
# os.environ["CHANNEL3_API_KEY"] = "your_api_key"
|
|
27
|
-
# client = Channel3Client()
|
|
28
|
-
```
|
|
29
|
-
"""
|
|
30
|
-
self.api_key = api_key or os.getenv("CHANNEL3_API_KEY")
|
|
31
|
-
if not self.api_key:
|
|
32
|
-
raise ValueError("No API key provided for Channel3Client")
|
|
33
|
-
self.headers = {"x-api-key": self.api_key}
|
|
34
|
-
self.api_version = "v0"
|
|
35
|
-
self.base_url = f"https://api.channel3.com/{self.api_version}"
|
|
36
|
-
|
|
37
|
-
def search(
|
|
38
|
-
self,
|
|
39
|
-
query: Optional[str] = None,
|
|
40
|
-
image_url: Optional[str] = None,
|
|
41
|
-
base64_image: Optional[str] = None,
|
|
42
|
-
filters: Optional[Dict[str, Any]] = None,
|
|
43
|
-
limit: int = 20,
|
|
44
|
-
) -> List[Dict[str, Any]]:
|
|
45
|
-
"""
|
|
46
|
-
Search for products using text query, image, or both with optional filters.
|
|
47
|
-
|
|
48
|
-
Args:
|
|
49
|
-
query: Text search query
|
|
50
|
-
image_url: URL to an image to use for visual search
|
|
51
|
-
base64_image: Base64-encoded image to use for visual search
|
|
52
|
-
filters: Dict containing optional filters with these possible keys:
|
|
53
|
-
- colors: List of color strings
|
|
54
|
-
- materials: List of material strings
|
|
55
|
-
- min_price: Minimum price (float)
|
|
56
|
-
- max_price: Maximum price (float)
|
|
57
|
-
limit: Maximum number of products to return (default: 20)
|
|
58
|
-
|
|
59
|
-
Returns:
|
|
60
|
-
List of product dictionaries containing:
|
|
61
|
-
id, url, score, price, brand_id, brand_name, title, description, image_url, variants
|
|
62
|
-
|
|
63
|
-
Examples:
|
|
64
|
-
```python
|
|
65
|
-
# Text search
|
|
66
|
-
products = client.search(query="blue denim jacket")
|
|
67
|
-
|
|
68
|
-
# Image search
|
|
69
|
-
products = client.search(image_url="https://example.com/image.jpg")
|
|
70
|
-
|
|
71
|
-
# Multimodal search
|
|
72
|
-
products = client.search(
|
|
73
|
-
query="blue denim jacket",
|
|
74
|
-
base64_image="data:image/jpeg;base64,...",
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
# Search with filters
|
|
78
|
-
products = client.search(
|
|
79
|
-
query="running shoes",
|
|
80
|
-
filters={
|
|
81
|
-
"colors": ["black", "white"],
|
|
82
|
-
"min_price": 50.0,
|
|
83
|
-
"max_price": 150.0
|
|
84
|
-
},
|
|
85
|
-
limit=10
|
|
86
|
-
)
|
|
87
|
-
```
|
|
88
|
-
"""
|
|
89
|
-
payload = {
|
|
90
|
-
"query": query,
|
|
91
|
-
"image_url": image_url,
|
|
92
|
-
"base64_image": base64_image,
|
|
93
|
-
"limit": limit,
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if filters:
|
|
97
|
-
payload["filters"] = filters
|
|
98
|
-
|
|
99
|
-
response = requests.post(
|
|
100
|
-
f"{self.base_url}/search",
|
|
101
|
-
json={k: v for k, v in payload.items() if v is not None},
|
|
102
|
-
headers=self.headers,
|
|
103
|
-
)
|
|
104
|
-
response.raise_for_status()
|
|
105
|
-
return response.json()
|
|
106
|
-
|
|
107
|
-
def get_product(self, product_id: str) -> Dict[str, Any]:
|
|
108
|
-
"""
|
|
109
|
-
Get detailed information about a specific product by its ID.
|
|
110
|
-
|
|
111
|
-
Args:
|
|
112
|
-
product_id: The unique identifier of the product
|
|
113
|
-
|
|
114
|
-
Returns:
|
|
115
|
-
Dictionary containing detailed product information:
|
|
116
|
-
url, brand_id, brand_name, title, description, image_urls, variants,
|
|
117
|
-
price, gender, materials, key_features
|
|
118
|
-
|
|
119
|
-
Raises:
|
|
120
|
-
requests.HTTPError: If the product does not exist or other API errors occur
|
|
121
|
-
|
|
122
|
-
Example:
|
|
123
|
-
```python
|
|
124
|
-
product_detail = client.get_product("prod_123456")
|
|
125
|
-
print(product_detail["title"])
|
|
126
|
-
print(product_detail["price"]["price"])
|
|
127
|
-
```
|
|
128
|
-
"""
|
|
129
|
-
response = requests.get(
|
|
130
|
-
f"{self.base_url}/products/{product_id}",
|
|
131
|
-
headers=self.headers,
|
|
132
|
-
)
|
|
133
|
-
response.raise_for_status()
|
|
134
|
-
return response.json()
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
[tool.poetry]
|
|
2
|
-
name = "channel3-sdk"
|
|
3
|
-
version = "0.0.1"
|
|
4
|
-
description = "The API for AI Shopping"
|
|
5
|
-
authors = ["Alex <alex@trychannel3.com>"]
|
|
6
|
-
readme = "README.md"
|
|
7
|
-
license = "MIT"
|
|
8
|
-
packages = [{ include = "channel3_sdk" }]
|
|
9
|
-
|
|
10
|
-
[tool.poetry.dependencies]
|
|
11
|
-
python = "^3.10"
|
|
12
|
-
requests = "^2.31.0"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
[build-system]
|
|
16
|
-
requires = ["poetry-core"]
|
|
17
|
-
build-backend = "poetry.core.masonry.api"
|