Shopify / shopify_python_api

ShopifyAPI library allows Python developers to programmatically access the admin section of stores
http://shopify.github.io/shopify_python_api
MIT License
1.23k stars 350 forks source link

Incorrect URL generated when creating fulfillment records - API version 2023-01 #634

Closed iCodeTechnologies closed 4 months ago

iCodeTechnologies commented 1 year ago

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 orderid parameter.

Steps to reproduce the problem

  1. Create Shopify session with api_version set to '2023-01'
  2. 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

iCodeTechnologies commented 1 year ago

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.

class Order(ShopifyResource, mixins.Metafields, mixins.Events):
    _prefix_source = "/customers/$customer_id/"

    @classmethod
    def _prefix(cls, options={}):
        customer_id = options.get("customer_id")
        if customer_id:
            return "%s/customers/%s" % (cls.site, customer_id)
        else:
            return cls.site

would this approach work for the Fulfillment resource?

MohamedAliHamza commented 1 year ago

Hi @iCodeTechnologies, I have the same issue too, but the generated URL from lib is /admin/api/<API_VERSION>/orders/<order_id>/fulfillments.json NOT /admin/api/<API_VERSION>/orders//fulfillments.json so that we're getting a ResourceNotFound error

I was using Shopify version 2022-04 and it works fine but after upgrading to the last version which is 2023-01 and the lib to 12.2.0 I'm getting that error.

Based on this release this endpoint has been removed and we're supposed to consume the alternative one.

xavierpeich commented 1 year ago

I'm having trouble updating my fulfillment python script now that we are supposed to migrate to the new API. I was using shopify.Fulfillment() before. My understanding is that we should now use something like shopify.FulfillmentOrder() instead, but this method is not available yet in this library? Not sure what to do.

hubertlepicki commented 1 year ago

So I have just stumbled upon this, and I found a temporary fix, by monkey-patching the prefix on the Fulfillment resource:

shopify.Fulfillment._prefix_source = ''

Added this on top of my script after import shopify and 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. ;)

bjquinn commented 1 year ago
shopify.Fulfillment._prefix_source = ''

@hubertlepicki Thank you, this worked for me. The patch proposed by @iCodeTechnologies also works for me, but until it gets merged, I like this workaround better since I only have to modify my own code.

acar commented 1 year ago

@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//fulfillments.json' endpoint. I suppose you could wipe it at the beginning of your function and add it back again at the end.

def example:
   shopify.Fulfillment._prefix_source = ''
   ...
   shopify.Fulfillment._prefix_source = "/orders/$order_id/"
pama1999 commented 1 year ago

thank you all for this hack. I wonder how its possible that such bug lasts for a month. Feeling they dont give enough attention to python api :(

Ekct00 commented 1 year ago

coolllllllllll,it works!!!!!!!!!

So I have just stumbled upon this, and I found a temporary fix, by monkey-patching the prefix on the Fulfillment resource:

shopify.Fulfillment._prefix_source = ''

Added this on top of my script after import shopify and 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. ;)

gmonacho commented 1 year ago

Could the issue be related to the deprecation of the old order's fulfillment resource, which entered a breaking state in 2022-07? I'm not entirely certain if my comment is particularly useful, but I noticed that no one has mentioned this possibility yet. Please refer to the Shopify Changelogs for more information.

github-actions[bot] commented 1 year ago

This issue is stale because it has been open for 60 days with no activity. It will be closed if no further action occurs in 14 days.

hubertlepicki commented 1 year ago

Lol

faqvoi commented 1 year ago

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 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

jvillegasd commented 1 year ago

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 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_CALLLIMIT': '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

same here!

github-actions[bot] commented 12 months ago

This issue is stale because it has been open for 60 days with no activity. It will be closed if no further action occurs in 14 days.

bjquinn commented 12 months ago

No, we should really not close this issue until it's resolved. There's even a draft pull request that hasn't been finished/merged.

Brueni92 commented 11 months ago

No, we should really not close this issue until it's resolved. There's even a draft pull request that hasn't been finished/merged.

Link for reference: https://github.com/Shopify/shopify_python_api/pull/652

github-actions[bot] commented 9 months ago

This issue is stale because it has been open for 60 days with no activity. It will be closed if no further action occurs in 14 days.

pama1999 commented 9 months ago

is this ever going to be fixed?

github-actions[bot] commented 7 months ago

This issue is stale because it has been open for 60 days with no activity. It will be closed if no further action occurs in 14 days.

bjquinn commented 7 months ago

No, we should really not close this issue until it's resolved. There's even a draft pull request that hasn't been finished/merged.

Link for reference: #652

+1

github-actions[bot] commented 5 months ago

This issue is stale because it has been open for 60 days with no activity. It will be closed if no further action occurs in 14 days.

github-actions[bot] commented 4 months ago

We are closing this issue because it has been inactive for a few months. This probably means that it is not reproducible or it has been fixed in a newer version. If it’s an enhancement and hasn’t been taken on since it was submitted, then it seems other issues have taken priority.

If you still encounter this issue with the latest stable version, please reopen using the issue template. You can also contribute directly by submitting a pull request– see the CONTRIBUTING.md file for guidelines

Thank you!