├── .gitignore
├── LICENSE.txt
├── README.md
├── example.py
├── requirements.txt
├── setup.cfg
├── setup.py
└── shopify
├── __init__.py
├── base.py
├── fulfillments
├── __init__.py
├── api_wrapper.py
└── objects
│ ├── __init__.py
│ └── fulfillment.py
├── orders
├── __init__.py
├── api_wrapper.py
├── objects
│ ├── __init__.py
│ ├── address.py
│ ├── client_detail.py
│ ├── customer.py
│ ├── discount_code.py
│ ├── line_item.py
│ ├── note_attribute.py
│ ├── order.py
│ ├── payment_detail.py
│ ├── refund.py
│ ├── shipping_line.py
│ ├── tax_line.py
│ └── transaction.py
└── tests
│ ├── __init__.py
│ ├── address.json
│ ├── client_detail.json
│ ├── customer.json
│ ├── line_item.json
│ ├── location.json
│ ├── order.json
│ ├── payment_detail.json
│ ├── response.json
│ ├── shipping_line.json
│ ├── tax_line.json
│ ├── test_address.py
│ ├── test_all.py
│ ├── test_clientDetail.py
│ ├── test_customer.py
│ ├── test_lineItem.py
│ ├── test_order.py
│ ├── test_paymentDetail.py
│ ├── test_shippingLine.py
│ └── test_taxLine.py
└── products
├── __init__.py
├── api_wrapper.py
├── objects
├── __init__.py
├── image.py
├── option.py
├── product.py
└── variant.py
└── tests
├── __init__.py
├── image.json
├── option.json
├── product.json
├── response.json
├── test_all.py
├── test_image.py
├── test_option.py
├── test_product.py
├── test_variant.py
└── variant.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Python template
3 | # Byte-compiled / optimized / DLL files
4 | __pycache__/
5 | *.py[cod]
6 | *$py.class
7 |
8 | # C extensions
9 | *.so
10 |
11 | # Distribution / packaging
12 | .Python
13 | env/
14 | build/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .coverage
43 | .coverage.*
44 | .cache
45 | nosetests.xml
46 | coverage.xml
47 | *,cover
48 | .hypothesis/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 |
58 | # Flask instance folder
59 | instance/
60 |
61 | # Scrapy stuff:
62 | .scrapy
63 |
64 | # Sphinx documentation
65 | docs/_build/
66 |
67 | # PyBuilder
68 | target/
69 |
70 | # IPython Notebook
71 | .ipynb_checkpoints
72 |
73 | # pyenv
74 | .python-version
75 |
76 | # celery beat schedule file
77 | celerybeat-schedule
78 |
79 | # dotenv
80 | .env
81 |
82 | # virtualenv
83 | venv/
84 | ENV/
85 |
86 | # Spyder project settings
87 | .spyderproject
88 |
89 | # Rope project settings
90 | .ropeproject
91 | ### JetBrains template
92 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
93 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
94 |
95 | # User-specific stuff:
96 | .idea/workspace.xml
97 | .idea/tasks.xml
98 | .idea/dictionaries
99 | .idea/vcs.xml
100 | .idea/jsLibraryMappings.xml
101 |
102 | # Sensitive or high-churn files:
103 | .idea/dataSources.ids
104 | .idea/dataSources.xml
105 | .idea/dataSources.local.xml
106 | .idea/sqlDataSources.xml
107 | .idea/dynamic.xml
108 | .idea/uiDesigner.xml
109 |
110 | # Gradle:
111 | .idea/gradle.xml
112 | .idea/libraries
113 |
114 | # Mongo Explorer plugin:
115 | .idea/mongoSettings.xml
116 |
117 | ## File-based project format:
118 | *.iws
119 |
120 | ## Plugin-specific files:
121 |
122 | # IntelliJ
123 | /out/
124 |
125 | # mpeltonen/sbt-idea plugin
126 | .idea_modules/
127 |
128 | # JIRA plugin
129 | atlassian-ide-plugin.xml
130 |
131 | # Crashlytics plugin (for Android Studio and IntelliJ)
132 | com_crashlytics_export_strings.xml
133 | crashlytics.properties
134 | crashlytics-build.properties
135 | fabric.properties
136 |
137 | .idea
138 |
139 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Description
2 |
3 | Contains wrappers and parsers for Shopify's Products API.
4 |
5 | # Usage
6 |
7 | ```python
8 | from shopify.products import ProductsApiWrapper, Product, Image
9 |
10 | api_key = ''
11 | password = ''
12 | store_name = ''
13 |
14 | paw = ProductsApiWrapper(api_key, password, store_name)
15 | for product in paw.list():
16 | print product.title
17 |
18 | product = Product(title='My test product')
19 | image = Image()
20 | image.src = 'https://www.imageurl.com/myimage.jpg'
21 | product.image = image
22 | product.add_tags('testing', 'product')
23 |
24 | product = paw.create(product)
25 |
26 | product.title = 'My New Title For My Test Product'
27 |
28 | paw.update(product)
29 | ```
30 |
31 | # Installation
32 |
33 | `pip install git+https://github.com/ziplokk1/python-shopify-api.git`
--------------------------------------------------------------------------------
/example.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | """
3 | This example shows how to create new products via API
4 | reading from a list of dictionaries containing products and
5 | variants.
6 | __author__ = Bruno Rocha - brunorocha.org
7 | """
8 |
9 | # REQUIREMENTS
10 | # pip install slugify
11 | # pip install python-shopify
12 |
13 | import os
14 | from slugify import slugify
15 | from shopify.products import (
16 | ProductsApiWrapper, Product, Image, Variant, Option
17 | )
18 |
19 |
20 | # FILL THE DATA below
21 | api_key = ''
22 | password = ''
23 | store_name = ''
24 |
25 | paw = ProductsApiWrapper(api_key, password, store_name)
26 |
27 | # Get a list of existing products, limited to 250 :(
28 | existing = [item.title for item in paw.list(limit=250)]
29 |
30 |
31 | def create_product(items):
32 | """Items is a list of dictionaries representing each product variant
33 | of the same product with the same ID and other data
34 | keys: ['description', 'price', 'name', 'link', 'size', 'stock']
35 | items = [
36 | # first variant holds full data and is default
37 | {'name': 'Awesome t-shirt',
38 | 'code': '123456',
39 | 'description': '',
40 | 'size': 'P',
41 | 'price': '22.5',
42 | 'stock': 2},
43 | # Other variants
44 | {'size': 'M',
45 | 'price': '25.5',
46 | 'stock': 2},
47 | {'size': 'G',
48 | 'price': '29.5',
49 | 'stock': 0},
50 | ]
51 | """
52 |
53 | # The first item should be the complete item holding all the fields
54 | # other items can have only the variants
55 | data = items[0]
56 |
57 | # Use data['name'].decode('utf-8') if you are reading csv files
58 | name = data['name']
59 |
60 |
61 | if name in existing or paw.list(title=name):
62 | # skip existing
63 | print "Already registered, skipping..."
64 | return
65 |
66 | product = Product(
67 | title=data['name'],
68 | body_html=data['description'],
69 | )
70 |
71 | # There should be a 123456.jpg file in the same folder
72 | # alternatively you can use a URL provided in data
73 | image_filename = "{0}.jpg".format(data['code'])
74 | if os.path.exists(image_filename):
75 | image = Image()
76 | image.attach(image_filename)
77 | product.add_image(image)
78 | elif data.get('image_url'):
79 | product.add_image(Image(src=data['image_url']))
80 |
81 | # using the first word in title as tag
82 | # Product "T-shirt Blue 09" got tag "t-shirt"
83 | tag = data['name'].split()[0]
84 | tag = u''.join(i for i in tag if not i.isdigit())
85 |
86 | product.add_tags(tag.strip().lower())
87 |
88 | # You can add only 3 options
89 | # at positions 1, 2 and 3
90 | # you should add options before adding its variants
91 | product.add_option(
92 | Option(
93 | name="Size",
94 | position=1,
95 | )
96 | )
97 |
98 | for item in items:
99 | product.add_variant(
100 | Variant(
101 | option1=item['size'],
102 | # option2=data['size'],
103 | # option3=data['size'],
104 | title="Size",
105 | price=item['price'],
106 | # SKU should be different for each variant
107 | sku=data["code"] + slugify(item['size']),
108 | position=1,
109 | inventory_policy="continue",
110 | fulfillment_service="manual",
111 | inventory_management="shopify",
112 | inventory_quantity=int(item['stock']),
113 | taxable=False,
114 | weight=300,
115 | weight_unit="g", # g, kg
116 | requires_shipping=True
117 | )
118 | )
119 |
120 | try:
121 | product = paw.create(product)
122 | except Exception as e:
123 | # do a proper logging here please!!!
124 | print e
125 | print product
126 | print items
127 |
128 | return product
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | python-dateutil==2.5.3
2 | requests==2.10.0
3 | six==1.10.0
4 | wheel==0.24.0
5 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.md
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | version = '0.1.3'
4 |
5 | REQUIREMENTS = [
6 | 'python-dateutil',
7 | 'requests'
8 | ]
9 |
10 | setup(
11 | name='python-shopify',
12 | version=version,
13 | packages=find_packages(),
14 | url='https://github.com/ziplokk1/python-shopify-api',
15 | license='LICENSE.txt',
16 | author='Mark Sanders',
17 | author_email='sdscdeveloper@gmail.com',
18 | install_requires=REQUIREMENTS,
19 | description='Wrapper and parser modules for Shopify\'s API.',
20 | include_package_data=True
21 | )
22 |
--------------------------------------------------------------------------------
/shopify/__init__.py:
--------------------------------------------------------------------------------
1 | from .orders import OrdersApiWrapper
2 | from .products import ProductsApiWrapper
3 |
--------------------------------------------------------------------------------
/shopify/base.py:
--------------------------------------------------------------------------------
1 | import json
2 | import datetime
3 | import urlparse
4 |
5 | import requests
6 | from dateutil import parser as date_parser
7 |
8 |
9 | def datetime_to_string(dt):
10 | """
11 | Convert a datetime object to the preferred format for the shopify api. (2016-01-01T11:00:00-5:00)
12 |
13 | :param dt: Datetime object to convert to timestamp.
14 | :return: Timestamp string for the datetime object.
15 | """
16 | if not dt:
17 | return None
18 | if not isinstance(dt, datetime.datetime):
19 | raise ValueError('Must supply an instance of `datetime`.')
20 | # Calculate the utc offset of the current timezone
21 | # 1 is added to the total seconds to account for the time which it takes the operation to calculate
22 | # utcnow and local now.
23 | offset = int(divmod((datetime.datetime.utcnow() - datetime.datetime.now()).total_seconds() + 1, 60)[0] / 60)
24 | offset_str = '-%d:00' % offset
25 | dt_str = dt.strftime('%Y-%m-%dT%H:%M:%S')
26 | return dt_str + offset_str
27 |
28 |
29 | def string_to_datetime(dt_str):
30 | """
31 | Convert a formatted timestamp to a datetime object.
32 |
33 | :param dt_str: Timestamp string (2016-01-01T11:00:00-5:00)
34 | :return: datetime object from timestamp.
35 | """
36 | if dt_str:
37 | return date_parser.parse(dt_str)
38 | return None
39 |
40 |
41 | def string_to_float(float_str):
42 | """
43 | Convert a float in string format to a float.
44 |
45 | :param float_str: Float in string format ("25.01")
46 | :return: float from string
47 | """
48 | if float_str:
49 | return float(float_str)
50 | return None
51 |
52 |
53 | class BaseParser(object):
54 |
55 | def __init__(self, d=None, **kwargs):
56 | self._dict = d or kwargs or {} # Prevent exceptions when getting data from d when d is None
57 |
58 | @property
59 | def id(self):
60 | return self._dict.get('id')
61 |
62 | @property
63 | def __dict__(self):
64 | return self._dict
65 |
66 | def __str__(self):
67 | return str(self._dict)
68 |
69 | def __nonzero__(self):
70 | return bool(self._dict)
71 |
72 |
73 | class ShopifyApiError(Exception):
74 | """
75 | Used to check if a response is an error and parse out the error.
76 | """
77 |
78 | def __init__(self, response):
79 | self.response = response
80 | content = self.response.content
81 | if content:
82 | self.content = json.loads(self.response.content)
83 | else:
84 | self.content = {}
85 | Exception.__init__(self, self.message)
86 |
87 | def has_error(self):
88 | return bool(self.content.get('errors'))
89 |
90 | @property
91 | def message(self):
92 | errors = self.content.get('errors', {})
93 | msg = ''
94 | if isinstance(errors, dict):
95 | for category, message in errors.items():
96 | # Error Category, Message
97 | msg += '({} - {})'.format(category, message)
98 | else:
99 | msg += errors
100 | return msg
101 |
102 |
103 | class ShopifyApiWrapper(object):
104 |
105 | def __init__(self, api_key, password, store_name):
106 | self.api_key = api_key
107 | self.password = password
108 | self.store_name = store_name
109 |
110 | @staticmethod
111 | def remove_empty(d):
112 | """
113 | Return a new dictionary object from the old one without any items with falsey values.
114 | :param d:
115 | :return:
116 | """
117 | return {k: v for k, v in d.items() if v}
118 |
119 | def url_host(self):
120 | """
121 | Format the URL host with the api_key, password, and store name for access to shopify's api.
122 | :return: Formatted URL (https://api_key:password@store_name.myshopify.com)
123 | """
124 | return 'https://{}:{}@{}.myshopify.com'.format(self.api_key, self.password, self.store_name)
125 |
--------------------------------------------------------------------------------
/shopify/fulfillments/__init__.py:
--------------------------------------------------------------------------------
1 | from .objects import *
2 | from api_wrapper import FulfillmentsApiWrapper
3 |
--------------------------------------------------------------------------------
/shopify/fulfillments/api_wrapper.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import json
3 |
4 | from ..base import ShopifyApiWrapper, datetime_to_string, ShopifyApiError
5 |
6 |
7 | class FulfillmentsApiWrapper(ShopifyApiWrapper):
8 |
9 | max_results_limit = 250
10 |
11 | list_endpoint = '/admin/orders/{}/fulfillments.json'
12 |
13 | def list(self, id_, limit=50, page=1, since_id=None, created_at_min=None, created_at_max=None, updated_at_min=None, updated_at_max=None, fields=()):
14 | """
15 | Return a list of fulfillments.
16 |
17 | :param id_: The ID element's value from an order.
18 | :param limit: Amount of results. (default 50).
19 | :param page: Page to show. (default 1).
20 | :param since_id: Restrict results to after the specified ID.
21 | :param created_at_min: Show fulfillments created after date.
22 | :param created_at_max: Show fulfillments created before date.
23 | :param updated_at_min: Show fulfillments last updated after date.
24 | :param updated_at_max: Show fulfillments last updated before date.
25 | :param fields: List of fields to include in the response.
26 | :return:
27 | """
28 | if limit > self.max_results_limit:
29 | raise ValueError('`limit` cannot exceed {}'.format(self.max_results_limit))
30 | params = dict(
31 | limit=limit,
32 | page=page,
33 | since_id=since_id,
34 | created_at_min=datetime_to_string(created_at_min),
35 | created_at_max=datetime_to_string(created_at_max),
36 | updated_at_min=datetime_to_string(updated_at_min),
37 | updated_at_max=datetime_to_string(updated_at_max),
38 | fields=','.join(fields)
39 | )
40 | params = self.remove_empty(params)
41 |
42 | endpoint = self.list_endpoint.format(id_)
43 | url = self.url_host() + endpoint
44 | response = requests.get(url, params=params)
45 |
46 | err = ShopifyApiError(response)
47 | if err.has_error():
48 | raise err
49 |
50 | with open('fulfillments-list.json', 'wb') as f:
51 | f.write(response.content)
52 |
53 | response.raise_for_status()
54 |
55 | data = json.loads(response.content)
56 |
57 | return data
58 |
--------------------------------------------------------------------------------
/shopify/fulfillments/objects/__init__.py:
--------------------------------------------------------------------------------
1 | from .fulfillment import Fulfillment
2 |
--------------------------------------------------------------------------------
/shopify/fulfillments/objects/fulfillment.py:
--------------------------------------------------------------------------------
1 | from ...base import BaseParser, string_to_datetime
2 | from ...orders.objects.line_item import LineItem
3 |
4 |
5 | class Receipt(BaseParser):
6 | """
7 | Text field that provides information about the receipt.
8 | """
9 |
10 | @property
11 | def testcase(self):
12 | """
13 | States whether or not the fulfillment was a testcase.
14 |
15 | Valid values are True or False.
16 | :return:
17 | """
18 | return self._dict.get('testcase')
19 |
20 | @property
21 | def authorization(self):
22 | """
23 | The authorization code.
24 | :return:
25 | """
26 | return self._dict.get('authorization')
27 |
28 |
29 | class Fulfillment(BaseParser):
30 |
31 | @property
32 | def order_id(self):
33 | """
34 | The unique numeric identifier for the order.
35 | :return:
36 | """
37 | return self._dict.get('order_id')
38 |
39 | @property
40 | def status(self):
41 | """
42 | The status of the fulfillment.
43 |
44 | Valid values are:
45 | - pending (The fulfillment is pending).
46 | - open (The fulfillment has been acknowledged by the service and is in processing).
47 | - success (The fulfillment was successful).
48 | - cancelled (The fulfillment was cancelled).
49 | - error (There was an error with the fulfillment request).
50 | - failure (The fulfillment request failed).
51 | :return:
52 | """
53 | return self._dict.get('status')
54 |
55 | @property
56 | def created_at(self):
57 | """
58 | The date and time when the fulfillment was created.
59 | :return:
60 | """
61 | return string_to_datetime(self._dict.get('created_at'))
62 |
63 | @property
64 | def service(self):
65 | # ToDo: Find documentation
66 | return self._dict.get('service')
67 |
68 | @property
69 | def updated_at(self):
70 | """
71 | The date and time when the fulfillment was last modified.
72 | :return:
73 | """
74 | return string_to_datetime(self._dict.get('updated_at'))
75 |
76 | @property
77 | def tracking_company(self):
78 | """
79 | The name of the shipping company.
80 | :return:
81 | """
82 | return self._dict.get('tracking_company')
83 |
84 | @property
85 | def shipment_status(self):
86 | # ToDo: Find documentation
87 | return self._dict.get('shipment_status')
88 |
89 | @property
90 | def tracking_number(self):
91 | # ToDo: Find documentation
92 | return self._dict.get('tracking_number')
93 |
94 | @property
95 | def tracking_numbers(self):
96 | """
97 | A list of shipping numbers, provided by the shipping company.
98 | :return:
99 | """
100 | return self._dict.get('tracking_numbers', [])
101 |
102 | @property
103 | def tracking_url(self):
104 | # ToDo: Find documentation
105 | return self._dict.get('tracking_url')
106 |
107 | @property
108 | def tracking_urls(self):
109 | """
110 | The URLs to track the fulfillment.
111 | :return:
112 | """
113 | return self._dict.get('tracking_urls', [])
114 |
115 | @property
116 | def receipt(self):
117 | """
118 | Text field that provides information about the receipt.
119 | :return:
120 | """
121 | return Receipt(self._dict.get('receipt'))
122 |
123 | @property
124 | def line_items(self):
125 | """
126 | A historical record of each item in the fulfillment.
127 | :return:
128 | """
129 | return [LineItem(x) for x in self._dict.get('line_items', [])]
130 |
--------------------------------------------------------------------------------
/shopify/orders/__init__.py:
--------------------------------------------------------------------------------
1 | from .objects import *
2 | from .api_wrapper import OrdersApiWrapper
3 |
--------------------------------------------------------------------------------
/shopify/orders/api_wrapper.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import json
3 |
4 | from ..base import ShopifyApiWrapper, datetime_to_string, ShopifyApiError
5 | from .objects import Order
6 |
7 |
8 | class OrdersApiWrapper(ShopifyApiWrapper):
9 |
10 | max_results_limit = 250
11 |
12 | default_endpoint = '/admin/orders.json'
13 | operational_endpoint = '/admin/orders/{}.json'
14 | close_endpoint = '/admin/orders/{}/close.json'
15 | open_endpoint = '/admin/orders/{}/open.json'
16 | cancel_endpoint = '/admin/orders/{}/cancel.json'
17 |
18 | valid_status_values = [
19 | 'open',
20 | 'closed',
21 | 'cancelled',
22 | 'any'
23 | ]
24 |
25 | valid_financial_status_values = [
26 | 'authorized',
27 | 'pending',
28 | 'paid',
29 | 'partially_paid',
30 | 'refunded',
31 | 'voided',
32 | 'partially_refunded',
33 | 'any',
34 | 'unpaid'
35 | ]
36 |
37 | valid_fulfillment_status_values = [
38 | 'shipped',
39 | 'partial',
40 | 'unshipped',
41 | 'any'
42 | ]
43 |
44 | def list(self, ids=(), limit=50, page=1, since_id=None, created_at_min=None, created_at_max=None,
45 | updated_at_min=None, updated_at_max=None, processed_at_min=None, processed_at_max=None, status='open',
46 | financial_status='any', fulfillment_status='any', fields=()):
47 | if limit > self.max_results_limit:
48 | raise ValueError('`limit` cannot exceed {}'.format(self.max_results_limit))
49 | if status not in self.valid_status_values:
50 | raise ValueError('`status` must be one of {}'.format(self.valid_status_values))
51 | if financial_status not in self.valid_financial_status_values:
52 | raise ValueError('`financial_status` must be one of {}'.format(self.valid_financial_status_values))
53 | if fulfillment_status not in self.valid_fulfillment_status_values:
54 | raise ValueError('`fulfillment_status` must be one of {}'.format(self.valid_fulfillment_status_values))
55 | params = dict(
56 | ids=','.join(ids),
57 | limit=limit,
58 | page=page,
59 | since_id=since_id,
60 | created_at_min=datetime_to_string(created_at_min),
61 | created_at_max=datetime_to_string(created_at_max),
62 | updated_at_min=datetime_to_string(updated_at_min),
63 | updated_at_max=datetime_to_string(updated_at_max),
64 | processed_at_min=datetime_to_string(processed_at_min),
65 | processed_at_max=datetime_to_string(processed_at_max),
66 | status=status,
67 | financial_status=financial_status,
68 | fulfillment_status=fulfillment_status,
69 | fields=','.join(fields)
70 | )
71 | params = self.remove_empty(params)
72 | url = self.url_host() + self.default_endpoint
73 | response = requests.get(url, params=params)
74 |
75 | with open('orders-list.json', 'wb') as f:
76 | f.write(response.content)
77 |
78 | # Check if the response was an error and raise if necessary.
79 | err = ShopifyApiError(response)
80 | if err.has_error():
81 | raise err
82 |
83 | data = json.loads(response.content)
84 | return [Order(x) for x in data.get('orders', [])]
85 |
86 | def _close(self, order_id):
87 | endpoint = self.close_endpoint.format(order_id)
88 | url = self.url_host() + endpoint
89 |
90 | r = requests.post(url, data='{}', headers={"Content-Type": 'application/json'})
91 |
92 | with open('order-close.json', 'wb') as f:
93 | f.write(r.content)
94 |
95 | err = ShopifyApiError(r)
96 | if err.has_error():
97 | raise err
98 |
99 | return json.loads(r.content)
100 |
101 | def close(self, order):
102 | if not order.id:
103 | raise ValueError('`order` object must have id.')
104 | return Order(self._close(order.id).get('order'))
105 |
106 | def close_by_id(self, id_):
107 | return Order(self._close(id_).get('order'))
108 |
--------------------------------------------------------------------------------
/shopify/orders/objects/__init__.py:
--------------------------------------------------------------------------------
1 | from .order import Order
2 | from .address import Address
3 | from .client_detail import ClientDetail
4 | from .customer import Customer
5 | from .line_item import LineItem
6 | from .payment_detail import PaymentDetail
7 | from .shipping_line import ShippingLine
8 | from .tax_line import TaxLine
9 |
--------------------------------------------------------------------------------
/shopify/orders/objects/address.py:
--------------------------------------------------------------------------------
1 | from ...base import BaseParser, string_to_float
2 |
3 |
4 | class Location(BaseParser):
5 | """
6 | Parser for basic location information such as city, state, and zip.
7 | """
8 |
9 | @property
10 | def country_code(self):
11 | """
12 | The two-letter code (ISO 3166-1 alpha-2 two-letter country code) for the country of the address.
13 | :return:
14 | """
15 | return self._dict.get('country_code')
16 |
17 | @property
18 | def province_code(self):
19 | """
20 | The two-letter abbreviation of the state or province of the address.
21 | :return:
22 | """
23 | return self._dict.get('province_code')
24 |
25 | @property
26 | def name(self):
27 | """
28 | The full name of the person associated with the address.
29 | :return:
30 | """
31 | return self._dict.get('name')
32 |
33 | @property
34 | def address_1(self):
35 | """
36 | Street address.
37 | :return:
38 | """
39 | return self._dict.get('address1')
40 |
41 | @property
42 | def address_2(self):
43 | """
44 | Optional field for the street address.
45 | :return:
46 | """
47 | return self._dict.get('address2')
48 |
49 | @property
50 | def city(self):
51 | """
52 | The city of the address.
53 | :return:
54 | """
55 | return self._dict.get('city')
56 |
57 | @property
58 | def zip(self):
59 | """
60 | The zip/postal-code of the address.
61 | :return:
62 | """
63 | return self._dict.get('zip')
64 |
65 |
66 | class Address(Location):
67 | """
68 | Parser which contains more descriptive information about a location/address such as name and phone number.
69 | """
70 |
71 | # Address has no id field
72 |
73 | @property
74 | def first_name(self):
75 | """
76 | The first name of the person associated with the address.
77 | :return:
78 | """
79 | return self._dict.get('first_name')
80 |
81 | @property
82 | def phone(self):
83 | """
84 | The phone number of the address.
85 | :return:
86 | """
87 | return self._dict.get('phone')
88 |
89 | @property
90 | def province(self):
91 | """
92 | The name of the state or province of the address.
93 | :return:
94 | """
95 | return self._dict.get('province')
96 |
97 | @property
98 | def country(self):
99 | """
100 | The name of the country of the address.
101 | :return:
102 | """
103 | return self._dict.get('country')
104 |
105 | @property
106 | def last_name(self):
107 | """
108 | The last name of the person associated with the address.
109 | :return:
110 | """
111 | return self._dict.get('last_name')
112 |
113 | @property
114 | def company(self):
115 | """
116 | The company of the person associated with the address.
117 | :return:
118 | """
119 | return self._dict.get('company')
120 |
121 | @property
122 | def longitude(self):
123 | """
124 | The longitude of the address.
125 | :return:
126 | """
127 | return self._dict.get('longitude')
128 |
129 | @property
130 | def latitude(self):
131 | """
132 | The latitude of the address.
133 | :return:
134 | """
135 | return self._dict.get('latitude')
136 |
137 | @property
138 | def default(self):
139 | """
140 | Boolean value whether or not the address is to be used as the default address.
141 | :return:
142 | """
143 | return self._dict.get('default')
144 |
--------------------------------------------------------------------------------
/shopify/orders/objects/client_detail.py:
--------------------------------------------------------------------------------
1 | from ...base import BaseParser
2 |
3 |
4 | class ClientDetail(BaseParser):
5 | """
6 | An object containing information about the client machine such as the browser IP and user agent.
7 | """
8 |
9 | @property
10 | def browser_ip(self):
11 | """
12 | The browser IP address.
13 | :return:
14 | """
15 | return self._dict.get('browser_ip')
16 |
17 | @property
18 | def accept_language(self):
19 | """
20 | Accept-Language header value.
21 | :return:
22 | """
23 | return self._dict.get('accept_language')
24 |
25 | @property
26 | def user_agent(self):
27 | """
28 | User agent string from the User-Agent header.
29 | :return:
30 | """
31 | return self._dict.get('user_agent')
32 |
33 | @property
34 | def session_hash(self):
35 | """
36 | A hash of the session.
37 | :return:
38 | """
39 | return self._dict.get('session_hash')
40 |
41 | @property
42 | def browser_width(self):
43 | """
44 | The browser screen width in pixels, if available.
45 | :return:
46 | """
47 | return self._dict.get('browser_width')
48 |
49 | @property
50 | def browser_height(self):
51 | """
52 | The browser screen height in pixels, if available.
53 | :return:
54 | """
55 | return self._dict.get('browser_height')
56 |
--------------------------------------------------------------------------------
/shopify/orders/objects/customer.py:
--------------------------------------------------------------------------------
1 | from ...base import BaseParser, string_to_datetime, string_to_float
2 | from .address import Address
3 |
4 |
5 | class Customer(BaseParser):
6 | """
7 | Parser which contains descriptive information about the customer such as name and email.
8 | """
9 |
10 | @property
11 | def email(self):
12 | """
13 | The customer's email address.
14 | :return:
15 | """
16 | return self._dict.get('email')
17 |
18 | @property
19 | def accepts_marketing(self):
20 | """
21 | Indicates whether or not the customer would like to receive email updates from the shop.
22 |
23 | Valid values are True and False.
24 | :return:
25 | """
26 | return self._dict.get('accepts_marketing')
27 |
28 | @property
29 | def created_at(self):
30 | """
31 | The date and time when the customer record was created.
32 | :return:
33 | """
34 | return string_to_datetime(self._dict.get('created_at'))
35 |
36 | @property
37 | def updated_at(self):
38 | """
39 | The date and time when the customer record was last updated.
40 | :return:
41 | """
42 | return string_to_datetime(self._dict.get('updated_at'))
43 |
44 | @property
45 | def first_name(self):
46 | """
47 | The customer's first name.
48 | :return:
49 | """
50 | return self._dict.get('first_name')
51 |
52 | @property
53 | def last_name(self):
54 | """
55 | The customer's last name.
56 | :return:
57 | """
58 | return self._dict.get('last_name')
59 |
60 | @property
61 | def orders_count(self):
62 | """
63 | The number of orders placed by this customer to a shop.
64 |
65 | Pulls the current information.
66 | :return:
67 | """
68 | return self._dict.get('orders_count')
69 |
70 | @property
71 | def state(self):
72 | """
73 | No documentation available.
74 |
75 | :return:
76 | """
77 | # ToDo: find documentation
78 | return self._dict.get('state')
79 |
80 | @property
81 | def total_spent(self):
82 | """
83 | No documentation available.
84 | :return:
85 | """
86 | # ToDo: find documentation
87 | return string_to_float(self._dict.get('total_spent'))
88 |
89 | @property
90 | def last_order_id(self):
91 | """
92 | No documentation available.
93 | :return:
94 | """
95 | # ToDo: find documentation
96 | return self._dict.get('last_order_id')
97 |
98 | @property
99 | def note(self):
100 | """
101 | Extra information about the customer.
102 | :return:
103 | """
104 | return self._dict.get('note')
105 |
106 | @property
107 | def verified_email(self):
108 | """
109 | No documentation available.
110 | :return:
111 | """
112 | # ToDo: find documentation
113 | return self._dict.get('verified_email')
114 |
115 | @property
116 | def multipass_identifier(self):
117 | """
118 | No documentation available.
119 | :return:
120 | """
121 | # ToDo: find documentation
122 | return self._dict.get('multipass_identifier')
123 |
124 | @property
125 | def tax_exempt(self):
126 | """
127 | No documentation available.
128 | :return:
129 | """
130 | # ToDo: find documentation
131 | return self._dict.get('tax_exempt')
132 |
133 | @property
134 | def tags(self):
135 | """
136 | Tags are additional short descriptors, commonly used for filtering and searching.
137 | :return:
138 | """
139 | return tuple([x.strip() for x in self._dict.get('tags', '').split(',')])
140 |
141 | @property
142 | def last_order_name(self):
143 | """
144 | No documentation available.
145 | :return:
146 | """
147 | # ToDo: find documentation
148 | return self._dict.get('last_order_name')
149 |
150 | @property
151 | def default_address(self):
152 | """
153 | No documentation available.
154 | :return:
155 | """
156 | # ToDo: find documentation
157 | return Address(self._dict.get('default_address'))
158 |
--------------------------------------------------------------------------------
/shopify/orders/objects/discount_code.py:
--------------------------------------------------------------------------------
1 | from ...base import BaseParser, string_to_float
2 |
3 |
4 | class DiscountCode(BaseParser):
5 | """
6 | Parser for any applicable discounts to an order.
7 | """
8 |
9 | @property
10 | def code(self):
11 | """
12 | The discount code.
13 | :return:
14 | """
15 | return self._dict.get('code')
16 |
17 | @property
18 | def amount(self):
19 | """
20 | The amount of the discount.
21 | :return:
22 | """
23 | return string_to_float(self._dict.get('amount'))
24 |
25 | @property
26 | def type(self):
27 | """
28 | The type of discount.
29 |
30 | Can be one of:
31 | - percentage
32 | - shipping
33 | - fixed_amount (default)
34 | :return:
35 | """
36 | return self._dict.get('type')
37 |
--------------------------------------------------------------------------------
/shopify/orders/objects/line_item.py:
--------------------------------------------------------------------------------
1 | from .address import Location
2 | from .tax_line import TaxLine
3 | from .note_attribute import NoteAttribute
4 | from ...base import BaseParser, string_to_float
5 |
6 |
7 | class LineItem(BaseParser):
8 | """
9 | Parser for an item in an order.
10 | """
11 |
12 | @property
13 | def variant_id(self):
14 | """
15 | The id of the product variant being fulfilled.
16 | :return:
17 | """
18 | return self._dict.get('variant_id')
19 |
20 | @property
21 | def title(self):
22 | """
23 | The title of the product.
24 | :return:
25 | """
26 | return self._dict.get('title')
27 |
28 | @property
29 | def quantity(self):
30 | """
31 | The number of items in the fulfillment.
32 | :return:
33 | """
34 | return self._dict.get('quantity')
35 |
36 | @property
37 | def price(self):
38 | """
39 | The price of the item.
40 | :return:
41 | """
42 | return string_to_float(self._dict.get('price'))
43 |
44 | @property
45 | def grams(self):
46 | """
47 | The weight of the item in grams.
48 | :return:
49 | """
50 | return self._dict.get('grams')
51 |
52 | @property
53 | def sku(self):
54 | """
55 | A unique identifier of the item in the fulfillment.
56 | :return:
57 | """
58 | return self._dict.get('sku')
59 |
60 | @property
61 | def variant_title(self):
62 | """
63 | The title of the product variant being fulfilled.
64 | :return:
65 | """
66 | return self._dict.get('variant_title')
67 |
68 | @property
69 | def vendor(self):
70 | """
71 | The name of the supplier of the item.
72 | :return:
73 | """
74 | return self._dict.get('vendor')
75 |
76 | @property
77 | def fulfillment_service(self):
78 | """
79 | Service provider who is doing the fulfillment.
80 | :return:
81 | """
82 | return self._dict.get('fulfillment_service')
83 |
84 | @property
85 | def product_id(self):
86 | """
87 | The unique numeric identifier for the product in the fulfillment.
88 | :return:
89 | """
90 | return self._dict.get('product_id')
91 |
92 | @property
93 | def requires_shipping(self):
94 | """
95 | Specifies whether or not a customer needs to provide a shipping address when placing an order for this product variant.
96 |
97 | Valid values are: True or False
98 | :return:
99 | """
100 | return self._dict.get('requires_shipping')
101 |
102 | @property
103 | def taxable(self):
104 | # ToDo: Find documentation
105 | return self._dict.get('taxable')
106 |
107 | @property
108 | def gift_card(self):
109 | # ToDo: Find documentation
110 | return self._dict.get('gift_card')
111 |
112 | @property
113 | def name(self):
114 | # ToDo: Find documentation
115 | return self._dict.get('name')
116 |
117 | @property
118 | def variant_inventory_management(self):
119 | """
120 | Returns the name of the inventory management system.
121 | :return:
122 | """
123 | return self._dict.get('variant_inventory_management')
124 |
125 | @property
126 | def properties(self):
127 | """
128 | Returns additional properties associated with the line item.
129 | :return:
130 | """
131 | # NoteAttribute instance is used since it contains the same data as
132 | # the properties element.
133 | return [NoteAttribute(x) for x in self._dict.get('properties', [])]
134 |
135 | @property
136 | def product_exists(self):
137 | """
138 | States whether or not the product exists. Valid values are True or False.
139 | :return:
140 | """
141 | return self._dict.get('product_exists')
142 |
143 | @property
144 | def fulfillable_quantity(self):
145 | """
146 | The amount available to fulfill.
147 |
148 | This is the quantity - max(refunded_quantity, fulfilled_quantity) - pending_fulfilled_quantity - open_fulfilled_quantity.
149 | :return:
150 | """
151 | return self._dict.get('fulfillable_quantity')
152 |
153 | @property
154 | def total_discount(self):
155 | # ToDo: Find documentation
156 | return string_to_float(self._dict.get('total_discount'))
157 |
158 | @property
159 | def fulfillment_status(self):
160 | """
161 | Status of an order in terms of the line_items being fulfilled.
162 |
163 | Values:
164 | - fulfilled
165 | - None
166 | - partial
167 | :return:
168 | """
169 | return self._dict.get('fulfillment_status')
170 |
171 | @property
172 | def tax_lines(self):
173 | # ToDo: Find documentation
174 | return [TaxLine(x) for x in self._dict.get('tax_lines', [])]
175 |
176 | @property
177 | def origin_location(self):
178 | # ToDo: Find documentation
179 | return Location(self._dict.get('origin_location'))
180 |
181 | @property
182 | def destination_location(self):
183 | # ToDo: Find documentation
184 | return Location(self._dict.get('destination_location'))
185 |
--------------------------------------------------------------------------------
/shopify/orders/objects/note_attribute.py:
--------------------------------------------------------------------------------
1 | from ...base import BaseParser
2 |
3 |
4 | class NoteAttribute(BaseParser):
5 |
6 | @property
7 | def name(self):
8 | return self._dict.get('name')
9 |
10 | @property
11 | def value(self):
12 | return self._dict.get('value')
13 |
--------------------------------------------------------------------------------
/shopify/orders/objects/order.py:
--------------------------------------------------------------------------------
1 | from ...base import BaseParser, string_to_datetime, string_to_float
2 | from .address import Address
3 | from .line_item import LineItem
4 | from .tax_line import TaxLine
5 | from .shipping_line import ShippingLine
6 | from .client_detail import ClientDetail
7 | from .payment_detail import PaymentDetail
8 | from .customer import Customer
9 | from .discount_code import DiscountCode
10 | from .note_attribute import NoteAttribute
11 | from ...fulfillments import Fulfillment
12 | from .refund import Refund
13 |
14 |
15 | class Order(BaseParser):
16 |
17 | @property
18 | def email(self):
19 | """
20 | The customer's email address.
21 |
22 | Is required when a billing address is present.
23 | :return:
24 | """
25 | return self._dict.get('email')
26 |
27 | @property
28 | def closed_at(self):
29 | """
30 | The date and time when the order was closed.
31 |
32 | If the order was not closed, this value is None.
33 | :return:
34 | """
35 | return string_to_datetime(self._dict.get('closed_at'))
36 |
37 | @property
38 | def created_at(self):
39 | """
40 | By default, this auto-generated property is the date and time when the order was created in Shopify.
41 |
42 | If you are importing orders to the Shopify platform from another system,
43 | the writable processed_at property will override the created_at date.
44 | :return:
45 | """
46 | return string_to_datetime(self._dict.get('created_at'))
47 |
48 | @property
49 | def updated_at(self):
50 | return string_to_datetime(self._dict.get('updated_at'))
51 |
52 | @property
53 | def number(self):
54 | return self._dict.get('number')
55 |
56 | @property
57 | def note(self):
58 | return self._dict.get('note')
59 |
60 | @property
61 | def token(self):
62 | return self._dict.get('token')
63 |
64 | @property
65 | def gateway(self):
66 | return self._dict.get('gateway')
67 |
68 | @property
69 | def test(self):
70 | return self._dict.get('test')
71 |
72 | @property
73 | def total_price(self):
74 | return string_to_float(self._dict.get('total_price'))
75 |
76 | @property
77 | def subtotal_price(self):
78 | return string_to_float(self._dict.get('subtotal_price'))
79 |
80 | @property
81 | def total_weight(self):
82 | return self._dict.get('total_weight')
83 |
84 | @property
85 | def total_tax(self):
86 | return string_to_float(self._dict.get('total_tax'))
87 |
88 | @property
89 | def taxes_included(self):
90 | return self._dict.get('taxes_included')
91 |
92 | @property
93 | def currency(self):
94 | """
95 | The three letter code (ISO 4217) for the currency used for the payment.
96 | :return:
97 | """
98 | return self._dict.get('currency')
99 |
100 | @property
101 | def financial_status(self):
102 | """
103 | The financial processing status for the order.
104 |
105 | Values can be one of:
106 | - pending
107 | - authorized
108 | - partially_paid
109 | - paid (default)
110 | - partially_refunded
111 | - refunded
112 | - voided
113 | :return:
114 | """
115 | return self._dict.get('financial_status')
116 |
117 | @property
118 | def confirmed(self):
119 | return self._dict.get('confirmed')
120 |
121 | @property
122 | def total_discounts(self):
123 | return string_to_float(self._dict.get('total_discounts'))
124 |
125 | @property
126 | def total_line_items_price(self):
127 | return string_to_float(self._dict.get('total_line_items_price'))
128 |
129 | @property
130 | def cart_token(self):
131 | """
132 | Unique identifier for a particular cart that is attached to a particular order.
133 | :return:
134 | """
135 | return self._dict.get('cart_token')
136 |
137 | @property
138 | def buyer_accepts_marketing(self):
139 | """
140 | Indicates whether or not the person who placed the order would like to receive email updates from the shop.
141 |
142 | This is set when checking the "I want to receive occasional emails about new products,
143 | promotions and other news" checkbox during checkout.
144 | Valid values are True and False.
145 | :return:
146 | """
147 | return self._dict.get('buyer_accepts_marketing')
148 |
149 | @property
150 | def name(self):
151 | return self._dict.get('name')
152 |
153 | @property
154 | def referring_site(self):
155 | return self._dict.get('referring_site')
156 |
157 | @property
158 | def landing_site(self):
159 | return self._dict.get('landing_site')
160 |
161 | @property
162 | def cancelled_at(self):
163 | """
164 | The date and time when the order was cancelled.
165 |
166 | If the order was not cancelled, this value is None.
167 | :return:
168 | """
169 | return string_to_datetime(self._dict.get('cancelled_at'))
170 |
171 | @property
172 | def cancel_reason(self):
173 | """
174 | The reason why the order was cancelled.
175 |
176 | If the order was not cancelled, this value is None.
177 | If the order was cancelled, the value will be one of the following:
178 | - customer: The customer changed or cancelled the order.
179 | - fraud: The order was fraudulent.
180 | - inventory: Items in the order were not in inventory.
181 | - other: The order was cancelled for a reason not in the list above.
182 | :return:
183 | """
184 | return self._dict.get('cancel_reason')
185 |
186 | @property
187 | def total_price_usd(self):
188 | return string_to_float(self._dict.get('total_price_usd'))
189 |
190 | @property
191 | def checkout_token(self):
192 | return self._dict.get('checkout_token')
193 |
194 | @property
195 | def reference(self):
196 | return self._dict.get('reference')
197 |
198 | @property
199 | def user_id(self):
200 | return self._dict.get('user_id')
201 |
202 | @property
203 | def location_id(self):
204 | return self._dict.get('location_id')
205 |
206 | @property
207 | def source_identifier(self):
208 | return self._dict.get('source_identifier')
209 |
210 | @property
211 | def source_url(self):
212 | return self._dict.get('source_url')
213 |
214 | @property
215 | def processed_at(self):
216 | return string_to_datetime(self._dict.get('processed_at'))
217 |
218 | @property
219 | def device_id(self):
220 | return self._dict.get('device_id')
221 |
222 | @property
223 | def browser_ip(self):
224 | """
225 | The IP address of the browser used by the customer when placing the order.
226 | :return:
227 | """
228 | return self._dict.get('browser_ip')
229 |
230 | @property
231 | def landing_site_ref(self):
232 | return self._dict.get('landing_site_ref')
233 |
234 | @property
235 | def order_number(self):
236 | return self._dict.get('order_number')
237 |
238 | @property
239 | def discount_codes(self):
240 | """
241 | Applicable discount codes that can be applied to the order.
242 |
243 | If no codes exist the value will default to an empty list.
244 | :return:
245 | """
246 | return [DiscountCode(x) for x in self._dict.get('discount_codes', [])]
247 |
248 | @property
249 | def note_attributes(self):
250 | return [NoteAttribute(x) for x in self._dict.get('note_attributes', [])]
251 |
252 | @property
253 | def payment_gateway_names(self):
254 | return self._dict.get('payment_gateway_names', [])
255 |
256 | @property
257 | def processing_method(self):
258 | return self._dict.get('processing_method')
259 |
260 | @property
261 | def checkout_id(self):
262 | return self._dict.get('checkout_id')
263 |
264 | @property
265 | def source_name(self):
266 | return self._dict.get('source_name')
267 |
268 | @property
269 | def fulfillment_status(self):
270 | """
271 | Show fulfillment status.
272 |
273 | Values:
274 | - fulfilled (Every line item in the order has been fulfilled)
275 | - null (None of the line items in the order have been fulfilled)
276 | - partial (At least one line item in the order has been fulfilled)
277 | :return:
278 | """
279 | return self._dict.get('fulfillment_status')
280 |
281 | @fulfillment_status.setter
282 | def fulfillment_status(self, val):
283 | self._dict['fulfillment_status'] = val
284 |
285 | @property
286 | def tax_lines(self):
287 | return [TaxLine(x) for x in self._dict.get('tax_lines', [])]
288 |
289 | @property
290 | def tags(self):
291 | """
292 | Tags are additional short descriptors, commonly used for filtering and searching.
293 | :return:
294 | """
295 | return tuple([x.strip() for x in self._dict.get('tags').split(',')])
296 |
297 | @property
298 | def contact_email(self):
299 | return self._dict.get('contact_email')
300 |
301 | @property
302 | def order_status_url(self):
303 | return self._dict.get('order_status_url')
304 |
305 | @property
306 | def line_items(self):
307 | return [LineItem(x) for x in self._dict.get('line_items', [])]
308 |
309 | @property
310 | def shipping_lines(self):
311 | return [ShippingLine(x) for x in self._dict.get('shipping_lines', [])]
312 |
313 | @property
314 | def billing_address(self):
315 | return Address(self._dict.get('billing_address'))
316 |
317 | @property
318 | def shipping_address(self):
319 | """
320 | The mailing address associated with the payment method.
321 |
322 | This address is an optional field that will not be available on orders that do not require one.
323 | :return:
324 | """
325 | return Address(self._dict.get('shipping_address'))
326 |
327 | @property
328 | def fulfillments(self):
329 | """
330 | Return a list of Fulfillment objects parsed from the fulfillments element of the order.
331 | :return:
332 | """
333 | return [Fulfillment(x) for x in self._dict.get('fulfillments', [])]
334 |
335 | @property
336 | def client_details(self):
337 | """
338 | An object containing information about the client.
339 | return:
340 | """
341 | return ClientDetail(self._dict.get('client_details'))
342 |
343 | @property
344 | def refunds(self):
345 | return [Refund(x) for x in self._dict.get('refunds', [])]
346 |
347 | @property
348 | def payment_details(self):
349 | return PaymentDetail(self._dict.get('payment_details'))
350 |
351 | @property
352 | def customer(self):
353 | """
354 | An object containing information about the customer.
355 |
356 | It is important to note that the order may not have a customer and consumers
357 | should not depend on the existence of a customer object.
358 | This value may be falsey if the order was created through Shopify POS.
359 | :return:
360 | """
361 | return Customer(self._dict.get('customer'))
362 |
--------------------------------------------------------------------------------
/shopify/orders/objects/payment_detail.py:
--------------------------------------------------------------------------------
1 | from ...base import BaseParser
2 |
3 |
4 | class PaymentDetail(BaseParser):
5 |
6 | @property
7 | def credit_card_bin(self):
8 | return self._dict.get('credit_card_bin')
9 |
10 | @property
11 | def avs_result_code(self):
12 | return self._dict.get('avs_result_code')
13 |
14 | @property
15 | def cvv_result_code(self):
16 | return self._dict.get('cvv_result_code')
17 |
18 | @property
19 | def credit_card_number(self):
20 | return self._dict.get('credit_card_number')
21 |
22 | @property
23 | def credit_card_company(self):
24 | return self._dict.get('credit_card_company')
25 |
--------------------------------------------------------------------------------
/shopify/orders/objects/refund.py:
--------------------------------------------------------------------------------
1 | from ...base import BaseParser, string_to_datetime
2 | from .transaction import Transaction
3 | from .line_item import LineItem
4 |
5 |
6 | class Refund(BaseParser):
7 |
8 | @property
9 | def order_id(self):
10 | return self._dict.get('order_id')
11 |
12 | @property
13 | def created_at(self):
14 | return string_to_datetime(self._dict.get('created_at'))
15 |
16 | @property
17 | def note(self):
18 | return self._dict.get('note')
19 |
20 | @property
21 | def restock(self):
22 | return self._dict.get('restock')
23 |
24 | @property
25 | def user_id(self):
26 | return self._dict.get('user_id')
27 |
28 | @property
29 | def refund_line_items(self):
30 | return [LineItem(x) for x in self._dict.get('refund_line_items', [])]
31 |
32 | @property
33 | def transactions(self):
34 | return [Transaction(x) for x in self._dict.get('transactions', [])]
35 |
36 | @property
37 | def order_adjustments(self):
38 | # ToDo: Figure out what order_adjustments returns
39 | return [x for x in self._dict.get('order_adjustments', [])]
40 |
--------------------------------------------------------------------------------
/shopify/orders/objects/shipping_line.py:
--------------------------------------------------------------------------------
1 | from ...base import BaseParser, string_to_float
2 | from .tax_line import TaxLine
3 |
4 |
5 | class ShippingLine(BaseParser):
6 |
7 | @property
8 | def title(self):
9 | return self._dict.get('title')
10 |
11 | @property
12 | def price(self):
13 | return string_to_float(self._dict.get('price'))
14 |
15 | @property
16 | def code(self):
17 | return self._dict.get('code')
18 |
19 | @property
20 | def source(self):
21 | return self._dict.get('source')
22 |
23 | @property
24 | def phone(self):
25 | return self._dict.get('phone')
26 |
27 | @property
28 | def requested_fulfillment_service_id(self):
29 | return self._dict.get('requested_fulfillment_service_id')
30 |
31 | @property
32 | def delivery_category(self):
33 | return self._dict.get('delivery_category')
34 |
35 | @property
36 | def carrier_identifier(self):
37 | return self._dict.get('carrier_identifier')
38 |
39 | @property
40 | def tax_lines(self):
41 | return [TaxLine(x) for x in self._dict.get('tax_lines', [])]
42 |
--------------------------------------------------------------------------------
/shopify/orders/objects/tax_line.py:
--------------------------------------------------------------------------------
1 | from ...base import BaseParser, string_to_float
2 |
3 |
4 | class TaxLine(BaseParser):
5 | """
6 | Parser for objects in the tax_lines list object.
7 | """
8 |
9 | @property
10 | def title(self):
11 | return self._dict.get('title')
12 |
13 | @title.setter
14 | def title(self, val):
15 | self._dict['title'] = val
16 |
17 | @property
18 | def price(self):
19 | return string_to_float(self._dict.get('price'))
20 |
21 | @price.setter
22 | def price(self, val):
23 | self._dict['price'] = str(val)
24 |
25 | @property
26 | def rate(self):
27 | return self._dict.get('rate')
28 |
29 | @rate.setter
30 | def rate(self, val):
31 | self._dict['rate'] = float(val)
32 |
--------------------------------------------------------------------------------
/shopify/orders/objects/transaction.py:
--------------------------------------------------------------------------------
1 | from ...base import BaseParser, string_to_datetime, string_to_float
2 |
3 |
4 | class Transaction(BaseParser):
5 |
6 | @property
7 | def order_id(self):
8 | return self._dict.get('order_id')
9 |
10 | @property
11 | def amount(self):
12 | return string_to_float(self._dict.get('amount'))
13 |
14 | @property
15 | def kind(self):
16 | return self._dict.get('kind')
17 |
18 | @property
19 | def gateway(self):
20 | return self._dict.get('gateway')
21 |
22 | @property
23 | def status(self):
24 | return self._dict.get('status')
25 |
26 | @property
27 | def message(self):
28 | return self._dict.get('message')
29 |
30 | @property
31 | def created_at(self):
32 | return string_to_datetime(self._dict.get('created_at'))
33 |
34 | @property
35 | def test(self):
36 | return self._dict.get('test')
37 |
38 | @property
39 | def authorization(self):
40 | return self._dict.get('authorization')
41 |
42 | @property
43 | def currency(self):
44 | return self._dict.get('currency')
45 |
46 | @property
47 | def location_id(self):
48 | return self._dict.get('location_id')
49 |
50 | @property
51 | def user_id(self):
52 | return self._dict.get('user_id')
53 |
54 | @property
55 | def parent_id(self):
56 | return self._dict.get('parent_id')
57 |
58 | @property
59 | def device_id(self):
60 | return self._dict.get('device_id')
61 |
62 | @property
63 | def receipt(self):
64 | # ToDo: Figure out what elements are in receipt
65 | return self._dict.get('receipt')
66 |
67 | @property
68 | def error_code(self):
69 | return self._dict.get('error_code')
70 |
71 | @property
72 | def source_name(self):
73 | return self._dict.get('source_name')
74 |
--------------------------------------------------------------------------------
/shopify/orders/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ziplokk1/python-shopify-api/fe7481e98d2197265ab44f13682d54bbd6679b92/shopify/orders/tests/__init__.py
--------------------------------------------------------------------------------
/shopify/orders/tests/address.json:
--------------------------------------------------------------------------------
1 | {
2 | "first_name": "Test",
3 | "address1": "123 Test Ln",
4 | "phone": null,
5 | "city": "City",
6 | "zip": "12345",
7 | "province": "State",
8 | "country": "United States",
9 | "last_name": "Customer",
10 | "address2": "",
11 | "company": null,
12 | "latitude": 0.0,
13 | "longitude": 0.0,
14 | "name": "Test Customer",
15 | "country_code": "US",
16 | "province_code": "ST"
17 | }
--------------------------------------------------------------------------------
/shopify/orders/tests/client_detail.json:
--------------------------------------------------------------------------------
1 | {
2 | "browser_ip": "192.168.1.1",
3 | "accept_language": "en-US,en;q=0.5",
4 | "user_agent": "Mozilla\/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko\/20100101 Firefox\/47.0",
5 | "session_hash": "1f29043960fc3c472ccda7d7f22c6fbe",
6 | "browser_width": 1855,
7 | "browser_height": 985
8 | }
--------------------------------------------------------------------------------
/shopify/orders/tests/customer.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 3778180163,
3 | "email": "test@email.com",
4 | "accepts_marketing": false,
5 | "created_at": "2016-07-26T09:52:25-05:00",
6 | "updated_at": "2016-07-26T09:52:57-05:00",
7 | "first_name": "Test",
8 | "last_name": "Customer",
9 | "orders_count": 0,
10 | "state": "disabled",
11 | "total_spent": "0.00",
12 | "last_order_id": null,
13 | "note": null,
14 | "verified_email": true,
15 | "multipass_identifier": null,
16 | "tax_exempt": false,
17 | "tags": "",
18 | "last_order_name": null,
19 | "default_address": {}
20 | }
--------------------------------------------------------------------------------
/shopify/orders/tests/line_item.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 6207715523,
3 | "variant_id": 23950183043,
4 | "title": "Nature Made, Vitamin D3 2,000 I.U. Liquid Softgels, 250-Count",
5 | "quantity": 3,
6 | "price": "15.99",
7 | "grams": 454,
8 | "sku": "test-sku",
9 | "variant_title": "",
10 | "vendor": "Nature Made",
11 | "fulfillment_service": "manual",
12 | "product_id": 7583017923,
13 | "requires_shipping": true,
14 | "taxable": true,
15 | "gift_card": false,
16 | "name": "Nature Made, Vitamin D3 2,000 I.U. Liquid Softgels, 250-Count",
17 | "variant_inventory_management": "shopify",
18 | "properties": [],
19 | "product_exists": true,
20 | "fulfillable_quantity": 3,
21 | "total_discount": "0.00",
22 | "fulfillment_status": null,
23 | "tax_lines": [],
24 | "origin_location": {},
25 | "destination_location": {}
26 | }
--------------------------------------------------------------------------------
/shopify/orders/tests/location.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 1573630275,
3 | "country_code": "US",
4 | "province_code": "ST",
5 | "name": "Test Store",
6 | "address1": "456 Test Dr",
7 | "address2": "",
8 | "city": "City",
9 | "zip": "67890"
10 | }
--------------------------------------------------------------------------------
/shopify/orders/tests/order.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 3389059459,
3 | "email": "test@email.com",
4 | "closed_at": null,
5 | "created_at": "2016-07-26T09:52:57-05:00",
6 | "updated_at": "2016-07-26T09:52:57-05:00",
7 | "number": 1,
8 | "note": null,
9 | "token": "31a03d57375c347a7e1baf64a6147841",
10 | "gateway": "bogus",
11 | "test": true,
12 | "total_price": "60.03",
13 | "subtotal_price": "47.97",
14 | "total_weight": 1361,
15 | "total_tax": "4.06",
16 | "taxes_included": false,
17 | "currency": "USD",
18 | "financial_status": "paid",
19 | "confirmed": true,
20 | "total_discounts": "0.00",
21 | "total_line_items_price": "47.97",
22 | "cart_token": "4b921d873bf3bba40a619926fd390e15",
23 | "buyer_accepts_marketing": false,
24 | "name": "#1001",
25 | "referring_site": "",
26 | "landing_site": "\/",
27 | "cancelled_at": null,
28 | "cancel_reason": null,
29 | "total_price_usd": "60.03",
30 | "checkout_token": "78fedd56e5750664cfc2701cc26cbe5e",
31 | "reference": null,
32 | "user_id": null,
33 | "location_id": null,
34 | "source_identifier": null,
35 | "source_url": null,
36 | "processed_at": "2016-07-26T09:52:57-05:00",
37 | "device_id": null,
38 | "browser_ip": "192.168.1.1",
39 | "landing_site_ref": null,
40 | "order_number": 1001,
41 | "discount_codes": [],
42 | "note_attributes": [],
43 | "payment_gateway_names": [
44 | "bogus"
45 | ],
46 | "processing_method": "direct",
47 | "checkout_id": 8240611139,
48 | "source_name": "web",
49 | "fulfillment_status": null,
50 | "tax_lines": [],
51 | "tags": "",
52 | "contact_email": "test@email.com",
53 | "order_status_url": "https:\/\/checkout.shopify.com\/",
54 | "line_items": [],
55 | "shipping_lines": [],
56 | "billing_address": {},
57 | "shipping_address": {},
58 | "fulfillments": [],
59 | "client_details": {},
60 | "refunds": [],
61 | "payment_details": {},
62 | "customer": {}
63 | }
--------------------------------------------------------------------------------
/shopify/orders/tests/payment_detail.json:
--------------------------------------------------------------------------------
1 | {
2 | "credit_card_bin": "1",
3 | "avs_result_code": null,
4 | "cvv_result_code": null,
5 | "credit_card_number": "•••• •••• •••• 1",
6 | "credit_card_company": "Bogus"
7 | }
--------------------------------------------------------------------------------
/shopify/orders/tests/response.json:
--------------------------------------------------------------------------------
1 | {
2 | "orders": [
3 | {
4 | "id": 3389059459,
5 | "email": "test@email.com",
6 | "closed_at": null,
7 | "created_at": "2016-07-26T09:52:57-05:00",
8 | "updated_at": "2016-07-26T09:52:57-05:00",
9 | "number": 1,
10 | "note": null,
11 | "token": "31a03d57375c347a7e1baf64a6147841",
12 | "gateway": "bogus",
13 | "test": true,
14 | "total_price": "60.03",
15 | "subtotal_price": "47.97",
16 | "total_weight": 1361,
17 | "total_tax": "4.06",
18 | "taxes_included": false,
19 | "currency": "USD",
20 | "financial_status": "paid",
21 | "confirmed": true,
22 | "total_discounts": "0.00",
23 | "total_line_items_price": "47.97",
24 | "cart_token": "4b921d873bf3bba40a619926fd390e15",
25 | "buyer_accepts_marketing": false,
26 | "name": "#1001",
27 | "referring_site": "",
28 | "landing_site": "\/",
29 | "cancelled_at": null,
30 | "cancel_reason": null,
31 | "total_price_usd": "60.03",
32 | "checkout_token": "78fedd56e5750664cfc2701cc26cbe5e",
33 | "reference": null,
34 | "user_id": null,
35 | "location_id": null,
36 | "source_identifier": null,
37 | "source_url": null,
38 | "processed_at": "2016-07-26T09:52:57-05:00",
39 | "device_id": null,
40 | "browser_ip": "192.168.1.1",
41 | "landing_site_ref": null,
42 | "order_number": 1001,
43 | "discount_codes": [],
44 | "note_attributes": [],
45 | "payment_gateway_names": [
46 | "bogus"
47 | ],
48 | "processing_method": "direct",
49 | "checkout_id": 8240611139,
50 | "source_name": "web",
51 | "fulfillment_status": null,
52 | "tax_lines": [
53 | {
54 | "title": "State Tax",
55 | "price": "3.50",
56 | "rate": 0.0625
57 | },
58 | {
59 | "title": "Municipal Tax",
60 | "price": "0.56",
61 | "rate": 0.01
62 | }
63 | ],
64 | "tags": "",
65 | "contact_email": "test@email.com",
66 | "order_status_url": "https:\/\/checkout.shopify.com\/",
67 | "line_items": [
68 | {
69 | "id": 6207715523,
70 | "variant_id": 23950183043,
71 | "title": "Nature Made, Vitamin D3 2,000 I.U. Liquid Softgels, 250-Count",
72 | "quantity": 3,
73 | "price": "15.99",
74 | "grams": 454,
75 | "sku": "test-sku",
76 | "variant_title": "",
77 | "vendor": "Nature Made",
78 | "fulfillment_service": "manual",
79 | "product_id": 7583017923,
80 | "requires_shipping": true,
81 | "taxable": true,
82 | "gift_card": false,
83 | "name": "Nature Made, Vitamin D3 2,000 I.U. Liquid Softgels, 250-Count",
84 | "variant_inventory_management": "shopify",
85 | "properties": [],
86 | "product_exists": true,
87 | "fulfillable_quantity": 3,
88 | "total_discount": "0.00",
89 | "fulfillment_status": null,
90 | "tax_lines": [
91 | {
92 | "title": "State Tax",
93 | "price": "3.00",
94 | "rate": 0.0625
95 | },
96 | {
97 | "title": "Municipal Tax",
98 | "price": "0.48",
99 | "rate": 0.01
100 | }
101 | ],
102 | "origin_location": {
103 | "id": 1573630275,
104 | "country_code": "US",
105 | "province_code": "ST",
106 | "name": "Test Store",
107 | "address1": "456 Test Dr",
108 | "address2": "",
109 | "city": "City",
110 | "zip": "67890"
111 | },
112 | "destination_location": {
113 | "id": 1616145027,
114 | "country_code": "US",
115 | "province_code": "ST",
116 | "name": "Test Customer",
117 | "address1": "123 Test Ln",
118 | "address2": "",
119 | "city": "City",
120 | "zip": "12345"
121 | }
122 | }
123 | ],
124 | "shipping_lines": [
125 | {
126 | "id": 2772048707,
127 | "title": "Standard Shipping",
128 | "price": "8.00",
129 | "code": "Standard Shipping",
130 | "source": "shopify",
131 | "phone": null,
132 | "requested_fulfillment_service_id": null,
133 | "delivery_category": null,
134 | "carrier_identifier": null,
135 | "tax_lines": [
136 | {
137 | "title": "State Tax",
138 | "price": "0.50",
139 | "rate": 0.0625
140 | },
141 | {
142 | "title": "Municipal Tax",
143 | "price": "0.08",
144 | "rate": 0.01
145 | }
146 | ]
147 | }
148 | ],
149 | "billing_address": {
150 | "first_name": "Test",
151 | "address1": "123 Test Ln",
152 | "phone": null,
153 | "city": "City",
154 | "zip": "12345",
155 | "province": "State",
156 | "country": "United States",
157 | "last_name": "Customer",
158 | "address2": "",
159 | "company": null,
160 | "latitude": 0.0,
161 | "longitude": 0.0,
162 | "name": "Test Customer",
163 | "country_code": "US",
164 | "province_code": "ST"
165 | },
166 | "shipping_address": {
167 | "first_name": "Test",
168 | "address1": "123 Test Ln",
169 | "phone": null,
170 | "city": "City",
171 | "zip": "12345",
172 | "province": "State",
173 | "country": "United States",
174 | "last_name": "Customer",
175 | "address2": "",
176 | "company": null,
177 | "latitude": 0.0,
178 | "longitude": 0.0,
179 | "name": "Test Customer",
180 | "country_code": "US",
181 | "province_code": "ST"
182 | },
183 | "fulfillments": [],
184 | "client_details": {
185 | "browser_ip": "192.168.1.1",
186 | "accept_language": "en-US,en;q=0.5",
187 | "user_agent": "Mozilla\/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko\/20100101 Firefox\/47.0",
188 | "session_hash": "1f29043960fc3c472ccda7d7f22c6fbe",
189 | "browser_width": 1855,
190 | "browser_height": 985
191 | },
192 | "refunds": [],
193 | "payment_details": {
194 | "credit_card_bin": "1",
195 | "avs_result_code": null,
196 | "cvv_result_code": null,
197 | "credit_card_number": "•••• •••• •••• 1",
198 | "credit_card_company": "Bogus"
199 | },
200 | "customer": {
201 | "id": 3778180163,
202 | "email": "test@email.com",
203 | "accepts_marketing": false,
204 | "created_at": "2016-07-26T09:52:25-05:00",
205 | "updated_at": "2016-07-26T09:52:57-05:00",
206 | "first_name": "Test",
207 | "last_name": "Customer",
208 | "orders_count": 0,
209 | "state": "disabled",
210 | "total_spent": "0.00",
211 | "last_order_id": null,
212 | "note": null,
213 | "verified_email": true,
214 | "multipass_identifier": null,
215 | "tax_exempt": false,
216 | "tags": "",
217 | "last_order_name": null,
218 | "default_address": {
219 | "id": 3988356099,
220 | "first_name": "Test",
221 | "last_name": "Customer",
222 | "company": null,
223 | "address1": "123 Test Ln",
224 | "address2": "",
225 | "city": "City",
226 | "province": "State",
227 | "country": "United States",
228 | "zip": "12345",
229 | "phone": null,
230 | "name": "Test Customer",
231 | "province_code": "ST",
232 | "country_code": "US",
233 | "country_name": "United States",
234 | "default": true
235 | }
236 | }
237 | }
238 | ]
239 | }
--------------------------------------------------------------------------------
/shopify/orders/tests/shipping_line.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 2772048707,
3 | "title": "Standard Shipping",
4 | "price": "8.00",
5 | "code": "Standard Shipping",
6 | "source": "shopify",
7 | "phone": null,
8 | "requested_fulfillment_service_id": null,
9 | "delivery_category": null,
10 | "carrier_identifier": null,
11 | "tax_lines": []
12 | }
--------------------------------------------------------------------------------
/shopify/orders/tests/tax_line.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "State Tax",
3 | "price": "3.50",
4 | "rate": 0.0625
5 | }
--------------------------------------------------------------------------------
/shopify/orders/tests/test_address.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import sys
4 |
5 | from unittest import TestCase
6 |
7 | # Prevent relative import errors
8 | file_ = os.path.abspath(__file__)
9 | tests_ = os.path.dirname(file_)
10 | products_ = os.path.dirname(tests_)
11 | shopify_ = os.path.dirname(products_)
12 | root = os.path.dirname(shopify_)
13 | sys.path.append(root)
14 |
15 | from shopify.orders import Address
16 |
17 |
18 | class TestAddress(TestCase):
19 |
20 | def setUp(self):
21 | with open(os.path.join(tests_, 'address.json'), 'rb') as f:
22 | self.data = json.load(f)
23 | self.address = Address(self.data)
24 |
25 | def test_first_name(self):
26 | self.failUnlessEqual(self.data['first_name'], self.address.first_name)
27 |
28 | def test_address_1(self):
29 | self.failUnlessEqual(self.data['address1'], self.address.address_1)
30 |
31 | def test_phone(self):
32 | self.failUnlessEqual(self.data['phone'], self.address.phone)
33 |
34 | def test_city(self):
35 | self.failUnlessEqual(self.data['city'], self.address.city)
36 |
37 | def test_zip(self):
38 | self.failUnlessEqual(self.data['zip'], self.address.zip)
39 |
40 | def test_province(self):
41 | self.failUnlessEqual(self.data['province'], self.address.province)
42 |
43 | def test_country(self):
44 | self.failUnlessEqual(self.data['country'], self.address.country)
45 |
46 | def test_last_name(self):
47 | self.failUnlessEqual(self.data['last_name'], self.address.last_name)
48 |
49 | def test_address_2(self):
50 | self.failUnlessEqual(self.data['address2'], self.address.address_2)
51 |
52 | def test_company(self):
53 | self.failUnlessEqual(self.data['company'], self.address.company)
54 |
55 | def test_longitude(self):
56 | self.failUnlessEqual(self.data['latitude'], self.address.latitude)
57 |
58 | def test_latitude(self):
59 | self.failUnlessEqual(self.data['longitude'], self.address.longitude)
60 |
61 | def test_country_code(self):
62 | self.failUnlessEqual(self.data['country_code'], self.address.country_code)
63 |
64 | def test_province_code(self):
65 | self.failUnlessEqual(self.data['province_code'], self.address.province_code)
66 |
67 |
68 | if __name__ == '__main__':
69 | import unittest
70 | unittest.main()
71 |
--------------------------------------------------------------------------------
/shopify/orders/tests/test_all.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from test_address import TestAddress
4 | from test_clientDetail import TestClientDetail
5 | from test_customer import TestCustomer
6 | # from test_lineItem import TestLineItem
7 | # #from test_order import TestOrder
8 | from test_paymentDetail import TestPaymentDetail
9 | from test_shippingLine import TestShippingLine
10 | from test_taxLine import TestTaxLine
11 |
12 |
13 | if __name__ == '__main__':
14 | unittest.main()
15 |
--------------------------------------------------------------------------------
/shopify/orders/tests/test_clientDetail.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import sys
4 | import datetime
5 |
6 | from unittest import TestCase
7 |
8 | # Prevent relative import errors
9 | file_ = os.path.abspath(__file__)
10 | tests_ = os.path.dirname(file_)
11 | products_ = os.path.dirname(tests_)
12 | shopify_ = os.path.dirname(products_)
13 | root = os.path.dirname(shopify_)
14 | sys.path.append(root)
15 |
16 | from shopify.orders import ClientDetail
17 |
18 |
19 | class TestClientDetail(TestCase):
20 |
21 | def setUp(self):
22 | with open(os.path.join(tests_, 'client_detail.json'), 'rb') as f:
23 | self.data = json.load(f)
24 | self.client_detail = ClientDetail(self.data)
25 |
26 | def test_browser_ip(self):
27 | self.failUnlessEqual(self.data['browser_ip'], self.client_detail.browser_ip)
28 |
29 | def test_accept_language(self):
30 | self.failUnlessEqual(self.data['accept_language'], self.client_detail.accept_language)
31 |
32 | def test_user_agent(self):
33 | self.failUnlessEqual(self.data['user_agent'], self.client_detail.user_agent)
34 |
35 | def test_session_hash(self):
36 | self.failUnlessEqual(self.data['session_hash'], self.client_detail.session_hash)
37 |
38 | def test_browser_width(self):
39 | self.failUnlessEqual(self.data['browser_width'], self.client_detail.browser_width)
40 |
41 | def test_browser_height(self):
42 | self.failUnlessEqual(self.data['browser_height'], self.client_detail.browser_height)
43 |
44 |
45 | if __name__ == '__main__':
46 | import unittest
47 | unittest.main()
48 |
--------------------------------------------------------------------------------
/shopify/orders/tests/test_customer.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import sys
4 | import datetime
5 |
6 | from unittest import TestCase
7 |
8 | # Prevent relative import errors
9 | file_ = os.path.abspath(__file__)
10 | tests_ = os.path.dirname(file_)
11 | products_ = os.path.dirname(tests_)
12 | shopify_ = os.path.dirname(products_)
13 | root = os.path.dirname(shopify_)
14 | sys.path.append(root)
15 |
16 | from shopify.orders import Customer
17 |
18 |
19 | class TestCustomer(TestCase):
20 |
21 | def setUp(self):
22 | with open(os.path.join(tests_, 'customer.json'), 'rb') as f:
23 | self.data = json.load(f)
24 | self.customer = Customer(self.data)
25 |
26 | def _compare_dt(self, og_dt_key, dt2):
27 | og = self.data[og_dt_key].rpartition('-')[0] # Remove the utc offset from the end of the timestamp
28 | og_dt = datetime.datetime.strptime(og, '%Y-%m-%dT%H:%M:%S')
29 | psr_dt = dt2.replace(tzinfo=None)
30 | self.failUnlessEqual(og_dt, psr_dt)
31 |
32 | def test_id(self):
33 | self.failUnlessEqual(self.data['id'], self.customer.id)
34 |
35 | def test_email(self):
36 | self.failUnlessEqual(self.data['email'], self.customer.email)
37 |
38 | def test_accepts_marketing(self):
39 | self.failUnlessEqual(self.data['accepts_marketing'], self.customer.accepts_marketing)
40 |
41 | def test_created_at(self):
42 | self._compare_dt('created_at', self.customer.created_at)
43 |
44 | def test_updated_at(self):
45 | self._compare_dt('updated_at', self.customer.updated_at)
46 |
47 | def test_first_name(self):
48 | self.failUnlessEqual(self.data['first_name'], self.customer.first_name)
49 |
50 | def test_last_name(self):
51 | self.failUnlessEqual(self.data['last_name'], self.customer.last_name)
52 |
53 | def test_orders_count(self):
54 | self.failUnlessEqual(self.data['orders_count'], self.customer.orders_count)
55 |
56 | def test_state(self):
57 | self.failUnlessEqual(self.data['state'], self.customer.state)
58 |
59 | def test_total_spent(self):
60 | self.failUnlessEqual(float(self.data['total_spent']), self.customer.total_spent)
61 |
62 | def test_last_order_id(self):
63 | self.failUnlessEqual(self.data['last_order_id'], self.customer.last_order_id)
64 |
65 | def test_note(self):
66 | self.failUnlessEqual(self.data['note'], self.customer.note)
67 |
68 | def test_verified_email(self):
69 | self.failUnlessEqual(self.data['verified_email'], self.customer.verified_email)
70 |
71 | def test_multipass_identifier(self):
72 | self.failUnlessEqual(self.data['multipass_identifier'], self.customer.multipass_identifier)
73 |
74 | def test_tax_exempt(self):
75 | self.failUnlessEqual(self.data['tax_exempt'], self.customer.tax_exempt)
76 |
77 | def test_tags(self):
78 | self.failUnlessEqual(self.data['tags'], self.customer.tags)
79 |
80 | def test_last_order_name(self):
81 | self.failUnlessEqual(self.data['last_order_name'], self.customer.last_order_name)
82 |
83 |
84 | if __name__ == '__main__':
85 | import unittest
86 | unittest.main()
87 |
--------------------------------------------------------------------------------
/shopify/orders/tests/test_lineItem.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase
2 |
3 |
4 | class TestLineItem(TestCase):
5 | def test_variant_id(self):
6 | self.fail()
7 |
8 | def test_title(self):
9 | self.fail()
10 |
11 | def test_quantity(self):
12 | self.fail()
13 |
14 | def test_price(self):
15 | self.fail()
16 |
17 | def test_grams(self):
18 | self.fail()
19 |
20 | def test_sku(self):
21 | self.fail()
22 |
23 | def test_variant_title(self):
24 | self.fail()
25 |
26 | def test_vendor(self):
27 | self.fail()
28 |
29 | def test_fulfillment_service(self):
30 | self.fail()
31 |
32 | def test_product_id(self):
33 | self.fail()
34 |
35 | def test_requires_shipping(self):
36 | self.fail()
37 |
38 | def test_taxable(self):
39 | self.fail()
40 |
41 | def test_gift_card(self):
42 | self.fail()
43 |
44 | def test_name(self):
45 | self.fail()
46 |
47 | def test_variant_inventory_management(self):
48 | self.fail()
49 |
50 | def test_properties(self):
51 | self.fail()
52 |
53 | def test_product_exists(self):
54 | self.fail()
55 |
56 | def test_fulfillable_quantity(self):
57 | self.fail()
58 |
59 | def test_total_discount(self):
60 | self.fail()
61 |
62 | def test_fulfillment_status(self):
63 | self.fail()
64 |
65 | def test_tax_lines(self):
66 | self.fail()
67 |
68 | def test_origin_location(self):
69 | self.fail()
70 |
71 | def test_destination_location(self):
72 | self.fail()
73 |
--------------------------------------------------------------------------------
/shopify/orders/tests/test_order.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase
2 |
3 |
4 | class TestOrder(TestCase):
5 | def test_email(self):
6 | self.fail()
7 |
8 | def test_closed_at(self):
9 | self.fail()
10 |
11 | def test_created_at(self):
12 | self.fail()
13 |
14 | def test_updated_at(self):
15 | self.fail()
16 |
17 | def test_number(self):
18 | self.fail()
19 |
20 | def test_note(self):
21 | self.fail()
22 |
23 | def test_token(self):
24 | self.fail()
25 |
26 | def test_gateway(self):
27 | self.fail()
28 |
29 | def test_test(self):
30 | self.fail()
31 |
32 | def test_total_price(self):
33 | self.fail()
34 |
35 | def test_subtotal_price(self):
36 | self.fail()
37 |
38 | def test_total_weight(self):
39 | self.fail()
40 |
41 | def test_total_tax(self):
42 | self.fail()
43 |
44 | def test_taxes_included(self):
45 | self.fail()
46 |
47 | def test_currency(self):
48 | self.fail()
49 |
50 | def test_financial_status(self):
51 | self.fail()
52 |
53 | def test_confirmed(self):
54 | self.fail()
55 |
56 | def test_total_discounts(self):
57 | self.fail()
58 |
59 | def test_total_line_items_price(self):
60 | self.fail()
61 |
62 | def test_cart_token(self):
63 | self.fail()
64 |
65 | def test_buyer_accepts_marketing(self):
66 | self.fail()
67 |
68 | def test_name(self):
69 | self.fail()
70 |
71 | def test_referring_site(self):
72 | self.fail()
73 |
74 | def test_landing_site(self):
75 | self.fail()
76 |
77 | def test_cancelled_at(self):
78 | self.fail()
79 |
80 | def test_cancel_reason(self):
81 | self.fail()
82 |
83 | def test_total_price_usd(self):
84 | self.fail()
85 |
86 | def test_checkout_token(self):
87 | self.fail()
88 |
89 | def test_reference(self):
90 | self.fail()
91 |
92 | def test_user_id(self):
93 | self.fail()
94 |
95 | def test_location_id(self):
96 | self.fail()
97 |
98 | def test_source_identifier(self):
99 | self.fail()
100 |
101 | def test_source_url(self):
102 | self.fail()
103 |
104 | def test_processed_at(self):
105 | self.fail()
106 |
107 | def test_device_id(self):
108 | self.fail()
109 |
110 | def test_browser_ip(self):
111 | self.fail()
112 |
113 | def test_landing_site_ref(self):
114 | self.fail()
115 |
116 | def test_order_number(self):
117 | self.fail()
118 |
119 | def test_discount_codes(self):
120 | self.fail()
121 |
122 | def test_note_attributes(self):
123 | self.fail()
124 |
125 | def test_payment_gateway_names(self):
126 | self.fail()
127 |
128 | def test_processing_method(self):
129 | self.fail()
130 |
131 | def test_checkout_id(self):
132 | self.fail()
133 |
134 | def test_source_name(self):
135 | self.fail()
136 |
137 | def test_fulfillment_status(self):
138 | self.fail()
139 |
140 | def test_tax_lines(self):
141 | self.fail()
142 |
143 | def test_tags(self):
144 | self.fail()
145 |
--------------------------------------------------------------------------------
/shopify/orders/tests/test_paymentDetail.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import sys
4 |
5 | from unittest import TestCase
6 |
7 | # Prevent relative import errors
8 | file_ = os.path.abspath(__file__)
9 | tests_ = os.path.dirname(file_)
10 | products_ = os.path.dirname(tests_)
11 | shopify_ = os.path.dirname(products_)
12 | root = os.path.dirname(shopify_)
13 | sys.path.append(root)
14 |
15 | from shopify.orders import PaymentDetail
16 |
17 |
18 | class TestPaymentDetail(TestCase):
19 |
20 | def setUp(self):
21 | with open(os.path.join(tests_, 'payment_detail.json'), 'rb') as f:
22 | self.data = json.load(f)
23 | self.payment_detail = PaymentDetail(self.data)
24 |
25 | def test_credit_card_bin(self):
26 | self.failUnlessEqual(self.data['credit_card_bin'], self.payment_detail.credit_card_bin)
27 |
28 | def test_avs_result_code(self):
29 | self.failUnlessEqual(self.data['avs_result_code'], self.payment_detail.avs_result_code)
30 |
31 | def test_cvv_result_code(self):
32 | self.failUnlessEqual(self.data['cvv_result_code'], self.payment_detail.cvv_result_code)
33 |
34 | def test_credit_card_number(self):
35 | self.failUnlessEqual(self.data['credit_card_number'], self.payment_detail.credit_card_number)
36 |
37 | def test_credit_card_company(self):
38 | self.failUnlessEqual(self.data['credit_card_company'], self.payment_detail.credit_card_company)
39 |
40 |
41 | if __name__ == '__main__':
42 | import unittest
43 | unittest.main()
44 |
--------------------------------------------------------------------------------
/shopify/orders/tests/test_shippingLine.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import sys
4 |
5 | from unittest import TestCase
6 |
7 | # Prevent relative import errors
8 | file_ = os.path.abspath(__file__)
9 | tests_ = os.path.dirname(file_)
10 | products_ = os.path.dirname(tests_)
11 | shopify_ = os.path.dirname(products_)
12 | root = os.path.dirname(shopify_)
13 | sys.path.append(root)
14 |
15 | from shopify.orders import ShippingLine
16 |
17 |
18 | class TestShippingLine(TestCase):
19 |
20 | def setUp(self):
21 | with open(os.path.join(tests_, 'shipping_line.json'), 'rb') as f:
22 | self.data = json.load(f)
23 | self.shipping_line = ShippingLine(self.data)
24 |
25 | def test_title(self):
26 | self.failUnlessEqual(self.data['title'], self.shipping_line.title)
27 |
28 | def test_price(self):
29 | self.failUnlessEqual(float(self.data['price']), self.shipping_line.price)
30 |
31 | def test_code(self):
32 | self.failUnlessEqual(self.data['code'], self.shipping_line.code)
33 |
34 | def test_source(self):
35 | self.failUnlessEqual(self.data['source'], self.shipping_line.source)
36 |
37 | def test_phone(self):
38 | self.failUnlessEqual(self.data['phone'], self.shipping_line.phone)
39 |
40 | def test_requested_fulfillment_service_id(self):
41 | self.failUnlessEqual(self.data['requested_fulfillment_service_id'], self.shipping_line.requested_fulfillment_service_id)
42 |
43 | def test_delivery_category(self):
44 | self.failUnlessEqual(self.data['delivery_category'], self.shipping_line.delivery_category)
45 |
46 | def test_carrier_identifier(self):
47 | self.failUnlessEqual(self.data['carrier_identifier'], self.shipping_line.carrier_identifier)
48 |
49 |
50 | if __name__ == '__main__':
51 | import unittest
52 | unittest.main()
53 |
--------------------------------------------------------------------------------
/shopify/orders/tests/test_taxLine.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import sys
4 |
5 | from unittest import TestCase
6 |
7 | # Prevent relative import errors
8 | file_ = os.path.abspath(__file__)
9 | tests_ = os.path.dirname(file_)
10 | products_ = os.path.dirname(tests_)
11 | shopify_ = os.path.dirname(products_)
12 | root = os.path.dirname(shopify_)
13 | sys.path.append(root)
14 |
15 | from shopify.orders import TaxLine
16 |
17 |
18 | class TestTaxLine(TestCase):
19 |
20 | def setUp(self):
21 | with open(os.path.join(tests_, 'tax_line.json'), 'rb') as f:
22 | self.data = json.load(f)
23 | self.tax_line = TaxLine(self.data)
24 |
25 | def test_title(self):
26 | self.failUnlessEqual(self.data['title'], self.tax_line.title)
27 |
28 | def test_price(self):
29 | self.failUnlessEqual(float(self.data['price']), self.tax_line.price)
30 |
31 | def test_rate(self):
32 | self.failUnlessEqual(self.data['rate'], self.tax_line.rate)
33 |
34 |
35 | if __name__ == '__main__':
36 | import unittest
37 | unittest.main()
38 |
--------------------------------------------------------------------------------
/shopify/products/__init__.py:
--------------------------------------------------------------------------------
1 | from objects import *
2 | from api_wrapper import ProductsApiWrapper
3 |
4 |
5 | def count(api_key, api_password, store_name):
6 | import json
7 | import requests
8 |
9 | url = 'https://{}:{}@{}.myshopify.com/admin/products/count.json'.format(api_key, api_password, store_name)
10 | response = requests.get(url)
11 | data = json.loads(response.content)
12 | return int(data['count'])
13 |
--------------------------------------------------------------------------------
/shopify/products/api_wrapper.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import requests
4 |
5 | from .objects import Product
6 | from ..base import ShopifyApiWrapper, ShopifyApiError, datetime_to_string
7 |
8 |
9 | class ProductsApiWrapper(ShopifyApiWrapper):
10 |
11 | valid_published_status_values = [
12 | 'published',
13 | 'unpublished',
14 | 'any'
15 | ]
16 |
17 | max_results_limit = 250
18 |
19 | default_endpoint = '/admin/products.json'
20 | operational_endpoint = '/admin/products/{}.json'
21 |
22 | def update(self, product):
23 | if not product.id:
24 | raise ValueError('product must have an ID to use `update`')
25 | endpoint = self.operational_endpoint.format(product.id)
26 | url = self.url_host() + endpoint
27 | d = json.dumps(product.__dict__)
28 |
29 | response = requests.put(url, data=d, headers={'Content-Type': 'application/json'})
30 |
31 | err_check = ShopifyApiError(response)
32 | if err_check.has_error():
33 | raise err_check
34 |
35 | def create(self, product):
36 | """
37 | Create a product in inventory.
38 |
39 | :param product: A Product object instance.
40 | :return:
41 | """
42 | url = self.url_host() + self.default_endpoint
43 | d = json.dumps(product.__dict__)
44 |
45 | # with open('shopify-create-product-body.json', 'wb') as f:
46 | # f.write(d)
47 |
48 | response = requests.post(
49 | url,
50 | data=d,
51 | headers={'Content-Type': 'application/json'}
52 | )
53 |
54 | # with open('shopfiy-create-product-response.json', 'wb') as f:
55 | # f.write(response.content)
56 |
57 | err_check = ShopifyApiError(response)
58 | if err_check.has_error():
59 | raise err_check
60 |
61 | return Product(json.loads(response.content).get('product'))
62 |
63 | def _delete(self, id_):
64 | """
65 | Delete product from inventory using the products ID.
66 |
67 | :param id_: The products ID to delete.
68 | :return:
69 | """
70 | endpoint = self.operational_endpoint.format(id_)
71 | url = self.url_host() + endpoint
72 | response = requests.delete(url)
73 |
74 | err_check = ShopifyApiError(response)
75 | if err_check.has_error():
76 | raise err_check
77 |
78 | def delete(self, product):
79 | """
80 | Delete a product from inventory.
81 |
82 | Use when you have a product instance returned from list().
83 |
84 | :param product: A Product object.
85 | :return:
86 | """
87 | if not product.id:
88 | raise ValueError('product must have an ID to use `delete`')
89 | self._delete(product.id)
90 |
91 | def delete_by_id(self, id_):
92 | """
93 | Delete a product from inventory using the product's ID.
94 |
95 | :param id_: The numerical id for a product.
96 | :return:
97 | """
98 | self._delete(id_)
99 |
100 | def list(self, ids=(), limit=50, page=1, since_id=None, title=None,
101 | vendor=None, handle=None, product_type=None, collection_id=None,
102 | created_at_min=None, created_at_max=None, updated_at_min=None,
103 | updated_at_max=None, published_at_min=None, published_at_max=None,
104 | published_status='any', fields=()):
105 | """
106 | Return a list of products which are associated with your shopify account.
107 |
108 | :param ids: product ids to request.
109 | :param limit: Amount of results. (maximum: 250).
110 | :param page: Page to show.
111 | :param since_id: Restrict results to after the specified ID.
112 | :param title: Filter by product title.
113 | :param vendor: Filter by product vendor.
114 | :param handle: Filter by product handle.
115 | :param product_type: Filter by product type.
116 | :param collection_id: Filter by collection ID.
117 | :param created_at_min: Show products created after date.
118 | :param created_at_max: Show products create before date.
119 | :param updated_at_min: Show products last update after date.
120 | :param updated_at_max: Show products last updated before date.
121 | :param published_at_min: Show products published after date.
122 | :param published_at_max: Show products published before date.
123 | :param published_status: Filter by published status.
124 | Valid Values:
125 | published - Show only published products.
126 | unpublished - Show only unpublished products.
127 | any - Show all products.
128 | :param fields: List of fields to include in the response.
129 | :return:
130 | """
131 | url = self.url_host() + self.default_endpoint
132 | if published_status not in self.valid_published_status_values:
133 | raise ValueError('`published_status` must be one of {}'.format(self.valid_published_status_values))
134 | if limit > self.max_results_limit:
135 | raise ValueError('`limit` cannot exceed {}'.format(self.max_results_limit))
136 | params = dict(
137 | ids=','.join(ids),
138 | limit=limit,
139 | page=page,
140 | since_id=since_id,
141 | title=title,
142 | vendor=vendor,
143 | handle=handle,
144 | product_type=product_type,
145 | collection_id=collection_id,
146 | created_at_min=datetime_to_string(created_at_min),
147 | created_at_max=datetime_to_string(created_at_max),
148 | updated_at_min=datetime_to_string(updated_at_min),
149 | updated_at_max=datetime_to_string(updated_at_max),
150 | published_at_min=datetime_to_string(published_at_min),
151 | published_at_max=datetime_to_string(published_at_max),
152 | published_status=published_status,
153 | fields=','.join(fields)
154 | )
155 | params = self.remove_empty(params)
156 | response = requests.get(url, params=params)
157 | # with open('products-list.json', 'wb') as f:
158 | # f.write(response.content)
159 | data = json.loads(response.content)
160 | return [Product(x) for x in data.get('products', [])]
161 |
--------------------------------------------------------------------------------
/shopify/products/objects/__init__.py:
--------------------------------------------------------------------------------
1 | from .product import Product
2 | from .image import Image
3 | from .variant import Variant
4 | from .option import Option
5 |
--------------------------------------------------------------------------------
/shopify/products/objects/image.py:
--------------------------------------------------------------------------------
1 | import base64
2 |
3 | from ...base import BaseParser, datetime_to_string, string_to_datetime
4 |
5 |
6 | class Image(BaseParser):
7 |
8 | def __init__(self, d=None, **kwargs):
9 | BaseParser.__init__(self, d, **kwargs)
10 | self.image_location = None
11 |
12 | @property
13 | def product_id(self):
14 | return self._dict.get('product_id')
15 |
16 | # No product_id setter since that value shouldn't be modified.
17 |
18 | @property
19 | def position(self):
20 | return self._dict.get('position')
21 |
22 | @position.setter
23 | def position(self, val):
24 | self._dict['position'] = int(val)
25 |
26 | @property
27 | def created_at(self):
28 | return string_to_datetime(self._dict.get('created_at'))
29 |
30 | @created_at.setter
31 | def created_at(self, val):
32 | self._dict['created_at'] = datetime_to_string(val)
33 |
34 | @property
35 | def updated_at(self):
36 | return string_to_datetime(self._dict.get('updated_at'))
37 |
38 | @updated_at.setter
39 | def updated_at(self, val):
40 | self._dict['updated_at'] = datetime_to_string(val)
41 |
42 | @property
43 | def src(self):
44 | return self._dict.get('src')
45 |
46 | @src.setter
47 | def src(self, val):
48 | self._dict['src'] = val
49 |
50 | def attach(self, f):
51 | """
52 | Attach an image file instead of using a url.
53 | :param f: Path to image file.
54 | :return:
55 | """
56 | with open(f, 'rb') as f:
57 | encoded = base64.b64encode(f.read())
58 | self._dict['attachment'] = encoded
59 | self.image_location = f
60 |
61 | def __repr__(self):
62 | return ''.format(self.id, self.src, self.image_location)
63 |
--------------------------------------------------------------------------------
/shopify/products/objects/option.py:
--------------------------------------------------------------------------------
1 | from ...base import BaseParser
2 |
3 |
4 | class Option(BaseParser):
5 |
6 | @property
7 | def product_id(self):
8 | return self._dict.get('product_id')
9 |
10 | # No product_id setter since that value shouldn't be modified.
11 |
12 | @property
13 | def name(self):
14 | return self._dict.get('name')
15 |
16 | @name.setter
17 | def name(self, val):
18 | self._dict['name'] = val
19 |
20 | @property
21 | def position(self):
22 | return self._dict.get('position')
23 |
24 | @position.setter
25 | def position(self, val):
26 | self._dict['position'] = int(val)
27 |
28 | @property
29 | def values(self):
30 | return self._dict.get('values')
31 |
32 | @values.setter
33 | def values(self, val):
34 | self._dict['values'] = val
35 |
36 | def __repr__(self):
37 | return '