GithubHelp home page GithubHelp logo

shopify / shopify_python_api Goto Github PK

View Code? Open in Web Editor NEW
1.2K 527.0 344.0 885 KB

ShopifyAPI library allows Python developers to programmatically access the admin section of stores

Home Page: http://shopify.github.io/shopify_python_api

License: MIT License

Python 100.00%

shopify_python_api's Introduction

Shopify API

Build Status PyPI version Supported Python Versions codecov License: MIT pre-commit

The Shopify Admin API Python Library

Usage

Requirements

You should be signed up as a partner on the Shopify Partners Dashboard so that you can create and manage shopify applications.

Installation

To easily install or upgrade to the latest release, use pip.

pip install --upgrade ShopifyAPI

Table of Contents

Getting Started

Public and Custom Apps

  1. First create a new application in the Partners Dashboard, and retrieve your API Key and API Secret Key.

  2. We then need to supply these keys to the Shopify Session Class so that it knows how to authenticate.

    import shopify
    
    shopify.Session.setup(api_key=API_KEY, secret=API_SECRET)
  3. In order to access a shop's data, apps need an access token from that specific shop. We need to authenticate with that shop using OAuth, which we can start in the following way:

    shop_url = "SHOP_NAME.myshopify.com"
    api_version = '2024-07'
    state = binascii.b2a_hex(os.urandom(15)).decode("utf-8")
    redirect_uri = "http://myapp.com/auth/shopify/callback"
    scopes = ['read_products', 'read_orders']
    
    newSession = shopify.Session(shop_url, api_version)
    auth_url = newSession.create_permission_url(scopes, redirect_uri, state)
    # redirect to auth_url
  4. Once the merchant accepts, the shop redirects the owner to the redirect_uri of your application with a parameter named 'code'. This is a temporary token that the app can exchange for a permanent access token. You should compare the state you provided above with the one you received back to ensure the request is correct. Now we can exchange the code for an access_token when you get the request from shopify in your callback handler:

    session = shopify.Session(shop_url, api_version)
    access_token = session.request_token(request_params) # request_token will validate hmac and timing attacks
    # you should save the access token now for future use.
  5. Now you're ready to make authorized API requests to your shop!:

    session = shopify.Session(shop_url, api_version, access_token)
    shopify.ShopifyResource.activate_session(session)
    
    shop = shopify.Shop.current() # Get the current shop
    product = shopify.Product.find(179761209) # Get a specific product
    
    # execute a graphQL call
    shopify.GraphQL().execute("{ shop { name id } }")

    Alternatively, you can use temp to initialize a Session and execute a command:

    with shopify.Session.temp(shop_url, api_version, token):
       product = shopify.Product.find()
  6. It is best practice to clear your session when you're done. A temporary session does this automatically:

    shopify.ShopifyResource.clear_session()

Private Apps

Private apps are a bit quicker to use because OAuth is not needed. You can create the private app in the Shopify Merchant Admin. You can use the Private App password as your access_token:

With full session
session = shopify.Session(shop_url, api_version, private_app_password)
shopify.ShopifyResource.activate_session(session)
# ...
shopify.ShopifyResource.clear_session()
With temporary session
with shopify.Session.temp(shop_url, api_version, private_app_password):
    shopify.GraphQL().execute("{ shop { name id } }")

Billing

Note: Your application must be public to test the billing process. To test on a development store use the 'test': True flag

  1. Create charge after session has been activated
    application_charge = shopify.ApplicationCharge.create({
        'name': 'My public app',
        'price': 123,
        'test': True,
        'return_url': 'https://domain.com/approve'
    })
    # Redirect user to application_charge.confirmation_url so they can approve the charge
  2. After approving the charge, the user is redirected to return_url with charge_id parameter (Note: This action is no longer necessary if the charge is created with API version 2021-01 or later)
    charge = shopify.ApplicationCharge.find(charge_id)
    shopify.ApplicationCharge.activate(charge)
  3. Check that activated_charge status is active
    activated_charge = shopify.ApplicationCharge.find(charge_id)
    has_been_billed = activated_charge.status == 'active'

Advanced Usage

It is recommended to have at least a basic grasp on the principles of the pyactiveresource library, which is a port of rails/ActiveResource to Python and upon which this package relies heavily.

Instances of pyactiveresource resources map to RESTful resources in the Shopify API.

pyactiveresource exposes life cycle methods for creating, finding, updating, and deleting resources which are equivalent to the POST, GET, PUT, and DELETE HTTP verbs.

product = shopify.Product()
product.title = "Shopify Logo T-Shirt"
product.id                          # => 292082188312
product.save()                      # => True
shopify.Product.exists(product.id)  # => True
product = shopify.Product.find(292082188312)
# Resource holding our newly created Product object
# Inspect attributes with product.attributes
product.price = 19.99
product.save()                      # => True
product.destroy()
# Delete the resource from the remote server (i.e. Shopify)

Here is another example to retrieve a list of open orders using certain parameters:

new_orders = shopify.Order.find(status="open", limit="50")

Prefix options

Some resources such as Fulfillment are prefixed by a parent resource in the Shopify API (e.g. orders/450789469/fulfillments/255858046). In order to interact with these resources, you must specify the identifier of the parent resource in your request.

shopify.Fulfillment.find(255858046, order_id=450789469)

Console

This package also includes the shopify_api.py script to make it easy to open an interactive console to use the API with a shop.

  1. Obtain a private API key and password to use with your shop (step 2 in "Getting Started")
  2. Save your default credentials: shopify_api.py add yourshopname
  3. Start the console for the connection: shopify_api.py console
  4. To see the full list of commands, type: shopify_api.py help

GraphQL

This library also supports Shopify's new GraphQL API. The authentication process is identical. Once your session is activated, simply construct a new graphql client and use execute to execute the query.

result = shopify.GraphQL().execute('{ shop { name id } }')

You can perform more complex operations using the variables and operation_name parameters of execute.

For example, this GraphQL document uses a fragment to construct two named queries - one for a single order, and one for multiple orders:

    # ./order_queries.graphql

    fragment OrderInfo on Order {
        id
        name
        createdAt
    }

    query GetOneOrder($order_id: ID!){
        node(id: $order_id){
            ...OrderInfo
        }
    }

    query GetManyOrders($order_ids: [ID]!){
        nodes(ids: $order_ids){
           ...OrderInfo
        }
    }

Now you can choose which operation to execute:

# Load the document with both queries
document = Path("./order_queries.graphql").read_text()

# Specify the named operation to execute, and the parameters for the query
result = shopify.GraphQL().execute(
    query=document,
    variables={"order_id": "gid://shopify/Order/12345"},
    operation_name="GetOneOrder",
)

Using Development Version

Building and installing dev version

python setup.py sdist
pip install --upgrade dist/ShopifyAPI-*.tar.gz

Note Use the bin/shopify_api.py script when running from the source tree. It will add the lib directory to start of sys.path, so the installed version won't be used.

Running Tests

pip install setuptools --upgrade
python setup.py test

Relative Cursor Pagination

Cursor based pagination support has been added in 6.0.0.

import shopify

page1 = shopify.Product.find()
if page1.has_next_page():
  page2 = page1.next_page()

# to persist across requests you can use next_page_url and previous_page_url
next_url = page1.next_page_url
page2 = shopify.Product.find(from_=next_url)

Set up pre-commit locally [OPTIONAL]

Pre-commit is set up as a GitHub action that runs on pull requests and pushes to the main branch. If you want to run pre-commit locally, install it and set up the git hook scripts

pip install -r requirements.txt
pre-commit install

Limitations

Currently there is no support for:

  • asynchronous requests
  • persistent connections

Additional Resources

Sample apps built using this library

shopify_python_api's People

Contributors

allanarmstrong avatar andyw8 avatar arkham avatar asiviero avatar ch33sybr3ad avatar dylanahsmith avatar elsom25 avatar flux627 avatar gavinballard avatar ghostapps1 avatar jamiemtdwyer avatar jjsphar avatar jtgrenz avatar juanjoa avatar kevinhughes27 avatar klenotiw avatar lizkenyon avatar maks3w avatar marcvanolmen avatar matteodepalo avatar mkevinosullivan avatar mllemango avatar nabeelahsen avatar ncrazed avatar paulinakhew avatar paulomarg avatar rezaansyed avatar shaynep avatar smubbs avatar tylerball avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

shopify_python_api's Issues

trying to save fulfillment does not go through

First let me say thanks for this python API it is awesome and have been able to develop way faster i'm stuck now trying to update fulfilment. I can create fulfillments no problems. but when I try to save it doesn't go through. I'm I doing this correctly?

f = shopify.Fulfillment.find(154016057,order_id=249708129)
f.tracking_number = 'some tracking number'
f.save()

How do I retrieve Location information?

I see that dir(shopify) doesn't show me Location as an attribute, but I'm wondering if there's another way to use the API to get it (even though perhaps it's not wrapped as nicely as these other resources):

https://docs.shopify.com/api/location

dir(shopify)
['Address', 'ApplicationCharge', 'Article', 'Asset', 'BillingAddress', 'Blog', 'CarrierService', 'Cart', 'Checkout', 'Collect', 'Comment', 'Country', 'CustomCollection', 'Customer', 'CustomerGroup', 'CustomerSavedSearch', 'Event', 'Fulfillment', 'FulfillmentService', 'GiftCard', 'Image', 'LineItem', 'Metafield', 'NoteAttribute', 'Option', 'Order', 'OrderRisk', 'Page', 'PaymentDetails', 'Policy', 'Product', 'ProductSearchEngine', 'Province', 'Receipt', 'RecurringApplicationCharge', 'Redirect', 'Rule', 'ScriptTag', 'Session', 'ShippingAddress', 'ShippingLine', 'Shop', 'ShopifyResource', 'SmartCollection', 'TaxLine', 'Theme', 'Transaction', 'VERSION', 'ValidationException', 'Variant', 'Webhook', 'builtins', 'doc', 'file', 'name', 'package', 'path', 'address', 'application_charge', 'article', 'asset', 'base', 'billing_address', 'blog', 'carrier_service', 'cart', 'checkout', 'collect', 'comment', 'country', 'custom_collection', 'customer', 'customer_group', 'customer_saved_search', 'event', 'fulfillment', 'fulfillment_service', 'gift_card', 'image', 'line_item', 'metafield', 'mixins', 'note_attribute', 'option', 'order', 'order_risk', 'page', 'payment_details', 'policy', 'product', 'product_search_engine', 'province', 'receipt', 'recurring_application_charge', 'redirect', 'resources', 'rule', 'script_tag', 'session', 'shipping_address', 'shipping_line', 'shop', 'smart_collection', 'tax_line', 'theme', 'transaction', 'variant', 'version', 'webhook', 'yamlobjects']

Can I use this module to get location information?

Thanks!

Getting list of created webhooks

Hi there,

I may be missing something really obvious but I seem to be struggling to find documentation on how I can call the list of created webhooks. I know I can create a webhook using...

     product_hook = shopify.Webhook()
    product_hook.topic ='order/payment'
    product_hook.address = settings.SHOPIFY_APP_URL + '/webhooks/order-payments/'
    product_hook.format = 'json'

How do I then check that this has suceeded?

Console Error on Windows: 'Module has no attribute symlink'

After succesfully configuring the yaml file (after applying fix proposed in issue #103), I got the symlink error on Windows.

Looks like this might be a potential fix (add to top of shopify_api.py):

if os.name == "nt":
    def symlink_ms(source, link_name):
        import ctypes
        csl = ctypes.windll.kernel32.CreateSymbolicLinkW
        csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
        csl.restype = ctypes.c_ubyte
        flags = 1 if os.path.isdir(source) else 0
        try:
            if csl(link_name, source.replace('/', '\\'), flags) == 0:
                raise ctypes.WinError()
        except:
            pass
    os.symlink = symlink_ms

Fix Source
Needs to be tested further.

Support for independently creating/modifying product images.

Please correct me if I'm wrong, but at the moment the only way to create/update product images through the Python API is directly through a POST/PUT call to the product endpoint.

As per the Shopify API documentation on product images, we should be able to make POST and PUT calls directly to /admin/products/#{id}/images.json and PUT /admin/products/#{id}/images/#{id}.json. However, this doesn't seem to work currently with the API as the prefix for the Image class isn't properly being built. Thus, something like this...

image = shopify.Image()
image.src = "http://example.com/example.png"
image.product_id = 123456789
image.save()

... tries to POST to /admin/products//images.json which returns a 406.

Looking at the source, I think a fix would be to implement the prefix() class method, similar to what's been done with the Variant class. Just wanted to run this by folks who might be more familiar with this to see if that might be a valid solution.

Image src regex returns wrong url

In shopify/resources.py, line 121

When using Image.small, the returned url is missing a forward slash in the protocol part of the url. For example for Image.small, the url:
http://cdn.shopify.com/a/long/path/theimage.jpg
becomes:
http:/cdn.shopify.com/a/long/path/theimage.jpg

The issue can be fixed by putting the forward slash inside the first regex group:

# Wrong
re.sub(r"/(.*)\.(\w{2,4})", r"\1_%s.\2" % (name), self.src)

becomes:

# Ok
re.sub(r"(/.*)\.(\w{2,4})", r"\1_%s.\2" % (name), self.src)

or the forward slash can be removed entirely:

# Ok
re.sub(r"(.*)\.(\w{2,4})", r"\1_%s.\2" % (name), self.src)

A whole other option would be to use Python's os.path.splitext like this (tested):

import os
# Down further
return ('_%s' % name).join(os.path.splitext(self.src))

Someone with more knowledge about regex and Python in general should probably weigh in on this.

CarrierService API cannot set .format

cs = shopify.CarrierService()
cs.format = 'json'
...
cs.save()

JSON sent includes no "format" key.
{ "carrier_service": {"active": "true", "callback_url": "...", "name": "...", "service_discovery": true}}

instead using
cs.attributes["format"] = "json"
...
cs.save()

actually sends the format tag.

I tried
cs.setattr('format', 'json') too, but of course that didn't work, so I'm assuming the error is somewhere in 2.0.0's activeresource.py:918-926

I'm still filing this here because that's where others would look for it.

Correct usage for creating new fulfillments?

Is this the correct way to fulfil an order?

shopify.Fulfillment.create(dict(order_id=order.id, notify_customer=True))

I don't get any errors with this, but neither does it fulfil the order!

Any help appreciated, thanks.

The library is not thread-safe

ActiveResource doesn't seem to be suitable for multi-threaded applications because state is stored on the class that multiple threads may want to modify and use at the same time.

Perhaps this could be solved by storing the connection in a thread-local variable. In this way, a new connection could lazily be created for each thread when the ShopifyResource.connection property is accessed, and no locking would be needed in order to concurrently make requests from different threads.

Cannot create order with transactions

order.transactions = [{"amount":20.0,"kind":"capture","status":"success", "source_name" : "other"}]

This doesn't work.

If I do order.transaction = [{"amount":20.0,"kind":"capture","status":"success", "source_name" : "other"}] intercept the request and add an s to transaction it works.

I think this is because the order model has a method named transactions

Looking for Step 7 in README.txt

Hey great job with this library! I have an issue to file:

On line 63 of your README file, you mention "That's it you're done, skip to step 7 and start using the API!"

Is there a numbering mistake? I can't find Step 7.

Thanks,
Tosin

Save many metafields with a single request

There does not seem to be a way to attach metafields locally, and then simply save the product with the metafields along with it. I'm forced to use add_metafields() or Metafield().save(), both of which adds a single metafield per API call. This is very slow.

If I take a product with 50 metafields, and save it as a new product, it saves almost instantly, like so:

p = shopify.Product.find()[0]
print len(p.metafields())
> 50
p.id = None
p.images = None
p.save()

How can I attach metafields to a product in the same way they are attached when they are fetched?

Cannot fetch Shop information via API

Hi,

for my application to generate the urls for abanadoned orders I need the order token (which I get with the order) and the shop id. In my understanding there is one way to get the shop id: retrieve it via shopify.Shop.find(). But this does not work - it gives me a nice 404er instead:

>>> shopify.Shop.find()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File ".../lib/python2.7/site-packages/pyactiveresource/activeresource.py", line 342, in find
    return cls._find_every(from_=from_, **kwargs)
  File ".../lib/python2.7/site-packages/pyactiveresource/activeresource.py", line 478, in _find_every
    return cls._build_list(cls.connection.get(path, cls.headers),
  File ".../lib/python2.7/site-packages/pyactiveresource/connection.py", line 278, in get
    return self.format.decode(self._open('GET', path, headers=headers).body)
  File ".../lib/python2.7/site-packages/shopify/base.py", line 18, in _open
    self.response = super(ShopifyConnection, self)._open(*args, **kwargs)
  File ".../lib/python2.7/site-packages/pyactiveresource/connection.py", line 258, in _open
    response = Response.from_httpresponse(self._handle_error(err))
  File ".../lib/python2.7/site-packages/pyactiveresource/connection.py", line 357, in _handle_error
    raise ResourceNotFound(err)
ResourceNotFound: HTTP Error 404: Not Found

Broken release on PyPi

Your release today is broken.

  File "Initialize.py", line 9, in <module>
    import lib.store as store
  File "/task/lib/store.py", line 17, in <module>
    import shopify
  File "/task/__pips__/shopify/__init__.py", line 3, in <module>
    from shopify.resources import *

shopify.Order.find() of newly created test store raise Exception

Here is a dump of a ShopifyOrder object that would result in Exception - https://gist.github.com/4473151

The value in 'tracking_url' will try to create a corresponding shopify.resources.TrackingUrl object but the value is not applicable to the constructor.

Env:

  • Python==2.7
  • ShopifyAPI==1.0.5
  • pyactiveresource==1.0.2

Repo:

  1. Create a new test store
  2. Invoke Shopify API console by shopify_api.py console
  3. Execute shopify.Order.find()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/mrkschan/env/sandbox/local/lib/python2.7/site-packages/pyactiveresource/activeresource.py", line 351, in find
    return cls._find_every(from_=from_, **kwargs)
  File "/home/mrkschan/env/sandbox/local/lib/python2.7/site-packages/pyactiveresource/activeresource.py", line 490, in _find_every
    prefix_options)
  File "/home/mrkschan/env/sandbox/local/lib/python2.7/site-packages/pyactiveresource/activeresource.py", line 521, in _build_list
    resources.append(cls(element, prefix_options))
  File "/home/mrkschan/env/sandbox/local/lib/python2.7/site-packages/shopify/base.py", line 134, in __init__
    return super(ShopifyResource, self).__init__(attributes, prefix_options)
  File "/home/mrkschan/env/sandbox/local/lib/python2.7/site-packages/pyactiveresource/activeresource.py", line 328, in __init__
    self._update(attributes)
  File "/home/mrkschan/env/sandbox/local/lib/python2.7/site-packages/pyactiveresource/activeresource.py", line 904, in _update
    attr = [klass(child) for child in value]
  File "/home/mrkschan/env/sandbox/local/lib/python2.7/site-packages/shopify/base.py", line 134, in __init__
    return super(ShopifyResource, self).__init__(attributes, prefix_options)
  File "/home/mrkschan/env/sandbox/local/lib/python2.7/site-packages/pyactiveresource/activeresource.py", line 328, in __init__
    self._update(attributes)
  File "/home/mrkschan/env/sandbox/local/lib/python2.7/site-packages/pyactiveresource/activeresource.py", line 904, in _update
    attr = [klass(child) for child in value]
  File "/home/mrkschan/env/sandbox/local/lib/python2.7/site-packages/shopify/base.py", line 133, in __init__
    prefix_options, attributes = self.__class__._split_options(attributes)
  File "/home/mrkschan/env/sandbox/local/lib/python2.7/site-packages/pyactiveresource/activeresource.py", line 431, in _split_options
    for key, value in options.iteritems():
AttributeError: 'str' object has no attribute 'iteritems'

Accessing Product Image Metafields

While it's a bit clunky, the Shopify API allows reading image metafields - but because the URL syntax is different, the Metafields mixin doesn't work for the Image class in the Python API.

I wanted to read image metafields in a similar way to regular metafields using Python recently (ie, image.metafields()) and came up with a quick solution. Happy to work up a pull request if this is something that's interesting (and won't be changed soon on the Shopify end).

Also, haven't looked in to editing/deleting these metafields but I imagine it would be pretty much along the same lines.

issues with release 2.0.1 order.py is not importing Transaction

Just switched from 1.0.7 to 2.0.1 and trying to run my python code, and my unit tests are failing on this:

Installed the plugin from the github source code and using shopify/pyactiveresource 2.0

python setup.py install

I think this issue is in more locations (not just order.py) where dependent models are not imported.

File "build/bdist.macosx-10.6-intel/egg/shopify/resources/order.py", line 16, in transactions
return Transaction.find(order_id=self.id)
NameError: global name 'Transaction' is not defined

Can't pickle <class 'shopify.resources.Announcement'>: attribute lookup shopify.resources.Announcement failed

Sometimes when I try to login a user on the django using the memcahed engine for storing sessions I get the following error:

PicklingError at /shopify/preferences

Can't pickle <class 'shopify.resources.Announcement'>: attribute lookup shopify.resources.Announcement failed

This happens after executing the built-in function "login" that comes with the django auth module. When using the memcached engine this will try to pickle the session and save it on memcached:

from django.contrib.auth import login
#(...)

login(request, user)

The reason this line raises an exception is that the shopify library is adding something to the request session that can't be pickled:

Can't pickle <class 'shopify.resources.Announcement'>: attribute lookup shopify.resources.Announcement failed

Could you look into it? A weird thing is this just happens for a few users, not every time. So I'm not sure what goes on behind the shopify session when the error occurs.

Thanks

Shopify and gevent/eventlet

When, say, importing the orders of many users, it makes a lot of sense to use a gevent/eventlet style concurrency model, as the process is purely I/O bound. However, that means that the operations for many concurrent users are executed on one single instance of the shopify module, resulting in a mess that's hard to keep in check (I think this is likely the same issue as the one about the lib not being thread safe from ~2yrs back). I'm assuming that the "temp session" feature does not really do anything other than temporarily changing that one instance, so it won't be of help.

Is there any way to separate the individual sessions? Ideally starting a shopify session should return a shopify instance on which I can operate on that specific user's data.
Appreciate your input.

Best,
Tobias

XML pyactiveresource parse error

Calling shopify.Country.find() for a client returned an XML string containing the following:

<carrier-shipping-rate-providers type="array">
  <carrier-shipping-rate-provider>
    <service-filter>
      <USPS-Express-Mail-International>-</USPS-Express-Mail-International>
      <USPS-First-Class-Mail-International-Parcel>-</USPS-First-Class-Mail-International-Parcel>
      <USPS-Global-Express-Guaranteed-(GXG)>-</USPS-Global-Express-Guaranteed-(GXG)>
      <USPS-Priority-Mail-International>+</USPS-Priority-Mail-International>
      <*>-</*>
    </service-filter>
  </carrier-shipping-rate-provider>
</carrier-shipping-rate-providers>

The attribute name of the third service "USPS-Global-Express-Guaranteed-(GXG)" contains brackets, triggering parse errors in pyactiveresource. The fifth attribute with the name "*", is also invalid.

API error "Could not acquire lock on fulfillment" handled incorrectly

When saving a fulfillment, every now and then a fulfillment.save() call will result in:

Response(code=422, body="{"errors":"Could not acquire lock on fulfillment."}", headers=...)

This then goes through from_json error handling to from_hash, but from_hash is called with the string "Could not acquire lock on fulfillment".

This seems very much parallel to
Shopify/shopify-api-ruby#103

and should probably be fixed. Right now it will raise a KeyError and the user will be completely in the dark as to what went wrong.

Best,
Tobias

Session.request_token throws Exception which is too broad

Exception is the base class for all in-built and user-defined exceptions so doing:

try:
   token = session.request_token(params)
except Exception:
   # Do something.

Will catch any kind of exception which in this case includes all the urllib2 exceptions.

Consider making custom exceptions and throwing those. APIv1 had custom exceptions so this is a regression.

Affected lines:

Unable to do shopify.Webhook.post

I need to POST to /admin/webhooks.json. I use

shopify.Webhook.post('',json.dumps(json_data)) which uses _class_post which uses this url format -

'%(prefix)s/%(plural)s/%(method_name)s.%(format)s%(query)s' (_custom_method_collection_url)

However for POST request needed is '%(prefix)s/%(plural)s.%(format)s%(query)s' (_collection_path)

GET Metafields for a Product

Is there a way to GET the metafields for a particular product if I have the product ID? Couldn't find it in the docs.

Problem running tests on Linux

I have tried to run the test suite in a Linux environment, however I came across something in one of the base_test.py tests. I suspect it has something to do with the mock module. I have installed it following the instructions in the readme:

    git clone [email protected]:asiviero/shopify_python_api.git
    python setup.py sdist
    pip install --upgrade dist/ShopifyAPI-*.tar.gz

Then I ran $ python setup.py test and the output was:

running test
Searching for six>=1.7
Best match: six 1.9.0

Using /usr/local/lib/python2.7/dist-packages
running egg_info
writing requirements to ShopifyAPI.egg-info/requires.txt
writing ShopifyAPI.egg-info/PKG-INFO
writing top-level names to ShopifyAPI.egg-info/top_level.txt
writing dependency_links to ShopifyAPI.egg-info/dependency_links.txt
writing pbr to ShopifyAPI.egg-info/pbr.json
reading manifest file 'ShopifyAPI.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
writing manifest file 'ShopifyAPI.egg-info/SOURCES.txt'
running build_ext
Traceback (most recent call last):
  File "setup.py", line 44, in <module>
    'Topic :: Software Development :: Libraries :: Python Modules']
  File "/usr/lib/python2.7/distutils/core.py", line 151, in setup
    dist.run_commands()
  File "/usr/lib/python2.7/distutils/dist.py", line 953, in run_commands
    self.run_command(cmd)
  File "/usr/lib/python2.7/distutils/dist.py", line 972, in run_command
    cmd_obj.run()
  File "/usr/lib/python2.7/dist-packages/setuptools/command/test.py", line 135, in run
    self.with_project_on_sys_path(self.run_tests)
  File "/usr/lib/python2.7/dist-packages/setuptools/command/test.py", line 116, in with_project_on_sys_path
    func()
  File "/usr/lib/python2.7/dist-packages/setuptools/command/test.py", line 160, in run_tests
    testLoader = cks
  File "/usr/lib/python2.7/unittest/main.py", line 94, in __init__
    self.parseArgs(argv)
  File "/usr/lib/python2.7/unittest/main.py", line 149, in parseArgs
    self.createTests()
  File "/usr/lib/python2.7/unittest/main.py", line 158, in createTests
    self.module)
  File "/usr/lib/python2.7/unittest/loader.py", line 130, in loadTestsFromNames
    suites = [self.loadTestsFromName(name, module) for name in names]
  File "/usr/lib/python2.7/unittest/loader.py", line 103, in loadTestsFromName
    return self.loadTestsFromModule(obj)
  File "/usr/lib/python2.7/dist-packages/setuptools/command/test.py", line 36, in loadTestsFromModule
    tests.append(self.loadTestsFromName(submodule))
  File "/usr/lib/python2.7/unittest/loader.py", line 100, in loadTestsFromName
    parent, obj = obj, getattr(obj, part)
AttributeError: 'module' object has no attribute 'base_test'

After some trial and error, I have removed from mock import patch and test_delete_should_send_custom_headers_with_request, and all tests passed:

----------------------------------------------------------------------
Ran 98 tests in 0.114s

OK

Am I missing something? Maybe a requirement missing or something like that?

Cannot duplicate product with multiple options if first option has only one variant.

This is a strange one. If a product has multiple options and the first option only has a single variant, that product cannot be duplicated to a new product via the Python API. If the first option has multiple variants, no problem. If the options are reordered so the single-variant option is second, no problem.

Here's what my test product looks like:
screen shot 2015-08-04 at 6 47 39 pm

It has a single Size variant and two Color variants, in that order. Here's how to reproduce the issue in shopify_api.py console:

>>> test_product = shopify.Product.find(1744898049)
>>> test_product.variants
[variant(5308899457), variant(5309143745)]
>>> test_product.variants[0].attributes
{u'weight': 0.0, u'requires_shipping': True, u'updated_at': u'2015-08-04T21:45:50-04:00', u'inventory_quantity': 1, u'compare_at_price': None, u'id': 5308899457, u'sku': u'', u'title': u'Big / Red', u'inventory_policy': u'deny', u'inventory_management': None, u'fulfillment_service': u'manual', u'option2': u'Red', u'option3': None, u'weight_unit': u'lb', u'option1': u'Big', u'price': u'80.00', u'barcode': u'', u'image_id': None, u'taxable': True, u'old_inventory_quantity': 1, u'grams': 0, u'created_at': u'2015-08-04T21:42:00-04:00', u'position': 1}
>>> test_product.variants[1].attributes
{u'weight': 0.0, u'requires_shipping': True, u'updated_at': u'2015-08-04T21:46:04-04:00', u'inventory_quantity': 1, u'compare_at_price': None, u'id': 5309143745, u'sku': u'', u'title': u'Big / Blue', u'inventory_policy': u'deny', u'inventory_management': None, u'fulfillment_service': u'manual', u'option2': u'Blue', u'option3': None, u'weight_unit': u'lb', u'option1': u'Big', u'price': u'80.00', u'barcode': u'', u'image_id': None, u'taxable': True, u'old_inventory_quantity': 1, u'grams': 0, u'created_at': u'2015-08-04T21:46:04-04:00', u'position': 1}
>>> new_product = shopify.Product()
>>> new_product.title = 'New Product'
>>> new_product.variants = test_product.variants
>>> new_product.save()
False

Saving variants returns ResourceNotFound error

If I make a Variant like:

v = shopify.Variant()
v.product_id = 12345
v.option1 = 'Option 1'
v.whatever = something
...
v.save()

I get the following error:

ResourceNotFound: Not Found: https://my-shop.myshopify.com/admin//variants.xml

To create a variant it should be posting to:

/admin/products/#{id}/variants.xml

So it seems like the prefixing in the Variant object isn't getting the product id. I don't know squat about pyactiveresource but it seems like it should be something like:

def _prefix(self, options={}):
   product_id = self.attributes.get("product_id")
   return "/admin/" if product_id is None else "/admin/products/%s" % (product_id)

But that doesn't work right with pyactiveresource...

Cannot create new Product options or copy existing ones.

So I seem to be getting this issue where I cannot create new product options or copying existing ones. Both give me the error <pyactiveresource.activeresource.Errors object at 0x107173110>

On python 2.7.6, shopify 2.1.3, shopifyAPI 2.1.5, pyactiveresource 2.1.2

How to reproduce the issue in console by copying existing product:
Here are the options: Image of options

>>> products = shopify.Product.find()
>>> new_product = shopify.Product()
>>> new_product.title = 'test'
>>> new_product.body_html = 'asdfasdf'
>>> new_product.product_type = 'T-shirt'
>>> new_product.vendor = 'vendor'
>>> new_product.options = products[0].options
>>> new_product.save()
False

And to reproduce by creating new options:

>>> new_product = shopify.Product()
>>> new_product.title = 'test'
>>> new_product.body_html = 'asdfasdf'
>>> new_product.product_type = 'T-shirt'
>>> new_product.vendor = 'vendor'
>>> new_product.options = [{"name": "Style"}, {"name": "Size"}]
>>> new_product.save()
False

Signature validation should support validating with an old secret

Problem

Oauth2 signature validation will fail when during credential rotation, since the signature is generated with the oldest secret, and validation can only be configured to validate against a single secret in shopify_python_api.

Solution

This needs to be handled similar to webhook validation, where it must be possible to specify the old API secret as well as the new one for signature validation, and accept the signature if it matches the ones generated with either secret.

Exception raised when using Product.price_range()

In shopify/resources.py, line 90

Exception is raised when a product has a price range:

TypeError: float argument required, not str

The issue can be fixed by changing %f to %s inside the return string:

# Raises exception
return "%f - %f" % (f % min_price, f % max_price)

becomes:

# Ok
return "%s - %s" % (f % min_price, f % max_price)

Cannot create customer with password

The following code will fail with an error {u'password_confirmation': [u"doesn't match Password"]}:

import shopify

c = shopify.Customer()
c.email = '[email protected]'
c.password = 'foo'
c.password_confirmation = 'foo'
c.save()

c.__dict__ is:

{'errors': <pyactiveresource.activeresource.Errors object at 0x431df10>, '_initialized': True, '_prefix_options': {}, 'klass': <class 'shopify.resources.customer.Customer'>, 'attributes': {'password_confirmation': 'foo', 'email': u'[email protected]'}, 'password': 'foo'}

As you can see the password attribute is not in the attributes. A workaround is to set the password in the __init__:

c = shopify.Customer(attributes={'password': 'foo', 'password_confirmation': 'foo'})

RecurringApplicationCharge.current() does not work as expected

In RecurringApplicationCharge class there is class method "current" that doesn't work as expexted.

Problem

I have few RecurringApplicationCharge instances for my test shop but none of them has status=active. I was expecting to get None as a result when using current() method but instead a RecurringApplicationCharge with status rejected was returned.

class RecurringApplicationCharge(ShopifyResource):

    @classmethod
    def current(cls):
        return cls.find_first(status="active")

Excpected result

First RecurringApplicationCharge object with status=active should be returned or None if no matches found.

Anyway I noticed that documentation (http://docs.shopify.com/api/recurringapplicationcharge) states that it is only possible to filter with field "since_id". If this is correct, then this method should be removed.

Impossible to install with pip -r requirements.txt unless pyactiveresource is already installed

I'm trying to distribute this module to a django app running on heroku, which uses requirements.txt to manage required python libraries. Because setup.py imports shopify to get the version, it is impossible for pip to install this module unless pyactiveresource is already installed.

This might be true for all the dependencies. Here is the pip output:

Downloading/unpacking pyactiveresource==1.0.1 (from -r requirements.txt (line 23))
  Running setup.py egg_info for package pyactiveresource

Downloading/unpacking ShopifyAPI==0.3.1 (from -r requirements.txt (line 24))
  Running setup.py egg_info for package ShopifyAPI
    Traceback (most recent call last):
      File "<string>", line 14, in <module>
      File "/var/lib/jenkins/jobs/makie.me_main_ci/workspace/venv/build/ShopifyAPI/setup.py", line 4, in <module>
        VERSION=__import__('shopify').VERSION
      File "shopify/__init__.py", line 3, in <module>
        from shopify.resources import *
      File "shopify/resources.py", line 1, in <module>
        from shopify.base import ShopifyResource
      File "shopify/base.py", line 1, in <module>
        import pyactiveresource.connection
    ImportError: No module named pyactiveresource.connection
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):

  File "<string>", line 14, in <module>

  File "/var/lib/jenkins/jobs/makie.me_main_ci/workspace/venv/build/ShopifyAPI/setup.py", line 4, in <module>

    VERSION=__import__('shopify').VERSION

  File "shopify/__init__.py", line 3, in <module>

    from shopify.resources import *

  File "shopify/resources.py", line 1, in <module>

    from shopify.base import ShopifyResource

  File "shopify/base.py", line 1, in <module>

    import pyactiveresource.connection

ImportError: No module named pyactiveresource.connection

Exception on API calls which return empty body

RecurringApplicationCharge.activate() makes an API call which returns a 200 response with an empty body. Since the switch to JSON format as a default, this causes an error as no JSON object is detected.

>>> charge = shopify.RecurringApplicationCharge({'id':charge_id})
>>> charge.activate()
Traceback (most recent call last):
  File "<ipython-input-52-3b644541f2ad>", line 1, in <module>
    charge.activate()
  File "/home/daniel/Projects/blogfeeder/local/lib/python2.7/site-packages/shopify/resources/recurring_application_charge.py", line 14, in activate
    self._load_attributes_from_response(self.post("activate"))
  File "/home/daniel/Projects/blogfeeder/local/lib/python2.7/site-packages/shopify/base.py", line 136, in _load_attributes_from_response
    self._update(self.__class__.format.decode(response.body))
  File "/home/daniel/Projects/blogfeeder/local/lib/python2.7/site-packages/pyactiveresource/formats.py", line 54, in decode
    raise Error(err)
Error: Expecting value: line 1 column 2 (char 1)

Monkeypatching RecurringApplicationCharge.format to XMLFormat allows the call to proceed, but this affects all API calls for all resource types, so is probably undesirable.

I'm not certain if this should be dealt with at the level of ShopifyAPI or pyactiveresource, but raising it here for now.

Bug: Unable to retrieve all the records from shopify !!!

Hello Experts,

I had try to get all the orders, buy doing

shopify.Product.find(status='any')

But it least returns 50 orders, other older can't be retrieves.

Also please be note that even I try since_id parameter.

It failed to perform it.

Please resolve it or give me hits so I can fix it my way.

best of luck.

Thanks,
Tejas Tank.
Snippetbucket.com

Cannot save assets in 2.0.3

It doesn't seem possible to .save() an existing asset in 2.0.3 (without making any changes). It still works in 2.0.1. Example below, same store, same template, same access token (oauth2).

template = shopify.Asset.find('layout/theme.liquid')
print shopify.version.VERSION
print template
print template.save()
2.0.1
asset(layout/theme.liquid)
True
2.0.3
asset(layout/theme.liquid)
BadRequest: Response(code=400, body="{"errors":{"theme":"Required parameter missing"}}", ...)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.