1# Copyright (c) 2012-2014 Andy Davidoff http://www.disruptek.com/ 2# 3# Permission is hereby granted, free of charge, to any person obtaining a copy 4# of this software and associated documentation files (the "Software"), to 5# deal in the Software without restriction, including without limitation the 6# rights to use, copy, modify, merge, publish, dis- tribute, sublicense, and/or 7# sell copies of the Software, and to permit persons to whom the Software is 8# furnished to do so, subject to the fol- lowing conditions: 9# 10# The above copyright notice and this permission notice shall be included in 11# all copies or substantial portions of the Software. 12# 13# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- ITY, 15# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16# AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 17# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19from decimal import Decimal 20from boto.compat import filter, map 21 22 23class ComplexType(dict): 24 _value = 'Value' 25 26 def __repr__(self): 27 return '{0}{1}'.format(getattr(self, self._value, None), self.copy()) 28 29 def __str__(self): 30 return str(getattr(self, self._value, '')) 31 32 33class DeclarativeType(object): 34 def __init__(self, _hint=None, **kw): 35 self._value = None 36 if _hint is not None: 37 self._hint = _hint 38 return 39 40 class JITResponse(ResponseElement): 41 pass 42 self._hint = JITResponse 43 self._hint.__name__ = 'JIT_{0}/{1}'.format(self.__class__.__name__, 44 hex(id(self._hint))[2:]) 45 for name, value in kw.items(): 46 setattr(self._hint, name, value) 47 48 def __repr__(self): 49 parent = getattr(self, '_parent', None) 50 return '<{0}_{1}/{2}_{3}>'.format(self.__class__.__name__, 51 parent and parent._name or '?', 52 getattr(self, '_name', '?'), 53 hex(id(self.__class__))) 54 55 def setup(self, parent, name, *args, **kw): 56 self._parent = parent 57 self._name = name 58 self._clone = self.__class__(_hint=self._hint) 59 self._clone._parent = parent 60 self._clone._name = name 61 setattr(self._parent, self._name, self._clone) 62 63 def start(self, *args, **kw): 64 raise NotImplementedError 65 66 def end(self, *args, **kw): 67 raise NotImplementedError 68 69 def teardown(self, *args, **kw): 70 setattr(self._parent, self._name, self._value) 71 72 73class Element(DeclarativeType): 74 def start(self, *args, **kw): 75 self._value = self._hint(parent=self._parent, **kw) 76 return self._value 77 78 def end(self, *args, **kw): 79 pass 80 81 82class SimpleList(DeclarativeType): 83 def __init__(self, *args, **kw): 84 super(SimpleList, self).__init__(*args, **kw) 85 self._value = [] 86 87 def start(self, *args, **kw): 88 return None 89 90 def end(self, name, value, *args, **kw): 91 self._value.append(value) 92 93 94class ElementList(SimpleList): 95 def start(self, *args, **kw): 96 value = self._hint(parent=self._parent, **kw) 97 self._value.append(value) 98 return value 99 100 def end(self, *args, **kw): 101 pass 102 103 104class MemberList(Element): 105 def __init__(self, _member=None, _hint=None, *args, **kw): 106 message = 'Invalid `member` specification in {0}'.format(self.__class__.__name__) 107 assert 'member' not in kw, message 108 if _member is None: 109 if _hint is None: 110 super(MemberList, self).__init__(*args, member=ElementList(**kw)) 111 else: 112 super(MemberList, self).__init__(_hint=_hint) 113 else: 114 if _hint is None: 115 if issubclass(_member, DeclarativeType): 116 member = _member(**kw) 117 else: 118 member = ElementList(_member, **kw) 119 super(MemberList, self).__init__(*args, member=member) 120 else: 121 message = 'Nonsensical {0} hint {1!r}'.format(self.__class__.__name__, 122 _hint) 123 raise AssertionError(message) 124 125 def teardown(self, *args, **kw): 126 if self._value is None: 127 self._value = [] 128 else: 129 if isinstance(self._value.member, DeclarativeType): 130 self._value.member = [] 131 self._value = self._value.member 132 super(MemberList, self).teardown(*args, **kw) 133 134 135class ResponseFactory(object): 136 def __init__(self, scopes=None): 137 self.scopes = [] if scopes is None else scopes 138 139 def element_factory(self, name, parent): 140 class DynamicElement(parent): 141 _name = name 142 setattr(DynamicElement, '__name__', str(name)) 143 return DynamicElement 144 145 def search_scopes(self, key): 146 for scope in self.scopes: 147 if hasattr(scope, key): 148 return getattr(scope, key) 149 if hasattr(scope, '__getitem__'): 150 if key in scope: 151 return scope[key] 152 153 def find_element(self, action, suffix, parent): 154 element = self.search_scopes(action + suffix) 155 if element is not None: 156 return element 157 if action.endswith('ByNextToken'): 158 element = self.search_scopes(action[:-len('ByNextToken')] + suffix) 159 if element is not None: 160 return self.element_factory(action + suffix, element) 161 return self.element_factory(action + suffix, parent) 162 163 def __call__(self, action, connection=None): 164 response = self.find_element(action, 'Response', Response) 165 if not hasattr(response, action + 'Result'): 166 result = self.find_element(action, 'Result', ResponseElement) 167 setattr(response, action + 'Result', Element(result)) 168 return response(connection=connection) 169 170 171def strip_namespace(func): 172 def wrapper(self, name, *args, **kw): 173 if self._namespace is not None: 174 if name.startswith(self._namespace + ':'): 175 name = name[len(self._namespace + ':'):] 176 return func(self, name, *args, **kw) 177 return wrapper 178 179 180class ResponseElement(dict): 181 _override = {} 182 _name = None 183 _namespace = None 184 185 def __init__(self, connection=None, name=None, parent=None, attrs=None): 186 if parent is not None and self._namespace is None: 187 self._namespace = parent._namespace 188 if connection is not None: 189 self._connection = connection 190 self._name = name or self._name or self.__class__.__name__ 191 self._declared('setup', attrs=attrs) 192 dict.__init__(self, attrs and attrs.copy() or {}) 193 194 def _declared(self, op, **kw): 195 def inherit(obj): 196 result = {} 197 for cls in getattr(obj, '__bases__', ()): 198 result.update(inherit(cls)) 199 result.update(obj.__dict__) 200 return result 201 202 scope = inherit(self.__class__) 203 scope.update(self.__dict__) 204 declared = lambda attr: isinstance(attr[1], DeclarativeType) 205 for name, node in filter(declared, scope.items()): 206 getattr(node, op)(self, name, parentname=self._name, **kw) 207 208 @property 209 def connection(self): 210 return self._connection 211 212 def __repr__(self): 213 render = lambda pair: '{0!s}: {1!r}'.format(*pair) 214 do_show = lambda pair: not pair[0].startswith('_') 215 attrs = filter(do_show, self.__dict__.items()) 216 name = self.__class__.__name__ 217 if name.startswith('JIT_'): 218 name = '^{0}^'.format(self._name or '') 219 return '{0}{1!r}({2})'.format( 220 name, self.copy(), ', '.join(map(render, attrs))) 221 222 def _type_for(self, name, attrs): 223 return self._override.get(name, globals().get(name, ResponseElement)) 224 225 @strip_namespace 226 def startElement(self, name, attrs, connection): 227 attribute = getattr(self, name, None) 228 if isinstance(attribute, DeclarativeType): 229 return attribute.start(name=name, attrs=attrs, 230 connection=connection) 231 elif attrs.getLength(): 232 setattr(self, name, ComplexType(attrs.copy())) 233 else: 234 return None 235 236 @strip_namespace 237 def endElement(self, name, value, connection): 238 attribute = getattr(self, name, None) 239 if name == self._name: 240 self._declared('teardown') 241 elif isinstance(attribute, DeclarativeType): 242 attribute.end(name=name, value=value, connection=connection) 243 elif isinstance(attribute, ComplexType): 244 setattr(attribute, attribute._value, value) 245 else: 246 setattr(self, name, value) 247 248 249class Response(ResponseElement): 250 ResponseMetadata = Element() 251 252 @strip_namespace 253 def startElement(self, name, attrs, connection): 254 if name == self._name: 255 self.update(attrs) 256 else: 257 return super(Response, self).startElement(name, attrs, connection) 258 259 @property 260 def _result(self): 261 return getattr(self, self._action + 'Result', None) 262 263 @property 264 def _action(self): 265 return (self._name or self.__class__.__name__)[:-len('Response')] 266 267 268class ResponseResultList(Response): 269 _ResultClass = ResponseElement 270 271 def __init__(self, *args, **kw): 272 setattr(self, self._action + 'Result', ElementList(self._ResultClass)) 273 super(ResponseResultList, self).__init__(*args, **kw) 274 275 276class FeedSubmissionInfo(ResponseElement): 277 pass 278 279 280class SubmitFeedResult(ResponseElement): 281 FeedSubmissionInfo = Element(FeedSubmissionInfo) 282 283 284class GetFeedSubmissionListResult(ResponseElement): 285 FeedSubmissionInfo = ElementList(FeedSubmissionInfo) 286 287 288class GetFeedSubmissionCountResult(ResponseElement): 289 pass 290 291 292class CancelFeedSubmissionsResult(GetFeedSubmissionListResult): 293 pass 294 295 296class GetServiceStatusResult(ResponseElement): 297 Messages = Element(Messages=ElementList()) 298 299 300class ReportRequestInfo(ResponseElement): 301 pass 302 303 304class RequestReportResult(ResponseElement): 305 ReportRequestInfo = Element() 306 307 308class GetReportRequestListResult(RequestReportResult): 309 ReportRequestInfo = ElementList() 310 311 312class CancelReportRequestsResult(RequestReportResult): 313 pass 314 315 316class GetReportListResult(ResponseElement): 317 ReportInfo = ElementList() 318 319 320class ManageReportScheduleResult(ResponseElement): 321 ReportSchedule = Element() 322 323 324class GetReportScheduleListResult(ManageReportScheduleResult): 325 pass 326 327 328class UpdateReportAcknowledgementsResult(GetReportListResult): 329 pass 330 331 332class CreateInboundShipmentPlanResult(ResponseElement): 333 InboundShipmentPlans = MemberList(ShipToAddress=Element(), 334 Items=MemberList()) 335 336 337class ListInboundShipmentsResult(ResponseElement): 338 ShipmentData = MemberList(ShipFromAddress=Element()) 339 340 341class ListInboundShipmentItemsResult(ResponseElement): 342 ItemData = MemberList() 343 344 345class ListInventorySupplyResult(ResponseElement): 346 InventorySupplyList = MemberList( 347 EarliestAvailability=Element(), 348 SupplyDetail=MemberList( 349 EarliestAvailableToPick=Element(), 350 LatestAvailableToPick=Element(), 351 ) 352 ) 353 354 355class ComplexAmount(ResponseElement): 356 _amount = 'Value' 357 358 def __repr__(self): 359 return '{0} {1}'.format(self.CurrencyCode, getattr(self, self._amount)) 360 361 def __float__(self): 362 return float(getattr(self, self._amount)) 363 364 def __str__(self): 365 return str(getattr(self, self._amount)) 366 367 @strip_namespace 368 def startElement(self, name, attrs, connection): 369 if name not in ('CurrencyCode', self._amount): 370 message = 'Unrecognized tag {0} in ComplexAmount'.format(name) 371 raise AssertionError(message) 372 return super(ComplexAmount, self).startElement(name, attrs, connection) 373 374 @strip_namespace 375 def endElement(self, name, value, connection): 376 if name == self._amount: 377 value = Decimal(value) 378 super(ComplexAmount, self).endElement(name, value, connection) 379 380 381class ComplexMoney(ComplexAmount): 382 _amount = 'Amount' 383 384 385class ComplexWeight(ResponseElement): 386 def __repr__(self): 387 return '{0} {1}'.format(self.Value, self.Unit) 388 389 def __float__(self): 390 return float(self.Value) 391 392 def __str__(self): 393 return str(self.Value) 394 395 @strip_namespace 396 def startElement(self, name, attrs, connection): 397 if name not in ('Unit', 'Value'): 398 message = 'Unrecognized tag {0} in ComplexWeight'.format(name) 399 raise AssertionError(message) 400 return super(ComplexWeight, self).startElement(name, attrs, connection) 401 402 @strip_namespace 403 def endElement(self, name, value, connection): 404 if name == 'Value': 405 value = Decimal(value) 406 super(ComplexWeight, self).endElement(name, value, connection) 407 408 409class Dimension(ComplexType): 410 _value = 'Value' 411 412 413class ComplexDimensions(ResponseElement): 414 _dimensions = ('Height', 'Length', 'Width', 'Weight') 415 416 def __repr__(self): 417 values = [getattr(self, key, None) for key in self._dimensions] 418 values = filter(None, values) 419 return 'x'.join(map('{0.Value:0.2f}{0[Units]}'.format, values)) 420 421 @strip_namespace 422 def startElement(self, name, attrs, connection): 423 if name not in self._dimensions: 424 message = 'Unrecognized tag {0} in ComplexDimensions'.format(name) 425 raise AssertionError(message) 426 setattr(self, name, Dimension(attrs.copy())) 427 428 @strip_namespace 429 def endElement(self, name, value, connection): 430 if name in self._dimensions: 431 value = Decimal(value or '0') 432 ResponseElement.endElement(self, name, value, connection) 433 434 435class FulfillmentPreviewItem(ResponseElement): 436 EstimatedShippingWeight = Element(ComplexWeight) 437 438 439class FulfillmentPreview(ResponseElement): 440 EstimatedShippingWeight = Element(ComplexWeight) 441 EstimatedFees = MemberList(Amount=Element(ComplexAmount)) 442 UnfulfillablePreviewItems = MemberList(FulfillmentPreviewItem) 443 FulfillmentPreviewShipments = MemberList( 444 FulfillmentPreviewItems=MemberList(FulfillmentPreviewItem), 445 ) 446 447 448class GetFulfillmentPreviewResult(ResponseElement): 449 FulfillmentPreviews = MemberList(FulfillmentPreview) 450 451 452class FulfillmentOrder(ResponseElement): 453 DestinationAddress = Element() 454 NotificationEmailList = MemberList(SimpleList) 455 456 457class GetFulfillmentOrderResult(ResponseElement): 458 FulfillmentOrder = Element(FulfillmentOrder) 459 FulfillmentShipment = MemberList( 460 FulfillmentShipmentItem=MemberList(), 461 FulfillmentShipmentPackage=MemberList(), 462 ) 463 FulfillmentOrderItem = MemberList() 464 465 466class ListAllFulfillmentOrdersResult(ResponseElement): 467 FulfillmentOrders = MemberList(FulfillmentOrder) 468 469 470class GetPackageTrackingDetailsResult(ResponseElement): 471 ShipToAddress = Element() 472 TrackingEvents = MemberList(EventAddress=Element()) 473 474 475class Image(ResponseElement): 476 pass 477 478 479class AttributeSet(ResponseElement): 480 ItemDimensions = Element(ComplexDimensions) 481 ListPrice = Element(ComplexMoney) 482 PackageDimensions = Element(ComplexDimensions) 483 SmallImage = Element(Image) 484 485 486class ItemAttributes(AttributeSet): 487 Languages = Element(Language=ElementList()) 488 489 def __init__(self, *args, **kw): 490 names = ('Actor', 'Artist', 'Author', 'Creator', 'Director', 491 'Feature', 'Format', 'GemType', 'MaterialType', 492 'MediaType', 'OperatingSystem', 'Platform') 493 for name in names: 494 setattr(self, name, SimpleList()) 495 super(ItemAttributes, self).__init__(*args, **kw) 496 497 498class VariationRelationship(ResponseElement): 499 Identifiers = Element(MarketplaceASIN=Element(), 500 SKUIdentifier=Element()) 501 GemType = SimpleList() 502 MaterialType = SimpleList() 503 OperatingSystem = SimpleList() 504 505 506class Price(ResponseElement): 507 LandedPrice = Element(ComplexMoney) 508 ListingPrice = Element(ComplexMoney) 509 Shipping = Element(ComplexMoney) 510 511 512class CompetitivePrice(ResponseElement): 513 Price = Element(Price) 514 515 516class CompetitivePriceList(ResponseElement): 517 CompetitivePrice = ElementList(CompetitivePrice) 518 519 520class CompetitivePricing(ResponseElement): 521 CompetitivePrices = Element(CompetitivePriceList) 522 NumberOfOfferListings = SimpleList() 523 TradeInValue = Element(ComplexMoney) 524 525 526class SalesRank(ResponseElement): 527 pass 528 529 530class LowestOfferListing(ResponseElement): 531 Qualifiers = Element(ShippingTime=Element()) 532 Price = Element(Price) 533 534 535class Offer(ResponseElement): 536 BuyingPrice = Element(Price) 537 RegularPrice = Element(ComplexMoney) 538 539 540class Product(ResponseElement): 541 _namespace = 'ns2' 542 Identifiers = Element(MarketplaceASIN=Element(), 543 SKUIdentifier=Element()) 544 AttributeSets = Element( 545 ItemAttributes=ElementList(ItemAttributes), 546 ) 547 Relationships = Element( 548 VariationParent=ElementList(VariationRelationship), 549 ) 550 CompetitivePricing = ElementList(CompetitivePricing) 551 SalesRankings = Element( 552 SalesRank=ElementList(SalesRank), 553 ) 554 LowestOfferListings = Element( 555 LowestOfferListing=ElementList(LowestOfferListing), 556 ) 557 Offers = Element( 558 Offer=ElementList(Offer), 559 ) 560 561 562class ListMatchingProductsResult(ResponseElement): 563 Products = Element(Product=ElementList(Product)) 564 565 566class ProductsBulkOperationResult(ResponseElement): 567 Product = Element(Product) 568 Error = Element() 569 570 571class ProductsBulkOperationResponse(ResponseResultList): 572 _ResultClass = ProductsBulkOperationResult 573 574 575class GetMatchingProductResponse(ProductsBulkOperationResponse): 576 pass 577 578 579class GetMatchingProductForIdResult(ListMatchingProductsResult): 580 pass 581 582 583class GetMatchingProductForIdResponse(ResponseResultList): 584 _ResultClass = GetMatchingProductForIdResult 585 586 587class GetCompetitivePricingForSKUResponse(ProductsBulkOperationResponse): 588 pass 589 590 591class GetCompetitivePricingForASINResponse(ProductsBulkOperationResponse): 592 pass 593 594 595class GetLowestOfferListingsForSKUResponse(ProductsBulkOperationResponse): 596 pass 597 598 599class GetLowestOfferListingsForASINResponse(ProductsBulkOperationResponse): 600 pass 601 602 603class GetMyPriceForSKUResponse(ProductsBulkOperationResponse): 604 pass 605 606 607class GetMyPriceForASINResponse(ProductsBulkOperationResponse): 608 pass 609 610 611class ProductCategory(ResponseElement): 612 613 def __init__(self, *args, **kw): 614 setattr(self, 'Parent', Element(ProductCategory)) 615 super(ProductCategory, self).__init__(*args, **kw) 616 617 618class GetProductCategoriesResult(ResponseElement): 619 Self = ElementList(ProductCategory) 620 621 622class GetProductCategoriesForSKUResult(GetProductCategoriesResult): 623 pass 624 625 626class GetProductCategoriesForASINResult(GetProductCategoriesResult): 627 pass 628 629 630class Order(ResponseElement): 631 OrderTotal = Element(ComplexMoney) 632 ShippingAddress = Element() 633 PaymentExecutionDetail = Element( 634 PaymentExecutionDetailItem=ElementList( 635 PaymentExecutionDetailItem=Element( 636 Payment=Element(ComplexMoney) 637 ) 638 ) 639 ) 640 641 642class ListOrdersResult(ResponseElement): 643 Orders = Element(Order=ElementList(Order)) 644 645 646class GetOrderResult(ListOrdersResult): 647 pass 648 649 650class OrderItem(ResponseElement): 651 ItemPrice = Element(ComplexMoney) 652 ShippingPrice = Element(ComplexMoney) 653 GiftWrapPrice = Element(ComplexMoney) 654 ItemTax = Element(ComplexMoney) 655 ShippingTax = Element(ComplexMoney) 656 GiftWrapTax = Element(ComplexMoney) 657 ShippingDiscount = Element(ComplexMoney) 658 PromotionDiscount = Element(ComplexMoney) 659 PromotionIds = SimpleList() 660 CODFee = Element(ComplexMoney) 661 CODFeeDiscount = Element(ComplexMoney) 662 663 664class ListOrderItemsResult(ResponseElement): 665 OrderItems = Element(OrderItem=ElementList(OrderItem)) 666 667 668class ListMarketplaceParticipationsResult(ResponseElement): 669 ListParticipations = Element(Participation=ElementList()) 670 ListMarketplaces = Element(Marketplace=ElementList()) 671 672 673class ListRecommendationsResult(ResponseElement): 674 ListingQualityRecommendations = MemberList(ItemIdentifier=Element()) 675 676 677class Customer(ResponseElement): 678 PrimaryContactInfo = Element() 679 ShippingAddressList = Element(ShippingAddress=ElementList()) 680 AssociatedMarketplaces = Element(MarketplaceDomain=ElementList()) 681 682 683class ListCustomersResult(ResponseElement): 684 CustomerList = Element(Customer=ElementList(Customer)) 685 686 687class GetCustomersForCustomerIdResult(ListCustomersResult): 688 pass 689 690 691class CartItem(ResponseElement): 692 CurrentPrice = Element(ComplexMoney) 693 SalePrice = Element(ComplexMoney) 694 695 696class Cart(ResponseElement): 697 ActiveCartItemList = Element(CartItem=ElementList(CartItem)) 698 SavedCartItemList = Element(CartItem=ElementList(CartItem)) 699 700 701class ListCartsResult(ResponseElement): 702 CartList = Element(Cart=ElementList(Cart)) 703 704 705class GetCartsResult(ListCartsResult): 706 pass 707 708 709class Destination(ResponseElement): 710 AttributeList = MemberList() 711 712 713class ListRegisteredDestinationsResult(ResponseElement): 714 DestinationList = MemberList(Destination) 715 716 717class Subscription(ResponseElement): 718 Destination = Element(Destination) 719 720 721class GetSubscriptionResult(ResponseElement): 722 Subscription = Element(Subscription) 723 724 725class ListSubscriptionsResult(ResponseElement): 726 SubscriptionList = MemberList(Subscription) 727 728 729class OrderReferenceDetails(ResponseElement): 730 Buyer = Element() 731 OrderTotal = Element(ComplexMoney) 732 Destination = Element(PhysicalDestination=Element()) 733 SellerOrderAttributes = Element() 734 OrderReferenceStatus = Element() 735 Constraints = ElementList() 736 737 738class SetOrderReferenceDetailsResult(ResponseElement): 739 OrderReferenceDetails = Element(OrderReferenceDetails) 740 741 742class GetOrderReferenceDetailsResult(SetOrderReferenceDetailsResult): 743 pass 744 745 746class AuthorizationDetails(ResponseElement): 747 AuthorizationAmount = Element(ComplexMoney) 748 CapturedAmount = Element(ComplexMoney) 749 AuthorizationFee = Element(ComplexMoney) 750 AuthorizationStatus = Element() 751 752 753class AuthorizeResult(ResponseElement): 754 AuthorizationDetails = Element(AuthorizationDetails) 755 756 757class GetAuthorizationDetailsResult(AuthorizeResult): 758 pass 759 760 761class CaptureDetails(ResponseElement): 762 CaptureAmount = Element(ComplexMoney) 763 RefundedAmount = Element(ComplexMoney) 764 CaptureFee = Element(ComplexMoney) 765 CaptureStatus = Element() 766 767 768class CaptureResult(ResponseElement): 769 CaptureDetails = Element(CaptureDetails) 770 771 772class GetCaptureDetailsResult(CaptureResult): 773 pass 774 775 776class RefundDetails(ResponseElement): 777 RefundAmount = Element(ComplexMoney) 778 FeeRefunded = Element(ComplexMoney) 779 RefundStatus = Element() 780 781 782class RefundResult(ResponseElement): 783 RefundDetails = Element(RefundDetails) 784 785 786class GetRefundDetails(RefundResult): 787 pass 788