channel3-sdk 1.0.0__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (85) hide show
  1. channel3_sdk/__init__.py +94 -44
  2. channel3_sdk/_base_client.py +1995 -0
  3. channel3_sdk/_client.py +549 -0
  4. channel3_sdk/_compat.py +219 -0
  5. channel3_sdk/_constants.py +14 -0
  6. channel3_sdk/_exceptions.py +108 -0
  7. channel3_sdk/_files.py +123 -0
  8. channel3_sdk/_models.py +829 -0
  9. channel3_sdk/_qs.py +150 -0
  10. channel3_sdk/_resource.py +43 -0
  11. channel3_sdk/_response.py +832 -0
  12. channel3_sdk/_streaming.py +333 -0
  13. channel3_sdk/_types.py +253 -0
  14. channel3_sdk/_utils/__init__.py +64 -0
  15. channel3_sdk/_utils/_compat.py +45 -0
  16. channel3_sdk/_utils/_datetime_parse.py +136 -0
  17. channel3_sdk/_utils/_logs.py +25 -0
  18. channel3_sdk/_utils/_proxy.py +65 -0
  19. channel3_sdk/_utils/_reflection.py +42 -0
  20. channel3_sdk/_utils/_resources_proxy.py +24 -0
  21. channel3_sdk/_utils/_streams.py +12 -0
  22. channel3_sdk/_utils/_sync.py +86 -0
  23. channel3_sdk/_utils/_transform.py +457 -0
  24. channel3_sdk/_utils/_typing.py +156 -0
  25. channel3_sdk/_utils/_utils.py +421 -0
  26. channel3_sdk/_version.py +4 -0
  27. channel3_sdk/lib/.keep +4 -0
  28. channel3_sdk/py.typed +0 -0
  29. channel3_sdk/resources/__init__.py +61 -0
  30. channel3_sdk/resources/brands.py +268 -0
  31. channel3_sdk/resources/enrich.py +167 -0
  32. channel3_sdk/resources/products.py +163 -0
  33. channel3_sdk/resources/search.py +227 -0
  34. channel3_sdk/types/__init__.py +15 -0
  35. channel3_sdk/types/availability_status.py +9 -0
  36. channel3_sdk/types/brand.py +17 -0
  37. channel3_sdk/types/brand_list_params.py +16 -0
  38. channel3_sdk/types/brand_list_response.py +25 -0
  39. channel3_sdk/types/enrich_enrich_url_params.py +12 -0
  40. channel3_sdk/types/enrich_enrich_url_response.py +20 -0
  41. channel3_sdk/types/price.py +18 -0
  42. channel3_sdk/types/product_retrieve_response.py +39 -0
  43. channel3_sdk/types/search_perform_params.py +61 -0
  44. channel3_sdk/types/search_perform_response.py +36 -0
  45. channel3_sdk/types/variant.py +13 -0
  46. channel3_sdk-2.0.0.dist-info/METADATA +414 -0
  47. channel3_sdk-2.0.0.dist-info/RECORD +49 -0
  48. {channel3_sdk-1.0.0.dist-info → channel3_sdk-2.0.0.dist-info}/WHEEL +1 -1
  49. channel3_sdk-2.0.0.dist-info/licenses/LICENSE +201 -0
  50. channel3_sdk/_gen/.gitignore +0 -23
  51. channel3_sdk/_gen/README.md +0 -124
  52. channel3_sdk/_gen/fast_api_client/__init__.py +0 -8
  53. channel3_sdk/_gen/fast_api_client/api/__init__.py +0 -1
  54. channel3_sdk/_gen/fast_api_client/api/channel3_api/__init__.py +0 -1
  55. channel3_sdk/_gen/fast_api_client/api/channel3_api/get_brand_detail_v0_brands_brand_id_get.py +0 -179
  56. channel3_sdk/_gen/fast_api_client/api/channel3_api/get_brands_v0_brands_get.py +0 -218
  57. channel3_sdk/_gen/fast_api_client/api/channel3_api/get_product_detail_v0_products_product_id_get.py +0 -179
  58. channel3_sdk/_gen/fast_api_client/api/channel3_api/search_v0_search_post.py +0 -193
  59. channel3_sdk/_gen/fast_api_client/api/default/__init__.py +0 -1
  60. channel3_sdk/_gen/fast_api_client/api/default/root_get.py +0 -79
  61. channel3_sdk/_gen/fast_api_client/client.py +0 -268
  62. channel3_sdk/_gen/fast_api_client/errors.py +0 -16
  63. channel3_sdk/_gen/fast_api_client/models/__init__.py +0 -35
  64. channel3_sdk/_gen/fast_api_client/models/availability_status.py +0 -15
  65. channel3_sdk/_gen/fast_api_client/models/brand.py +0 -109
  66. channel3_sdk/_gen/fast_api_client/models/error_response.py +0 -59
  67. channel3_sdk/_gen/fast_api_client/models/paginated_response_brand.py +0 -83
  68. channel3_sdk/_gen/fast_api_client/models/pagination_meta.py +0 -84
  69. channel3_sdk/_gen/fast_api_client/models/price.py +0 -89
  70. channel3_sdk/_gen/fast_api_client/models/product.py +0 -166
  71. channel3_sdk/_gen/fast_api_client/models/product_detail.py +0 -306
  72. channel3_sdk/_gen/fast_api_client/models/product_detail_gender_type_0.py +0 -10
  73. channel3_sdk/_gen/fast_api_client/models/search_config.py +0 -69
  74. channel3_sdk/_gen/fast_api_client/models/search_filter_price.py +0 -92
  75. channel3_sdk/_gen/fast_api_client/models/search_filters.py +0 -191
  76. channel3_sdk/_gen/fast_api_client/models/search_filters_gender_type_0.py +0 -10
  77. channel3_sdk/_gen/fast_api_client/models/search_request.py +0 -191
  78. channel3_sdk/_gen/fast_api_client/models/variant.py +0 -75
  79. channel3_sdk/_gen/fast_api_client/py.typed +0 -1
  80. channel3_sdk/_gen/fast_api_client/types.py +0 -54
  81. channel3_sdk/_gen/pyproject.toml +0 -26
  82. channel3_sdk/client.py +0 -361
  83. channel3_sdk/exceptions.py +0 -48
  84. channel3_sdk-1.0.0.dist-info/METADATA +0 -341
  85. channel3_sdk-1.0.0.dist-info/RECORD +0 -38
channel3_sdk/client.py DELETED
@@ -1,361 +0,0 @@
1
- # channel3_sdk/client.py
2
- import os
3
- from typing import Any, Dict, List, Optional, Union
4
-
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
56
- from .exceptions import (
57
- Channel3AuthenticationError,
58
- Channel3ConnectionError,
59
- Channel3Error,
60
- Channel3NotFoundError,
61
- Channel3ServerError,
62
- Channel3ValidationError,
63
- )
64
-
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
-
148
- class BaseChannel3Client:
149
- """Base client with common functionality."""
150
-
151
- def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
152
- self.api_key = api_key or os.getenv("CHANNEL3_API_KEY")
153
- if not self.api_key:
154
- raise ValueError(
155
- "No API key provided. Set CHANNEL3_API_KEY environment variable or pass api_key parameter."
156
- )
157
-
158
- self.base_url = base_url or "https://api.trychannel3.com/v0"
159
- self.headers = {"x-api-key": self.api_key, "Content-Type": "application/json"}
160
-
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",
168
- )
169
- client = client.with_headers({"Content-Type": "application/json"})
170
- return client
171
-
172
-
173
- class Channel3Client(BaseChannel3Client):
174
- """Synchronous Channel3 API client (returns generated models)."""
175
-
176
- def search(
177
- self,
178
- query: Optional[str] = None,
179
- image_url: Optional[str] = None,
180
- base64_image: Optional[str] = None,
181
- filters: Optional[Union[GenSearchFilters, Dict[str, Any]]] = None,
182
- limit: int = 20,
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(
188
- query=query,
189
- image_url=image_url,
190
- base64_image=base64_image,
191
- filters=_convert_filters_to_generated(filters)
192
- if filters is not None
193
- else UNSET, # type: ignore[arg-type]
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,
199
- )
200
-
201
- try:
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")
217
-
218
- def get_product(self, product_id: str) -> GenProductDetail:
219
- if not product_id or not product_id.strip():
220
- raise ValueError("product_id cannot be empty")
221
-
222
- gen_client = self._build_generated_client()
223
- try:
224
- response = get_product_sync_detailed(
225
- product_id=product_id, client=gen_client
226
- )
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)}")
234
-
235
- def get_brands(
236
- self,
237
- query: Optional[str] = None,
238
- page: int = 1,
239
- size: int = 100,
240
- ) -> GenPaginatedResponseBrand:
241
- gen_client = self._build_generated_client()
242
- try:
243
- response = get_brands_sync_detailed(
244
- client=gen_client, query=query, page=page, size=size
245
- )
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:
255
- if not brand_id or not brand_id.strip():
256
- raise ValueError("brand_id cannot be empty")
257
-
258
- gen_client = self._build_generated_client()
259
- try:
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)}")
268
-
269
-
270
- class AsyncChannel3Client(BaseChannel3Client):
271
- """Asynchronous Channel3 API client (returns generated models)."""
272
-
273
- async def search(
274
- self,
275
- query: Optional[str] = None,
276
- image_url: Optional[str] = None,
277
- base64_image: Optional[str] = None,
278
- filters: Optional[Union[GenSearchFilters, Dict[str, Any]]] = None,
279
- limit: int = 20,
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(
285
- query=query,
286
- image_url=image_url,
287
- base64_image=base64_image,
288
- filters=_convert_filters_to_generated(filters)
289
- if filters is not None
290
- else UNSET, # type: ignore[arg-type]
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,
296
- )
297
-
298
- try:
299
- response = await search_asyncio_detailed(
300
- client=gen_client, body=request_body
301
- )
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:
311
- if not product_id or not product_id.strip():
312
- raise ValueError("product_id cannot be empty")
313
-
314
- gen_client = self._build_generated_client()
315
- try:
316
- response = await get_product_asyncio_detailed(
317
- product_id=product_id, client=gen_client
318
- )
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)}")
326
-
327
- async def get_brands(
328
- self,
329
- query: Optional[str] = None,
330
- page: int = 1,
331
- size: int = 100,
332
- ) -> GenPaginatedResponseBrand:
333
- gen_client = self._build_generated_client()
334
- try:
335
- response = await get_brands_asyncio_detailed(
336
- client=gen_client, query=query, page=page, size=size
337
- )
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:
347
- if not brand_id or not brand_id.strip():
348
- raise ValueError("brand_id cannot be empty")
349
-
350
- gen_client = self._build_generated_client()
351
- try:
352
- response = await get_brand_asyncio_detailed(
353
- brand_id=brand_id, client=gen_client
354
- )
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)}")
@@ -1,48 +0,0 @@
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