Skip to content

Commit 8d84eb4

Browse files
committed
fix(by_sku): added escaping filters
2 parents a851633 + e89fff7 commit 8d84eb4

File tree

2 files changed

+8
-1
lines changed

2 files changed

+8
-1
lines changed

channel_advisor_api/models/channel_advisor.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,17 @@ def all(cls, limit: int = None, filter: str = None, order_by: str = "Sku") -> Li
6565

6666
@classmethod
6767
def by_sku(cls, sku: str) -> "MinProduct":
68-
products = cls.all(filter=f"Sku eq '{sku}'")
68+
escaped_sku = cls.escape_filter_value(sku)
69+
products = cls.all(filter=f"Sku eq '{escaped_sku}'")
6970
if not products:
7071
raise ValueError(f"Product with SKU {sku} not found")
7172
return products[0]
7273

74+
@classmethod
75+
def escape_filter_value(cls, value: str) -> str:
76+
"""Escapes special characters in filter values for the ChannelAdvisor API."""
77+
return value.replace("'", "''")
78+
7379
@classmethod
7480
def search_by_sku(cls, sku: str, limit: int = None, include_children: bool = False) -> List["MinProduct"]:
7581
"""We have to check each page for the sku because the API doesn't support fuzzy search"""

notebooks/sku.ipynb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"cells":[{"cell_type":"code","execution_count":5,"metadata":{},"outputs":[{"data":{"text/plain":["True"]},"execution_count":5,"metadata":{},"output_type":"execute_result"}],"source":["from importlib import reload\n","from channel_advisor_api.models import channel_advisor\n","from dotenv import load_dotenv\n","import os\n","\n","os.environ[\"POWERTOOLS_LOG_LEVEL\"] = \"DEBUG\"\n","\n","load_dotenv()\n"]},{"cell_type":"code","execution_count":17,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["{\"message\":\"Request: GET: https://api.channeladvisor.com/v1/Products?%24orderby=Sku&%24select=ID%2CSku%2CTitle%2CSubtitle%2CBrand%2CDescription%2CShortDescription%2CASIN%2CProductType%2CIsParent%2CIsInRelationship%2CParentProductID%2CParentSku%2CRelationshipName%2CVaryBy&%24filter=Sku+eq+%27Men%27%27s+Rise+Series+Monosuit%27\",\"service\":\"channel_advisor_api.models.channel_advisor_client\",\"location\":\"request:68\",\"level\":\"INFO\",\"timestamp\":\"2025-02-18 09:28:38,935-0600\",\"method\":\"get\",\"url\":\"https://api.channeladvisor.com/v1/Products?%24orderby=Sku&%24select=ID%2CSku%2CTitle%2CSubtitle%2CBrand%2CDescription%2CShortDescription%2CASIN%2CProductType%2CIsParent%2CIsInRelationship%2CParentProductID%2CParentSku%2CRelationshipName%2CVaryBy&%24filter=Sku+eq+%27Men%27%27s+Rise+Series+Monosuit%27\",\"kwargs\":{}}\n","{\"message\":\"get_all_pages('Products?%24orderby=Sku&%24select=ID%2CSku%2CTitle%2CSubtitle%2CBrand%2CDescription%2CShortDescription%2CASIN%2CProductType%2CIsParent%2CIsInRelationship%2CParentProductID%2CParentSku%2CRelationshipName%2CVaryBy&%24filter=Sku+eq+%27Men%27%27s+Rise+Series+Monosuit%27') returned 1 items from 1 pages in 0.49 seconds\",\"service\":\"channel_advisor_api.models.channel_advisor_client\",\"location\":\"get_all_pages:133\",\"level\":\"INFO\",\"timestamp\":\"2025-02-18 09:28:39,424-0600\",\"method\":\"get_all_pages\",\"url\":\"Products?%24orderby=Sku&%24select=ID%2CSku%2CTitle%2CSubtitle%2CBrand%2CDescription%2CShortDescription%2CASIN%2CProductType%2CIsParent%2CIsInRelationship%2CParentProductID%2CParentSku%2CRelationshipName%2CVaryBy&%24filter=Sku+eq+%27Men%27%27s+Rise+Series+Monosuit%27\",\"item_count\":1,\"page_count\":1,\"duration\":0.49}\n"]},{"data":{"text/plain":["MinProduct(id=10455152, sku=\"Men's Rise Series Monosuit\", title=\"Pure Adrenaline Men's Rise Series Snowmobile Monosuit\", subtitle='', brand='PureAdrenaline', description=\"Men's Rise Series Monosuit is designed for active riding. Nylon waterproof shell for extreme performance with extra durable oxford weave fabric on knees, bottom inner leg, and cuff. Active stretch panel for aggressive mobility. Durable Water Repellent (DWR) 4 and Technical Waterproof and Breathable PU Coating. Waterproofness: 17,000 mm; Breathability: 10,000g/m2/24hrs.<BR>\\n<BR>\\n• Taped waterproof seams<BR>\\n• YKK zippers<BR>\\n• Full-length side leg zippers for optimal access and versatile ventilation<BR>\\n• Internal suspension system (padded shoulder straps, adjustable and removable)<BR>\\n• Five external zippered pockets (2 chests and 2 hands w/soft brushed interior, 1 top forearm)<BR>\\n• Tethered Goggle chamois in the chest pocket<BR>\\n• 2 internal zippered pockets (one has a clear window with touch action ability and headphone port)<BR>\\n• Internal Snow Gaiters with boot loop attachment (elastic hem retention loop to prevent deep snow penetration)<BR>\", short_description=\"Men's Rise Series Monosuit is designed for active riding. Nylon waterproof shell for extreme performance with extra durable oxford weave fabric on knees, bottom inner leg, and cuff. Active stretch panel for aggressive mobility. Durable Water Repellent (DWR) 4 and Technical Waterproof and Breathable PU Coating. Waterproofness: 17,000 mm; Breathability: 10,000g/m2/24hrs.\", asin='B0CH4778C4', product_type='Parent', is_parent=True, is_in_relationship=True, parent_product_id=None, parent_sku=None, relationship_name='Size-Color', vary_by=None)"]},"execution_count":17,"metadata":{},"output_type":"execute_result"}],"source":["reload(channel_advisor)\n","channel_advisor.MinProduct.by_sku(\"Men's Rise Series Monosuit\")"]},{"cell_type":"code","execution_count":7,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["{\"message\":\"Searching for product with sku: Men and include_children: False\",\"service\":\"channel_advisor_api.models.channel_advisor\",\"location\":\"search_by_sku:76\",\"level\":\"INFO\",\"timestamp\":\"2025-02-18 09:10:31,762-0600\"}\n","{\"message\":\"Request: GET: https://api.channeladvisor.com/v1/Products?%24orderby=Sku&%24filter=Sku+ge+%27Men%27+and+%28IsParent+eq+true+or+IsInRelationship+eq+false%29&%24select=ID%2CSku%2CTitle%2CSubtitle%2CBrand%2CDescription%2CShortDescription%2CASIN%2CProductType%2CIsParent%2CIsInRelationship%2CParentProductID%2CParentSku%2CRelationshipName%2CVaryBy\",\"service\":\"channel_advisor_api.models.channel_advisor_client\",\"location\":\"request:68\",\"level\":\"INFO\",\"timestamp\":\"2025-02-18 09:10:31,763-0600\",\"method\":\"get\",\"url\":\"https://api.channeladvisor.com/v1/Products?%24orderby=Sku&%24filter=Sku+ge+%27Men%27+and+%28IsParent+eq+true+or+IsInRelationship+eq+false%29&%24select=ID%2CSku%2CTitle%2CSubtitle%2CBrand%2CDescription%2CShortDescription%2CASIN%2CProductType%2CIsParent%2CIsInRelationship%2CParentProductID%2CParentSku%2CRelationshipName%2CVaryBy\",\"kwargs\":{}}\n","{\"message\":\"Request: GET: https://api.channeladvisor.com/v1/https://api.channeladvisor.com/v1/Products?%24orderby=Sku&%24filter=Sku+ge+%27Men%27+and+%28IsParent+eq+true+or+IsInRelationship+eq+false%29&%24select=ID%2CSku%2CTitle%2CSubtitle%2CBrand%2CDescription%2CShortDescription%2CASIN%2CProductType%2CIsParent%2CIsInRelationship%2CParentProductID%2CParentSku%2CRelationshipName%2CVaryBy&$skip=100\",\"service\":\"channel_advisor_api.models.channel_advisor_client\",\"location\":\"request:68\",\"level\":\"INFO\",\"timestamp\":\"2025-02-18 09:10:34,358-0600\",\"method\":\"get\",\"url\":\"https://api.channeladvisor.com/v1/https://api.channeladvisor.com/v1/Products?%24orderby=Sku&%24filter=Sku+ge+%27Men%27+and+%28IsParent+eq+true+or+IsInRelationship+eq+false%29&%24select=ID%2CSku%2CTitle%2CSubtitle%2CBrand%2CDescription%2CShortDescription%2CASIN%2CProductType%2CIsParent%2CIsInRelationship%2CParentProductID%2CParentSku%2CRelationshipName%2CVaryBy&$skip=100\",\"kwargs\":{}}\n","{\"message\":\"Rate Limit: 429 for GET https://api.channeladvisor.com/v1/Products?%24orderby=Sku&%24filter=Sku+ge+%27Men%27+and+%28IsParent+eq+true+or+IsInRelationship+eq+false%29&%24select=ID%2CSku%2CTitle%2CSubtitle%2CBrand%2CDescription%2CShortDescription%2CASIN%2CProductType%2CIsParent%2CIsInRelationship%2CParentProductID%2CParentSku%2CRelationshipName%2CVaryBy&$skip=100 attempt 1 in 5 seconds. Error: ''\",\"service\":\"channel_advisor_api.models.channel_advisor_client\",\"location\":\"request:96\",\"level\":\"INFO\",\"timestamp\":\"2025-02-18 09:10:34,592-0600\"}\n"]},{"ename":"KeyboardInterrupt","evalue":"","output_type":"error","traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)","Cell \u001b[0;32mIn[7], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m reload(channel_advisor)\n\u001b[0;32m----> 2\u001b[0m \u001b[43mchannel_advisor\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mMinProduct\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msearch_by_sku\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mMen\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n","File \u001b[0;32m~/Projects/bhmoto/channel_advisor_api_python/channel_advisor_api/models/channel_advisor.py:90\u001b[0m, in \u001b[0;36mBaseProduct.search_by_sku\u001b[0;34m(cls, sku, limit, include_children)\u001b[0m\n\u001b[1;32m 88\u001b[0m client \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mcls\u001b[39m\u001b[38;5;241m.\u001b[39mget_client()\n\u001b[1;32m 89\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m next_link:\n\u001b[0;32m---> 90\u001b[0m response \u001b[38;5;241m=\u001b[39m \u001b[43mclient\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrequest\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mget\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnext_link\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 91\u001b[0m content \u001b[38;5;241m=\u001b[39m json\u001b[38;5;241m.\u001b[39mloads(response\u001b[38;5;241m.\u001b[39mcontent)\n\u001b[1;32m 92\u001b[0m next_link \u001b[38;5;241m=\u001b[39m content\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m@odata.nextLink\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m)\n","File \u001b[0;32m~/Projects/bhmoto/channel_advisor_api_python/channel_advisor_api/models/channel_advisor_client.py:100\u001b[0m, in \u001b[0;36mChannelAdvisorClient.request\u001b[0;34m(self, method, uri, data, attempts, **kwargs)\u001b[0m\n\u001b[1;32m 97\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m attempts \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m5\u001b[39m:\n\u001b[1;32m 98\u001b[0m \u001b[38;5;66;03m# eventually give up\u001b[39;00m\n\u001b[1;32m 99\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m RateLimitExceeded(msg)\n\u001b[0;32m--> 100\u001b[0m \u001b[43mtime\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msleep\u001b[49m\u001b[43m(\u001b[49m\u001b[43msleep_time\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# TODO make this async\u001b[39;00m\n\u001b[1;32m 101\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mrequest(method, uri, attempts\u001b[38;5;241m=\u001b[39mattempts, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 102\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m(\n\u001b[1;32m 103\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mRequest \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mmethod\u001b[38;5;241m.\u001b[39mupper()\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m \u001b[39m\u001b[38;5;132;01m{\u001b[39;00murl\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m failed. Status: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mresponse\u001b[38;5;241m.\u001b[39mstatus_code\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m. Response: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mresponse\u001b[38;5;241m.\u001b[39mtext\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 104\u001b[0m )\n","\u001b[0;31mKeyboardInterrupt\u001b[0m: "]}],"source":["reload(channel_advisor)\n","channel_advisor.MinProduct.search_by_sku(\"Men\")"]}],"metadata":{"kernelspec":{"display_name":".venv","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.1"}},"nbformat":4,"nbformat_minor":2}

0 commit comments

Comments
 (0)