Covers all questions related to inventory management, order fulfillment, and shipping.
hi everyone
I need your help, please.
can you provide me with a Python code that will update the inventory automatically? Also, I need a Python code that allows me to fulfill or update unfulfilled orders with the tracking number and the name of the carrier as well as the location from where this product is shipped.
thank you in advance for your solutions and your suggestions.
Best Regards,
Hi Simochouchoudan,
You can make calls to the GraphQL Admin API using Python via a library like the requests library and including your API access key, that you'd get once you create an app. For changing inventory, you'd be using the inventoryAdjustQuantities mutation and it would look something like:
import requests
# Your Shopify store URL and access token
shop_url = "yourshop.myshopify.com"
access_token = "your_access_token_here"
# The GraphQL endpoint
graphql_url = f"https://{shop_url}/admin/api/2024-01/graphql.json"
# The GraphQL mutation for updating inventory quantity
mutation = """
mutation inventoryAdjustQuantity($input: InventoryAdjustQuantityInput!) {
inventoryAdjustQuantity(input: $input) {
inventoryLevel {
id
available
}
userErrors {
field
message
}
}
}
"""
# Variables for the mutation
variables = {
"input": {
"inventoryItemId": "the_inventory_item_id",
"locationId": "the_location_id",
"availableDelta": 5 # Adjust by this amount, e.g., +5 to increase, -5 to decrease
}
}
# Headers including the access token and indicating JSON content
headers = {
"X-Shopify-Access-Token": access_token,
"Content-Type": "application/json"
}
# Performing the POST request with the mutation and variables
response = requests.post(graphql_url, json={"query": mutation, "variables": variables}, headers=headers)
# Checking the response
if response.status_code == 200:
# Parsing the JSON response
json_response = response.json()
print(json_response)
else:
print(f"Failed to update inventory: {response.status_code}")
For order fulfilment you'd do something similar using the fulfillmentCreateV2 mutation.
Hope this helps!
Liam | Developer Advocate @ Shopify
- Was my reply helpful? Click Like to let me know!
- Was your question answered? Mark it as an Accepted Solution
- To learn more visit Shopify.dev or the Shopify Web Design and Development Blog
Hi Liam thank you for your assistance, ill try the code and let you know if it is working for me or not. I have this python code :
import shopify
# Initialize the Shopify session
session = shopify.Session("xxxxxxxxxxx.myshopify.com", "2023-01", TOKEN)
shopify.ShopifyResource.activate_session(session)
# Specify the order ID for the order to be fulfilled
order_id_to_fulfill = 5906428854597 # Replace with the actual order ID
# Create a fulfillment object for the specified order
fulfillment = shopify.Fulfillment()
fulfillment.order_id = order_id_to_fulfill
fulfillment.notify_customer = False # Set to True if you want to notify the customer
fulfillment.tracking_info = {
"number": "1Z001985YW99744790",
"url": "https://www.dhl.com/de-en/home/tracking.html",
"company": "DHL"
}
fulfillment.save()
but when i run it i receive this error :
--------------------------------------------------------------------------- HTTPError Traceback (most recent call last) File ~\AppData\Local\anaconda3\Lib\site-packages\pyactiveresource\connection.py:286, in Connection._open(self, method, path, headers, data) 285 try: --> 286 http_response = self._handle_error(self._urlopen(request)) 287 except urllib.error.HTTPError as err: File ~\AppData\Local\anaconda3\Lib\site-packages\pyactiveresource\connection.py:316, in Connection._urlopen(self, request) 315 if _urllib_has_timeout(): --> 316 return urllib.request.urlopen(request, timeout=self.timeout) 317 else: File ~\AppData\Local\anaconda3\Lib\urllib\request.py:216, in urlopen(url, data, timeout, cafile, capath, cadefault, context) 215 opener = _opener --> 216 return opener.open(url, data, timeout) File ~\AppData\Local\anaconda3\Lib\urllib\request.py:525, in OpenerDirector.open(self, fullurl, data, timeout) 524 meth = getattr(processor, meth_name) --> 525 response = meth(req, response) 527 return response File ~\AppData\Local\anaconda3\Lib\urllib\request.py:634, in HTTPErrorProcessor.http_response(self, request, response) 633 if not (200 <= code < 300): --> 634 response = self.parent.error( 635 'http', request, response, code, msg, hdrs) 637 return response File ~\AppData\Local\anaconda3\Lib\urllib\request.py:557, in OpenerDirector.error(self, proto, *args) 556 args = (dict, proto, meth_name) + args --> 557 result = self._call_chain(*args) 558 if result: File ~\AppData\Local\anaconda3\Lib\urllib\request.py:496, in OpenerDirector._call_chain(self, chain, kind, meth_name, *args) 495 func = getattr(handler, meth_name) --> 496 result = func(*args) 497 if result is not None: File ~\AppData\Local\anaconda3\Lib\urllib\request.py:749, in HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers) 747 fp.close() --> 749 return self.parent.open(new, timeout=req.timeout) File ~\AppData\Local\anaconda3\Lib\urllib\request.py:525, in OpenerDirector.open(self, fullurl, data, timeout) 524 meth = getattr(processor, meth_name) --> 525 response = meth(req, response) 527 return response File ~\AppData\Local\anaconda3\Lib\urllib\request.py:634, in HTTPErrorProcessor.http_response(self, request, response) 633 if not (200 <= code < 300): --> 634 response = self.parent.error( 635 'http', request, response, code, msg, hdrs) 637 return response File ~\AppData\Local\anaconda3\Lib\urllib\request.py:563, in OpenerDirector.error(self, proto, *args) 562 args = (dict, 'default', 'http_error_default') + orig_args --> 563 return self._call_chain(*args) File ~\AppData\Local\anaconda3\Lib\urllib\request.py:496, in OpenerDirector._call_chain(self, chain, kind, meth_name, *args) 495 func = getattr(handler, meth_name) --> 496 result = func(*args) 497 if result is not None: File ~\AppData\Local\anaconda3\Lib\urllib\request.py:643, in HTTPDefaultErrorHandler.http_error_default(self, req, fp, code, msg, hdrs) 642 def http_error_default(self, req, fp, code, msg, hdrs): --> 643 raise HTTPError(req.full_url, code, msg, hdrs, fp) HTTPError: HTTP Error 400: Bad Request During handling of the above exception, another exception occurred: BadRequest Traceback (most recent call last) Cell In[67], line 19 13 fulfillment.notify_customer = False # Set to True if you want to notify the customer 14 fulfillment.tracking_info = { 15 "number": "1Z001985YW99744790", 16 "url": "https://www.dhl.com/de-en/home/tracking.html", 17 "company": "DHL" 18 } ---> 19 fulfillment.save() File ~\AppData\Local\anaconda3\Lib\site-packages\pyactiveresource\activeresource.py:836, in ActiveResource.save(self) 831 response = self.klass.connection.put( 832 self._element_path(self.id, self._prefix_options), 833 self.klass.headers, 834 data=self.encode()) 835 else: --> 836 response = self.klass.connection.post( 837 self._collection_path(self._prefix_options), 838 self.klass.headers, 839 data=self.encode()) 840 new_id = self._id_from_response(response) 841 if new_id: File ~\AppData\Local\anaconda3\Lib\site-packages\pyactiveresource\connection.py:375, in Connection.post(self, path, headers, data) 365 def post(self, path, headers=None, data=None): 366 """Perform an HTTP post request. 367 368 Args: (...) 373 A Response object. 374 """ --> 375 return self._open('POST', path, headers=headers, data=data) File ~\AppData\Local\anaconda3\Lib\site-packages\shopify\base.py:23, in ShopifyConnection._open(self, *args, **kwargs) 21 self.response = None 22 try: ---> 23 self.response = super(ShopifyConnection, self)._open(*args, **kwargs) 24 except pyactiveresource.connection.ConnectionError as err: 25 self.response = err.response File ~\AppData\Local\anaconda3\Lib\site-packages\pyactiveresource\connection.py:288, in Connection._open(self, method, path, headers, data) 286 http_response = self._handle_error(self._urlopen(request)) 287 except urllib.error.HTTPError as err: --> 288 http_response = self._handle_error(err) 289 except urllib.error.URLError as err: 290 raise Error(err, url) File ~\AppData\Local\anaconda3\Lib\site-packages\pyactiveresource\connection.py:413, in Connection._handle_error(self, err) 411 return err 412 elif err.code == 400: --> 413 raise BadRequest(err) 414 elif err.code == 401: 415 raise UnauthorizedAccess(err) BadRequest: Response(code=400, body="b'{"errors":{"id":"expected String to be a id"}}'", headers={'Date': 'Thu, 08 Feb 2024 13:15:57 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'close', 'X-Sorting-Hat-PodId': '324', 'X-Sorting-Hat-ShopId': '80726786373', 'referrer-policy': 'origin-when-cross-origin', 'x-frame-options': 'DENY', 'x-shopid': '80726786373', 'x-shardid': '324', 'x-stats-userid': '', 'x-stats-apiclientid': '86061809665', 'x-stats-apipermissionid': '596308590917', 'x-shopify-api-terms': 'By accessing or using the Shopify API you agree to the Shopify API License and Terms of Use at https://www.shopify.com/legal/api-terms', 'x-shopify-api-version': '2023-04', 'x-shopify-api-version-warning': 'https://shopify.dev/concepts/about-apis/versioning', 'http_x_shopify_shop_api_call_limit': '1/40', 'x-shopify-shop-api-call-limit': '1/40', 'server-timing': 'processing;dur=59', 'x-shopify-stage': 'production', 'content-security-policy': "default-src 'self' data: blob: 'unsafe-inline' 'unsafe-eval' https://* shopify-pos://*; block-all-mixed-content; child-src 'self' https://* shopify-pos://*; connect-src 'self' wss://* https://*; frame-ancestors 'none'; img-src 'self' data: blob: https:; script-src https://cdn.shopify.com https://cdn.shopifycdn.net https://checkout.shopifycs.com https://api.stripe.com https://mpsnare.iesnare.com https://appcenter.intuit.com https://www.paypal.com https://js.braintreegateway.com https://c.paypal.com https://maps.googleapis.com https://www.google-analytics.com https://v.shopify.com 'self' 'unsafe-inline' 'unsafe-eval'; upgrade-insecure-requests; report-uri /csp-report?source%5Baction%5D=show&source%5Bapp%5D=Shopify&source%5Bcontroller%5D=admin%2Forders&source%5Bsection%5D=admin_api&source%5Buuid%5D=f8795202-94d8-499c-acc6-b65380905279", 'x-content-type-options': 'nosniff', 'x-download-options': 'noopen', 'x-permitted-cross-domain-policies': 'none', 'x-xss-protection': '1; mode=block; report=/xss-report?source%5Baction%5D=show&source%5Bapp%5D=Shopify&source%5Bcontroller%5D=admin%2Forders&source%5Bsection%5D=admin_api&source%5Buuid%5D=f8795202-94d8-499c-acc6-b65380905279', 'x-envoy-upstream-service-time': '62', 'X-Dc': 'gcp-europe-west2,gcp-europe-west3', 'X-Request-ID': 'f8795202-94d8-499c-acc6-b65380905279', 'CF-Cache-Status': 'DYNAMIC', 'Report-To': '{"endpoints":[{"url":"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=I6%2BKPrbYrz8yrZMw4SWTV5tPtpUtmiS3IaUBVa3aSzWfz1qvZfRAKAf%2B3LUQA94o9NIOHpCk3vkKVvjLTzouMavJy5UW%2BBVx6cw0BFhD8svj8qPS4pLNsTf%2BUZkZn4GD98iUkwAE9Hs%3D"}],"group":"cf-nel","max_age":604800}', 'NEL': '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}', 'Server-Timing': 'processing;dur=59', 'Server': 'cloudflare', 'CF-RAY': '852424f268821b95-DUB', 'alt-svc': 'h3=":443"; ma=86400'}, msg="Bad Request")
any solution please or ideas on how to solve this problem.
Hi Liam unfortunatelly the code doesnt work, when i run the code, it says
Failed to update inventory: 404
hi Liam,
this is what i get from the code you shared , any ideas please
{'errors': [{'message': 'Variable $input of type InventoryAdjustQuantityInput! was provided invalid value for inventoryItemId (Field is not defined on InventoryAdjustQuantityInput), locationId (Field is not defined on InventoryAdjustQuantityInput), inventoryLevelId (Expected value to not be null)', 'locations': [{'line': 2, 'column': 34}], 'extensions': {'value': {'inventoryItemId': '37106128519331', 'locationId': '77564969224', 'availableDelta': 1}, 'problems': [{'path': ['inventoryItemId'], 'explanation': 'Field is not defined on InventoryAdjustQuantityInput'}, {'path': ['locationId'], 'explanation': 'Field is not defined on InventoryAdjustQuantityInput'}, {'path': ['inventoryLevelId'], 'explanation': 'Expected value to not be null'}]}}]}
hi everyone any updates please