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 4# copy of this software and associated documentation files (the 5# "Software"), to deal in the Software without restriction, including 6# without limitation the rights to use, copy, modify, merge, publish, dis- 7# tribute, sublicense, and/or sell copies of the Software, and to permit 8# persons to whom the Software is furnished to do so, subject to the fol- 9# lowing conditions: 10# 11# The above copyright notice and this permission notice shall be included 12# in all copies or substantial portions of the Software. 13# 14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 16# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 17# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20# IN THE SOFTWARE. 21import xml.sax 22import hashlib 23import string 24import collections 25from boto.connection import AWSQueryConnection 26from boto.exception import BotoServerError 27import boto.mws.exception 28import boto.mws.response 29from boto.handler import XmlHandler 30from boto.compat import filter, map, six, encodebytes 31 32__all__ = ['MWSConnection'] 33 34api_version_path = { 35 'Feeds': ('2009-01-01', 'Merchant', '/'), 36 'Reports': ('2009-01-01', 'Merchant', '/'), 37 'Orders': ('2013-09-01', 'SellerId', '/Orders/2013-09-01'), 38 'Products': ('2011-10-01', 'SellerId', '/Products/2011-10-01'), 39 'Sellers': ('2011-07-01', 'SellerId', '/Sellers/2011-07-01'), 40 'Inbound': ('2010-10-01', 'SellerId', 41 '/FulfillmentInboundShipment/2010-10-01'), 42 'Outbound': ('2010-10-01', 'SellerId', 43 '/FulfillmentOutboundShipment/2010-10-01'), 44 'Inventory': ('2010-10-01', 'SellerId', 45 '/FulfillmentInventory/2010-10-01'), 46 'Recommendations': ('2013-04-01', 'SellerId', 47 '/Recommendations/2013-04-01'), 48 'CustomerInfo': ('2014-03-01', 'SellerId', 49 '/CustomerInformation/2014-03-01'), 50 'CartInfo': ('2014-03-01', 'SellerId', 51 '/CartInformation/2014-03-01'), 52 'Subscriptions': ('2013-07-01', 'SellerId', 53 '/Subscriptions/2013-07-01'), 54 'OffAmazonPayments': ('2013-01-01', 'SellerId', 55 '/OffAmazonPayments/2013-01-01'), 56} 57content_md5 = lambda c: encodebytes(hashlib.md5(c).digest()).strip() 58decorated_attrs = ('action', 'response', 'section', 59 'quota', 'restore', 'version') 60api_call_map = {} 61 62 63def add_attrs_from(func, to): 64 for attr in decorated_attrs: 65 setattr(to, attr, getattr(func, attr, None)) 66 to.__wrapped__ = func 67 return to 68 69 70def structured_lists(*fields): 71 72 def decorator(func): 73 74 def wrapper(self, *args, **kw): 75 for key, acc in [f.split('.') for f in fields]: 76 if key in kw: 77 newkey = key + '.' + acc + (acc and '.' or '') 78 for i in range(len(kw[key])): 79 kw[newkey + str(i + 1)] = kw[key][i] 80 kw.pop(key) 81 return func(self, *args, **kw) 82 wrapper.__doc__ = "{0}\nLists: {1}".format(func.__doc__, 83 ', '.join(fields)) 84 return add_attrs_from(func, to=wrapper) 85 return decorator 86 87 88def http_body(field): 89 90 def decorator(func): 91 92 def wrapper(*args, **kw): 93 if any([f not in kw for f in (field, 'content_type')]): 94 message = "{0} requires {1} and content_type arguments for " \ 95 "building HTTP body".format(func.action, field) 96 raise KeyError(message) 97 kw['body'] = kw.pop(field) 98 kw['headers'] = { 99 'Content-Type': kw.pop('content_type'), 100 'Content-MD5': content_md5(kw['body']), 101 } 102 return func(*args, **kw) 103 wrapper.__doc__ = "{0}\nRequired HTTP Body: " \ 104 "{1}".format(func.__doc__, field) 105 return add_attrs_from(func, to=wrapper) 106 return decorator 107 108 109def destructure_object(value, into, prefix, members=False): 110 if isinstance(value, boto.mws.response.ResponseElement): 111 destructure_object(value.__dict__, into, prefix, members=members) 112 elif isinstance(value, collections.Mapping): 113 for name in value: 114 if name.startswith('_'): 115 continue 116 destructure_object(value[name], into, prefix + '.' + name, 117 members=members) 118 elif isinstance(value, six.string_types): 119 into[prefix] = value 120 elif isinstance(value, collections.Iterable): 121 for index, element in enumerate(value): 122 suffix = (members and '.member.' or '.') + str(index + 1) 123 destructure_object(element, into, prefix + suffix, 124 members=members) 125 elif isinstance(value, bool): 126 into[prefix] = str(value).lower() 127 else: 128 into[prefix] = value 129 130 131def structured_objects(*fields, **kwargs): 132 133 def decorator(func): 134 135 def wrapper(*args, **kw): 136 members = kwargs.get('members', False) 137 for field in filter(lambda i: i in kw, fields): 138 destructure_object(kw.pop(field), kw, field, members=members) 139 return func(*args, **kw) 140 wrapper.__doc__ = "{0}\nElement|Iter|Map: {1}\n" \ 141 "(ResponseElement or anything iterable/dict-like)" \ 142 .format(func.__doc__, ', '.join(fields)) 143 return add_attrs_from(func, to=wrapper) 144 return decorator 145 146 147def requires(*groups): 148 149 def decorator(func): 150 151 def requires(*args, **kw): 152 hasgroup = lambda group: all(key in kw for key in group) 153 if 1 != len(list(filter(hasgroup, groups))): 154 message = ' OR '.join(['+'.join(g) for g in groups]) 155 message = "{0} requires {1} argument(s)" \ 156 "".format(func.action, message) 157 raise KeyError(message) 158 return func(*args, **kw) 159 message = ' OR '.join(['+'.join(g) for g in groups]) 160 requires.__doc__ = "{0}\nRequired: {1}".format(func.__doc__, 161 message) 162 return add_attrs_from(func, to=requires) 163 return decorator 164 165 166def exclusive(*groups): 167 168 def decorator(func): 169 170 def wrapper(*args, **kw): 171 hasgroup = lambda group: all(key in kw for key in group) 172 if len(list(filter(hasgroup, groups))) not in (0, 1): 173 message = ' OR '.join(['+'.join(g) for g in groups]) 174 message = "{0} requires either {1}" \ 175 "".format(func.action, message) 176 raise KeyError(message) 177 return func(*args, **kw) 178 message = ' OR '.join(['+'.join(g) for g in groups]) 179 wrapper.__doc__ = "{0}\nEither: {1}".format(func.__doc__, 180 message) 181 return add_attrs_from(func, to=wrapper) 182 return decorator 183 184 185def dependent(field, *groups): 186 187 def decorator(func): 188 189 def wrapper(*args, **kw): 190 hasgroup = lambda group: all(key in kw for key in group) 191 if field in kw and not any(hasgroup(g) for g in groups): 192 message = ' OR '.join(['+'.join(g) for g in groups]) 193 message = "{0} argument {1} requires {2}" \ 194 "".format(func.action, field, message) 195 raise KeyError(message) 196 return func(*args, **kw) 197 message = ' OR '.join(['+'.join(g) for g in groups]) 198 wrapper.__doc__ = "{0}\n{1} requires: {2}".format(func.__doc__, 199 field, 200 message) 201 return add_attrs_from(func, to=wrapper) 202 return decorator 203 204 205def requires_some_of(*fields): 206 207 def decorator(func): 208 209 def requires(*args, **kw): 210 if not any(i in kw for i in fields): 211 message = "{0} requires at least one of {1} argument(s)" \ 212 "".format(func.action, ', '.join(fields)) 213 raise KeyError(message) 214 return func(*args, **kw) 215 requires.__doc__ = "{0}\nSome Required: {1}".format(func.__doc__, 216 ', '.join(fields)) 217 return add_attrs_from(func, to=requires) 218 return decorator 219 220 221def boolean_arguments(*fields): 222 223 def decorator(func): 224 225 def wrapper(*args, **kw): 226 for field in [f for f in fields if isinstance(kw.get(f), bool)]: 227 kw[field] = str(kw[field]).lower() 228 return func(*args, **kw) 229 wrapper.__doc__ = "{0}\nBooleans: {1}".format(func.__doc__, 230 ', '.join(fields)) 231 return add_attrs_from(func, to=wrapper) 232 return decorator 233 234 235def api_action(section, quota, restore, *api): 236 237 def decorator(func, quota=int(quota), restore=float(restore)): 238 version, accesskey, path = api_version_path[section] 239 action = ''.join(api or map(str.capitalize, func.__name__.split('_'))) 240 241 def wrapper(self, *args, **kw): 242 kw.setdefault(accesskey, getattr(self, accesskey, None)) 243 if kw[accesskey] is None: 244 message = "{0} requires {1} argument. Set the " \ 245 "MWSConnection.{2} attribute?" \ 246 "".format(action, accesskey, accesskey) 247 raise KeyError(message) 248 kw['Action'] = action 249 kw['Version'] = version 250 response = self._response_factory(action, connection=self) 251 request = dict(path=path, quota=quota, restore=restore) 252 return func(self, request, response, *args, **kw) 253 for attr in decorated_attrs: 254 setattr(wrapper, attr, locals().get(attr)) 255 wrapper.__doc__ = "MWS {0}/{1} API call; quota={2} restore={3:.2f}\n" \ 256 "{4}".format(action, version, quota, restore, 257 func.__doc__) 258 api_call_map[action] = func.__name__ 259 return wrapper 260 return decorator 261 262 263class MWSConnection(AWSQueryConnection): 264 265 ResponseFactory = boto.mws.response.ResponseFactory 266 ResponseErrorFactory = boto.mws.exception.ResponseErrorFactory 267 268 def __init__(self, *args, **kw): 269 kw.setdefault('host', 'mws.amazonservices.com') 270 self._sandboxed = kw.pop('sandbox', False) 271 self.Merchant = kw.pop('Merchant', None) or kw.get('SellerId') 272 self.SellerId = kw.pop('SellerId', None) or self.Merchant 273 kw = self._setup_factories(kw.pop('factory_scopes', []), **kw) 274 super(MWSConnection, self).__init__(*args, **kw) 275 276 def _setup_factories(self, extrascopes, **kw): 277 for factory, (scope, Default) in { 278 'response_factory': 279 (boto.mws.response, self.ResponseFactory), 280 'response_error_factory': 281 (boto.mws.exception, self.ResponseErrorFactory), 282 }.items(): 283 if factory in kw: 284 setattr(self, '_' + factory, kw.pop(factory)) 285 else: 286 scopes = extrascopes + [scope] 287 setattr(self, '_' + factory, Default(scopes=scopes)) 288 return kw 289 290 def _sandboxify(self, path): 291 if not self._sandboxed: 292 return path 293 splat = path.split('/') 294 splat[-2] += '_Sandbox' 295 return '/'.join(splat) 296 297 def _required_auth_capability(self): 298 return ['mws'] 299 300 def _post_request(self, request, params, parser, body='', headers=None): 301 """Make a POST request, optionally with a content body, 302 and return the response, optionally as raw text. 303 """ 304 headers = headers or {} 305 path = self._sandboxify(request['path']) 306 request = self.build_base_http_request('POST', path, None, data=body, 307 params=params, headers=headers, 308 host=self.host) 309 try: 310 response = self._mexe(request, override_num_retries=None) 311 except BotoServerError as bs: 312 raise self._response_error_factory(bs.status, bs.reason, bs.body) 313 body = response.read() 314 boto.log.debug(body) 315 if not body: 316 boto.log.error('Null body %s' % body) 317 raise self._response_error_factory(response.status, 318 response.reason, body) 319 if response.status != 200: 320 boto.log.error('%s %s' % (response.status, response.reason)) 321 boto.log.error('%s' % body) 322 raise self._response_error_factory(response.status, 323 response.reason, body) 324 digest = response.getheader('Content-MD5') 325 if digest is not None: 326 assert content_md5(body) == digest 327 contenttype = response.getheader('Content-Type') 328 return self._parse_response(parser, contenttype, body) 329 330 def _parse_response(self, parser, contenttype, body): 331 if not contenttype.startswith('text/xml'): 332 return body 333 handler = XmlHandler(parser, self) 334 xml.sax.parseString(body, handler) 335 return parser 336 337 def method_for(self, name): 338 """Return the MWS API method referred to in the argument. 339 The named method can be in CamelCase or underlined_lower_case. 340 This is the complement to MWSConnection.any_call.action 341 """ 342 action = '_' in name and string.capwords(name, '_') or name 343 if action in api_call_map: 344 return getattr(self, api_call_map[action]) 345 return None 346 347 def iter_call(self, call, *args, **kw): 348 """Pass a call name as the first argument and a generator 349 is returned for the initial response and any continuation 350 call responses made using the NextToken. 351 """ 352 method = self.method_for(call) 353 assert method, 'No call named "{0}"'.format(call) 354 return self.iter_response(method(*args, **kw)) 355 356 def iter_response(self, response): 357 """Pass a call's response as the initial argument and a 358 generator is returned for the initial response and any 359 continuation call responses made using the NextToken. 360 """ 361 yield response 362 more = self.method_for(response._action + 'ByNextToken') 363 while more and response._result.HasNext == 'true': 364 response = more(NextToken=response._result.NextToken) 365 yield response 366 367 @requires(['FeedType']) 368 @boolean_arguments('PurgeAndReplace') 369 @http_body('FeedContent') 370 @structured_lists('MarketplaceIdList.Id') 371 @api_action('Feeds', 15, 120) 372 def submit_feed(self, request, response, headers=None, body='', **kw): 373 """Uploads a feed for processing by Amazon MWS. 374 """ 375 headers = headers or {} 376 return self._post_request(request, kw, response, body=body, 377 headers=headers) 378 379 @structured_lists('FeedSubmissionIdList.Id', 'FeedTypeList.Type', 380 'FeedProcessingStatusList.Status') 381 @api_action('Feeds', 10, 45) 382 def get_feed_submission_list(self, request, response, **kw): 383 """Returns a list of all feed submissions submitted in the 384 previous 90 days. 385 """ 386 return self._post_request(request, kw, response) 387 388 @requires(['NextToken']) 389 @api_action('Feeds', 0, 0) 390 def get_feed_submission_list_by_next_token(self, request, response, **kw): 391 """Returns a list of feed submissions using the NextToken parameter. 392 """ 393 return self._post_request(request, kw, response) 394 395 @structured_lists('FeedTypeList.Type', 'FeedProcessingStatusList.Status') 396 @api_action('Feeds', 10, 45) 397 def get_feed_submission_count(self, request, response, **kw): 398 """Returns a count of the feeds submitted in the previous 90 days. 399 """ 400 return self._post_request(request, kw, response) 401 402 @structured_lists('FeedSubmissionIdList.Id', 'FeedTypeList.Type') 403 @api_action('Feeds', 10, 45) 404 def cancel_feed_submissions(self, request, response, **kw): 405 """Cancels one or more feed submissions and returns a 406 count of the feed submissions that were canceled. 407 """ 408 return self._post_request(request, kw, response) 409 410 @requires(['FeedSubmissionId']) 411 @api_action('Feeds', 15, 60) 412 def get_feed_submission_result(self, request, response, **kw): 413 """Returns the feed processing report. 414 """ 415 return self._post_request(request, kw, response) 416 417 def get_service_status(self, **kw): 418 """Instruct the user on how to get service status. 419 """ 420 sections = ', '.join(map(str.lower, api_version_path.keys())) 421 message = "Use {0}.get_(section)_service_status(), " \ 422 "where (section) is one of the following: " \ 423 "{1}".format(self.__class__.__name__, sections) 424 raise AttributeError(message) 425 426 @requires(['ReportType']) 427 @structured_lists('MarketplaceIdList.Id') 428 @boolean_arguments('ReportOptions=ShowSalesChannel') 429 @api_action('Reports', 15, 60) 430 def request_report(self, request, response, **kw): 431 """Creates a report request and submits the request to Amazon MWS. 432 """ 433 return self._post_request(request, kw, response) 434 435 @structured_lists('ReportRequestIdList.Id', 'ReportTypeList.Type', 436 'ReportProcessingStatusList.Status') 437 @api_action('Reports', 10, 45) 438 def get_report_request_list(self, request, response, **kw): 439 """Returns a list of report requests that you can use to get the 440 ReportRequestId for a report. 441 """ 442 return self._post_request(request, kw, response) 443 444 @requires(['NextToken']) 445 @api_action('Reports', 0, 0) 446 def get_report_request_list_by_next_token(self, request, response, **kw): 447 """Returns a list of report requests using the NextToken, 448 which was supplied by a previous request to either 449 GetReportRequestListByNextToken or GetReportRequestList, where 450 the value of HasNext was true in that previous request. 451 """ 452 return self._post_request(request, kw, response) 453 454 @structured_lists('ReportTypeList.Type', 455 'ReportProcessingStatusList.Status') 456 @api_action('Reports', 10, 45) 457 def get_report_request_count(self, request, response, **kw): 458 """Returns a count of report requests that have been submitted 459 to Amazon MWS for processing. 460 """ 461 return self._post_request(request, kw, response) 462 463 @api_action('Reports', 10, 45) 464 def cancel_report_requests(self, request, response, **kw): 465 """Cancel one or more report requests, returning the count of the 466 canceled report requests and the report request information. 467 """ 468 return self._post_request(request, kw, response) 469 470 @boolean_arguments('Acknowledged') 471 @structured_lists('ReportRequestIdList.Id', 'ReportTypeList.Type') 472 @api_action('Reports', 10, 60) 473 def get_report_list(self, request, response, **kw): 474 """Returns a list of reports that were created in the previous 475 90 days that match the query parameters. 476 """ 477 return self._post_request(request, kw, response) 478 479 @requires(['NextToken']) 480 @api_action('Reports', 0, 0) 481 def get_report_list_by_next_token(self, request, response, **kw): 482 """Returns a list of reports using the NextToken, which 483 was supplied by a previous request to either 484 GetReportListByNextToken or GetReportList, where the 485 value of HasNext was true in the previous call. 486 """ 487 return self._post_request(request, kw, response) 488 489 @boolean_arguments('Acknowledged') 490 @structured_lists('ReportTypeList.Type') 491 @api_action('Reports', 10, 45) 492 def get_report_count(self, request, response, **kw): 493 """Returns a count of the reports, created in the previous 90 days, 494 with a status of _DONE_ and that are available for download. 495 """ 496 return self._post_request(request, kw, response) 497 498 @requires(['ReportId']) 499 @api_action('Reports', 15, 60) 500 def get_report(self, request, response, **kw): 501 """Returns the contents of a report. 502 """ 503 return self._post_request(request, kw, response) 504 505 @requires(['ReportType', 'Schedule']) 506 @api_action('Reports', 10, 45) 507 def manage_report_schedule(self, request, response, **kw): 508 """Creates, updates, or deletes a report request schedule for 509 a specified report type. 510 """ 511 return self._post_request(request, kw, response) 512 513 @structured_lists('ReportTypeList.Type') 514 @api_action('Reports', 10, 45) 515 def get_report_schedule_list(self, request, response, **kw): 516 """Returns a list of order report requests that are scheduled 517 to be submitted to Amazon MWS for processing. 518 """ 519 return self._post_request(request, kw, response) 520 521 @requires(['NextToken']) 522 @api_action('Reports', 0, 0) 523 def get_report_schedule_list_by_next_token(self, request, response, **kw): 524 """Returns a list of report requests using the NextToken, 525 which was supplied by a previous request to either 526 GetReportScheduleListByNextToken or GetReportScheduleList, 527 where the value of HasNext was true in that previous request. 528 """ 529 return self._post_request(request, kw, response) 530 531 @structured_lists('ReportTypeList.Type') 532 @api_action('Reports', 10, 45) 533 def get_report_schedule_count(self, request, response, **kw): 534 """Returns a count of order report requests that are scheduled 535 to be submitted to Amazon MWS. 536 """ 537 return self._post_request(request, kw, response) 538 539 @requires(['ReportIdList']) 540 @boolean_arguments('Acknowledged') 541 @structured_lists('ReportIdList.Id') 542 @api_action('Reports', 10, 45) 543 def update_report_acknowledgements(self, request, response, **kw): 544 """Updates the acknowledged status of one or more reports. 545 """ 546 return self._post_request(request, kw, response) 547 548 @requires(['ShipFromAddress', 'InboundShipmentPlanRequestItems']) 549 @structured_objects('ShipFromAddress', 'InboundShipmentPlanRequestItems') 550 @api_action('Inbound', 30, 0.5) 551 def create_inbound_shipment_plan(self, request, response, **kw): 552 """Returns the information required to create an inbound shipment. 553 """ 554 return self._post_request(request, kw, response) 555 556 @requires(['ShipmentId', 'InboundShipmentHeader', 'InboundShipmentItems']) 557 @structured_objects('InboundShipmentHeader', 'InboundShipmentItems') 558 @api_action('Inbound', 30, 0.5) 559 def create_inbound_shipment(self, request, response, **kw): 560 """Creates an inbound shipment. 561 """ 562 return self._post_request(request, kw, response) 563 564 @requires(['ShipmentId']) 565 @structured_objects('InboundShipmentHeader', 'InboundShipmentItems') 566 @api_action('Inbound', 30, 0.5) 567 def update_inbound_shipment(self, request, response, **kw): 568 """Updates an existing inbound shipment. Amazon documentation 569 is ambiguous as to whether the InboundShipmentHeader and 570 InboundShipmentItems arguments are required. 571 """ 572 return self._post_request(request, kw, response) 573 574 @requires_some_of('ShipmentIdList', 'ShipmentStatusList') 575 @structured_lists('ShipmentIdList.Id', 'ShipmentStatusList.Status') 576 @api_action('Inbound', 30, 0.5) 577 def list_inbound_shipments(self, request, response, **kw): 578 """Returns a list of inbound shipments based on criteria that 579 you specify. 580 """ 581 return self._post_request(request, kw, response) 582 583 @requires(['NextToken']) 584 @api_action('Inbound', 30, 0.5) 585 def list_inbound_shipments_by_next_token(self, request, response, **kw): 586 """Returns the next page of inbound shipments using the NextToken 587 parameter. 588 """ 589 return self._post_request(request, kw, response) 590 591 @requires(['ShipmentId'], ['LastUpdatedAfter', 'LastUpdatedBefore']) 592 @api_action('Inbound', 30, 0.5) 593 def list_inbound_shipment_items(self, request, response, **kw): 594 """Returns a list of items in a specified inbound shipment, or a 595 list of items that were updated within a specified time frame. 596 """ 597 return self._post_request(request, kw, response) 598 599 @requires(['NextToken']) 600 @api_action('Inbound', 30, 0.5) 601 def list_inbound_shipment_items_by_next_token(self, request, response, **kw): 602 """Returns the next page of inbound shipment items using the 603 NextToken parameter. 604 """ 605 return self._post_request(request, kw, response) 606 607 @api_action('Inbound', 2, 300, 'GetServiceStatus') 608 def get_inbound_service_status(self, request, response, **kw): 609 """Returns the operational status of the Fulfillment Inbound 610 Shipment API section. 611 """ 612 return self._post_request(request, kw, response) 613 614 @requires(['SellerSkus'], ['QueryStartDateTime']) 615 @structured_lists('SellerSkus.member') 616 @api_action('Inventory', 30, 0.5) 617 def list_inventory_supply(self, request, response, **kw): 618 """Returns information about the availability of a seller's 619 inventory. 620 """ 621 return self._post_request(request, kw, response) 622 623 @requires(['NextToken']) 624 @api_action('Inventory', 30, 0.5) 625 def list_inventory_supply_by_next_token(self, request, response, **kw): 626 """Returns the next page of information about the availability 627 of a seller's inventory using the NextToken parameter. 628 """ 629 return self._post_request(request, kw, response) 630 631 @api_action('Inventory', 2, 300, 'GetServiceStatus') 632 def get_inventory_service_status(self, request, response, **kw): 633 """Returns the operational status of the Fulfillment Inventory 634 API section. 635 """ 636 return self._post_request(request, kw, response) 637 638 @requires(['PackageNumber']) 639 @api_action('Outbound', 30, 0.5) 640 def get_package_tracking_details(self, request, response, **kw): 641 """Returns delivery tracking information for a package in 642 an outbound shipment for a Multi-Channel Fulfillment order. 643 """ 644 return self._post_request(request, kw, response) 645 646 @requires(['Address', 'Items']) 647 @structured_objects('Address', 'Items') 648 @api_action('Outbound', 30, 0.5) 649 def get_fulfillment_preview(self, request, response, **kw): 650 """Returns a list of fulfillment order previews based on items 651 and shipping speed categories that you specify. 652 """ 653 return self._post_request(request, kw, response) 654 655 @requires(['SellerFulfillmentOrderId', 'DisplayableOrderId', 656 'ShippingSpeedCategory', 'DisplayableOrderDateTime', 657 'DestinationAddress', 'DisplayableOrderComment', 658 'Items']) 659 @structured_objects('DestinationAddress', 'Items') 660 @api_action('Outbound', 30, 0.5) 661 def create_fulfillment_order(self, request, response, **kw): 662 """Requests that Amazon ship items from the seller's inventory 663 to a destination address. 664 """ 665 return self._post_request(request, kw, response) 666 667 @requires(['SellerFulfillmentOrderId']) 668 @api_action('Outbound', 30, 0.5) 669 def get_fulfillment_order(self, request, response, **kw): 670 """Returns a fulfillment order based on a specified 671 SellerFulfillmentOrderId. 672 """ 673 return self._post_request(request, kw, response) 674 675 @api_action('Outbound', 30, 0.5) 676 def list_all_fulfillment_orders(self, request, response, **kw): 677 """Returns a list of fulfillment orders fulfilled after (or 678 at) a specified date or by fulfillment method. 679 """ 680 return self._post_request(request, kw, response) 681 682 @requires(['NextToken']) 683 @api_action('Outbound', 30, 0.5) 684 def list_all_fulfillment_orders_by_next_token(self, request, response, **kw): 685 """Returns the next page of inbound shipment items using the 686 NextToken parameter. 687 """ 688 return self._post_request(request, kw, response) 689 690 @requires(['SellerFulfillmentOrderId']) 691 @api_action('Outbound', 30, 0.5) 692 def cancel_fulfillment_order(self, request, response, **kw): 693 """Requests that Amazon stop attempting to fulfill an existing 694 fulfillment order. 695 """ 696 return self._post_request(request, kw, response) 697 698 @api_action('Outbound', 2, 300, 'GetServiceStatus') 699 def get_outbound_service_status(self, request, response, **kw): 700 """Returns the operational status of the Fulfillment Outbound 701 API section. 702 """ 703 return self._post_request(request, kw, response) 704 705 @requires(['CreatedAfter'], ['LastUpdatedAfter']) 706 @requires(['MarketplaceId']) 707 @exclusive(['CreatedAfter'], ['LastUpdatedAfter']) 708 @dependent('CreatedBefore', ['CreatedAfter']) 709 @exclusive(['LastUpdatedAfter'], ['BuyerEmail'], ['SellerOrderId']) 710 @dependent('LastUpdatedBefore', ['LastUpdatedAfter']) 711 @exclusive(['CreatedAfter'], ['LastUpdatedBefore']) 712 @structured_objects('OrderTotal', 'ShippingAddress', 713 'PaymentExecutionDetail') 714 @structured_lists('MarketplaceId.Id', 'OrderStatus.Status', 715 'FulfillmentChannel.Channel', 'PaymentMethod.') 716 @api_action('Orders', 6, 60) 717 def list_orders(self, request, response, **kw): 718 """Returns a list of orders created or updated during a time 719 frame that you specify. 720 """ 721 toggle = set(('FulfillmentChannel.Channel.1', 722 'OrderStatus.Status.1', 'PaymentMethod.1', 723 'LastUpdatedAfter', 'LastUpdatedBefore')) 724 for do, dont in { 725 'BuyerEmail': toggle.union(['SellerOrderId']), 726 'SellerOrderId': toggle.union(['BuyerEmail']), 727 }.items(): 728 if do in kw and any(i in dont for i in kw): 729 message = "Don't include {0} when specifying " \ 730 "{1}".format(' or '.join(dont), do) 731 raise AssertionError(message) 732 return self._post_request(request, kw, response) 733 734 @requires(['NextToken']) 735 @api_action('Orders', 6, 60) 736 def list_orders_by_next_token(self, request, response, **kw): 737 """Returns the next page of orders using the NextToken value 738 that was returned by your previous request to either 739 ListOrders or ListOrdersByNextToken. 740 """ 741 return self._post_request(request, kw, response) 742 743 @requires(['AmazonOrderId']) 744 @structured_lists('AmazonOrderId.Id') 745 @api_action('Orders', 6, 60) 746 def get_order(self, request, response, **kw): 747 """Returns an order for each AmazonOrderId that you specify. 748 """ 749 return self._post_request(request, kw, response) 750 751 @requires(['AmazonOrderId']) 752 @api_action('Orders', 30, 2) 753 def list_order_items(self, request, response, **kw): 754 """Returns order item information for an AmazonOrderId that 755 you specify. 756 """ 757 return self._post_request(request, kw, response) 758 759 @requires(['NextToken']) 760 @api_action('Orders', 30, 2) 761 def list_order_items_by_next_token(self, request, response, **kw): 762 """Returns the next page of order items using the NextToken 763 value that was returned by your previous request to either 764 ListOrderItems or ListOrderItemsByNextToken. 765 """ 766 return self._post_request(request, kw, response) 767 768 @api_action('Orders', 2, 300, 'GetServiceStatus') 769 def get_orders_service_status(self, request, response, **kw): 770 """Returns the operational status of the Orders API section. 771 """ 772 return self._post_request(request, kw, response) 773 774 @requires(['MarketplaceId', 'Query']) 775 @api_action('Products', 20, 20) 776 def list_matching_products(self, request, response, **kw): 777 """Returns a list of products and their attributes, ordered 778 by relevancy, based on a search query that you specify. 779 """ 780 return self._post_request(request, kw, response) 781 782 @requires(['MarketplaceId', 'ASINList']) 783 @structured_lists('ASINList.ASIN') 784 @api_action('Products', 20, 20) 785 def get_matching_product(self, request, response, **kw): 786 """Returns a list of products and their attributes, based on 787 a list of ASIN values that you specify. 788 """ 789 return self._post_request(request, kw, response) 790 791 @requires(['MarketplaceId', 'IdType', 'IdList']) 792 @structured_lists('IdList.Id') 793 @api_action('Products', 20, 20) 794 def get_matching_product_for_id(self, request, response, **kw): 795 """Returns a list of products and their attributes, based on 796 a list of Product IDs that you specify. 797 """ 798 return self._post_request(request, kw, response) 799 800 @requires(['MarketplaceId', 'SellerSKUList']) 801 @structured_lists('SellerSKUList.SellerSKU') 802 @api_action('Products', 20, 10, 'GetCompetitivePricingForSKU') 803 def get_competitive_pricing_for_sku(self, request, response, **kw): 804 """Returns the current competitive pricing of a product, 805 based on the SellerSKUs and MarketplaceId that you specify. 806 """ 807 return self._post_request(request, kw, response) 808 809 @requires(['MarketplaceId', 'ASINList']) 810 @structured_lists('ASINList.ASIN') 811 @api_action('Products', 20, 10, 'GetCompetitivePricingForASIN') 812 def get_competitive_pricing_for_asin(self, request, response, **kw): 813 """Returns the current competitive pricing of a product, 814 based on the ASINs and MarketplaceId that you specify. 815 """ 816 return self._post_request(request, kw, response) 817 818 @requires(['MarketplaceId', 'SellerSKUList']) 819 @structured_lists('SellerSKUList.SellerSKU') 820 @api_action('Products', 20, 5, 'GetLowestOfferListingsForSKU') 821 def get_lowest_offer_listings_for_sku(self, request, response, **kw): 822 """Returns the lowest price offer listings for a specific 823 product by item condition and SellerSKUs. 824 """ 825 return self._post_request(request, kw, response) 826 827 @requires(['MarketplaceId', 'ASINList']) 828 @structured_lists('ASINList.ASIN') 829 @api_action('Products', 20, 5, 'GetLowestOfferListingsForASIN') 830 def get_lowest_offer_listings_for_asin(self, request, response, **kw): 831 """Returns the lowest price offer listings for a specific 832 product by item condition and ASINs. 833 """ 834 return self._post_request(request, kw, response) 835 836 @requires(['MarketplaceId', 'SellerSKU']) 837 @api_action('Products', 20, 20, 'GetProductCategoriesForSKU') 838 def get_product_categories_for_sku(self, request, response, **kw): 839 """Returns the product categories that a SellerSKU belongs to. 840 """ 841 return self._post_request(request, kw, response) 842 843 @requires(['MarketplaceId', 'ASIN']) 844 @api_action('Products', 20, 20, 'GetProductCategoriesForASIN') 845 def get_product_categories_for_asin(self, request, response, **kw): 846 """Returns the product categories that an ASIN belongs to. 847 """ 848 return self._post_request(request, kw, response) 849 850 @api_action('Products', 2, 300, 'GetServiceStatus') 851 def get_products_service_status(self, request, response, **kw): 852 """Returns the operational status of the Products API section. 853 """ 854 return self._post_request(request, kw, response) 855 856 @requires(['MarketplaceId', 'SellerSKUList']) 857 @structured_lists('SellerSKUList.SellerSKU') 858 @api_action('Products', 20, 10, 'GetMyPriceForSKU') 859 def get_my_price_for_sku(self, request, response, **kw): 860 """Returns pricing information for your own offer listings, based on SellerSKU. 861 """ 862 return self._post_request(request, kw, response) 863 864 @requires(['MarketplaceId', 'ASINList']) 865 @structured_lists('ASINList.ASIN') 866 @api_action('Products', 20, 10, 'GetMyPriceForASIN') 867 def get_my_price_for_asin(self, request, response, **kw): 868 """Returns pricing information for your own offer listings, based on ASIN. 869 """ 870 return self._post_request(request, kw, response) 871 872 @api_action('Sellers', 15, 60) 873 def list_marketplace_participations(self, request, response, **kw): 874 """Returns a list of marketplaces that the seller submitting 875 the request can sell in, and a list of participations that 876 include seller-specific information in that marketplace. 877 """ 878 return self._post_request(request, kw, response) 879 880 @requires(['NextToken']) 881 @api_action('Sellers', 15, 60) 882 def list_marketplace_participations_by_next_token(self, request, response, 883 **kw): 884 """Returns the next page of marketplaces and participations 885 using the NextToken value that was returned by your 886 previous request to either ListMarketplaceParticipations 887 or ListMarketplaceParticipationsByNextToken. 888 """ 889 return self._post_request(request, kw, response) 890 891 @requires(['MarketplaceId']) 892 @api_action('Recommendations', 5, 2) 893 def get_last_updated_time_for_recommendations(self, request, response, 894 **kw): 895 """Checks whether there are active recommendations for each category 896 for the given marketplace, and if there are, returns the time when 897 recommendations were last updated for each category. 898 """ 899 return self._post_request(request, kw, response) 900 901 @requires(['MarketplaceId']) 902 @structured_lists('CategoryQueryList.CategoryQuery') 903 @api_action('Recommendations', 5, 2) 904 def list_recommendations(self, request, response, **kw): 905 """Returns your active recommendations for a specific category or for 906 all categories for a specific marketplace. 907 """ 908 return self._post_request(request, kw, response) 909 910 @requires(['NextToken']) 911 @api_action('Recommendations', 5, 2) 912 def list_recommendations_by_next_token(self, request, response, **kw): 913 """Returns the next page of recommendations using the NextToken 914 parameter. 915 """ 916 return self._post_request(request, kw, response) 917 918 @api_action('Recommendations', 2, 300, 'GetServiceStatus') 919 def get_recommendations_service_status(self, request, response, **kw): 920 """Returns the operational status of the Recommendations API section. 921 """ 922 return self._post_request(request, kw, response) 923 924 @api_action('CustomerInfo', 15, 12) 925 def list_customers(self, request, response, **kw): 926 """Returns a list of customer accounts based on search criteria that 927 you specify. 928 """ 929 return self._post_request(request, kw, response) 930 931 @requires(['NextToken']) 932 @api_action('CustomerInfo', 50, 3) 933 def list_customers_by_next_token(self, request, response, **kw): 934 """Returns the next page of customers using the NextToken parameter. 935 """ 936 return self._post_request(request, kw, response) 937 938 @requires(['CustomerIdList']) 939 @structured_lists('CustomerIdList.CustomerId') 940 @api_action('CustomerInfo', 15, 12) 941 def get_customers_for_customer_id(self, request, response, **kw): 942 """Returns a list of customer accounts based on search criteria that 943 you specify. 944 """ 945 return self._post_request(request, kw, response) 946 947 @api_action('CustomerInfo', 2, 300, 'GetServiceStatus') 948 def get_customerinfo_service_status(self, request, response, **kw): 949 """Returns the operational status of the Customer Information API 950 section. 951 """ 952 return self._post_request(request, kw, response) 953 954 @requires(['DateRangeStart']) 955 @api_action('CartInfo', 15, 12) 956 def list_carts(self, request, response, **kw): 957 """Returns a list of shopping carts in your Webstore that were last 958 updated during the time range that you specify. 959 """ 960 return self._post_request(request, kw, response) 961 962 @requires(['NextToken']) 963 @api_action('CartInfo', 50, 3) 964 def list_carts_by_next_token(self, request, response, **kw): 965 """Returns the next page of shopping carts using the NextToken 966 parameter. 967 """ 968 return self._post_request(request, kw, response) 969 970 @requires(['CartIdList']) 971 @structured_lists('CartIdList.CartId') 972 @api_action('CartInfo', 15, 12) 973 def get_carts(self, request, response, **kw): 974 """Returns shopping carts based on the CartId values that you specify. 975 """ 976 return self._post_request(request, kw, response) 977 978 @api_action('CartInfo', 2, 300, 'GetServiceStatus') 979 def get_cartinfo_service_status(self, request, response, **kw): 980 """Returns the operational status of the Cart Information API section. 981 """ 982 return self._post_request(request, kw, response) 983 984 @requires(['MarketplaceId', 'Destination']) 985 @structured_objects('Destination', members=True) 986 @api_action('Subscriptions', 25, 0.5) 987 def register_destination(self, request, response, **kw): 988 """Specifies a new destination where you want to receive notifications. 989 """ 990 return self._post_request(request, kw, response) 991 992 @requires(['MarketplaceId', 'Destination']) 993 @structured_objects('Destination', members=True) 994 @api_action('Subscriptions', 25, 0.5) 995 def deregister_destination(self, request, response, **kw): 996 """Removes an existing destination from the list of registered 997 destinations. 998 """ 999 return self._post_request(request, kw, response) 1000 1001 @requires(['MarketplaceId']) 1002 @api_action('Subscriptions', 25, 0.5) 1003 def list_registered_destinations(self, request, response, **kw): 1004 """Lists all current destinations that you have registered. 1005 """ 1006 return self._post_request(request, kw, response) 1007 1008 @requires(['MarketplaceId', 'Destination']) 1009 @structured_objects('Destination', members=True) 1010 @api_action('Subscriptions', 25, 0.5) 1011 def send_test_notification_to_destination(self, request, response, **kw): 1012 """Sends a test notification to an existing destination. 1013 """ 1014 return self._post_request(request, kw, response) 1015 1016 @requires(['MarketplaceId', 'Subscription']) 1017 @structured_objects('Subscription', members=True) 1018 @api_action('Subscriptions', 25, 0.5) 1019 def create_subscription(self, request, response, **kw): 1020 """Creates a new subscription for the specified notification type 1021 and destination. 1022 """ 1023 return self._post_request(request, kw, response) 1024 1025 @requires(['MarketplaceId', 'NotificationType', 'Destination']) 1026 @structured_objects('Destination', members=True) 1027 @api_action('Subscriptions', 25, 0.5) 1028 def get_subscription(self, request, response, **kw): 1029 """Gets the subscription for the specified notification type and 1030 destination. 1031 """ 1032 return self._post_request(request, kw, response) 1033 1034 @requires(['MarketplaceId', 'NotificationType', 'Destination']) 1035 @structured_objects('Destination', members=True) 1036 @api_action('Subscriptions', 25, 0.5) 1037 def delete_subscription(self, request, response, **kw): 1038 """Deletes the subscription for the specified notification type and 1039 destination. 1040 """ 1041 return self._post_request(request, kw, response) 1042 1043 @requires(['MarketplaceId']) 1044 @api_action('Subscriptions', 25, 0.5) 1045 def list_subscriptions(self, request, response, **kw): 1046 """Returns a list of all your current subscriptions. 1047 """ 1048 return self._post_request(request, kw, response) 1049 1050 @requires(['MarketplaceId', 'Subscription']) 1051 @structured_objects('Subscription', members=True) 1052 @api_action('Subscriptions', 25, 0.5) 1053 def update_subscription(self, request, response, **kw): 1054 """Updates the subscription for the specified notification type and 1055 destination. 1056 """ 1057 return self._post_request(request, kw, response) 1058 1059 @api_action('Subscriptions', 2, 300, 'GetServiceStatus') 1060 def get_subscriptions_service_status(self, request, response, **kw): 1061 """Returns the operational status of the Subscriptions API section. 1062 """ 1063 return self._post_request(request, kw, response) 1064 1065 @requires(['AmazonOrderReferenceId', 'OrderReferenceAttributes']) 1066 @structured_objects('OrderReferenceAttributes') 1067 @api_action('OffAmazonPayments', 10, 1) 1068 def set_order_reference_details(self, request, response, **kw): 1069 """Sets order reference details such as the order total and a 1070 description for the order. 1071 """ 1072 return self._post_request(request, kw, response) 1073 1074 @requires(['AmazonOrderReferenceId']) 1075 @api_action('OffAmazonPayments', 20, 2) 1076 def get_order_reference_details(self, request, response, **kw): 1077 """Returns details about the Order Reference object and its current 1078 state. 1079 """ 1080 return self._post_request(request, kw, response) 1081 1082 @requires(['AmazonOrderReferenceId']) 1083 @api_action('OffAmazonPayments', 10, 1) 1084 def confirm_order_reference(self, request, response, **kw): 1085 """Confirms that the order reference is free of constraints and all 1086 required information has been set on the order reference. 1087 """ 1088 return self._post_request(request, kw, response) 1089 1090 @requires(['AmazonOrderReferenceId']) 1091 @api_action('OffAmazonPayments', 10, 1) 1092 def cancel_order_reference(self, request, response, **kw): 1093 """Cancel an order reference; all authorizations associated with 1094 this order reference are also closed. 1095 """ 1096 return self._post_request(request, kw, response) 1097 1098 @requires(['AmazonOrderReferenceId']) 1099 @api_action('OffAmazonPayments', 10, 1) 1100 def close_order_reference(self, request, response, **kw): 1101 """Confirms that an order reference has been fulfilled (fully 1102 or partially) and that you do not expect to create any new 1103 authorizations on this order reference. 1104 """ 1105 return self._post_request(request, kw, response) 1106 1107 @requires(['AmazonOrderReferenceId', 'AuthorizationReferenceId', 1108 'AuthorizationAmount']) 1109 @structured_objects('AuthorizationAmount') 1110 @api_action('OffAmazonPayments', 10, 1) 1111 def authorize(self, request, response, **kw): 1112 """Reserves a specified amount against the payment method(s) stored in 1113 the order reference. 1114 """ 1115 return self._post_request(request, kw, response) 1116 1117 @requires(['AmazonAuthorizationId']) 1118 @api_action('OffAmazonPayments', 20, 2) 1119 def get_authorization_details(self, request, response, **kw): 1120 """Returns the status of a particular authorization and the total 1121 amount captured on the authorization. 1122 """ 1123 return self._post_request(request, kw, response) 1124 1125 @requires(['AmazonAuthorizationId', 'CaptureReferenceId', 'CaptureAmount']) 1126 @structured_objects('CaptureAmount') 1127 @api_action('OffAmazonPayments', 10, 1) 1128 def capture(self, request, response, **kw): 1129 """Captures funds from an authorized payment instrument. 1130 """ 1131 return self._post_request(request, kw, response) 1132 1133 @requires(['AmazonCaptureId']) 1134 @api_action('OffAmazonPayments', 20, 2) 1135 def get_capture_details(self, request, response, **kw): 1136 """Returns the status of a particular capture and the total amount 1137 refunded on the capture. 1138 """ 1139 return self._post_request(request, kw, response) 1140 1141 @requires(['AmazonAuthorizationId']) 1142 @api_action('OffAmazonPayments', 10, 1) 1143 def close_authorization(self, request, response, **kw): 1144 """Closes an authorization. 1145 """ 1146 return self._post_request(request, kw, response) 1147 1148 @requires(['AmazonCaptureId', 'RefundReferenceId', 'RefundAmount']) 1149 @structured_objects('RefundAmount') 1150 @api_action('OffAmazonPayments', 10, 1) 1151 def refund(self, request, response, **kw): 1152 """Refunds a previously captured amount. 1153 """ 1154 return self._post_request(request, kw, response) 1155 1156 @requires(['AmazonRefundId']) 1157 @api_action('OffAmazonPayments', 20, 2) 1158 def get_refund_details(self, request, response, **kw): 1159 """Returns the status of a particular refund. 1160 """ 1161 return self._post_request(request, kw, response) 1162 1163 @api_action('OffAmazonPayments', 2, 300, 'GetServiceStatus') 1164 def get_offamazonpayments_service_status(self, request, response, **kw): 1165 """Returns the operational status of the Off-Amazon Payments API 1166 section. 1167 """ 1168 return self._post_request(request, kw, response) 1169