python-bestbuy 0.2.2__tar.gz → 0.2.3__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.
Files changed (31) hide show
  1. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/PKG-INFO +1 -1
  2. python_bestbuy-0.2.3/bestbuy/exceptions/__init__.py +123 -0
  3. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/models/__init__.py +4 -0
  4. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/models/commerce.py +80 -20
  5. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/utils/errors.py +19 -3
  6. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/pyproject.toml +1 -1
  7. python_bestbuy-0.2.2/bestbuy/exceptions.py +0 -59
  8. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/.gitignore +0 -0
  9. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/LICENSE +0 -0
  10. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/README.md +0 -0
  11. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/__init__.py +0 -0
  12. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/clients/__init__.py +0 -0
  13. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/clients/base.py +0 -0
  14. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/clients/catalog.py +0 -0
  15. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/clients/commerce.py +0 -0
  16. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/configs/__init__.py +0 -0
  17. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/configs/base.py +0 -0
  18. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/configs/catalog.py +0 -0
  19. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/configs/commerce.py +0 -0
  20. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/loggers.py +0 -0
  21. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/models/catalog.py +0 -0
  22. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/operations/__init__.py +0 -0
  23. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/operations/base.py +0 -0
  24. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/operations/catalog.py +0 -0
  25. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/operations/commerce.py +0 -0
  26. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/operations/pagination.py +0 -0
  27. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/py.typed +0 -0
  28. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/query.py +0 -0
  29. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/typing.py +0 -0
  30. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/utils/__init__.py +0 -0
  31. {python_bestbuy-0.2.2 → python_bestbuy-0.2.3}/bestbuy/utils/encryption.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-bestbuy
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Python client library for Best Buy's Catalog and Commerce APIs
5
5
  Project-URL: Homepage, https://github.com/bbify/python-bestbuy
6
6
  Project-URL: Repository, https://github.com/bbify/python-bestbuy
@@ -0,0 +1,123 @@
1
+ """Custom exceptions for the Best Buy API client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ class BestBuyError(Exception):
9
+ """Base exception for all Best Buy API errors."""
10
+
11
+ pass
12
+
13
+
14
+ class ConfigError(BestBuyError):
15
+ """Raised when there's an error with client configuration."""
16
+
17
+ pass
18
+
19
+
20
+ class AuthenticationError(BestBuyError):
21
+ """Raised when authentication fails."""
22
+
23
+ pass
24
+
25
+
26
+ class SessionRequiredError(BestBuyError):
27
+ """Raised when an operation requires an active session but none exists."""
28
+
29
+ pass
30
+
31
+
32
+ class APIError(BestBuyError):
33
+ """Raised when the API returns an error.
34
+
35
+ Used to handle errors from both Commerce and Catalog APIs.
36
+
37
+ Attributes:
38
+ message: Human-readable error message
39
+ code: Error code from the API (if available)
40
+ sku: SKU that caused the error (if available)
41
+ response_text: Raw response text (XML or JSON)
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ message: str,
47
+ code: str | None = None,
48
+ sku: str | None = None,
49
+ response_text: str | None = None,
50
+ ):
51
+ self.message = message
52
+ self.code = code
53
+ self.sku = sku
54
+ self.response_text = response_text
55
+
56
+ # Build error message
57
+ parts = [message]
58
+ if code:
59
+ parts.append(f"Error code: {code}")
60
+ if sku:
61
+ parts.append(f"SKU: {sku}")
62
+
63
+ super().__init__(" | ".join(parts))
64
+
65
+
66
+ # Commerce-specific exceptions
67
+
68
+
69
+ @dataclass
70
+ class OrderErrorMessage:
71
+ """A single message from a commerce order error response.
72
+
73
+ Attributes:
74
+ code: The error code (from the ``item-id`` XML attribute),
75
+ e.g. ``"INVALID_CID"``, ``"MISSINGPARTNERID"``.
76
+ text: The human-readable error message.
77
+ """
78
+
79
+ code: str
80
+ text: str
81
+
82
+
83
+ class CommerceOrderError(APIError):
84
+ """Raised when the Commerce API rejects an order (HTTP 400).
85
+
86
+ Contains structured error messages from the
87
+ ``<order-response><messages>`` element. Multiple messages may be
88
+ present when several validation errors occur simultaneously.
89
+
90
+ Inherits from :class:`APIError` so existing ``except APIError``
91
+ catch blocks still work. Callers can also catch
92
+ ``CommerceOrderError`` specifically for structured access to the
93
+ error messages list.
94
+
95
+ Attributes:
96
+ messages: List of structured error messages with codes.
97
+ """
98
+
99
+ def __init__(
100
+ self,
101
+ messages: list[OrderErrorMessage],
102
+ response_text: str | None = None,
103
+ ) -> None:
104
+ self.messages = messages
105
+ primary = (
106
+ messages[0]
107
+ if messages
108
+ else OrderErrorMessage(code="UNKNOWN", text="Unknown order error")
109
+ )
110
+
111
+ parts = [primary.text]
112
+ if primary.code:
113
+ parts.append(f"Error code: {primary.code}")
114
+ if len(messages) > 1:
115
+ parts.append(f"{len(messages)} total errors")
116
+
117
+ # Initialize APIError fields directly (skip APIError.__init__
118
+ # to control the str representation)
119
+ self.message = primary.text
120
+ self.code = primary.code
121
+ self.sku = None
122
+ self.response_text = response_text
123
+ BestBuyError.__init__(self, " | ".join(parts))
@@ -88,9 +88,11 @@ from .commerce import (
88
88
  OrderList,
89
89
  OrderQueryRequest,
90
90
  OrderResponse,
91
+ OrderResponseMessage,
91
92
  OrderSubmitGuestRequest,
92
93
  OrderSubmitRegisteredRequest,
93
94
  OrderSubmitResponse,
95
+ OrderItemStatus,
94
96
  OrderStatus,
95
97
  PriceQueryRequest,
96
98
  PriceResponse,
@@ -201,9 +203,11 @@ __all__ = [
201
203
  "OrderList",
202
204
  "OrderQueryRequest",
203
205
  "OrderResponse",
206
+ "OrderResponseMessage",
204
207
  "OrderSubmitGuestRequest",
205
208
  "OrderSubmitRegisteredRequest",
206
209
  "OrderSubmitResponse",
210
+ "OrderItemStatus",
207
211
  "OrderStatus",
208
212
  "PriceQueryRequest",
209
213
  "PriceResponse",
@@ -7,32 +7,75 @@ from pydantic_xml import BaseXmlModel, element, attr, wrapped
7
7
 
8
8
 
9
9
  class OrderStatus(str, Enum):
10
+ """Overall order status values returned by the Commerce API."""
11
+
12
+ CLOSED = "CLOSED"
13
+ INCOMPLETE = "INCOMPLETE"
14
+ PAYMENT_COLLECTED = "PAYMENT_COLLECTED"
15
+ RECEIVED = "RECEIVED"
16
+ SHIPPED = "SHIPPED"
17
+ SHIPPING = "SHIPPING"
18
+ SUBMITTED = "SUBMITTED"
19
+ WAITING_ACCEPTANCE = "WAITING_ACCEPTANCE"
20
+
21
+
22
+ class OrderItemStatus(str, Enum):
23
+ """Line-item status values for individual items in an order."""
24
+
25
+ # Item is waiting to be picked up for return.
10
26
  AWAITING_RETURN_PICKUP = "Awaiting Return Pickup"
27
+ # Your appointment has not been set
11
28
  AWAITING_SCHEDULING = "Awaiting Scheduling"
29
+ # Item is currently backordered and cannot be fulfilled until more
30
+ # inventory becomes available
12
31
  BACKORDERED = "Backordered"
32
+ # Your item was canceled
13
33
  CANCELED = "Canceled"
34
+ # The process of order through fulfillment is completed/delivered
35
+ # to the end user
14
36
  COMPLETED = "Completed"
37
+ # Carrier has indicated that your shipment may have been delayed
15
38
  DELAYED = "Delayed"
39
+ # The process of order through fulfillment is completed/delivered
40
+ # to the end user
16
41
  DELIVERED = "Delivered"
42
+ # Order is in progress — still going through statuses before delivery
17
43
  IN_PROGRESS = "In Progress"
18
- INCOMPLETE = "Incomplete"
44
+ # The item has been returned
19
45
  ITEM_RETURNED = "Item Returned"
46
+ # Your order has been received
20
47
  ORDER_RECEIVED = "Order Received"
48
+ # The item is out for delivery
21
49
  OUT_FOR_DELIVERY = "Out for Delivery"
50
+ # The item is out of stock and awaiting more inventory
22
51
  OUT_OF_STOCK = "Out of Stock"
52
+ # Pending cancellation (rarely seen)
23
53
  PENDING_CANCELLATION = "Pending Cancellation"
54
+ # The item was picked up (usually store pickup fulfillment)
24
55
  PICKED_UP = "Picked Up"
56
+ # Item is on pre-order and not yet available for shipping
25
57
  PRE_ORDERED = "Pre-Ordered"
58
+ # Getting the item ready for shipment or delivery
26
59
  PREPARING = "Preparing"
60
+ # The item is ready to be picked up at the store
27
61
  READY_TO_PICK_UP = "Ready to Pick Up"
62
+ # Item is pending a refund
28
63
  REFUND_PENDING = "Refund Pending"
64
+ # Your appointment needs to be rescheduled
29
65
  RESCHEDULE_REQUIRED = "Reschedule Required"
66
+ # The returning item is in transit to the return warehouse
30
67
  RETURN_IN_TRANSIT = "Return In Transit"
68
+ # The item has been received by the return warehouse and is in review
31
69
  RETURN_RECEIVED = "Return Received"
70
+ # Confirmation of a return request
32
71
  RETURN_REQUEST_CONFIRMED = "Return Request Confirmed"
72
+ # Item has been returned
33
73
  RETURNED = "Returned"
74
+ # Item is scheduled for fulfillment
34
75
  SCHEDULED = "Scheduled"
76
+ # Item needs to be scheduled
35
77
  SCHEDULING_NEEDED = "Scheduling Needed"
78
+ # Item has shipped
36
79
  SHIPPED = "Shipped"
37
80
 
38
81
 
@@ -80,13 +123,17 @@ class AvailabilityQueryRequest(BaseXmlModel, tag="availability-query"):
80
123
 
81
124
 
82
125
  class AvailabilityQueryResponse(BaseXmlModel, tag="availability-query"):
83
- sku_id: str = attr(name="sku-id")
84
- order_code: int = element(tag="order-code")
85
- display_message: str = element(tag="display-message")
86
- home_delivery_code: int = element(tag="home-delivery-code")
87
- home_delivery_message: str = element(tag="home-delivery-message")
88
- instore_availability: bool = element(tag="instore-availability")
89
- max_quantity: int = element(tag="max-quantity")
126
+ sku_id: Optional[str] = attr(name="sku-id", default=None)
127
+ order_code: Optional[int] = element(tag="order-code", default=None)
128
+ display_message: Optional[str] = element(tag="display-message", default=None)
129
+ home_delivery_code: Optional[int] = element(tag="home-delivery-code", default=None)
130
+ home_delivery_message: Optional[str] = element(
131
+ tag="home-delivery-message", default=None
132
+ )
133
+ instore_availability: Optional[bool] = element(
134
+ tag="instore-availability", default=None
135
+ )
136
+ max_quantity: Optional[int] = element(tag="max-quantity", default=None)
90
137
  available_for_shipping: Optional[bool] = element(
91
138
  tag="available-for-shipping", default=None
92
139
  )
@@ -125,8 +172,8 @@ class ShippingOption(BaseXmlModel, tag="option"):
125
172
 
126
173
 
127
174
  class ShippingOptionsResponse(BaseXmlModel, tag="shipping-options"):
128
- sku_id: str = attr(name="sku-id")
129
- free_shipping: bool = attr(name="free-shipping")
175
+ sku_id: Optional[str] = attr(name="sku-id", default=None)
176
+ free_shipping: Optional[bool] = attr(name="free-shipping", default=None)
130
177
  options: List[ShippingOption] = element(tag="option", default_factory=list)
131
178
 
132
179
 
@@ -188,13 +235,17 @@ class ProductServiceRequest(BaseXmlModel, tag="productservice-request"):
188
235
 
189
236
 
190
237
  class AvailabilityQuery(BaseXmlModel, tag="availability-query"):
191
- sku_id: str = attr(name="sku-id")
192
- order_code: int = element(tag="order-code")
193
- display_message: str = element(tag="display-message")
194
- home_delivery_code: int = element(tag="home-delivery-code")
195
- home_delivery_message: str = element(tag="home-delivery-message")
196
- instore_availability: bool = element(tag="instore-availability")
197
- max_quantity: int = element(tag="max-quantity")
238
+ sku_id: Optional[str] = attr(name="sku-id", default=None)
239
+ order_code: Optional[int] = element(tag="order-code", default=None)
240
+ display_message: Optional[str] = element(tag="display-message", default=None)
241
+ home_delivery_code: Optional[int] = element(tag="home-delivery-code", default=None)
242
+ home_delivery_message: Optional[str] = element(
243
+ tag="home-delivery-message", default=None
244
+ )
245
+ instore_availability: Optional[bool] = element(
246
+ tag="instore-availability", default=None
247
+ )
248
+ max_quantity: Optional[int] = element(tag="max-quantity", default=None)
198
249
 
199
250
 
200
251
  class ProductServiceResponse(BaseXmlModel, tag="productservice-response"):
@@ -239,7 +290,7 @@ class OrderItem(BaseXmlModel, tag="item"):
239
290
  cancellable: Optional[bool] = attr(name="cancellable", default=None)
240
291
  parent_item: Optional[str] = attr(name="parent-item", default=None)
241
292
  type: Optional[str] = attr(name="type", default=None)
242
- line_status: Optional[str] = attr(name="line-status", default=None)
293
+ line_status: Optional[OrderItemStatus] = attr(name="line-status", default=None)
243
294
  line_status_message: Optional[str] = attr(name="line-status-message", default=None)
244
295
  expected_delivery_date: Optional[str] = attr(
245
296
  name="expected-delivery-date", default=None
@@ -557,7 +608,7 @@ class OrderResponse(BaseXmlModel, tag="order", search_mode="unordered"):
557
608
  id: Optional[str] = attr(name="id", default=None)
558
609
  reviewable: Optional[bool] = attr(name="reviewable", default=None)
559
610
  partner_id: Optional[str] = attr(name="partner-id", default=None)
560
- status: Optional[OrderStatus] = attr(name="status", default=None)
611
+ status: Optional[str] = attr(name="status", default=None)
561
612
  order_date: Optional[date] = attr(name="order-date", default=None)
562
613
  total: Optional[Decimal] = attr(name="total", default=None)
563
614
  given_id: Optional[str] = attr(name="given-id", default=None)
@@ -576,6 +627,13 @@ class IdMapEntry(BaseXmlModel, tag="entry"):
576
627
  value: str = element(tag="value")
577
628
 
578
629
 
630
+ class OrderResponseMessage(BaseXmlModel, tag="message"):
631
+ """A message from an order response (warning/info on success, error on 400)."""
632
+
633
+ item_id: Optional[str] = attr(name="item-id", default=None)
634
+ text: Optional[str] = None
635
+
636
+
579
637
  class OrderSubmitResponse(BaseXmlModel, tag="order-response"):
580
638
  version: str = attr(name="version")
581
639
  status: str = attr(name="status")
@@ -583,7 +641,9 @@ class OrderSubmitResponse(BaseXmlModel, tag="order-response"):
583
641
  id_map: Optional[List[IdMapEntry]] = wrapped(
584
642
  "id-map", element(tag="entry", default_factory=list)
585
643
  )
586
- messages: Optional[str] = element(tag="messages", default=None)
644
+ messages: List[OrderResponseMessage] = wrapped(
645
+ "messages", element(tag="message", default_factory=list)
646
+ )
587
647
 
588
648
 
589
649
  class PublicKeyEncryptionResponse(BaseXmlModel, tag="publicKeyEncryption"):
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  from xml.etree import ElementTree as ET
3
3
 
4
- from ..exceptions import APIError
4
+ from ..exceptions import APIError, CommerceOrderError, OrderErrorMessage
5
5
  from ..models import ApiError, ApiErrors, SimpleError
6
6
 
7
7
 
@@ -80,19 +80,35 @@ def check_for_xml_errors(response_text: str | None) -> None:
80
80
  except Exception:
81
81
  pass
82
82
 
83
+ # Check for order-response messages format:
84
+ # <order-response><messages><message item-id="CODE">text</message>
85
+ elif root.tag == "order-response":
86
+ messages_elem = root.find("messages")
87
+ if messages_elem is not None:
88
+ order_messages = []
89
+ for msg_elem in messages_elem.findall("message"):
90
+ code = msg_elem.get("item-id", "UNKNOWN")
91
+ text = msg_elem.text or ""
92
+ order_messages.append(OrderErrorMessage(code=code, text=text))
93
+ if order_messages:
94
+ raise CommerceOrderError(
95
+ messages=order_messages,
96
+ response_text=response_text,
97
+ )
98
+
83
99
  # Check for errors embedded in other response types
84
100
  else:
85
101
  # Look for <error> child elements
86
102
  error_elem = root.find(".//error")
87
103
  if error_elem is not None:
88
- code = error_elem.get("code")
104
+ error_code = error_elem.get("code")
89
105
  message_elem = error_elem.find("message")
90
106
  if message_elem is not None:
91
107
  message = message_elem.text or "Unknown error"
92
108
  sku = message_elem.get("sku")
93
109
  raise APIError(
94
110
  message=message,
95
- code=code,
111
+ code=error_code,
96
112
  sku=sku,
97
113
  response_text=response_text,
98
114
  )
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "python-bestbuy"
7
- version = "0.2.2"
7
+ version = "0.2.3"
8
8
  description = "Python client library for Best Buy's Catalog and Commerce APIs"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,59 +0,0 @@
1
- """Custom exceptions for the Best Buy API client."""
2
-
3
-
4
- class BestBuyError(Exception):
5
- """Base exception for all Best Buy API errors."""
6
-
7
- pass
8
-
9
-
10
- class ConfigError(BestBuyError):
11
- """Raised when there's an error with client configuration."""
12
-
13
- pass
14
-
15
-
16
- class AuthenticationError(BestBuyError):
17
- """Raised when authentication fails."""
18
-
19
- pass
20
-
21
-
22
- class SessionRequiredError(BestBuyError):
23
- """Raised when an operation requires an active session but none exists."""
24
-
25
- pass
26
-
27
-
28
- class APIError(BestBuyError):
29
- """Raised when the API returns an error.
30
-
31
- Used to handle errors from both Commerce and Catalog APIs.
32
-
33
- Attributes:
34
- message: Human-readable error message
35
- code: Error code from the API (if available)
36
- sku: SKU that caused the error (if available)
37
- response_text: Raw response text (XML or JSON)
38
- """
39
-
40
- def __init__(
41
- self,
42
- message: str,
43
- code: str | None = None,
44
- sku: str | None = None,
45
- response_text: str | None = None,
46
- ):
47
- self.message = message
48
- self.code = code
49
- self.sku = sku
50
- self.response_text = response_text
51
-
52
- # Build error message
53
- parts = [message]
54
- if code:
55
- parts.append(f"Error code: {code}")
56
- if sku:
57
- parts.append(f"SKU: {sku}")
58
-
59
- super().__init__(" | ".join(parts))
File without changes
File without changes