shopify_python_api: Incorrect URL generated when creating fulfillment records - API version 2023-01
Issue summary
When calling shopify.Fulfillment.create the resulting URL is incorrect preventing the successful creation of the Fulfillment record.
Expected behavior
The URL generated by the Shopify client library should be
/admin/api/<API_VERSION>/fulfillments.json
Actual behavior
The URL generated by the Shopify client library is
/admin/api/<API_VERSION>/orders//fulfillments.json
NOTE: The call to shopify.Fulfillment.create does not accept an order_id parameter.
Steps to reproduce the problem
- Create Shopify session with api_version set to ‘2023-01’
- Call shopify.Fulfillment.create with a valid fulfillment payload
Reduced test case
import logging
import shopify
logging.basicConfig(level=logging.DEBUG)
shop_uri = 'xxx.myshopify.com'
api_version = '2023-01'
access_token = 'shppa_xxx'
session = shopify.Session(shop_uri, api_version, access_token)
shopify.ShopifyResource.activate_session(session)
payload = dict(
line_items_by_fulfillment_order=[dict(fulfillment_order_id="5894084460693", fulfillment_order_line_items=[
dict(id="12190922277013", quantity="1")])])
shopify.Fulfillment.create(payload)
The best way to get your bug fixed is to provide a reduced test case.
Checklist
- I have described this issue in a way that is actionable (if possible)
About this issue
- Original URL
- State: open
- Created a year ago
- Reactions: 2
- Comments: 20
So I have just stumbled upon this, and I found a temporary fix, by monkey-patching the prefix on the Fulfillment resource:
Added this on top of my script after
import shopifyand creating fulfillments for fullfillment_orders work.Having said that, I know nothing about Python and very little about Shopify api so please do correct me if that’s some herecy. I used to be Ruby developer. Be kind to me please. 😉
Looking in the code I can see that the Order resource implements a function called _prefix that appears to allow for the URL to contain a path element “/customers” if a customer_id is included in the options, or omit the “/customers” path element if no customer_id is present.
would this approach work for the Fulfillment resource?
hello i’m trying the solution you say but it doesn’t work.
I am putting ("shopify.Fulfillment._prefix_source = “”) after calling the shopify library
and here I call the FulfillmentOrders function
fulfillment = shopify.FulfillmentOrders({ ‘order_id’: info.id, ‘line_items’: info.line_items, ‘location_id’: “63910969405” }) fulfillment.save()
when I run it I get this error
Traceback (most recent call last): File “/usr/local/lib/python3.11/site-packages/pyactiveresource/connection.py”, line 286, in _open http_response = self._handle_error(self._urlopen(request)) ^^^^^^^^^^^^^^^^^^^^^^ File “/usr/local/lib/python3.11/site-packages/pyactiveresource/connection.py”, line 316, in _urlopen return urllib.request.urlopen(request, timeout=self.timeout) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File “/usr/local/lib/python3.11/urllib/request.py”, line 216, in urlopen return opener.open(url, data, timeout) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File “/usr/local/lib/python3.11/urllib/request.py”, line 525, in open response = meth(req, response) ^^^^^^^^^^^^^^^^^^^ File “/usr/local/lib/python3.11/urllib/request.py”, line 634, in http_response response = self.parent.error( ^^^^^^^^^^^^^^^^^^ File “/usr/local/lib/python3.11/urllib/request.py”, line 563, in error return self._call_chain(*args) ^^^^^^^^^^^^^^^^^^^^^^^ File “/usr/local/lib/python3.11/urllib/request.py”, line 496, in _call_chain result = func(*args) ^^^^^^^^^^^ File “/usr/local/lib/python3.11/urllib/request.py”, line 643, in http_error_default raise HTTPError(req.full_url, code, msg, hdrs, fp) urllib.error.HTTPError: HTTP Error 406: Not Acceptable
During handling of the above exception, another exception occurred:
Traceback (most recent call last): File “/workspace/script_new.py”, line 385, in <module> fulfillment.save() File “/usr/local/lib/python3.11/site-packages/pyactiveresource/activeresource.py”, line 836, in save response = self.klass.connection.post( ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File “/usr/local/lib/python3.11/site-packages/pyactiveresource/connection.py”, line 375, in post return self._open(‘POST’, path, headers=headers, data=data) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File “/usr/local/lib/python3.11/site-packages/shopify/base.py”, line 23, in _open self.response = super(ShopifyConnection, self)._open(args, *kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File “/usr/local/lib/python3.11/site-packages/pyactiveresource/connection.py”, line 288, in _open http_response = self._handle_error(err) ^^^^^^^^^^^^^^^^^^^^^^^ File “/usr/local/lib/python3.11/site-packages/pyactiveresource/connection.py”, line 427, in _handle_error raise ClientError(err) pyactiveresource.connection.ClientError: Response(code=406, body=“b’'”, headers={‘Date’: ‘Mon, 10 Jul 2023 09:24:49 GMT’, ‘Content-Type’: ‘application/json’, ‘Transfer-Encoding’: ‘chunked’, ‘Connection’: ‘close’, ‘X-Sorting-Hat-PodId’: ‘330’, ‘X-Sorting-Hat-ShopId’: ‘58167164989’, ‘Referrer-Policy’: ‘origin-when-cross-origin’, ‘X-Frame-Options’: ‘DENY’, ‘X-ShopId’: ‘58167164989’, ‘X-ShardId’: ‘330’, ‘X-Stats-UserId’: ‘’, ‘X-Stats-ApiClientId’: ‘35414474753’, ‘X-Stats-ApiPermissionId’: ‘481241432395’, ‘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-01’, ‘HTTP_X_SHOPIFY_SHOP_API_CALL_LIMIT’: ‘1/40’, ‘X-Shopify-Shop-Api-Call-Limit’: ‘1/40’, ‘Strict-Transport-Security’: ‘max-age=7889238’, ‘Server-Timing’: ‘processing;dur=39’, ‘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=error_404&source%5Bapp%5D=Shopify&source%5Bcontroller%5D=admin%2Ferrors&source%5Bsection%5D=admin_api&source%5Buuid%5D=043944de-41d2-41b0-98a4-e11a3a74230c”, ‘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=error_404&source%5Bapp%5D=Shopify&source%5Bcontroller%5D=admin%2Ferrors&source%5Bsection%5D=admin_api&source%5Buuid%5D=043944de-41d2-41b0-98a4-e11a3a74230c’, ‘X-Dc’: ‘gcp-europe-west3,gcp-europe-west3,gcp-europe-west3’, ‘X-Request-ID’: ‘043944de-41d2-41b0-98a4-e11a3a74230c’, ‘CF-Cache-Status’: ‘DYNAMIC’, ‘Set-Cookie’: ‘__cf_bm=IMXO3a5Jpv4z5ALoqtP1iEsJukdKlyRx448h2M8QXTw-1688981089-0-AZRQLsCQ7e7hkS3SZy3KxLTupFGbRO+RD01cnmcIzkJw4W5diUra0bo0h2mG5I2NIxb4CU8+WTXW2guyPSILKgs=; path=/; expires=Mon, 10-Jul-23 09:54:49 GMT; domain=.myshopify.com; HttpOnly; Secure; SameSite=None’, ‘Report-To’: ‘{“endpoints”:[{“url”:“https:\/\/a.nel.cloudflare.com\/report\/v3?s=Md4DqRc2f1F%2BUNTIyOrxWB41nAhol1E7ipixJtWzs%2Fh6hQ3nocRdkuKUYh5vFJ5hPB0VBeqBKeyrh2DxnnfxCTu9ngsLB%2B24%2B1SMOwWlQETrF%2BlHsunLiTj4NQRTrxtenPkkt8Zi80Ms”}],“group”:“cf-nel”,“max_age”:604800}’, ‘NEL’: ‘{“success_fraction”:0.01,“report_to”:“cf-nel”,“max_age”:604800}’, ‘Server’: ‘cloudflare’, ‘CF-RAY’: ‘7e47c181f8275c1a-FRA’, ‘alt-svc’: ‘h3=“:443”; ma=86400’}, msg=“Not Acceptable”)
what am I doing wrong
Lol
coolllllllllll,it works!!!
@hubertlepicki @bjquinn Great hack for that specific use case, but it breaks the ability to shopify.Fulfillment.find() a fulfillment because THAT request needs to go through the ‘…/order/<order_id>/fulfillments.json’ endpoint. I suppose you could wipe it at the beginning of your function and add it back again at the end.