1010import datetime
1111import logging
1212import time
13- from typing import Any , Generator
13+ from collections .abc import AsyncGenerator , Generator
14+ from typing import Any , Final
1415
1516import aiohttp
1617from Crypto .Hash import SHA256
1718from Crypto .PublicKey import RSA
1819from Crypto .Signature import PKCS1_v1_5
1920
20- from .utils import InvalidRequestException , _get_items_ids , _ttl_cache
21+ from .utils import InvalidRequestException , get_items_ids , ttl_cache
2122from .WalmartResponse import (
2223 WalmartCatalog ,
2324 WalmartProduct ,
@@ -54,7 +55,7 @@ class AsyncWalmartIO:
5455
5556 """
5657
57- __slots__ = (
58+ __slots__ : tuple [ str , ...] = (
5859 "_consumer_id" ,
5960 "_private_key" ,
6061 "_private_key_version" ,
@@ -65,7 +66,7 @@ class AsyncWalmartIO:
6566 "publisherId" ,
6667 )
6768
68- ENDPOINT = "https://developer.api.walmart.com/api-proxy/service"
69+ ENDPOINT : Final [ str ] = "https://developer.api.walmart.com/api-proxy/service"
6970
7071 def __init__ (
7172 self ,
@@ -102,27 +103,29 @@ def __init__(
102103 The filename will look something like `./WM_IO_private_key.pem`
103104
104105 """
105- self ._private_key_version = private_key_version
106+ self ._private_key_version : Final [ str ] = private_key_version
106107
107108 # IOError is triggered if the file cannot be opened
108109 with open (private_key_filename ) as f :
109- self ._private_key = RSA .importKey (f .read ())
110+ self ._private_key : Final [ RSA . RsaKey ] = RSA .importKey (f .read ())
110111
111- self ._consumer_id = consumer_id
112+ self ._consumer_id : Final [ str ] = consumer_id
112113
113- self .headers = {}
114+ self .headers : dict [ str , str ] = {}
114115 self .headers ["WM_CONSUMER.ID" ] = consumer_id
115116 self .headers ["WM_SEC.KEY_VERSION" ] = private_key_version
116117
117- self ._update_daily_calls_time = datetime .datetime .now () + datetime .timedelta (days = 1 )
118- self .daily_calls = daily_calls
119- self .daily_calls_remaining = daily_calls
118+ self ._update_daily_calls_time : datetime .datetime = (
119+ datetime .datetime .now () + datetime .timedelta (days = 1 )
120+ )
121+ self .daily_calls : int = daily_calls
122+ self .daily_calls_remaining : int = daily_calls
120123
121- self .publisherId = publisherId or None
124+ self .publisherId : Final [ str | None ] = publisherId or None
122125
123126 log .info (f"Walmart IO connection with consumer id ending in { consumer_id [- 6 :]} " )
124127
125- async def catalog_product (self , ** kwargs ) -> WalmartCatalog :
128+ async def catalog_product (self , ** kwargs : str | int | bool ) -> WalmartCatalog :
126129 """
127130 Catalog Product Endpoint.
128131
@@ -165,13 +168,15 @@ async def catalog_product(self, **kwargs) -> WalmartCatalog:
165168 https://www.walmart.io/docs/affiliate/catalog-product
166169
167170 """
168- if "nextPage" in kwargs :
169- url = "https://developer.api.walmart.com" + kwargs .pop ("nextPage" )
170- else :
171- url = self .ENDPOINT + "/affil/product/v2/paginated/items"
171+ if next_page := kwargs .pop ("nextPage" , "" ):
172+ assert isinstance (next_page , str ), ValueError (
173+ "Expected type string for kwarg 'nextPage'"
174+ )
175+ url = "https://developer.api.walmart.com" + next_page
176+ return WalmartCatalog (await self ._send_request (url ))
172177
173- response = await self ._send_request ( url , ** kwargs )
174- return WalmartCatalog (response )
178+ url = self .ENDPOINT + "/affil/product/v2/paginated/items"
179+ return WalmartCatalog (await self . _send_request ( url , ** kwargs ) )
175180
176181 async def post_browsed_products (self , itemId : str ) -> list [WalmartProduct ]:
177182 """
@@ -208,10 +213,10 @@ async def post_browsed_products(self, itemId: str) -> list[WalmartProduct]:
208213
209214 """
210215 url = f"{ self .ENDPOINT } /affil/product/v2/postbrowse?itemId={ itemId } "
211- response = await self ._send_request (url )
216+ response : list [ dict [ str , Any ]] = await self ._send_request (url )
212217 return [WalmartProduct (item ) for item in response ]
213218
214- async def product_lookup (self , ids : str | list [str ], ** kwargs ) -> list [WalmartProduct ]:
219+ async def product_lookup (self , ids : str | list [str ], ** kwargs : str ) -> list [WalmartProduct ]:
215220 """
216221 Walmart product lookup.
217222
@@ -244,13 +249,13 @@ async def product_lookup(self, ids: str | list[str], **kwargs) -> list[WalmartPr
244249 url = self .ENDPOINT + "/affil/product/v2/items"
245250
246251 params = kwargs
247- ids = _get_items_ids (ids )
252+ ids = get_items_ids (ids )
248253 if len (ids ) > 200 :
249254 log .debug (
250255 "For large id lists, try using bulk_product_lookup. "
251- "It will continue to run even if one chunk of ids raise an error"
256+ "It will continue to run even if one chunk of ids raise an error" ,
252257 )
253- products = []
258+ products : list [ WalmartProduct ] = []
254259
255260 for idGroup in self ._get_product_id_chunk (list (set (ids )), 20 ):
256261 params ["ids" ] = idGroup
@@ -261,8 +266,12 @@ async def product_lookup(self, ids: str | list[str], **kwargs) -> list[WalmartPr
261266 return products
262267
263268 async def bulk_product_lookup (
264- self , ids : str | list [str ], amount : int = 20 , retries : int = 1 , ** kwargs
265- ):
269+ self ,
270+ ids : str | list [str ],
271+ amount : int = 20 ,
272+ retries : int = 1 ,
273+ ** kwargs : str ,
274+ ) -> AsyncGenerator [list [WalmartProduct ]]:
266275 """
267276 Walmart product lookup for a bulk of products.
268277
@@ -286,8 +295,6 @@ async def bulk_product_lookup(
286295 Your Impact Radius Advertisement Id
287296 campaignId:
288297 Your Impact Radius Campaign Id
289- format:
290- Type of response required, allowed values [json, xml(deprecated)]. Default is json.
291298 upc:
292299 upc of the item
293300
@@ -308,7 +315,7 @@ async def bulk_product_lookup(
308315 url = self .ENDPOINT + "/affil/product/v2/items"
309316
310317 params = kwargs
311- ids = _get_items_ids (ids )
318+ ids = get_items_ids (ids )
312319
313320 # Clamp amount [1, 20]
314321 amount = min (max (1 , amount ), 20 )
@@ -367,7 +374,7 @@ async def product_recommendation(self, itemId: str) -> list[WalmartProduct]:
367374 response = await self ._send_request (url )
368375 return [WalmartProduct (item ) for item in response ]
369376
370- async def reviews (self , itemId : str , ** kwargs ) -> WalmartReviewResponse :
377+ async def reviews (self , itemId : str , ** kwargs : str ) -> WalmartReviewResponse :
371378 """
372379 Reviews Endpoint.
373380
@@ -405,7 +412,7 @@ async def reviews(self, itemId: str, **kwargs) -> WalmartReviewResponse:
405412 response = await self ._send_request (url , ** kwargs )
406413 return WalmartReviewResponse (response )
407414
408- async def search (self , query : str , ** kwargs ) -> WalmartSearch :
415+ async def search (self , query : str , ** kwargs : str | int ) -> WalmartSearch :
409416 """
410417 Search Endpoint.
411418
@@ -482,7 +489,7 @@ async def search(self, query: str, **kwargs) -> WalmartSearch:
482489 response = await self ._send_request (url , ** kwargs )
483490 return WalmartSearch (response )
484491
485- async def stores (self , ** kwargs ) -> list [WalmartStore ]:
492+ async def stores (self , ** kwargs : float ) -> list [WalmartStore ]:
486493 """
487494 Store Locator Endpoint.
488495
@@ -508,7 +515,7 @@ async def stores(self, **kwargs) -> list[WalmartStore]:
508515 response = await self ._send_request (url , ** kwargs )
509516 return [WalmartStore (store ) for store in response ]
510517
511- async def taxonomy (self , ** kwargs ) -> WalmartTaxonomy :
518+ async def taxonomy (self ) -> WalmartTaxonomy :
512519 """
513520 Taxonomy Endpoint.
514521
@@ -524,16 +531,11 @@ async def taxonomy(self, **kwargs) -> WalmartTaxonomy:
524531 For example, Search API can be restricted to search within a category by supplying id as
525532 per the taxonomy.
526533
527- Parameters
528- ----------
529- **kwargs
530- unknown; WalmartIO documentation does not expose what the acceptable
531-
532534 """
533535 url = self .ENDPOINT + "/affil/product/v2/taxonomy"
534- return WalmartTaxonomy (await self ._send_request (url , ** kwargs ))
536+ return WalmartTaxonomy (await self ._send_request (url ))
535537
536- async def trending (self , publisherId = None ) -> list [WalmartProduct ]:
538+ async def trending (self , publisherId : str | None = None ) -> list [WalmartProduct ]:
537539 """
538540 Trending Items Endpoint.
539541
@@ -557,13 +559,13 @@ async def trending(self, publisherId=None) -> list[WalmartProduct]:
557559 url = self .ENDPOINT + "/affil/product/v2/trends"
558560
559561 if publisherId :
560- response = await self ._send_request (url , publisherId = publisherId )
562+ response : dict [ str , Any ] = await self ._send_request (url , publisherId = publisherId )
561563 else :
562564 response = await self ._send_request (url )
563565 return [WalmartProduct (item ) for item in response ["items" ]]
564566
565- @_ttl_cache (maxsize = 2 , ttl = 170 )
566- def _get_headers (self ) -> dict :
567+ @ttl_cache (maxsize = 2 , ttl = 170 )
568+ def _get_headers (self ) -> dict [ str , str ] :
567569 """
568570 Get the headers required for making an API call.
569571
@@ -609,7 +611,7 @@ def _get_headers(self) -> dict:
609611
610612 return self .headers
611613
612- async def _send_request (self , url , ** kwargs ) -> Any :
614+ async def _send_request (self , url : str , ** kwargs : str | int | float | bool ) -> Any :
613615 """
614616 Send a request to the Walmart API and return the HTTP response.
615617
@@ -637,13 +639,13 @@ async def _send_request(self, url, **kwargs) -> Any:
637639 log .debug (f"Making connection to { url } " )
638640
639641 # Avoid format to be changed, always go for json
640- kwargs .pop ("format" , None )
642+ _ = kwargs .pop ("format" , None )
641643 request_params = kwargs
642644
643645 # Convert from native boolean python type to string 'true' or 'false'. This allows to set
644646 # richAttributes with python boolean types
645647 if "richAttributes" in request_params and isinstance (
646- request_params [ "richAttributes" ] , bool
648+ request_params . get ( "richAttributes" ) , bool
647649 ):
648650 if request_params ["richAttributes" ]:
649651 request_params ["richAttributes" ] = "true"
@@ -661,16 +663,17 @@ async def _send_request(self, url, **kwargs) -> Any:
661663 "Too many calls in one day. If this is incorrect, try increasing `daily_calls`"
662664 )
663665
664- async with aiohttp .ClientSession () as session , session .get (
665- url , headers = self ._get_headers (), params = request_params
666- ) as response :
666+ async with (
667+ aiohttp .ClientSession () as session ,
668+ session .get (url , headers = self ._get_headers (), params = request_params ) as response ,
669+ ):
667670 status_code = response .status
668671 if status_code in (200 , 201 ):
669672 return await response .json ()
670673
671674 if status_code == 400 :
672675 # Send exception detail when it is a 400 bad error
673- jsonData = await response .json ()
676+ jsonData : dict [ str , Any ] = await response .json ()
674677 raise InvalidRequestException (status_code , detail = jsonData ["errors" ][0 ]["message" ])
675678 raise InvalidRequestException (status_code )
676679
0 commit comments