Have your say in Community Polls: What was/is your greatest motivation to start your own business?
Our Partner & Developer boards on the community are moving to a brand new home: the .dev community forums! While you can still access past discussions here, for all your future app and storefront building questions, head over to the new forums.

inventory update using shopify api using python

inventory update using shopify api using python

simochouchoudan
Tourist
11 0 1

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, 

Replies 5 (5)

Liam
Community Manager
3108 344 889

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

simochouchoudan
Tourist
11 0 1

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.
simochouchoudan
Tourist
11 0 1

Hi Liam unfortunatelly the code doesnt work, when i run the code, it says 

Failed to update inventory: 404 
simochouchoudan
Tourist
11 0 1

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'}]}}]}

simochouchoudan
Tourist
11 0 1

hi everyone any updates please