├── .github ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.rst ├── requirements.txt ├── requirements_dev.txt ├── setup.py ├── tests ├── __init__.py ├── conftest.py ├── mocks.py └── test_walmart.py ├── tox.ini └── walmart ├── __init__.py ├── exceptions.py └── walmart.py /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | 4 | ## Actual Behavior 5 | 6 | 7 | ## Steps to Reproduce the Problem 8 | 9 | 1. 10 | 1. 11 | 1. 12 | 13 | ## Specifications 14 | 15 | - Version: 16 | - Platform: 17 | - Subsystem: 18 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description of the change 2 | 3 | > Description here 4 | 5 | ## Type of change 6 | - [ ] Bug fix (non-breaking change that fixes an issue) 7 | - [ ] New feature (non-breaking change that adds functionality) 8 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 9 | 10 | ## Related issues 11 | 12 | > Fix [#1]() 13 | 14 | ## Checklists 15 | 16 | ### Development 17 | 18 | - [ ] Lint rules pass locally 19 | - [ ] The code changed/added as part of this pull request has been covered with tests 20 | - [ ] All tests related to the changed code pass in development 21 | 22 | ### Code review 23 | 24 | - [ ] This pull request has a descriptive title and information useful to a reviewer. There may be a screenshot or screencast attached 25 | - [ ] "Ready for review" label attached to the PR and reviewers mentioned in a comment 26 | - [ ] Changes have been reviewed by at least one other engineer 27 | - [ ] Issue from task tracker has a link to this pull request 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.5" 5 | - "3.6" 6 | # command to install dependencies 7 | install: "pip install -r requirements_dev.txt" 8 | # before script run flake8 9 | before_script: flake8 . 10 | # command to run tests 11 | script: py.test 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | BSD License 3 | 4 | Copyright (c) 2016, Fulfil.IO Inc. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, this 14 | list of conditions and the following disclaimer in the documentation and/or 15 | other materials provided with the distribution. 16 | 17 | * Neither the name of Fulfil nor the names of its 18 | contributors may be used to endorse or promote products derived from this 19 | software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 28 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 | OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | python-walmart 2 | ============== 3 | 4 | .. image:: https://img.shields.io/pypi/v/python-walmart.svg 5 | :target: https://pypi.python.org/pypi/python-walmart 6 | :alt: Latest PyPI version 7 | 8 | .. image:: https://travis-ci.org/borntyping/cookiecutter-pypackage-minimal.png 9 | :target: https://travis-ci.org/borntyping/cookiecutter-pypackage-minimal 10 | :alt: Latest Travis CI build status 11 | 12 | Walmart Marketplace API 13 | 14 | Usage 15 | ----- 16 | 17 | Installation 18 | ------------ 19 | 20 | Requirements 21 | ^^^^^^^^^^^^ 22 | 23 | Compatibility 24 | ------------- 25 | 26 | Licence 27 | ------- 28 | 29 | Authors 30 | ------- 31 | 32 | `python-walmart` was written by `Fulfil.IO Inc. `_. 33 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | lxml 2 | pycrypto 3 | requests 4 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | 3 | pytest 4 | responses 5 | flake8 6 | requests-mock 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | 4 | requirements = [ 5 | 'lxml', 6 | 'requests', 7 | ] 8 | 9 | test_requirements = [ 10 | 'pytest', 11 | 'requests-mock', 12 | ] 13 | 14 | setuptools.setup( 15 | name="python-walmart", 16 | version="0.1.8", 17 | url="https://github.com/fulfilio/python-walmart", 18 | 19 | author="Fulfil.IO Inc.", 20 | author_email="hello@fulfil.io", 21 | 22 | description="Walmart Marketplace API", 23 | long_description=open('README.rst').read(), 24 | 25 | packages=setuptools.find_packages(), 26 | 27 | install_requires=requirements, 28 | 29 | classifiers=[ 30 | 'Development Status :: 2 - Pre-Alpha', 31 | 'Programming Language :: Python', 32 | 'Programming Language :: Python :: 2', 33 | 'Programming Language :: Python :: 2.7', 34 | 'Programming Language :: Python :: 3', 35 | 'Programming Language :: Python :: 3.4', 36 | 'Programming Language :: Python :: 3.5', 37 | ], 38 | test_suite='tests', 39 | tests_require=test_requirements 40 | ) 41 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fulfilio/python-walmart/b78b655c7b2d39e613c20bc9a628f8c920fbedbd/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from walmart import Walmart 4 | from .mocks import get_mock_for 5 | 6 | 7 | @pytest.fixture 8 | def walmart(requests_mock): 9 | requests_mock.post( 10 | "https://marketplace.walmartapis.com/v3/token", 11 | json=get_mock_for("token") 12 | ) 13 | walmart = Walmart( 14 | client_id="client_id", 15 | client_secret="client_secret", 16 | ) 17 | return walmart 18 | -------------------------------------------------------------------------------- /tests/mocks.py: -------------------------------------------------------------------------------- 1 | # flake8:noqa 2 | 3 | 4 | def get_mock_for(resource): 5 | if resource == "token": 6 | return { 7 | "access_token": "token", 8 | "token_type": "Bearer", 9 | "expires_in": 900 10 | } 11 | elif resource == "orders": 12 | return { 13 | 'list': { 14 | 'meta': { 15 | 'totalCount': 35, 16 | 'limit': 5, 17 | 'nextCursor': '?limit=5&hasMoreElements=true&soIndex=35&poIndex=5&partnerId=10000004040&sellerId=4010&createdStartDate=2019-07-19T00:00:00Z&createdEndDate=2019-07-23T11:52:53.226Z' 18 | }, 19 | 'elements': { 20 | 'order': [{ 21 | 'purchaseOrderId': '4792059978801', 22 | 'customerOrderId': '4751966035239', 23 | 'customerEmailId': '257050E418F3443DB6C22D63EBAE015D@relay.walmart.com', 24 | 'orderDate': 1563859433000, 25 | 'shippingInfo': { 26 | 'phone': '2563384465', 27 | 'estimatedDeliveryDate': 1565031600000, 28 | 'estimatedShipDate': 1564459200000, 29 | 'methodCode': 'Standard', 30 | 'postalAddress': { 31 | 'name': 'Leslie Rushing', 32 | 'address1': '2421 County Road 310', 33 | 'address2': None, 34 | 'city': 'Crane Hill', 35 | 'state': 'AL', 36 | 'postalCode': '35053', 37 | 'country': 'USA', 38 | 'addressType': 'RESIDENTIAL' 39 | } 40 | }, 41 | 'orderLines': { 42 | 'orderLine': [{ 43 | 'lineNumber': '3', 44 | 'item': { 45 | 'productName': '3/8" Grosgrain Ribbon Solid 235 Poppy Red 5yd', 46 | 'sku': '101010-43805235' 47 | }, 48 | 'charges': { 49 | 'charge': [{ 50 | 'chargeType': 'PRODUCT', 51 | 'chargeName': 'ItemPrice', 52 | 'chargeAmount': { 53 | 'currency': 'USD', 54 | 'amount': 0.89 55 | }, 56 | 'tax': { 57 | 'taxName': 'Tax1', 58 | 'taxAmount': { 59 | 'currency': 'USD', 60 | 'amount': 0.07 61 | } 62 | } 63 | }, 64 | { 65 | 'chargeType': 'SHIPPING', 66 | 'chargeName': 'Shipping', 67 | 'chargeAmount': { 68 | 'currency': 'USD', 69 | 'amount': 0.98 70 | }, 71 | 'tax': { 72 | 'taxName': 'Tax1', 73 | 'taxAmount': { 74 | 'currency': 'USD', 75 | 'amount': 0.08 76 | } 77 | } 78 | } 79 | ] 80 | }, 81 | 'orderLineQuantity': { 82 | 'unitOfMeasurement': 'EACH', 83 | 'amount': '1' 84 | }, 85 | 'statusDate': 1563872584000, 86 | 'orderLineStatuses': { 87 | 'orderLineStatus': [{ 88 | 'status': 'Acknowledged', 89 | 'statusQuantity': { 90 | 'unitOfMeasurement': 'EACH', 91 | 'amount': '1' 92 | }, 93 | 'cancellationReason': None, 94 | 'trackingInfo': None 95 | }] 96 | }, 97 | 'refund': None, 98 | 'fulfillment': { 99 | 'fulfillmentOption': 'S2H', 100 | 'shipMethod': 'STANDARD', 101 | 'storeId': None, 102 | 'pickUpDateTime': 1564599600000, 103 | 'pickUpBy': None 104 | } 105 | }, 106 | { 107 | 'lineNumber': '4', 108 | 'item': { 109 | 'productName': '3/8" Grosgrain Ribbon Solid 156 Hot Pink 5yd', 110 | 'sku': '101010-43805156' 111 | }, 112 | 'charges': { 113 | 'charge': [{ 114 | 'chargeType': 'PRODUCT', 115 | 'chargeName': 'ItemPrice', 116 | 'chargeAmount': { 117 | 'currency': 'USD', 118 | 'amount': 0.89 119 | }, 120 | 'tax': { 121 | 'taxName': 'Tax1', 122 | 'taxAmount': { 123 | 'currency': 'USD', 124 | 'amount': 0.07 125 | } 126 | } 127 | }, 128 | { 129 | 'chargeType': 'SHIPPING', 130 | 'chargeName': 'Shipping', 131 | 'chargeAmount': { 132 | 'currency': 'USD', 133 | 'amount': 0.99 134 | }, 135 | 'tax': { 136 | 'taxName': 'Tax1', 137 | 'taxAmount': { 138 | 'currency': 'USD', 139 | 'amount': 0.08 140 | } 141 | } 142 | } 143 | ] 144 | }, 145 | 'orderLineQuantity': { 146 | 'unitOfMeasurement': 'EACH', 147 | 'amount': '1' 148 | }, 149 | 'statusDate': 1563872584000, 150 | 'orderLineStatuses': { 151 | 'orderLineStatus': [{ 152 | 'status': 'Acknowledged', 153 | 'statusQuantity': { 154 | 'unitOfMeasurement': 'EACH', 155 | 'amount': '1' 156 | }, 157 | 'cancellationReason': None, 158 | 'trackingInfo': None 159 | }] 160 | }, 161 | 'refund': None, 162 | 'fulfillment': { 163 | 'fulfillmentOption': 'S2H', 164 | 'shipMethod': 'STANDARD', 165 | 'storeId': None, 166 | 'pickUpDateTime': 1564599600000, 167 | 'pickUpBy': None 168 | } 169 | }, 170 | { 171 | 'lineNumber': '5', 172 | 'item': { 173 | 'productName': '3/8" Grosgrain Ribbon Solid 030 Black 5yd', 174 | 'sku': '101010-43805030' 175 | }, 176 | 'charges': { 177 | 'charge': [{ 178 | 'chargeType': 'PRODUCT', 179 | 'chargeName': 'ItemPrice', 180 | 'chargeAmount': { 181 | 'currency': 'USD', 182 | 'amount': 0.89 183 | }, 184 | 'tax': { 185 | 'taxName': 'Tax1', 186 | 'taxAmount': { 187 | 'currency': 'USD', 188 | 'amount': 0.07 189 | } 190 | } 191 | }, 192 | { 193 | 'chargeType': 'SHIPPING', 194 | 'chargeName': 'Shipping', 195 | 'chargeAmount': { 196 | 'currency': 'USD', 197 | 'amount': 0.98 198 | }, 199 | 'tax': { 200 | 'taxName': 'Tax1', 201 | 'taxAmount': { 202 | 'currency': 'USD', 203 | 'amount': 0.08 204 | } 205 | } 206 | } 207 | ] 208 | }, 209 | 'orderLineQuantity': { 210 | 'unitOfMeasurement': 'EACH', 211 | 'amount': '1' 212 | }, 213 | 'statusDate': 1563872584000, 214 | 'orderLineStatuses': { 215 | 'orderLineStatus': [{ 216 | 'status': 'Acknowledged', 217 | 'statusQuantity': { 218 | 'unitOfMeasurement': 'EACH', 219 | 'amount': '1' 220 | }, 221 | 'cancellationReason': None, 222 | 'trackingInfo': None 223 | }] 224 | }, 225 | 'refund': None, 226 | 'fulfillment': { 227 | 'fulfillmentOption': 'S2H', 228 | 'shipMethod': 'STANDARD', 229 | 'storeId': None, 230 | 'pickUpDateTime': 1564599600000, 231 | 'pickUpBy': None 232 | } 233 | } 234 | ] 235 | } 236 | }, 237 | { 238 | 'purchaseOrderId': '4792059978430', 239 | 'customerOrderId': '4751966531767', 240 | 'customerEmailId': '634259E9C72A4D43B608B16B830E6738@relay.walmart.com', 241 | 'orderDate': 1563859348000, 242 | 'shippingInfo': { 243 | 'phone': '3056008814', 244 | 'estimatedDeliveryDate': 1565031600000, 245 | 'estimatedShipDate': 1564459200000, 246 | 'methodCode': 'Standard', 247 | 'postalAddress': { 248 | 'name': 'Joie Simpkins', 249 | 'address1': '6603 nw 2nd pl', 250 | 'address2': None, 251 | 'city': 'Miami', 252 | 'state': 'FL', 253 | 'postalCode': '33150', 254 | 'country': 'USA', 255 | 'addressType': 'RESIDENTIAL' 256 | } 257 | }, 258 | 'orderLines': { 259 | 'orderLine': [{ 260 | 'lineNumber': '1', 261 | 'item': { 262 | 'productName': 'Little Girls Tutu 3-Layer Ballerina Silver', 263 | 'sku': '1702-007' 264 | }, 265 | 'charges': { 266 | 'charge': [{ 267 | 'chargeType': 'PRODUCT', 268 | 'chargeName': 'ItemPrice', 269 | 'chargeAmount': { 270 | 'currency': 'USD', 271 | 'amount': 3.99 272 | }, 273 | 'tax': { 274 | 'taxName': 'Tax1', 275 | 'taxAmount': { 276 | 'currency': 'USD', 277 | 'amount': 0.28 278 | } 279 | } 280 | }, 281 | { 282 | 'chargeType': 'SHIPPING', 283 | 'chargeName': 'Shipping', 284 | 'chargeAmount': { 285 | 'currency': 'USD', 286 | 'amount': 2.99 287 | }, 288 | 'tax': { 289 | 'taxName': 'Tax1', 290 | 'taxAmount': { 291 | 'currency': 'USD', 292 | 'amount': 0.21 293 | } 294 | } 295 | } 296 | ] 297 | }, 298 | 'orderLineQuantity': { 299 | 'unitOfMeasurement': 'EACH', 300 | 'amount': '1' 301 | }, 302 | 'statusDate': 1563865276000, 303 | 'orderLineStatuses': { 304 | 'orderLineStatus': [{ 305 | 'status': 'Acknowledged', 306 | 'statusQuantity': { 307 | 'unitOfMeasurement': 'EACH', 308 | 'amount': '1' 309 | }, 310 | 'cancellationReason': None, 311 | 'trackingInfo': None 312 | }] 313 | }, 314 | 'refund': None, 315 | 'fulfillment': { 316 | 'fulfillmentOption': 'S2H', 317 | 'shipMethod': 'STANDARD', 318 | 'storeId': None, 319 | 'pickUpDateTime': 1564599600000, 320 | 'pickUpBy': None 321 | } 322 | }] 323 | } 324 | }, 325 | { 326 | 'purchaseOrderId': '1795749953091', 327 | 'customerOrderId': '4751966824128', 328 | 'customerEmailId': 'C69B6547505944D7851FC44EA9429DD3@relay.walmart.com', 329 | 'orderDate': 1563853788000, 330 | 'shippingInfo': { 331 | 'phone': '6186045881', 332 | 'estimatedDeliveryDate': 1565031600000, 333 | 'estimatedShipDate': 1564459200000, 334 | 'methodCode': 'Standard', 335 | 'postalAddress': { 336 | 'name': 'KARIE Sweitzer', 337 | 'address1': '5020 N ILLINOIS ST', 338 | 'address2': None, 339 | 'city': 'Fairview Heights', 340 | 'state': 'IL', 341 | 'postalCode': '62208', 342 | 'country': 'USA', 343 | 'addressType': 'OFFICE' 344 | } 345 | }, 346 | 'orderLines': { 347 | 'orderLine': [{ 348 | 'lineNumber': '1', 349 | 'item': { 350 | 'productName': '2.25" Apple Green Grosgrain Ribbon Solid 5 yard', 351 | 'sku': '101010-321405550' 352 | }, 353 | 'charges': { 354 | 'charge': [{ 355 | 'chargeType': 'PRODUCT', 356 | 'chargeName': 'ItemPrice', 357 | 'chargeAmount': { 358 | 'currency': 'USD', 359 | 'amount': 2.45 360 | }, 361 | 'tax': { 362 | 'taxName': 'Tax1', 363 | 'taxAmount': { 364 | 'currency': 'USD', 365 | 'amount': 0.2 366 | } 367 | } 368 | }, 369 | { 370 | 'chargeType': 'SHIPPING', 371 | 'chargeName': 'Shipping', 372 | 'chargeAmount': { 373 | 'currency': 'USD', 374 | 'amount': 1.26 375 | }, 376 | 'tax': { 377 | 'taxName': 'Tax1', 378 | 'taxAmount': { 379 | 'currency': 'USD', 380 | 'amount': 0.1 381 | } 382 | } 383 | } 384 | ] 385 | }, 386 | 'orderLineQuantity': { 387 | 'unitOfMeasurement': 'EACH', 388 | 'amount': '1' 389 | }, 390 | 'statusDate': 1563865278000, 391 | 'orderLineStatuses': { 392 | 'orderLineStatus': [{ 393 | 'status': 'Acknowledged', 394 | 'statusQuantity': { 395 | 'unitOfMeasurement': 'EACH', 396 | 'amount': '1' 397 | }, 398 | 'cancellationReason': None, 399 | 'trackingInfo': None 400 | }] 401 | }, 402 | 'refund': None, 403 | 'fulfillment': { 404 | 'fulfillmentOption': 'S2H', 405 | 'shipMethod': 'STANDARD', 406 | 'storeId': None, 407 | 'pickUpDateTime': 1564599600000, 408 | 'pickUpBy': None 409 | } 410 | }, 411 | { 412 | 'lineNumber': '2', 413 | 'item': { 414 | 'productName': '2.25" Light Turquoise Grosgrain Ribbon Solid 5 yard', 415 | 'sku': '101010-321405319' 416 | }, 417 | 'charges': { 418 | 'charge': [{ 419 | 'chargeType': 'PRODUCT', 420 | 'chargeName': 'ItemPrice', 421 | 'chargeAmount': { 422 | 'currency': 'USD', 423 | 'amount': 2.45 424 | }, 425 | 'tax': { 426 | 'taxName': 'Tax1', 427 | 'taxAmount': { 428 | 'currency': 'USD', 429 | 'amount': 0.21 430 | } 431 | } 432 | }, 433 | { 434 | 'chargeType': 'SHIPPING', 435 | 'chargeName': 'Shipping', 436 | 'chargeAmount': { 437 | 'currency': 'USD', 438 | 'amount': 1.25 439 | }, 440 | 'tax': { 441 | 'taxName': 'Tax1', 442 | 'taxAmount': { 443 | 'currency': 'USD', 444 | 'amount': 0.1 445 | } 446 | } 447 | } 448 | ] 449 | }, 450 | 'orderLineQuantity': { 451 | 'unitOfMeasurement': 'EACH', 452 | 'amount': '1' 453 | }, 454 | 'statusDate': 1563865278000, 455 | 'orderLineStatuses': { 456 | 'orderLineStatus': [{ 457 | 'status': 'Acknowledged', 458 | 'statusQuantity': { 459 | 'unitOfMeasurement': 'EACH', 460 | 'amount': '1' 461 | }, 462 | 'cancellationReason': None, 463 | 'trackingInfo': None 464 | }] 465 | }, 466 | 'refund': None, 467 | 'fulfillment': { 468 | 'fulfillmentOption': 'S2H', 469 | 'shipMethod': 'STANDARD', 470 | 'storeId': None, 471 | 'pickUpDateTime': 1564599600000, 472 | 'pickUpBy': None 473 | } 474 | }, 475 | { 476 | 'lineNumber': '3', 477 | 'item': { 478 | 'productName': '2.25" Purple Grosgrain Ribbon Solid 5 yard', 479 | 'sku': '101010-321405465' 480 | }, 481 | 'charges': { 482 | 'charge': [{ 483 | 'chargeType': 'PRODUCT', 484 | 'chargeName': 'ItemPrice', 485 | 'chargeAmount': { 486 | 'currency': 'USD', 487 | 'amount': 2.45 488 | }, 489 | 'tax': { 490 | 'taxName': 'Tax1', 491 | 'taxAmount': { 492 | 'currency': 'USD', 493 | 'amount': 0.2 494 | } 495 | } 496 | }, 497 | { 498 | 'chargeType': 'SHIPPING', 499 | 'chargeName': 'Shipping', 500 | 'chargeAmount': { 501 | 'currency': 'USD', 502 | 'amount': 1.25 503 | }, 504 | 'tax': { 505 | 'taxName': 'Tax1', 506 | 'taxAmount': { 507 | 'currency': 'USD', 508 | 'amount': 0.1 509 | } 510 | } 511 | } 512 | ] 513 | }, 514 | 'orderLineQuantity': { 515 | 'unitOfMeasurement': 'EACH', 516 | 'amount': '1' 517 | }, 518 | 'statusDate': 1563865278000, 519 | 'orderLineStatuses': { 520 | 'orderLineStatus': [{ 521 | 'status': 'Acknowledged', 522 | 'statusQuantity': { 523 | 'unitOfMeasurement': 'EACH', 524 | 'amount': '1' 525 | }, 526 | 'cancellationReason': None, 527 | 'trackingInfo': None 528 | }] 529 | }, 530 | 'refund': None, 531 | 'fulfillment': { 532 | 'fulfillmentOption': 'S2H', 533 | 'shipMethod': 'STANDARD', 534 | 'storeId': None, 535 | 'pickUpDateTime': 1564599600000, 536 | 'pickUpBy': None 537 | } 538 | } 539 | ] 540 | } 541 | }, 542 | { 543 | 'purchaseOrderId': '4792049857354', 544 | 'customerOrderId': '4741965197147', 545 | 'customerEmailId': '4955FD2EADCD458DAEE9006B0A6E7E6A@relay.walmart.com', 546 | 'orderDate': 1563830916000, 547 | 'shippingInfo': { 548 | 'phone': '6783990955', 549 | 'estimatedDeliveryDate': 1565031600000, 550 | 'estimatedShipDate': 1564459200000, 551 | 'methodCode': 'Standard', 552 | 'postalAddress': { 553 | 'name': 'Sandra Quizza Garcia', 554 | 'address1': '2979 Lake Colony Dr NW, Norcross', 555 | 'address2': 'Apartament #16', 556 | 'city': 'Peachtree Corners', 557 | 'state': 'GA', 558 | 'postalCode': '30071', 559 | 'country': 'USA', 560 | 'addressType': 'RESIDENTIAL' 561 | } 562 | }, 563 | 'orderLines': { 564 | 'orderLine': [{ 565 | 'lineNumber': '2', 566 | 'item': { 567 | 'productName': 'Little Girls Tutu 3-Layer Ballerina Turquoise', 568 | 'sku': '1702-340' 569 | }, 570 | 'charges': { 571 | 'charge': [{ 572 | 'chargeType': 'PRODUCT', 573 | 'chargeName': 'ItemPrice', 574 | 'chargeAmount': { 575 | 'currency': 'USD', 576 | 'amount': 3.99 577 | }, 578 | 'tax': { 579 | 'taxName': 'Tax1', 580 | 'taxAmount': { 581 | 'currency': 'USD', 582 | 'amount': 0.24 583 | } 584 | } 585 | }, 586 | { 587 | 'chargeType': 'SHIPPING', 588 | 'chargeName': 'Shipping', 589 | 'chargeAmount': { 590 | 'currency': 'USD', 591 | 'amount': 2.99 592 | }, 593 | 'tax': { 594 | 'taxName': 'Tax1', 595 | 'taxAmount': { 596 | 'currency': 'USD', 597 | 'amount': 0.18 598 | } 599 | } 600 | } 601 | ] 602 | }, 603 | 'orderLineQuantity': { 604 | 'unitOfMeasurement': 'EACH', 605 | 'amount': '1' 606 | }, 607 | 'statusDate': 1563836072000, 608 | 'orderLineStatuses': { 609 | 'orderLineStatus': [{ 610 | 'status': 'Acknowledged', 611 | 'statusQuantity': { 612 | 'unitOfMeasurement': 'EACH', 613 | 'amount': '1' 614 | }, 615 | 'cancellationReason': None, 616 | 'trackingInfo': None 617 | }] 618 | }, 619 | 'refund': None, 620 | 'fulfillment': { 621 | 'fulfillmentOption': 'S2H', 622 | 'shipMethod': 'STANDARD', 623 | 'storeId': None, 624 | 'pickUpDateTime': 1564599600000, 625 | 'pickUpBy': 'Sandra Quizza Garcia' 626 | } 627 | }] 628 | } 629 | }, 630 | { 631 | 'purchaseOrderId': '3795739830204', 632 | 'customerOrderId': '4741966904458', 633 | 'customerEmailId': '1AD83B3162AD4B57ADC8F180FBCE1D0F@relay.walmart.com', 634 | 'orderDate': 1563830290000, 635 | 'shippingInfo': { 636 | 'phone': '7574393272', 637 | 'estimatedDeliveryDate': 1565031600000, 638 | 'estimatedShipDate': 1564459200000, 639 | 'methodCode': 'Standard', 640 | 'postalAddress': { 641 | 'name': 'Laura Elliott', 642 | 'address1': '1929 Orangewood Road', 643 | 'address2': None, 644 | 'city': 'Chesapeake', 645 | 'state': 'VA', 646 | 'postalCode': '23323', 647 | 'country': 'USA', 648 | 'addressType': 'RESIDENTIAL' 649 | } 650 | }, 651 | 'orderLines': { 652 | 'orderLine': [{ 653 | 'lineNumber': '6', 654 | 'item': { 655 | 'productName': '3" Fuchsia Grosgrain Ribbon Solid 3 yard', 656 | 'sku': '101010-3303187' 657 | }, 658 | 'charges': { 659 | 'charge': [{ 660 | 'chargeType': 'PRODUCT', 661 | 'chargeName': 'ItemPrice', 662 | 'chargeAmount': { 663 | 'currency': 'USD', 664 | 'amount': 2.5 665 | }, 666 | 'tax': { 667 | 'taxName': 'Tax1', 668 | 'taxAmount': { 669 | 'currency': 'USD', 670 | 'amount': 0.15 671 | } 672 | } 673 | }, 674 | { 675 | 'chargeType': 'SHIPPING', 676 | 'chargeName': 'Shipping', 677 | 'chargeAmount': { 678 | 'currency': 'USD', 679 | 'amount': 1.8 680 | }, 681 | 'tax': None 682 | } 683 | ] 684 | }, 685 | 'orderLineQuantity': { 686 | 'unitOfMeasurement': 'EACH', 687 | 'amount': '1' 688 | }, 689 | 'statusDate': 1563836074000, 690 | 'orderLineStatuses': { 691 | 'orderLineStatus': [{ 692 | 'status': 'Acknowledged', 693 | 'statusQuantity': { 694 | 'unitOfMeasurement': 'EACH', 695 | 'amount': '1' 696 | }, 697 | 'cancellationReason': None, 698 | 'trackingInfo': None 699 | }] 700 | }, 701 | 'refund': None, 702 | 'fulfillment': { 703 | 'fulfillmentOption': 'S2H', 704 | 'shipMethod': 'STANDARD', 705 | 'storeId': None, 706 | 'pickUpDateTime': 1564599600000, 707 | 'pickUpBy': 'Laura Elliott' 708 | } 709 | }, 710 | { 711 | 'lineNumber': '9', 712 | 'item': { 713 | 'productName': '3" Fuchsia Grosgrain Ribbon Solid 3 yard', 714 | 'sku': '101010-3303187' 715 | }, 716 | 'charges': { 717 | 'charge': [{ 718 | 'chargeType': 'PRODUCT', 719 | 'chargeName': 'ItemPrice', 720 | 'chargeAmount': { 721 | 'currency': 'USD', 722 | 'amount': 2.5 723 | }, 724 | 'tax': { 725 | 'taxName': 'Tax1', 726 | 'taxAmount': { 727 | 'currency': 'USD', 728 | 'amount': 0.15 729 | } 730 | } 731 | }, 732 | { 733 | 'chargeType': 'SHIPPING', 734 | 'chargeName': 'Shipping', 735 | 'chargeAmount': { 736 | 'currency': 'USD', 737 | 'amount': 1.79 738 | }, 739 | 'tax': None 740 | } 741 | ] 742 | }, 743 | 'orderLineQuantity': { 744 | 'unitOfMeasurement': 'EACH', 745 | 'amount': '1' 746 | }, 747 | 'statusDate': 1563836074000, 748 | 'orderLineStatuses': { 749 | 'orderLineStatus': [{ 750 | 'status': 'Acknowledged', 751 | 'statusQuantity': { 752 | 'unitOfMeasurement': 'EACH', 753 | 'amount': '1' 754 | }, 755 | 'cancellationReason': None, 756 | 'trackingInfo': None 757 | }] 758 | }, 759 | 'refund': None, 760 | 'fulfillment': { 761 | 'fulfillmentOption': 'S2H', 762 | 'shipMethod': 'STANDARD', 763 | 'storeId': None, 764 | 'pickUpDateTime': 1564599600000, 765 | 'pickUpBy': 'Laura Elliott' 766 | } 767 | } 768 | ] 769 | } 770 | } 771 | ] 772 | } 773 | } 774 | } 775 | elif resource == "items": 776 | return { 777 | 'ItemResponse': [{ 778 | 'mart': 'WALMART_US', 779 | 'sku': '101013-21550015340', 780 | 'wpid': '0REAG121OH3P', 781 | 'upc': '635510505068', 782 | 'gtin': '00635510505068', 783 | 'productName': '1.5" Large Turquoise Dot Grosgrain Ribbon 50yd', 784 | 'shelf': '["Home Page","Party & Occasions","Party Supplies","Gift Wrap & Supplies"]', 785 | 'productType': 'Ribbons & Bows', 786 | 'price': { 787 | 'currency': 'USD', 788 | 'amount': 29.62 789 | }, 790 | 'publishedStatus': 'PUBLISHED' 791 | }, 792 | { 793 | 'mart': 'WALMART_US', 794 | 'sku': '101010-43805580', 795 | 'wpid': '0RJG1KW2E37W', 796 | 'upc': '635510489702', 797 | 'gtin': '00635510489702', 798 | 'productName': '3/8" Grosgrain Ribbon Solid 580 Emerald 5yd', 799 | 'shelf': '["Home Page","Party & Occasions","Party Supplies","Gift Wrap & Supplies"]', 800 | 'productType': 'Ribbons & Bows', 801 | 'price': { 802 | 'currency': 'USD', 803 | 'amount': 0.89 804 | }, 805 | 'publishedStatus': 'PUBLISHED' 806 | }, 807 | { 808 | 'mart': 'WALMART_US', 809 | 'sku': '1012-8303550', 810 | 'wpid': '0RJXCK8SQ473', 811 | 'upc': '635510510468', 812 | 'gtin': '00635510510468', 813 | 'productName': '3" Apple Green Double Faced Satin Ribbon 3 yard', 814 | 'shelf': '["UNNAV"]', 815 | 'productType': 'Ribbons & Bows', 816 | 'price': { 817 | 'currency': 'USD', 818 | 'amount': 3.5 819 | }, 820 | 'publishedStatus': 'PUBLISHED' 821 | }, 822 | { 823 | 'mart': 'WALMART_US', 824 | 'sku': '101010-61550343', 825 | 'wpid': '0RLO0K8LGD1U', 826 | 'upc': '635510496908', 827 | 'gtin': '00635510496908', 828 | 'productName': '1.5" Grosgrain Ribbon Solid 343 Tornado Blue 50yd', 829 | 'shelf': '["Home Page","Party & Occasions","Party Supplies","Gift Wrap & Supplies"]', 830 | 'productType': 'Ribbons & Bows', 831 | 'price': { 832 | 'currency': 'USD', 833 | 'amount': 13.54 834 | }, 835 | 'publishedStatus': 'PUBLISHED' 836 | }, 837 | { 838 | 'mart': 'WALMART_US', 839 | 'sku': '101010-57805012', 840 | 'wpid': '0RN8TX8OZB5K', 841 | 'upc': '635510492108', 842 | 'gtin': '00635510492108', 843 | 'productName': '7/8" Grosgrain Ribbon Solid 012 Silver 5yd', 844 | 'shelf': '["Home Page","Party & Occasions","Party Supplies","Graduation Party Supplies","Graduation Decorations","Graduation Balloon Accessories"]', 845 | 'productType': 'Ribbons & Bows', 846 | 'price': { 847 | 'currency': 'USD', 848 | 'amount': 1.15 849 | }, 850 | 'publishedStatus': 'PUBLISHED' 851 | }, 852 | { 853 | 'mart': 'WALMART_US', 854 | 'sku': '101610-615100159', 855 | 'wpid': '0RNLB1V4LVVS', 856 | 'upc': '635510514220', 857 | 'gtin': '00635510514220', 858 | 'productName': '1.5" Grosgrain Ribbon Solid 159 Passion Fruit 100yd', 859 | 'shelf': '["Home Page","Party & Occasions","Party Supplies","Gift Wrap & Supplies"]', 860 | 'productType': 'Ribbons & Bows', 861 | 'price': { 862 | 'currency': 'USD', 863 | 'amount': 23.95 864 | }, 865 | 'publishedStatus': 'PUBLISHED' 866 | }, 867 | { 868 | 'mart': 'WALMART_US', 869 | 'sku': '1009-51503462', 870 | 'wpid': '0RNV1W9F1F54', 871 | 'upc': '635510482611', 872 | 'gtin': '00635510482611', 873 | 'productName': '1.5" Lavender Glitter Ribbon 3yd', 874 | 'shelf': '["UNNAV"]', 875 | 'productType': 'Ribbons & Bows', 876 | 'price': { 877 | 'currency': 'USD', 878 | 'amount': 5.39 879 | }, 880 | 'publishedStatus': 'PUBLISHED' 881 | }, 882 | { 883 | 'mart': 'WALMART_US', 884 | 'sku': '101010-578W05587', 885 | 'wpid': '0ROYJP2R8I1N', 886 | 'upc': '635510494515', 887 | 'gtin': '00635510494515', 888 | 'productName': '7/8" Grosgrain Ribbon White Dots 587 Forest Green 5 Yard', 889 | 'shelf': '["Home Page","Party & Occasions","Party Supplies","Gift Wrap & Supplies"]', 890 | 'productType': 'Ribbons & Bows', 891 | 'price': { 892 | 'currency': 'USD', 893 | 'amount': 3.25 894 | }, 895 | 'publishedStatus': 'PUBLISHED' 896 | }, 897 | { 898 | 'mart': 'WALMART_US', 899 | 'sku': '101610-33100460', 900 | 'wpid': '0RP0E74L2AF5', 901 | 'upc': '635510512561', 902 | 'gtin': '00635510512561', 903 | 'productName': '3" Lavender Grosgrain Ribbon Solid 100 yard', 904 | 'shelf': '["Home Page","Party & Occasions","Party Supplies","Gift Wrap & Supplies"]', 905 | 'productType': 'Ribbons & Bows', 906 | 'price': { 907 | 'currency': 'USD', 908 | 'amount': 56.49 909 | }, 910 | 'publishedStatus': 'PUBLISHED' 911 | }, 912 | { 913 | 'mart': 'WALMART_US', 914 | 'sku': '1012-722525660', 915 | 'wpid': '0RRCI3UAV1W8', 916 | 'upc': '635510509905', 917 | 'gtin': '00635510509905', 918 | 'productName': '2.25" Yellow Gold Double Face Satin Ribbon 25 yard Reel', 919 | 'shelf': '["Home Page","Party & Occasions","Party Supplies","Gift Wrap & Supplies"]', 920 | 'productType': 'Ribbons & Bows', 921 | 'price': { 922 | 'currency': 'USD', 923 | 'amount': 13.59 924 | }, 925 | 'publishedStatus': 'PUBLISHED' 926 | }, 927 | { 928 | 'mart': 'WALMART_US', 929 | 'sku': '101010-61505352', 930 | 'wpid': '0RWDDY5IJOUQ', 931 | 'upc': '635510495413', 932 | 'gtin': '00635510495413', 933 | 'productName': '1.5" Grosgrain Ribbon Solid 352 Royal Blue 5yd', 934 | 'shelf': '["Home Page","Party & Occasions","Party Supplies","Gift Wrap & Supplies"]', 935 | 'productType': 'Ribbons & Bows', 936 | 'price': { 937 | 'currency': 'USD', 938 | 'amount': 1.69 939 | }, 940 | 'publishedStatus': 'PUBLISHED' 941 | }, 942 | { 943 | 'mart': 'WALMART_US', 944 | 'sku': '1012-8303463', 945 | 'wpid': '0RWLG8N05H39', 946 | 'upc': '635510510420', 947 | 'gtin': '00635510510420', 948 | 'productName': '3" Grape Double Faced Satin Ribbon 3 yard', 949 | 'shelf': '["UNNAV"]', 950 | 'productType': 'Ribbons & Bows', 951 | 'price': { 952 | 'currency': 'USD', 953 | 'amount': 3.5 954 | }, 955 | 'publishedStatus': 'PUBLISHED' 956 | }, 957 | { 958 | 'mart': 'WALMART_US', 959 | 'sku': '101610-578100580', 960 | 'wpid': '0RYUK6TQ09UQ', 961 | 'upc': '635510513827', 962 | 'gtin': '00635510513827', 963 | 'productName': '7/8" Grosgrain Ribbon Solid 580 Emerald 100yd', 964 | 'shelf': '["Home Page","Party & Occasions","Party Supplies","Gift Wrap & Supplies"]', 965 | 'productType': 'Ribbons & Bows', 966 | 'price': { 967 | 'currency': 'USD', 968 | 'amount': 13.95 969 | }, 970 | 'publishedStatus': 'PUBLISHED' 971 | }, 972 | { 973 | 'mart': 'WALMART_US', 974 | 'sku': '101610-3214100835', 975 | 'wpid': '0RZPEXN3XP8V', 976 | 'upc': '635510512363', 977 | 'gtin': '00635510512363', 978 | 'productName': '2.25" Tan Grosgrain Ribbon Solid 100 yard', 979 | 'shelf': '["Home Page","Party & Occasions","Party Supplies","Gift Wrap & Supplies"]', 980 | 'productType': 'Ribbons & Bows', 981 | 'price': { 982 | 'currency': 'USD', 983 | 'amount': 35.95 984 | }, 985 | 'publishedStatus': 'PUBLISHED' 986 | }, 987 | { 988 | 'mart': 'WALMART_US', 989 | 'sku': '101010-321405460', 990 | 'wpid': '0S3YUTERLFTM', 991 | 'upc': '635510486855', 992 | 'gtin': '00635510486855', 993 | 'productName': '2.25" Lavender Grosgrain Ribbon Solid 5 yard', 994 | 'shelf': '["Home Page","Party & Occasions","Party Supplies","Gift Wrap & Supplies"]', 995 | 'productType': 'Ribbons & Bows', 996 | 'price': { 997 | 'currency': 'USD', 998 | 'amount': 2.45 999 | }, 1000 | 'publishedStatus': 'PUBLISHED' 1001 | }, 1002 | { 1003 | 'mart': 'WALMART_US', 1004 | 'sku': '101013-21525032030', 1005 | 'wpid': '0S6WZXI0KTXP', 1006 | 'upc': '635510516521', 1007 | 'gtin': '00635510516521', 1008 | 'productName': '1.5" Black Damask Grosgrain Ribbon 25yd', 1009 | 'shelf': '["Home Page","Party & Occasions","Party Supplies","Gift Wrap & Supplies"]', 1010 | 'productType': 'Ribbons & Bows', 1011 | 'price': { 1012 | 'currency': 'USD', 1013 | 'amount': 15.67 1014 | }, 1015 | 'publishedStatus': 'PUBLISHED' 1016 | }, 1017 | { 1018 | 'mart': 'WALMART_US', 1019 | 'sku': '101013-21505045OM', 1020 | 'wpid': '0S7CUGHJW3F3', 1021 | 'upc': '635510522799', 1022 | 'gtin': '00635510522799', 1023 | 'productName': '1.5" Ombre Rainbow Cheer Text Grosgrain Ribbon 5yd', 1024 | 'shelf': '["Home Page","Party & Occasions","Party Supplies","Gift Wrap & Supplies"]', 1025 | 'productType': 'Ribbons & Bows', 1026 | 'price': { 1027 | 'currency': 'USD', 1028 | 'amount': 5.29 1029 | }, 1030 | 'publishedStatus': 'PUBLISHED' 1031 | }, 1032 | { 1033 | 'mart': 'WALMART_US', 1034 | 'sku': '101010-61505123', 1035 | 'wpid': '0SBAGTW9UBXK', 1036 | 'upc': '635510495093', 1037 | 'gtin': '00635510495093', 1038 | 'productName': '1.5" Grosgrain Ribbon Solid 123 Pearl Pink 5yd', 1039 | 'shelf': '["Home Page","Party & Occasions","Party Supplies","Gift Wrap & Supplies"]', 1040 | 'productType': 'Ribbons & Bows', 1041 | 'price': { 1042 | 'currency': 'USD', 1043 | 'amount': 1.69 1044 | }, 1045 | 'publishedStatus': 'PUBLISHED' 1046 | }, 1047 | { 1048 | 'mart': 'WALMART_US', 1049 | 'sku': '101009-17810156030', 1050 | 'wpid': '0SIO1G8K4CCH', 1051 | 'upc': '635510483373', 1052 | 'gtin': '00635510483373', 1053 | 'productName': '7/8" Hot Pink / Black Moonstitch Grosgrain Ribbon 10yd', 1054 | 'shelf': '["UNNAV"]', 1055 | 'productType': 'Ribbons & Bows', 1056 | 'price': { 1057 | 'currency': 'USD', 1058 | 'amount': 5.25 1059 | }, 1060 | 'publishedStatus': 'PUBLISHED' 1061 | }, 1062 | { 1063 | 'mart': 'WALMART_US', 1064 | 'sku': '101010-25805524', 1065 | 'wpid': '0SJ5B6GAPLJN', 1066 | 'upc': '635510485759', 1067 | 'gtin': '00635510485759', 1068 | 'productName': '5/8" Lime Juice Grosgrain Ribbon Solid 5 yard reel (16mm) HairBow Center', 1069 | 'shelf': '["Home Page","Party & Occasions","Party Supplies","Gift Wrap & Supplies"]', 1070 | 'productType': 'Ribbons & Bows', 1071 | 'price': { 1072 | 'currency': 'USD', 1073 | 'amount': 1.99 1074 | }, 1075 | 'publishedStatus': 'PUBLISHED' 1076 | }], 1077 | 'totalItems': 3111, 1078 | 'nextCursor': 'AoE/GjBTSjVCNkdBUExKTjBTRUxMRVJfT0ZGRVJERTAwMDdENzMxQ0Q0RTU5ODcxMjkzMDZBQjE5OEVFMw==' 1079 | } 1080 | return [{}] 1081 | -------------------------------------------------------------------------------- /tests/test_walmart.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | test_walmart 6 | ---------------------------------- 7 | Tests for `walmart` module. 8 | """ 9 | 10 | import pytest # noqa 11 | 12 | from .mocks import get_mock_for 13 | 14 | 15 | def test_token_generation(walmart): 16 | "test if token authentication call is made" 17 | assert walmart.token is not None 18 | assert walmart.token_expires_in is not None 19 | 20 | 21 | def test_items_all(walmart, requests_mock): 22 | "test fetching all items" 23 | requests_mock.get( 24 | "https://marketplace.walmartapis.com/v3/items", 25 | json=get_mock_for("items") 26 | ) 27 | response = walmart.items.all() 28 | items = response["ItemResponse"] 29 | assert len(items) == 20 30 | 31 | assert items[0]["mart"] == "WALMART_US" 32 | assert items[0]["sku"] == "101013-21550015340" 33 | assert items[0]["gtin"] == "00635510505068" 34 | 35 | assert items[1]["mart"] == "WALMART_US" 36 | assert items[1]["sku"] == "101010-43805580" 37 | assert items[1]["gtin"] == "00635510489702" 38 | 39 | 40 | def test_orders_all(walmart, requests_mock): 41 | "test fetching all orders" 42 | requests_mock.get( 43 | "https://marketplace.walmartapis.com/v3/orders?" 44 | "createdStartDate=2019-07-19T00:00:00Z&limit=5", 45 | json=get_mock_for("orders") 46 | ) 47 | response = walmart.orders.all( 48 | createdStartDate="2019-07-19T00:00:00Z", 49 | limit=5 50 | ) 51 | orders = response["list"]["elements"]["order"] 52 | assert len(orders) == 5 53 | 54 | assert orders[0]["purchaseOrderId"] == "4792059978801" 55 | assert orders[0]["customerOrderId"] == "4751966035239" 56 | assert len(orders[0]["orderLines"]["orderLine"]) == 3 57 | 58 | assert orders[1]["purchaseOrderId"] == "4792059978430" 59 | assert orders[1]["customerOrderId"] == "4751966531767" 60 | assert len(orders[1]["orderLines"]["orderLine"]) == 1 61 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist=py27,py34,py35 3 | 4 | [testenv] 5 | commands=py.test python-walmart 6 | deps=pytest 7 | -------------------------------------------------------------------------------- /walmart/__init__.py: -------------------------------------------------------------------------------- 1 | """python-walmart - Walmart Marketplace API""" 2 | 3 | __version__ = '0.0.6' 4 | __author__ = 'Fulfil.IO Inc. ' 5 | __all__ = [] 6 | 7 | from .walmart import Walmart # noqa 8 | -------------------------------------------------------------------------------- /walmart/exceptions.py: -------------------------------------------------------------------------------- 1 | class BaseException(Exception): 2 | """ 3 | Base Exception which implements message attr on exceptions 4 | Required for: Python 3 5 | """ 6 | def __init__(self, message=None, *args, **kwargs): 7 | self.message = message 8 | super(BaseException, self).__init__( 9 | self.message, *args, **kwargs 10 | ) 11 | 12 | def __str__(self): 13 | return self.message or self.__class__.__name__ 14 | 15 | 16 | class WalmartException(BaseException): 17 | pass 18 | 19 | 20 | class WalmartAuthenticationError(WalmartException): 21 | pass 22 | -------------------------------------------------------------------------------- /walmart/walmart.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import requests 3 | import uuid 4 | import csv 5 | import io 6 | import zipfile 7 | 8 | from datetime import datetime 9 | from requests.auth import HTTPBasicAuth 10 | from lxml import etree 11 | from lxml.builder import E, ElementMaker 12 | 13 | from .exceptions import WalmartAuthenticationError 14 | 15 | 16 | def epoch_milliseconds(dt): 17 | "Walmart accepts timestamps as epoch time in milliseconds" 18 | epoch = datetime.utcfromtimestamp(0) 19 | return int((dt - epoch).total_seconds() * 1000.0) 20 | 21 | 22 | class Walmart(object): 23 | 24 | def __init__(self, client_id, client_secret): 25 | """To get client_id and client_secret for your Walmart Marketplace 26 | visit: https://developer.walmart.com/#/generateKey 27 | """ 28 | self.client_id = client_id 29 | self.client_secret = client_secret 30 | self.token = None 31 | self.token_expires_in = None 32 | self.base_url = "https://marketplace.walmartapis.com/v3" 33 | 34 | session = requests.Session() 35 | session.headers.update({ 36 | "WM_SVC.NAME": "Walmart Marketplace", 37 | "Content-Type": "application/x-www-form-urlencoded", 38 | "Accept": "application/json", 39 | }) 40 | session.auth = HTTPBasicAuth(self.client_id, self.client_secret) 41 | self.session = session 42 | 43 | # Get the token required for API requests 44 | self.authenticate() 45 | 46 | def authenticate(self): 47 | data = self.send_request( 48 | "POST", "{}/token".format(self.base_url), 49 | body={ 50 | "grant_type": "client_credentials", 51 | }, 52 | ) 53 | self.token = data["access_token"] 54 | self.token_expires_in = data["expires_in"] 55 | 56 | self.session.headers["WM_SEC.ACCESS_TOKEN"] = self.token 57 | 58 | @property 59 | def items(self): 60 | return Items(connection=self) 61 | 62 | @property 63 | def inventory(self): 64 | return Inventory(connection=self) 65 | 66 | @property 67 | def prices(self): 68 | return Prices(connection=self) 69 | 70 | @property 71 | def orders(self): 72 | return Orders(connection=self) 73 | 74 | @property 75 | def report(self): 76 | return Report(connection=self) 77 | 78 | @property 79 | def feed(self): 80 | return Feed(connection=self) 81 | 82 | def send_request( 83 | self, method, url, params=None, body=None, json=None, 84 | request_headers=None 85 | ): 86 | # A unique ID which identifies each API call and used to track 87 | # and debug issues; use a random generated GUID for this ID 88 | headers = { 89 | "WM_QOS.CORRELATION_ID": uuid.uuid4().hex, 90 | } 91 | if request_headers: 92 | headers.update(request_headers) 93 | 94 | response = None 95 | if method == "GET": 96 | response = self.session.get(url, params=params, headers=headers) 97 | elif method == "PUT": 98 | response = self.session.put( 99 | url, params=params, headers=headers, data=body 100 | ) 101 | elif method == "POST": 102 | request_params = { 103 | "params": params, 104 | "headers": headers, 105 | } 106 | if json is not None: 107 | request_params["json"] = json 108 | else: 109 | request_params["data"] = body 110 | response = self.session.post(url, **request_params) 111 | 112 | if response is not None: 113 | try: 114 | response.raise_for_status() 115 | except requests.exceptions.HTTPError: 116 | if response.status_code == 401: 117 | raise WalmartAuthenticationError(( 118 | "Invalid client_id or client_secret. Please verify " 119 | "your credentials from https://developer.walmart." 120 | "com/#/generateKey" 121 | )) 122 | elif response.status_code == 400: 123 | data = response.json() 124 | if "error" in data and data["error"][0]["code"] == \ 125 | "INVALID_TOKEN.GMP_GATEWAY_API": 126 | # Refresh the token as the current token has expired 127 | self.authenticate() 128 | return self.send_request( 129 | method, url, params, body, request_headers 130 | ) 131 | raise 132 | try: 133 | return response.json() 134 | except ValueError: 135 | # In case of reports, there is no JSON response, so return the 136 | # content instead which contains the actual report 137 | return response.content 138 | 139 | 140 | class Resource(object): 141 | """ 142 | A base class for all Resources to extend 143 | """ 144 | 145 | def __init__(self, connection): 146 | self.connection = connection 147 | 148 | @property 149 | def url(self): 150 | return "{}/{}".format(self.connection.base_url, self.path) 151 | 152 | def all(self, **kwargs): 153 | return self.connection.send_request( 154 | method="GET", url=self.url, params=kwargs 155 | ) 156 | 157 | def get(self, id): 158 | url = "{}/{}".format(self.url, id) 159 | return self.connection.send_request(method="GET", url=url) 160 | 161 | def update(self, **kwargs): 162 | return self.connection.send_request( 163 | method="PUT", url=self.url, params=kwargs 164 | ) 165 | 166 | 167 | class Items(Resource): 168 | """ 169 | Get all items 170 | """ 171 | 172 | path = 'items' 173 | 174 | def get_items(self): 175 | "Get all the items from the Item Report" 176 | response = self.connection.report.all(type="item") 177 | zf = zipfile.ZipFile(io.BytesIO(response), "r") 178 | product_report = zf.read(zf.infolist()[0]).decode("utf-8") 179 | 180 | return list(csv.DictReader(io.StringIO(product_report))) 181 | 182 | 183 | class Inventory(Resource): 184 | """ 185 | Retreives inventory of an item 186 | """ 187 | 188 | path = 'inventory' 189 | feedType = 'inventory' 190 | 191 | def bulk_update(self, items): 192 | """Updates the inventory for multiple items at once by creating the 193 | feed on Walmart. 194 | 195 | :param items: Items for which the inventory needs to be updated in 196 | the format of: 197 | [{ 198 | "sku": "XXXXXXXXX", 199 | "availability_code": "AC", 200 | "quantity": "10", 201 | "uom": "EACH", 202 | "fulfillment_lag_time": "1", 203 | }] 204 | """ 205 | inventory_data = [] 206 | for item in items: 207 | data = { 208 | "sku": item["sku"], 209 | "quantity": { 210 | "amount": item["quantity"], 211 | "unit": item.get("uom", "EACH"), 212 | }, 213 | "fulfillmentLagTime": item.get("fulfillment_lag_time"), 214 | } 215 | if item.get("availability_code"): 216 | data["availabilityCode"] = item["availability_code"] 217 | inventory_data.append(data) 218 | 219 | body = { 220 | "InventoryHeader": { 221 | "version": "1.4", 222 | }, 223 | "Inventory": inventory_data, 224 | } 225 | return self.connection.feed.create(resource="inventory", content=body) 226 | 227 | def update_inventory(self, sku, quantity): 228 | headers = { 229 | 'Content-Type': "application/xml" 230 | } 231 | return self.connection.send_request( 232 | method='PUT', 233 | url=self.url, 234 | params={'sku': sku}, 235 | body=self.get_inventory_payload(sku, quantity), 236 | request_headers=headers 237 | ) 238 | 239 | def get_inventory_payload(self, sku, quantity): 240 | element = ElementMaker( 241 | namespace='http://walmart.com/', 242 | nsmap={ 243 | 'wm': 'http://walmart.com/', 244 | } 245 | ) 246 | return etree.tostring( 247 | element( 248 | 'inventory', 249 | element('sku', sku), 250 | element( 251 | 'quantity', 252 | element('unit', 'EACH'), 253 | element('amount', str(quantity)), 254 | ), 255 | element('fulfillmentLagTime', '4'), 256 | ), xml_declaration=True, encoding='utf-8' 257 | ) 258 | 259 | def get_payload(self, items): 260 | return etree.tostring( 261 | E.InventoryFeed( 262 | E.InventoryHeader(E('version', '1.4')), 263 | *[E( 264 | 'inventory', 265 | E('sku', item['sku']), 266 | E( 267 | 'quantity', 268 | E('unit', 'EACH'), 269 | E('amount', item['quantity']), 270 | ) 271 | ) for item in items], 272 | xmlns='http://walmart.com/' 273 | ) 274 | ) 275 | 276 | 277 | class Prices(Resource): 278 | """ 279 | Retreives price of an item 280 | """ 281 | 282 | path = 'prices' 283 | feedType = 'price' 284 | 285 | def get_payload(self, items): 286 | root = ElementMaker( 287 | nsmap={'gmp': 'http://walmart.com/'} 288 | ) 289 | return etree.tostring( 290 | root.PriceFeed( 291 | E.PriceHeader(E('version', '1.5')), 292 | *[E.Price( 293 | E( 294 | 'itemIdentifier', 295 | E('sku', item['sku']) 296 | ), 297 | E( 298 | 'pricingList', 299 | E( 300 | 'pricing', 301 | E( 302 | 'currentPrice', 303 | E( 304 | 'value', 305 | **{ 306 | 'currency': item['currenctCurrency'], 307 | 'amount': item['currenctPrice'] 308 | } 309 | ) 310 | ), 311 | E('currentPriceType', item['priceType']), 312 | E( 313 | 'comparisonPrice', 314 | E( 315 | 'value', 316 | **{ 317 | 'currency': item['comparisonCurrency'], 318 | 'amount': item['comparisonPrice'] 319 | } 320 | ) 321 | ), 322 | E( 323 | 'priceDisplayCode', 324 | **{ 325 | 'submapType': item['displayCode'] 326 | } 327 | ), 328 | ) 329 | ) 330 | ) for item in items] 331 | ), xml_declaration=True, encoding='utf-8' 332 | ) 333 | 334 | 335 | class Orders(Resource): 336 | """ 337 | Retrieves Order details 338 | """ 339 | 340 | path = 'orders' 341 | 342 | def all(self, **kwargs): 343 | try: 344 | return super(Orders, self).all(**kwargs) 345 | except requests.exceptions.HTTPError as exc: 346 | if exc.response.status_code == 404: 347 | # If no orders are there on walmart matching the query 348 | # filters, it throws 404. In this case return an empty 349 | # list to make the API consistent 350 | return { 351 | "list": { 352 | "elements": { 353 | "order": [], 354 | } 355 | } 356 | } 357 | raise 358 | 359 | def acknowledge(self, id): 360 | url = self.url + '/%s/acknowledge' % id 361 | return self.send_request(method='POST', url=url) 362 | 363 | def cancel(self, id, lines): 364 | url = self.url + '/%s/cancel' % id 365 | return self.send_request( 366 | method='POST', url=url, data=self.get_cancel_payload(lines)) 367 | 368 | def get_cancel_payload(self, lines): 369 | element = ElementMaker( 370 | namespace='http://walmart.com/mp/orders', 371 | nsmap={ 372 | 'ns2': 'http://walmart.com/mp/orders', 373 | 'ns3': 'http://walmart.com/' 374 | } 375 | ) 376 | return etree.tostring( 377 | element( 378 | 'orderCancellation', 379 | element( 380 | 'orderLines', 381 | *[element( 382 | 'orderLine', 383 | element('lineNumber', line), 384 | element( 385 | 'orderLineStatuses', 386 | element( 387 | 'orderLineStatus', 388 | element('status', 'Cancelled'), 389 | element( 390 | 'cancellationReason', 'CANCEL_BY_SELLER'), 391 | element( 392 | 'statusQuantity', 393 | element('unitOfMeasurement', 'EACH'), 394 | element('amount', '1') 395 | ) 396 | ) 397 | ) 398 | ) for line in lines] 399 | ) 400 | ), xml_declaration=True, encoding='utf-8' 401 | ) 402 | 403 | def create_shipment(self, order_id, lines): 404 | """Send shipping updates to Walmart 405 | 406 | :param order_id: Purchase order ID of an order 407 | :param lines: Order lines to be fulfilled in the format: 408 | [{ 409 | "line_number": "123", 410 | "uom": "EACH", 411 | "quantity": 3, 412 | "ship_time": datetime(2019, 04, 04, 12, 00, 00), 413 | "other_carrier": None, 414 | "carrier": "USPS", 415 | "carrier_service": "Standard", 416 | "tracking_number": "34567890567890678", 417 | "tracking_url": "www.fedex.com", 418 | }] 419 | """ 420 | url = self.url + "/{}/shipping".format(order_id) 421 | 422 | order_lines = [] 423 | for line in lines: 424 | ship_time = line.get("ship_time", "") 425 | if ship_time: 426 | ship_time = epoch_milliseconds(ship_time) 427 | order_lines.append({ 428 | "lineNumber": line["line_number"], 429 | "orderLineStatuses": { 430 | "orderLineStatus": [{ 431 | "status": "Shipped", 432 | "statusQuantity": { 433 | "unitOfMeasurement": line.get("uom", "EACH"), 434 | "amount": str(int(line["quantity"])), 435 | }, 436 | "trackingInfo": { 437 | "shipDateTime": ship_time, 438 | "carrierName": { 439 | "otherCarrier": line.get("other_carrier"), 440 | "carrier": line["carrier"], 441 | }, 442 | "methodCode": line.get("carrier_service", ""), 443 | "trackingNumber": line["tracking_number"], 444 | "trackingURL": line.get("tracking_url", "") 445 | } 446 | }], 447 | } 448 | }) 449 | 450 | body = { 451 | "orderShipment": { 452 | "orderLines": { 453 | "orderLine": order_lines, 454 | } 455 | } 456 | } 457 | return self.connection.send_request( 458 | method="POST", 459 | url=url, 460 | json=body, 461 | ) 462 | 463 | 464 | class Report(Resource): 465 | """ 466 | Get report 467 | """ 468 | 469 | path = 'getReport' 470 | 471 | 472 | class Feed(Resource): 473 | path = "feeds" 474 | 475 | def create(self, resource, content): 476 | """Creates the feed on Walmart for respective resource 477 | 478 | Once you upload the Feed, you can use the Feed ID returned in the 479 | response to track the status of the feed and the status of the 480 | item within that Feed. 481 | 482 | :param resource: The resource for which the feed needs to be created. 483 | :param content: The content needed to create the Feed. 484 | """ 485 | return self.connection.send_request( 486 | method="POST", 487 | url=self.url, 488 | params={ 489 | "feedType": resource, 490 | }, 491 | json=content, 492 | ) 493 | 494 | def get_status(self, feed_id, offset=0, limit=1000): 495 | "Returns the feed and item status for a specified Feed ID" 496 | return self.connection.send_request( 497 | method="GET", 498 | url="{}/{}".format(self.url, feed_id), 499 | params={ 500 | "includeDetails": "true", 501 | "limit": limit, 502 | "offset": offset, 503 | }, 504 | ) 505 | --------------------------------------------------------------------------------