├── wapy ├── __init__.py └── api.py ├── requirements.txt ├── setup.cfg ├── .gitignore ├── CHANGELOG.md ├── LICENCE.md ├── setup.py └── README.md /wapy/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | script.py 3 | TODO.md 4 | __pycache__/ 5 | *.py[cod] 6 | dist/ 7 | MANIFEST 8 | release_pypi.sh 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 (01-12-2016) 2 | ### Docs 3 | * **API Documentation**: A pretty comprehensive API Documentation has been written in the README for the `Wapy`, `WalmartProduct` and `WalmartProductReview` classes. 4 | * **Source docs**: Fixed typos and some functions have now better documentations. 5 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | #The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Carlos Roso 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | setup( 3 | name = 'wapy', 4 | packages = ['wapy'], 5 | version = '0.1.0', 6 | description = 'A python wrapper for the Walmart Open API', 7 | author = 'Carlos Roso', 8 | author_email = 'ce.roso398@gmail.com', 9 | url = 'https://github.com/caroso1222/wapy', 10 | download_url = 'https://github.com/caroso1222/wapy/tarball/0.1.0', 11 | keywords = ['walmart', 'wrapper', 'walmart api', 'api', 'python client', 'python'], 12 | install_requires=["requests"], 13 | classifiers=[ 14 | "Development Status :: 5 - Production/Stable", 15 | "Environment :: Console", 16 | "Intended Audience :: Developers", 17 | "Natural Language :: English", 18 | "Operating System :: OS Independent", 19 | "Programming Language :: Python", 20 | "Programming Language :: Python :: 2.7", 21 | "Programming Language :: Python :: 3.3", 22 | "Programming Language :: Python :: 3.4", 23 | "Programming Language :: Python :: 3.5", 24 | "Topic :: Software Development :: Libraries :: Python Modules", 25 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", 26 | "Topic :: Utilities", 27 | "License :: OSI Approved :: MIT License", 28 | ], 29 | ) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wapy 2 | Wapy is a fully featured Python wrapper for the Walmart Open API. 3 | 4 | ## Features 5 | * Easy to use, object oriented interface to the Walmart Open API. (*Products and Reviews are retrieved as objects*) 6 | * Ready to use, parsed attributes. *e.g. prices as `float`, no strings with escaped html entities, numbers as `int`.* 7 | * Full support for Walmart Search API, Product Recommendation API, Post Browsed Products API, Trending API and more. 8 | * Get all responses with `richAttributes='true'` by default. *This lets you get weight and dimensions right off the shelf.* 9 | * Proper error handling according to original Walmart API documentation 10 | * Helper functions such as `bestseller_products()` and more from the Special Feeds section. 11 | * Silently fails when attribute not found in response 12 | * Fully documented source code 13 | * Support for Python 2.7, 3.2, 3.3, 3.4 and 3.5 14 | 15 | ## Requirements 16 | Before using Wapy, you want to make sure you have `requests` installed and a Walmart Open API key: 17 | 18 | * `pip install requests` 19 | * Register and grab your API key in the [Walmart Open API Developer portal](https://developer.walmartlabs.com/). 20 | 21 | ## Installation 22 | Installation via `pip` is recommended: 23 | ``` 24 | pip install wapy 25 | ``` 26 | 27 | You can also install it from source: 28 | ``` 29 | git clone https://github.com/caroso1222/wapy 30 | cd wapy 31 | python setup.py install 32 | ``` 33 | 34 | ## Basic usage 35 | ```Python 36 | from wapy.api import Wapy 37 | 38 | wapy = Wapy('your-walmart-api-key') # Create an instance of Wapy. 39 | 40 | #### PRODUCT LOOKUP #### 41 | product = wapy.product_lookup('21853453') # Perform a product lookup using the item ID. 42 | print (product.name) # Apple EarPods with Remote and Mic MD827LLA 43 | print (product.weight) # 1.0 44 | print (product.customer_rating) # 4.445 45 | print (product.medium_image) # https://i5.walmartimages.com/asr/6cd9c... 46 | 47 | #### PRODUCT SEARCH #### 48 | products = wapy.search('xbox') 49 | for product in products: 50 | print (product.name) 51 | 52 | #### PRODUCT REVIEWS #### 53 | reviews = wapy.product_reviews('21853453') 54 | for review in reviews: 55 | print (review.reviewer) 56 | ``` 57 | 58 | This example barely shows the power of Wapy. Read the API documentation to discover all you can achieve with this library. 59 | 60 | ## API documentation 61 | 62 | ### `class Wapy` 63 | This class models the main Walmart API proxy class and offers services such as product lookup, product search, trending products retrieval, and much more. 64 | 65 | ### Methods 66 | #### `__init__([api_key], **kwargs)` 67 | Initialize a Walmart API Proxy. 68 | 69 | ##### Params##### 70 | * **`api_key`** A string representing the Walmart Open API key. Can be found in 'My Account' when signing in your Walmartlabs account. 71 | * Named **optional** params passed in kwargs: 72 | * **`LinkShareID`** Your own LinkShare ID. It can be found in any link you generate from the LinkShare platform after the 'id=' parameter. It is an 11 digit alphanumeric string. 73 | 74 | #### `product_lookup([item_id], **kwargs)` 75 | Walmart product lookup. 76 | 77 | ##### Params ##### 78 | * **`item_id`** A string representing the product item id. 79 | * Named **optional** params passed in kwargs: 80 | * **`richAttributes`** A boolean to specify whether you want to get your reponse with rich attributes or not. It's True by default. 81 | 82 | ##### Return ##### 83 | An instance of `WalmartProduct`. <[*[WalmartProduct](#class-walmartproduct)*]> 84 | 85 | #### `search([query], **kwargs)` 86 | 87 | Search allows text search on the Walmart.com catalogue and returns matching items available for sale online. 88 | 89 | This implementation doesn't take into account the start parameter from the actual Walmart specification. 90 | Instead, I've abstracted the same concept to a paginated approach. 91 | You can specify which 'page' of results you get, according to the numItems you expect from every page. 92 | 93 | ##### Params ##### 94 | * **`query`** Search text - whitespace separated sequence of keywords to search for 95 | * Unnamed params passed in kwargs: 96 | * **`numItems`** Number of matching items to be returned, max value 25. Default is 10. 97 | * **`page`** Number of page retrieved. Each page contains [numItems] elements. If no numItems is specified, a default page contains 10 elements. 98 | * **`categoryId`** Category id of the category for search within a category. This should match the id field from Taxonomy API 99 | * **`sort`** Sorting criteria, allowed sort types are [relevance, price, title, bestseller, customerRating, new]. Default sort is by relevance. 100 | * **`order`** Sort ordering criteria, allowed values are [asc, desc]. This parameter is needed only for the sort types [price, title, customerRating]. 101 | * **`responseGroup`** Specifies the item fields returned in the response, allowed response groups are [base, full]. Default value is base. 102 | * **`facet`** Boolean flag to enable facets. Default value is off. Set this to on to enable facets. 103 | * **`facet.filter`** Filter on the facet attribute values. This parameter can be set to : (without the angles). Here facet-name and facet-value can be any valid facet picked from the search API response when facets are on. 104 | * **`facet.range`** Range filter for facets which take range values, like price. See usage above in the examples. 105 | 106 | ##### Return ##### 107 | A list of `WalmartProduct` instances. <[*[WalmartProduct](#class-walmartproduct)*]> 108 | 109 | ####`product_recommendations([item_id])` 110 | Returns a list of a product's related products. A maximum of 10 items are returned, being ordered from most relevant to least relevant for the customer. 111 | 112 | ##### Params ##### 113 | * **`item_id`** The id of the product from which the related products are returned 114 | 115 | ##### Return ##### 116 | A list of `WalmartProduct` instances. <[*[WalmartProduct](#class-walmartproduct)*]> 117 | 118 | #### `post_browsed_products([item_id])` 119 | 120 | Returns a list of recommended products based on their product viewing history. A maximum of 10 items are returned, being ordered from most relevant to least relevant for the customer. 121 | 122 | ##### Params ##### 123 | * **`item_id`** The id of the product from which the post browsed products are returned 124 | 125 | ##### Return ##### 126 | A list of `WalmartProduct` instances. <[*[WalmartProduct](#class-walmartproduct)*]> 127 | 128 | #### `product_reviews([item_id])` 129 | Returns the list of reviews written by Walmart users for a specific item. 130 | 131 | ##### Params ##### 132 | * **`item_id`** The id of the product which reviews are returned from 133 | 134 | ##### Return ##### 135 | A list of `WalmartProductReview` instances. <[*[WalmartProductReview](#class-walmartproductreview)*]> 136 | 137 | #### `trending_products()` 138 | 139 | Returns a list of items according to what is bestselling on Walmart.com right now. The items are curated on the basis of user browse activity and sales activity, and updated multiple times a day. 140 | 141 | ##### Return ##### 142 | A list of `WalmartProduct` instances. <[*[WalmartProduct](#class-walmartproduct)*]> 143 | 144 | #### `bestseller_products([category])` 145 | 146 | Return a list of bestselling items in their respective categories on Walmart.com. This method is part of the Special Feeds section of the Walmart API. 147 | 148 | ##### Params ##### 149 | * **`category`** The number id of the category from which the products are retrieved. 150 | 151 | ##### Return ##### 152 | A list of `WalmartProduct` instances. <[*[WalmartProduct](#class-walmartproduct)*]> 153 | 154 | #### `clearance_products([category])` 155 | 156 | Return a list of all items on clearance from a category. This method is part of the Special Feeds section of the Walmart API. 157 | 158 | ##### Params ##### 159 | * **`category`** The number id of the category from which the products are retrieved. 160 | 161 | ##### Return ##### 162 | A list of `WalmartProduct` instances. <[*[WalmartProduct](#class-walmartproduct)*]> 163 | 164 | #### `special_buy_products([category])` 165 | 166 | Return a list of all items on Special Buy on Walmart.com, which means there is a special offer on them. This method is part of the Special Feeds section of the Walmart API. 167 | 168 | ##### Params ##### 169 | * **`category`** The number id of the category from which the products are retrieved. 170 | 171 | ##### Return ##### 172 | A list of `WalmartProduct` instances. <[*[WalmartProduct](#class-walmartproduct)*]> 173 | 174 | --- 175 | 176 | ### `class WalmartProduct` 177 | This class models a Walmart Product as an object. A `WalmartProduct` instance will be returned when performing a `product_lookup` from your Wapy instance. 178 | ### Methods 179 | #### `get_attribute([name])` 180 | Returns any of the product attributes from the Full Response group. When using this method to get attribute values, you must parse the response to float or integer whenever needed. **I don't recommend accessing attributes using this method**. Direct attribute access is preferred. See **Atributes** section below. 181 | 182 | ##### Params ##### 183 | * **`name`** Product attribute's name. Check out the **Atributes** section below to see allowed names. 184 | 185 | ##### Return ##### 186 | 187 | The product attribute value. <*string*> 188 | 189 | #### `get_images_by_size([size])` 190 | A list with all the images URLs. Primary image is always returned in the first position of the list. 191 | 192 | ##### Params ##### 193 | * **`size`** Size of the desired images: possible options are: 'thumbnail', 'medium', 'large'. 194 | 195 | ##### Return ##### 196 | 197 | List with all the images URLs. <*[string]*> 198 | 199 | ### Attributes 200 | All properties return `None` if not found in the Walmart API response. 201 | #### `item_id` 202 | A positive integer that uniquely identifies an item. <*string*> 203 | #### `parent_item_id` 204 | Item Id of the base version for this item. This is present only if item is a variant of the base version, such as a different color or size. <*string*> 205 | #### `name` 206 | Standard name of the item. <*string*> 207 | #### `msrp` 208 | Manufacturer suggested retail price. <*string*> 209 | #### `sale_price` 210 | Selling price for the item in USD. <*float*> 211 | #### `upc` 212 | Unique Product Code. <*string*> 213 | #### `category_path` 214 | Product Category path: Breadcrumb for the item. This string describes the category level hierarchy that the item falls under. <*string*> 215 | #### `category_node` 216 | Category id for the category of this item. This value can be passed to APIs to pull this item's category level information. <*string*> 217 | #### `short_description` 218 | Short description for the item. <*string*> 219 | #### `long_description` 220 | Long description for the item. <*string*> 221 | #### `brand_name` 222 | Item's brand name. <*string*> 223 | #### `thumbnail_image` 224 | URL for the small sized image. This is a jpeg image with dimensions 100 x 100 pixels. <*string*> 225 | #### `medium_image` 226 | URL for the medium sized image. This is a jpeg image with dimensions 180 x 180 pixels. <*string*> 227 | #### `large_image` 228 | URL for the large sized image. This is a jpeg image with dimensions 450 x 450 pixels. <*string*> 229 | #### `images` 230 | A list with all the large size images URLs. Primary image is always returned in the first position of the list. <[*string*]> 231 | #### `product_tracking_url` 232 | Deep linked URL that directly links to the product page of this item on walmart.com. 233 | This link uniquely identifies the affiliate sending this request via a linkshare tracking id |LSNID|. 234 | The LSNID parameter needs to be replaced with your linkshare tracking id, and is used by us to correctly attribute the sales from your channel on walmart.com. 235 | Actual commission numbers will be made available through your linkshare account. <*string*> 236 | 237 | **Note:** If you created a Wapy instance without explicitly setting `LinkShareID`, you'll get a `NoLinkShareIDException` exception when trying to access this property. 238 | #### `size` 239 | Size attribute for the item. <*string*> 240 | #### `color` 241 | Color attribute for the item. <*string*> 242 | #### `model_number` 243 | Model number attribute for the item. <*string*> 244 | #### `product_url` 245 | Walmart.com url for the item. <*string*> 246 | #### `available_online` 247 | Whether or not the item is currently available for sale on Walmart.com. <*boolean*> 248 | #### `stock` 249 | Indicative quantity of the item available online. Possible values are [Available, Limited Supply, Last few items, Not available]. <*string*> 250 | 251 | Limited supply: can go out of stock in the near future, so needs to be refreshed for availability more frequently. 252 | 253 | Last few items: can go out of stock very quickly, so could be avoided in case you only need to show available items to your users. 254 | #### `customer_rating` 255 | Average customer rating out of 5. <*float*> 256 | #### `num_reviews` 257 | Number of customer reviews available on this item on Walmart.com. <*int*> 258 | #### `weight` 259 | Indicates the weight of the item. <*float*> 260 | #### `length` 261 | Indicates the length of the item. First dimension returned by attribute dimensions e.g. dimensions: "2.0 x 3.0 x 4.0" would return 2.0 as length. <*float*> 262 | #### `width` 263 | Indicates the width of the item. Second dimension returned by attribute dimensions e.g. dimensions: "2.0 x 3.0 x 3.0" would return 3.0 as width. <*float*> 264 | #### `height` 265 | Indicates the height of the item. Third dimension returned by attribute dimensions e.g. dimensions: "2.0 x 3.0 x 4.0" would return 4.0 as height. <*float*> 266 | #### `color` 267 | Color attribute for the item. <*string*> 268 | 269 | --- 270 | 271 | ### `class WalmartProductReview` 272 | This class models a Walmart Product review as an object. A list containing `WalmartProductReview` instances will be returned when calling the method `product_reviews` from your Wapy instance. 273 | ### Attributes 274 | All properties return `None` if not found in the Walmart API response. 275 | #### `reviewer` 276 | Name/alias of the reviewer. <*string*> 277 | #### `review` 278 | The complete product review. <*string*> 279 | ####`date` 280 | The product review date. <*string*> 281 | #### `up_votes` 282 | Number of up votes for this review. <*int*> 283 | #### `down_votes` 284 | Number of down votes for this review. <*int*> 285 | #### `rating` 286 | Overall rating given by the reviewer. <*int*> 287 | 288 | 289 | ## Contribution 290 | 291 | There are still some things to do to make Wapy a super badass Python wrapper for the Walmart Open API: 292 | * Return reviews statistics 293 | * Return all the Special Feeds. Already implemented: Bestsellers, Clearance, Special Buy. Still not implemented: 294 | * preOrder 295 | * rollback 296 | * Get the categories taxonomy. *Not sure whether or not this would be useful at all* 297 | * Support for Data Feed API. *Similar results can be achieved through the search method by passing a categoryId as an argument.* 298 | * Unit testing 299 | 300 | Please open up an issue and let me know what you're working on. Feel free to make a PR. 301 | 302 | ## Credits 303 | - Wapy was inspired by [this awesome Python wrapper for the Amazon product API](https://github.com/yoavaviram/python-amazon-simple-product-api). I took from it some of the code structure, several design parameters and best practices. 304 | - Most of the documentation is based on the original [Walmart API specification](https://developer.walmartlabs.com/docs). 305 | 306 | ## License 307 | Wapy is under [MIT licence](https://opensource.org/licenses/mit-license.php) 308 | -------------------------------------------------------------------------------- /wapy/api.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | try: 4 | # Python 2.6-2.7 5 | from HTMLParser import HTMLParser 6 | except ImportError: 7 | # Python 3 8 | from html.parser import HTMLParser 9 | 10 | h = HTMLParser() 11 | API_BASE_URL='http://api.walmartlabs.com/v1/' 12 | 13 | class WalmartException(Exception): 14 | """Base Class for Walmart Api Exceptions. 15 | """ 16 | 17 | class NoLinkShareIDException(WalmartException): 18 | """Exception thrown if no LinkShare ID was specified when creating the Walmart API instance 19 | """ 20 | pass 21 | 22 | class InvalidParameterException(WalmartException): 23 | """Exception thrown if an invalid parameter is passed to a function 24 | """ 25 | pass 26 | 27 | class InvalidRequestException(WalmartException): 28 | """Exception thrown if an invalid request response is return by Walmart 29 | """ 30 | 31 | def __init__(self, status_code, **kwargs): 32 | error_message = 'Error' 33 | if status_code == 400: 34 | error_message = 'Bad Request' 35 | if 'detail' in kwargs: 36 | error_message = error_message + ' - ' + kwargs['detail'] 37 | elif status_code == 403: 38 | error_message = 'Forbidden' 39 | elif status_code == 404: 40 | error_message = 'Wrong endpoint' 41 | elif status_code == 414: 42 | error_message = 'Request URI too long' 43 | elif status_code == 500: 44 | error_message = 'Internal Server Error' 45 | elif status_code == 502: 46 | error_message = 'Bad Gateway' 47 | elif status_code == 503: 48 | error_message = 'Service Unavailable/ API maintenance' 49 | elif status_code == 504: 50 | error_message = 'Gateway Timeout' 51 | message = '[Request failed] Walmart server answered with the following error: {0:s}. Status code: {1:d}'.format(error_message, status_code) 52 | super(self.__class__, self).__init__(message) 53 | pass 54 | 55 | class Wapy: 56 | """Models the main Walmart API proxy class 57 | """ 58 | 59 | def __init__(self, api_key, **kwargs): 60 | """Initialize a Walmart API Proxy 61 | 62 | :param api_key: 63 | A string representing the Walmart Open API key. Can be found in 'My Account' when signing in your Walmartlabs account. 64 | 65 | - Named params passed in kwargs: 66 | :param LinkShareID [Optional] 67 | Your own LinkShare ID. It can be found in any link you generate from the LinkShare platform after the 'id=' parameter. It is an 11 digit alphanumeric string. 68 | """ 69 | self.params = {'apiKey':api_key,'format':'json'} 70 | self.LSNID = kwargs.get('LinkShareID') 71 | 72 | def product_lookup(self, item_id, **kwargs): 73 | """Walmart product lookup 74 | 75 | :param item_id: 76 | A string representing the product item id 77 | 78 | - Named params passed in kwargs: 79 | :param richAttributes [Optional] 80 | A boolean to specify whether you want to get your reponse with rich attributes or not. It's True by default. 81 | 82 | :return: 83 | An instance of :class: `~.WalmartProduct 84 | 85 | """ 86 | url = API_BASE_URL + 'items/'+item_id 87 | data = self._send_request(url, **kwargs).json() 88 | return WalmartProduct(data, self.LSNID) 89 | 90 | def search(self, query, **kwargs): 91 | """Search allows text search on the Walmart.com catalogue and returns matching items available for sale online. 92 | 93 | This implementation doesn't take into account the start parameter from the actual Walmart specification. 94 | Instead, we've abstracted the same concept to a paginated approach. 95 | You can specify which 'page' of results you get, according to the numItems you expect from every page. 96 | 97 | :param query: 98 | Search text - whitespace separated sequence of keywords to search for 99 | 100 | - Named params passed in kwargs: 101 | :param numItems [Optional] 102 | Number of matching items to be returned, max value 25. Default is 10. 103 | 104 | :param page [Optional] 105 | Number of page retrieved. Each page contains [numItems] elements. If no numItems is specified, a default page contains 10 elements. 106 | 107 | :param categoryId [Optional] 108 | Category id of the category for search within a category. This should match the id field from Taxonomy API 109 | 110 | :param sort [Optional] 111 | Sorting criteria, allowed sort types are [relevance, price, title, bestseller, customerRating, new]. Default sort is by relevance. 112 | 113 | :param order [Optional] 114 | Sort ordering criteria, allowed values are [asc, desc]. This parameter is needed only for the sort types [price, title, customerRating]. 115 | 116 | :param responseGroup [Optional] 117 | Specifies the item fields returned in the response, allowed response groups are [base, full]. Default value is base. 118 | 119 | :param facet [Optional] 120 | Boolean flag to enable facets. Default value is off. Set this to on to enable facets. 121 | 122 | :param facet.filter [Optional] 123 | Filter on the facet attribute values. 124 | This parameter can be set to : (without the angles). 125 | Here facet-name and facet-value can be any valid facet picked from the search API response when facets are on. 126 | 127 | :param facet.range [Optional] 128 | Range filter for facets which take range values, like price. See usage above in the examples. 129 | 130 | :return: 131 | A list of :class:`~.WalmartProduct`. 132 | """ 133 | url = API_BASE_URL + 'search' 134 | kwargs['query'] = query 135 | if 'page' in kwargs: 136 | if type(kwargs['page']) != int: 137 | raise InvalidParameterException('Page should be a numeric value') 138 | if 'numItems' in kwargs: 139 | if type(kwargs['numItems']) != int: 140 | raise InvalidParameterException('Number of items should be a numeric value') 141 | if kwargs['numItems']>25: 142 | raise InvalidParameterException('Number of items must not exceed 25') 143 | kwargs['start'] = kwargs['numItems']*(kwargs['page']-1) + 1 144 | else: 145 | #if numItems not specified, use 10 as default items per page as Walmart does too 146 | kwargs['start'] = 10*(kwargs['page']-1) + 1 147 | kwargs.pop('page', None) 148 | data = self._send_request(url, **kwargs).json() 149 | products = [] 150 | for item in data["items"]: 151 | products.append(WalmartProduct(item, self.LSNID)) 152 | return products 153 | 154 | 155 | def product_recommendations(self, item_id): 156 | """Returns a list of a product's related products. 157 | 158 | A maximum of 10 items are returned, being ordered from most relevant to least relevant for the customer. 159 | 160 | :param item_id: 161 | The id of the product from which the related products are returned 162 | 163 | :return: 164 | A list of :class:`~.WalmartProduct`. 165 | """ 166 | url = API_BASE_URL + 'nbp' 167 | data = self._send_request(url, itemId=item_id).json() 168 | products = [] 169 | for item in data: 170 | products.append(WalmartProduct(item, self.LSNID)) 171 | return products 172 | 173 | def post_browsed_products(self, item_id): 174 | """Returns a list of recommended products based on their product viewing history. 175 | 176 | A maximum of 10 items are returned, being ordered from most relevant to least relevant for the customer. 177 | 178 | :param item_id: 179 | The id of the product from which the post browsed products are returned 180 | 181 | :return: 182 | A list of :class:`~.WalmartProduct`. 183 | """ 184 | url = API_BASE_URL + 'postbrowse' 185 | data = self._send_request(url, itemId=item_id).json() 186 | products = [] 187 | for item in data: 188 | products.append(WalmartProduct(item, self.LSNID)) 189 | return products 190 | 191 | def product_reviews(self, item_id): 192 | """Return the list of item reviews written by Walmart users. 193 | 194 | :param item_id: 195 | The id of the product which reviews are returned from 196 | 197 | :return: 198 | A list of :class:`~.WalmartProductReview`. 199 | """ 200 | url = API_BASE_URL + 'reviews/' + item_id 201 | data = self._send_request(url).json() 202 | reviews = [] 203 | for item in data['reviews']: 204 | reviews.append(WalmartProductReview(item)) 205 | return reviews 206 | 207 | def trending_products(self): 208 | """Return a list of items according to what is bestselling on Walmart.com right now. 209 | 210 | The items are curated on the basis of user browse activity and sales activity, and updated multiple times a day. 211 | 212 | :return: 213 | A list of :class:`~.WalmartProduct`. 214 | """ 215 | url = API_BASE_URL + 'trends' 216 | data = self._send_request(url).json() 217 | products = [] 218 | for item in data['items']: 219 | products.append(WalmartProduct(item, self.LSNID)) 220 | return products 221 | 222 | def bestseller_products(self, category): 223 | """Return a list of bestselling items in their respective categories on Walmart.com 224 | 225 | This method is part of the Special Feeds section of the Walmart API. 226 | 227 | :param category: 228 | The number id of the category from which the products are retrieved. 229 | 230 | :return: 231 | A list of :class:`~.WalmartProduct`. 232 | """ 233 | url = API_BASE_URL + 'feeds/bestsellers' 234 | return self._send_special_feed_request(url, category) 235 | 236 | def clearance_products(self, category): 237 | """Return a list of all items on clearance from a category 238 | 239 | This method is part of the Special Feeds section of the Walmart API. 240 | 241 | :param category: 242 | The number id of the category from which the products are retrieved. 243 | 244 | :return: 245 | A list of :class:`~.WalmartProduct`. 246 | """ 247 | url = API_BASE_URL + 'feeds/clearance' 248 | return self._send_special_feed_request(url, category) 249 | 250 | def special_buy_products(self, category): 251 | """Return a list of all items on Special Buy on Walmart.com, which means there is a special offer on them 252 | 253 | This method is part of the Special Feeds section of the Walmart API. 254 | 255 | :param category: 256 | The number id of the category from which the products are retrieved. 257 | 258 | :return: 259 | A list of :class:`~.WalmartProduct`. 260 | """ 261 | url = API_BASE_URL + 'feeds/specialbuy' 262 | return self._send_special_feed_request(url, category) 263 | 264 | def _send_special_feed_request(self, url, category): 265 | """Send a request to the Special Feeds endpoint and returns the desired list of products 266 | 267 | :param category: 268 | The number id of the category from which the products are retrieved. 269 | 270 | :return: 271 | A list of :class:`~.WalmartProduct`. 272 | """ 273 | if type(category) != int: 274 | raise InvalidParameterException('Category must be numeric. See Walmart Taxonomy API for more information.') 275 | data = self._send_request(url, categoryId=category).json() 276 | products = [] 277 | for item in data['items']: 278 | products.append(WalmartProduct(item, self.LSNID)) 279 | return products 280 | 281 | def _send_request(self, url, **kwargs): 282 | """Sends a request to the Walmart API and return the HTTP response. 283 | 284 | Important remarks: 285 | - If the response's status code is differente than 200 or 201, raise an InvalidRequestException with the appripiate code 286 | - Format is json by default and cannot be changed through kwargs 287 | - Send richAttributes='true' by default. Can be set to 'false' through kwargs 288 | 289 | :param url: 290 | The endpoint url to make the request 291 | 292 | - Named params passed in kwargs can be any of the optional GET arguments specified in the Walmart specification 293 | """ 294 | #Avoid format to be changed, always go for json 295 | kwargs.pop('format', None) 296 | request_params = self.params 297 | for key, value in kwargs.items(): 298 | request_params[key] = value 299 | 300 | #Convert from native boolean python type to string 'true' or 'false'. This allows to set richAttributes with python boolean types 301 | if 'richAttributes' in request_params and type(request_params['richAttributes'])==bool: 302 | if request_params['richAttributes']: 303 | request_params['richAttributes']='true' 304 | else: 305 | request_params['richAttributes']='false' 306 | else: 307 | #Even if not specified in arguments, send request with richAttributes='true' by default 308 | request_params['richAttributes']='true' 309 | 310 | r = requests.get(url, params=request_params) 311 | if r.status_code == 200 or r.status_code == 201: 312 | return r 313 | else: 314 | if r.status_code == 400: 315 | #Send exception detail when it is a 400 error 316 | raise InvalidRequestException(r.status_code, detail=r.json()['errors'][0]['message']) 317 | else: 318 | raise InvalidRequestException(r.status_code) 319 | 320 | 321 | class WalmartProduct: 322 | """Models a Walmart Product as an object 323 | """ 324 | 325 | def __init__(self, payload, LSNID): 326 | self.LSNID = LSNID 327 | self.response_handler = ResponseHandler(payload) 328 | 329 | @property 330 | def item_id(self): 331 | """Item id: A positive integer that uniquely identifies an item 332 | 333 | :return: 334 | Item id (string). Returns None if attribute is not found in the response. 335 | """ 336 | return self.response_handler._safe_get_attribute('itemId') 337 | 338 | @property 339 | def parent_item_id(self): 340 | """Parent Item id: Item Id of the base version for this item. This is present only if item is a variant of the base version, such as a different color or size. 341 | 342 | :return: 343 | Parent Item id (string). Returns None if attribute is not found in the response. 344 | """ 345 | return self.response_handler._safe_get_attribute('parentItemId') 346 | 347 | @property 348 | def name(self): 349 | """Item name 350 | 351 | :return: 352 | Standard name of the item (string). Returns None if attribute is not found in the response. 353 | """ 354 | return self.response_handler._safe_get_attribute_text('name') 355 | 356 | @property 357 | def msrp(self): 358 | """Manufacturer suggested retail price 359 | 360 | :return: 361 | Manufacturer suggested retail price (string). Returns None if attribute is not found in the response. 362 | """ 363 | return self.response_handler._safe_get_attribute('msrp') 364 | 365 | @property 366 | def sale_price(self): 367 | """Sale price 368 | 369 | :return: 370 | Selling price for the item in USD (float). Returns None if attribute is not found in the response. 371 | """ 372 | return self.response_handler._safe_get_attribute_float('salePrice') 373 | 374 | @property 375 | def upc(self): 376 | """Unique Product Code 377 | 378 | :return: 379 | Unique Product Code (string). Returns None if attribute is not found in the response. 380 | """ 381 | return self.response_handler._safe_get_attribute('upc') 382 | 383 | @property 384 | def category_path(self): 385 | """Product Category path: Breadcrumb for the item. This string describes the category level hierarchy that the item falls under. 386 | 387 | :return: 388 | Product Category path (string). Returns None if attribute is not found in the response. 389 | """ 390 | return self.response_handler._safe_get_attribute('categoryPath') 391 | 392 | @property 393 | def category_node(self): 394 | """Product Category node: Category id for the category of this item. This value can be passed to APIs to pull this item's category level information. 395 | 396 | :return: 397 | Product Category path (string). Returns None if attribute is not found in the response. 398 | """ 399 | return self.response_handler._safe_get_attribute('categoryNode') 400 | 401 | @property 402 | def short_description(self): 403 | """Short description: Short description for the item 404 | 405 | :return: 406 | Short description (string). Returns None if attribute is not found in the response. 407 | """ 408 | return self.response_handler._safe_get_attribute_text('shortDescription') 409 | 410 | @property 411 | def long_description(self): 412 | """Long description: Long description for the item. 413 | 414 | :return: 415 | Long description (string). Returns None if attribute is not found in the response. 416 | """ 417 | return self.response_handler._safe_get_attribute_text('shortDescription') 418 | 419 | @property 420 | def brand_name(self): 421 | """Brand name: Item's brand 422 | 423 | :return: 424 | Brand name (string). Returns None if attribute is not found in the response. 425 | """ 426 | return self.response_handler._safe_get_attribute('brandName') 427 | 428 | @property 429 | def thumbnail_image(self): 430 | """Thumbnail image: Small size image for the item in jpeg format with dimensions 100 x 100 pixels 431 | 432 | :return: 433 | URL of the thumbnail image (string). Returns None if attribute is not found in the response. 434 | """ 435 | return self.response_handler._safe_get_attribute('thumbnailImage') 436 | 437 | @property 438 | def medium_image(self): 439 | """Medium image: Medium size image for the item in jpeg format with dimensions 180 x 180 pixels 440 | 441 | :return: 442 | URL of the medium sized image (string). Returns None if attribute is not found in the response. 443 | """ 444 | return self.response_handler._safe_get_attribute('mediumImage') 445 | 446 | @property 447 | def large_image(self): 448 | """Large image: Large size image for the item in jpeg format with dimensions 450 x 450 pixels 449 | 450 | :return: 451 | URL of the large sized image (string). Returns None if attribute is not found in the response. 452 | """ 453 | return self.response_handler._safe_get_attribute('largeImage') 454 | 455 | @property 456 | def images(self): 457 | """Large image entities: All large images for this item 458 | 459 | :return: 460 | A list with all the large size images URLs. 461 | Primary image is always returned in the first position of the list 462 | """ 463 | return self.get_images_by_size('large') 464 | 465 | @property 466 | def product_tracking_url(self): 467 | """Product tracking url: Deep linked URL that directly links to the product page of this item on walmart.com. 468 | This link uniquely identifies the affiliate sending this request via a linkshare tracking id |LSNID|. 469 | The LSNID parameter needs to be replaced with your linkshare tracking id, and is used by us to correctly attribute the sales from your channel on walmart.com. 470 | Actual commission numbers will be made available through your linkshare account. 471 | 472 | :return: 473 | Deep link URL (string). Returns None if attribute is not found in the response. 474 | """ 475 | if self.LSNID is None: 476 | raise NoLinkShareIDException('No LinkShare ID specified. When retrieving the product tracking url, you must set LinkShareID = #YOUR_ID# when creating an instance of the API') 477 | else: 478 | return self.response_handler._safe_get_attribute('productTrackingUrl').replace('|LSNID|',self.LSNID) 479 | 480 | @property 481 | def size(self): 482 | """Size attribute for the item 483 | 484 | :return: 485 | Size attribute for the item (string). Returns None if attribute is not found in the response. 486 | """ 487 | return self.response_handler._safe_get_attribute('size') 488 | 489 | @property 490 | def color(self): 491 | """Color attribute for the item 492 | 493 | :return: 494 | Color attribute for the item (string). Returns None if attribute is not found in the response. 495 | """ 496 | return self.response_handler._safe_get_attribute('color') 497 | 498 | @property 499 | def model_number(self): 500 | """Model number: Model number attribute for the item 501 | 502 | :return: 503 | Model number. (string). Returns None if attribute is not found in the response. 504 | """ 505 | return self.response_handler._safe_get_attribute('modelNumber') 506 | 507 | @property 508 | def product_url(self): 509 | """Product url: Walmart.com url for the item 510 | 511 | :return: 512 | Product url. (string). Returns None if attribute is not found in the response. 513 | """ 514 | return self.response_handler._safe_get_attribute('productUrl') 515 | 516 | @property 517 | def available_online(self): 518 | """Available online: Whether the item is currently available for sale on Walmart.com 519 | 520 | :return: 521 | Available online. (boolean). Returns None if attribute is not found in the response. 522 | """ 523 | return self.response_handler._safe_get_attribute('availableOnline') 524 | 525 | @property 526 | def stock(self): 527 | """Stock: Indicative quantity of the item available online. 528 | 529 | Possible values are [Available, Limited Supply, Last few items, Not available]. 530 | Limited supply: can go out of stock in the near future, so needs to be refreshed for availability more frequently. 531 | Last few items: can go out of stock very quickly, so could be avoided in case you only need to show available items to your users. 532 | 533 | :return: 534 | Stock. (string). Returns None if attribute is not found in the response. 535 | """ 536 | return self.response_handler._safe_get_attribute('stock') 537 | 538 | @property 539 | def customer_rating(self): 540 | """Customer rating: Average customer rating out of 5 541 | 542 | :return: 543 | Customer rating. (float). Returns None if attribute is not found in the response. 544 | """ 545 | return self.response_handler._safe_get_attribute_float('customerRating') 546 | 547 | @property 548 | def num_reviews(self): 549 | """Number of customer reviews available on this item on Walmart.com 550 | 551 | :return: 552 | Number of reviews. (int). Returns None if attribute is not found in the response. 553 | """ 554 | return self.response_handler._safe_get_attribute_int('numReviews') 555 | 556 | @property 557 | def weight(self): 558 | """Weight: Indicates the weight of the item 559 | 560 | :return: 561 | Weight (float). Returns None if attribute is not found in the response. 562 | """ 563 | return self.response_handler._safe_get_attribute_float('weight') 564 | 565 | @property 566 | def length(self): 567 | """Length: Indicates the length of the item. First dimension returned by attribute dimensions 568 | e.g. dimensions: "2.0 x 3.0 x 4.0" would return 2.0 as length 569 | 570 | :return: 571 | Length (float). Returns None if attribute is not found in the response. 572 | """ 573 | dimensions = self.response_handler._safe_get_attribute('dimensions') 574 | if dimensions is not None: 575 | return float(dimensions.split('x')[0]) 576 | return None 577 | 578 | @property 579 | def width(self): 580 | """Width: Indicates the width of the item. Second dimension returned by attribute dimensions 581 | e.g. dimensions: "2.0 x 3.0 x 4.0" would return 3.0 as width 582 | :return: 583 | Width (float). Returns None if attribute is not found in the response. 584 | """ 585 | dimensions = self.response_handler._safe_get_attribute('dimensions') 586 | if dimensions is not None: 587 | return float(dimensions.split('x')[1]) 588 | return None 589 | 590 | @property 591 | def height(self): 592 | """Height: Indicates the height of the item. Third dimension returned by attribute dimensions 593 | e.g. dimensions: "2.0 x 3.0 x 4.0" would return 4.0 as height 594 | :return: 595 | Height (float). Returns None if attribute is not found in the response. 596 | """ 597 | dimensions = self.response_handler._safe_get_attribute('dimensions') 598 | if dimensions is not None: 599 | return float(dimensions.split('x')[2]) 600 | return None 601 | 602 | def get_attribute(self, name): 603 | """ Returns any of the product attributes from the Full Response group. 604 | 605 | :param name: 606 | Name of the product attribute. See docs to see the allowed params 607 | 608 | :return: 609 | The value of the product attribute. (String). User must be aware that he must parse the response to float or integer himself. 610 | Returns None if attribute is not found in the response. 611 | """ 612 | return self.response_handler._safe_get_attribute(name) 613 | 614 | def get_images_by_size(self, size): 615 | """Image entities: All images for this item 616 | 617 | :param size: 618 | Indicates the size of the returned images: possible options are: 'thumbnail', 'medium', 'large' 619 | 620 | :return: 621 | A list with all the images URLs. 622 | Primary image is always returned in the first position of the list 623 | """ 624 | if size != 'thumbnail' and size != 'medium' and size != 'large': 625 | raise InvalidParameterException("The image size should be 'thumbnail', 'medium' or 'large'") 626 | images = [] 627 | primary_image = None 628 | imageEntities = self.response_handler._safe_get_attribute('imageEntities') 629 | if imageEntities: 630 | for image in imageEntities: 631 | if image['entityType'] != 'PRIMARY': 632 | images.append(image[size+'Image']) 633 | else: 634 | primary_image = image[size+'Image'] 635 | if primary_image: 636 | images.insert(0, primary_image) 637 | return images 638 | else: 639 | return None 640 | 641 | class WalmartProductReview: 642 | """Models a Walmart Product review as an object 643 | """ 644 | 645 | def __init__(self, payload): 646 | self.response_handler = ResponseHandler(payload) 647 | 648 | @property 649 | def reviewer(self): 650 | """Product reviewer 651 | 652 | :return: 653 | Name/alias of the reviewer (string) 654 | """ 655 | return self.response_handler._safe_get_attribute('reviewer') 656 | 657 | @property 658 | def review(self): 659 | """Product review 660 | 661 | :return: 662 | The complete product review (string) 663 | """ 664 | return self.response_handler._safe_get_attribute_text('reviewText') 665 | 666 | @property 667 | def date(self): 668 | """Product review date 669 | 670 | :return: 671 | The product review date (string) 672 | """ 673 | return self.response_handler._safe_get_attribute_text('submissionTime') 674 | 675 | @property 676 | def title(self): 677 | """Product review title 678 | 679 | :return: 680 | Product review title (string) 681 | """ 682 | return self.response_handler._safe_get_attribute_text('title') 683 | 684 | @property 685 | def up_votes(self): 686 | """Product review up votes 687 | 688 | :return: 689 | Number of up votes for this review (int) 690 | """ 691 | return self.response_handler._safe_get_attribute_int('upVotes') 692 | 693 | @property 694 | def down_votes(self): 695 | """Product review down votes 696 | 697 | :return: 698 | Number of down votes for this review (int) 699 | """ 700 | return self.response_handler._safe_get_attribute_int('downVotes') 701 | 702 | @property 703 | def rating(self): 704 | """Overall review rating 705 | 706 | :return: 707 | Overall rating given by the reviewer (int) 708 | """ 709 | if 'overallRating' in self.response_handler.payload and 'rating' in self.response_handler.payload['overallRating']: 710 | return int(self.response_handler.payload['overallRating']['rating']) 711 | else: 712 | return None 713 | 714 | class ResponseHandler: 715 | """Gets a json payload and exposes some attributes to safely get attributes from it 716 | """ 717 | 718 | def __init__(self, payload): 719 | self.payload = payload 720 | 721 | def _safe_get_attribute(self, attr): 722 | """ Safe get element attribute. Fails silently if attribute not found. 723 | 724 | :param attr: 725 | The name of attribute 726 | 727 | :return: 728 | The value of the attribute in the response. Returns None if attribute is not found in the response. 729 | """ 730 | if attr in self.payload: 731 | return self.payload[attr] 732 | else: 733 | return None 734 | 735 | def _safe_get_attribute_text(self, attr): 736 | """ Safe get element attribute with unescaped html entities. Fails silently if attribute not found. 737 | 738 | :param attr: 739 | The name of attribute 740 | 741 | :return: 742 | The value of the attribute in the response. (string). Returns None if attribute is not found in the response. 743 | Some attributes come with escaped html entities. This method will unescape them. 744 | """ 745 | if attr in self.payload: 746 | # some strings contains escaped html formatting tags. 747 | return h.unescape(self.payload[attr]) 748 | else: 749 | return None 750 | 751 | def _safe_get_attribute_float(self, attr): 752 | """ Safe get element attribute parsed to float. Fails silently if attribute not found. 753 | 754 | :param attr: 755 | The name of attribute 756 | 757 | :return: 758 | The value of the attribute parsed to float. (float). Returns None if attribute is not found in the response. 759 | """ 760 | if attr in self.payload: 761 | return float(self.payload[attr]) 762 | else: 763 | return None 764 | 765 | def _safe_get_attribute_int(self, attr): 766 | """ Safe get element attribute parsed to int. Fails silently if attribute not found. 767 | 768 | :param attr: 769 | The name of attribute 770 | 771 | :return: 772 | The value of the attribute parsed to int. (int). Returns None if attribute is not found in the response. 773 | """ 774 | if attr in self.payload: 775 | return int(self.payload[attr]) 776 | else: 777 | return None 778 | --------------------------------------------------------------------------------