├── requirements.txt ├── .flake8 ├── requirements-test.txt ├── .gitignore ├── .editorconfig ├── woocommerce ├── __init__.py ├── api.py └── oauth.py ├── .github └── workflows │ ├── publish.yml │ └── ci.yml ├── LICENSE.txt ├── setup.py ├── CHANGELOG.md ├── test_api.py └── README.rst /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.25.1 2 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | per-file-ignores = __init__.py:F401 3 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | httmock==1.4.0 3 | pytest==6.2.2 4 | flake8==3.8.4 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | __pycache__ 4 | build/ 5 | dist/ 6 | *.egg-info/ 7 | sample.py 8 | .vscode/ 9 | env/ 10 | .pytest_cache/ 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.rst] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /woocommerce/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | woocommerce 5 | ~~~~~~~~~~~~~~~ 6 | A Python wrapper for WooCommerce API. 7 | 8 | :copyright: (c) 2019 by Automattic. 9 | :license: MIT, see LICENSE for details. 10 | """ 11 | 12 | __title__ = "woocommerce" 13 | __version__ = "3.0.0" 14 | __author__ = "Claudio Sanches @ Automattic" 15 | __license__ = "MIT" 16 | 17 | from woocommerce.api import API 18 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | name: Publish package to PyPI 4 | on: 5 | release: 6 | types: [created] 7 | jobs: 8 | deploy: 9 | name: Deploy 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | - name: Set up Python 15 | uses: actions/setup-python@v2 16 | with: 17 | python-version: '3.9' 18 | - name: Install dependencies 19 | run: | 20 | python -m pip install --upgrade pip 21 | pip install setuptools wheel twine 22 | - name: Build and publish 23 | env: 24 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 25 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 26 | run: | 27 | python setup.py sdist bdist_wheel 28 | twine upload dist/* 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021, Automattic (https://automattic.com/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | name: Run CI 4 | on: 5 | push: 6 | branches: [trunk] 7 | pull_request: 8 | branches: [trunk] 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: [3.6, 3.7, 3.8, 3.9] 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v2 19 | - name: Set up Python ${{ matrix.python-version }} 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | python -m pip install -r requirements-test.txt 27 | - name: Lint with flake8 28 | run: | 29 | # stop the build if there are Python syntax errors or undefined names 30 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 31 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 32 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 33 | - name: Test with pytest 34 | run: | 35 | pytest 36 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ Setup module """ 4 | 5 | from setuptools import setup 6 | import os 7 | import re 8 | 9 | 10 | # Get version from __init__.py file 11 | VERSION = "" 12 | with open("woocommerce/__init__.py", "r") as fd: 13 | VERSION = re.search(r"^__version__\s*=\s*['\"]([^\"]*)['\"]", fd.read(), re.MULTILINE).group(1) 14 | 15 | if not VERSION: 16 | raise RuntimeError("Cannot find version information") 17 | 18 | # Get long description 19 | README = open(os.path.join(os.path.dirname(__file__), "README.rst")).read() 20 | 21 | # allow setup.py to be run from any path 22 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 23 | 24 | setup( 25 | name="WooCommerce", 26 | version=VERSION, 27 | description="A Python wrapper for the WooCommerce REST API", 28 | long_description=README, 29 | author="Claudio Sanches @ Automattic", 30 | author_email="claudio+pypi@automattic.com", 31 | url="https://github.com/woocommerce/wc-api-python", 32 | license="MIT License", 33 | packages=[ 34 | "woocommerce" 35 | ], 36 | include_package_data=True, 37 | platforms=['any'], 38 | install_requires=[ 39 | "requests" 40 | ], 41 | python_requires=">=3.6", 42 | classifiers=[ 43 | "Development Status :: 5 - Production/Stable", 44 | "Intended Audience :: Developers", 45 | "Natural Language :: English", 46 | "License :: OSI Approved :: MIT License", 47 | "Programming Language :: Python", 48 | "Programming Language :: Python :: 3.6", 49 | "Programming Language :: Python :: 3.7", 50 | "Programming Language :: Python :: 3.8", 51 | "Programming Language :: Python :: 3.9", 52 | "Topic :: Software Development :: Libraries :: Python Modules" 53 | ], 54 | keywords='woocommerce rest api', 55 | project_urls={ 56 | 'Documentation': 'https://woocommerce.github.io/woocommerce-rest-api-docs/?python#libraries-and-tools', 57 | 'Source': 'https://github.com/woocommerce/wc-api-python', 58 | 'Tracker': 'https://github.com/woocommerce/wc-api-python/issues', 59 | }, 60 | ) 61 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [3.0.0] - 2021-03-13 10 | ### Removed 11 | - Removed support to legacy Python versions, now supports Python 3.6+. 12 | - Removed ordereddict package dependency. 13 | ### Added 14 | - Added support for Python 3.8 and Python 3.9. 15 | - Added option to set custom `user_agent`. 16 | ### Changed 17 | - Updated default "User-Agent" to `WooCommerce-Python-REST-API/3.0.0`. 18 | - Updated Request library to 2.25.1. 19 | ### Fixed 20 | - Fixed Basic Auth in Python 3.8. 21 | 22 | ## [2.1.1] - 2019-07-22 23 | ### Changed 24 | - Updated Request library to 2.22.0. 25 | - Updated examples. 26 | 27 | ## [2.1.0] - 2019-01-15 28 | ### Changed 29 | - Uses WP REST API by default, need to set `wp_api` as `False` in order to use the legacy WooCommerce API. 30 | - Updated default REST API version to `wc/v3`. 31 | 32 | ## [2.0.0] - 2019-01-15 33 | ### Added 34 | - Added support for custom timestamps in oAuth1.0a requests with `oauth_timestamp`. 35 | - Allow pass custom arguments to "Requests" library.. 36 | ### Changed 37 | - Updated "Requests" library to version 2.20.0. 38 | 39 | ## [1.2.1] - 2016-12-14 40 | ### Fixed 41 | - Fixed use of `content-type` to fix issues with WordPress 4.7. 42 | 43 | ## [1.2.0] - 2016-06-22 44 | ### Added 45 | - Added option `query_string_auth` to allow Basic Auth as query strings. 46 | 47 | ## [1.1.1] - 2016-06-03 48 | ### Fixed 49 | - Fixed oAuth signature for WP REST API. 50 | 51 | ## [1.1.0] - 2016-05-09 52 | ### Added 53 | - Added support for WP REST API. 54 | - Added method to handle HTTP OPTIONS requests. 55 | 56 | ## [1.0.5] - 2015-12-07 57 | ### Fixed 58 | - Fixed oAuth filters sorting. 59 | 60 | ## [1.0.4] - 2015-09-25 61 | ### Added 62 | - Adds `timeout` argument for `API` class. 63 | 64 | ## [1.0.3] - 2015-08-07 65 | ### Changed 66 | - Forced utf-8 encoding on `API.__request()` to avoid `UnicodeDecodeError`. 67 | 68 | ## [1.0.2] - 2015-08-05 69 | ### Fixed 70 | - Fixed handler for query strings. 71 | 72 | ## [1.0.1] - 2015-07-13 73 | ### Fixed 74 | - Fixed support for Python 2.6. 75 | 76 | ## [1.0.0] - 2015-07-12 77 | ### Added 78 | - Initial release. 79 | 80 | [Unreleased]: https://github.com/woocommerce/wc-api-python/compare/3.0.0...HEAD 81 | [3.0.0]: https://github.com/woocommerce/wc-api-python/compare/2.1.1...3.0.0 82 | [2.1.1]: https://github.com/woocommerce/wc-api-python/compare/2.0.1...2.1.1 83 | [2.1.0]: https://github.com/woocommerce/wc-api-python/compare/2.0.0...2.1.0 84 | [2.0.0]: https://github.com/woocommerce/wc-api-python/compare/1.2.1...2.0.0 85 | [1.2.1]: https://github.com/woocommerce/wc-api-python/compare/1.2.0...1.2.1 86 | [1.2.0]: https://github.com/woocommerce/wc-api-python/compare/1.1.1...1.2.0 87 | [1.1.1]: https://github.com/woocommerce/wc-api-python/compare/1.1.0...1.1.1 88 | [1.1.0]: https://github.com/woocommerce/wc-api-python/compare/1.0.5...1.1.0 89 | [1.0.5]: https://github.com/woocommerce/wc-api-python/compare/1.0.4...1.0.5 90 | [1.0.4]: https://github.com/woocommerce/wc-api-python/compare/1.0.3...1.0.4 91 | [1.0.3]: https://github.com/woocommerce/wc-api-python/compare/1.0.2...1.0.3 92 | [1.0.2]: https://github.com/woocommerce/wc-api-python/compare/1.0.1...1.0.2 93 | [1.0.1]: https://github.com/woocommerce/wc-api-python/compare/1.0.0...1.0.1 94 | [1.0.0]: https://github.com/woocommerce/wc-api-python/releases/tag/1.0.0 95 | -------------------------------------------------------------------------------- /woocommerce/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | WooCommerce API Class 5 | """ 6 | 7 | __title__ = "woocommerce-api" 8 | __version__ = "3.0.0" 9 | __author__ = "Claudio Sanches @ Automattic" 10 | __license__ = "MIT" 11 | 12 | from requests import request 13 | from json import dumps as jsonencode 14 | from time import time 15 | from woocommerce.oauth import OAuth 16 | from requests.auth import HTTPBasicAuth 17 | from urllib.parse import urlencode 18 | 19 | 20 | class API(object): 21 | """ API Class """ 22 | 23 | def __init__(self, url, consumer_key, consumer_secret, **kwargs): 24 | self.url = url 25 | self.consumer_key = consumer_key 26 | self.consumer_secret = consumer_secret 27 | self.wp_api = kwargs.get("wp_api", True) 28 | self.version = kwargs.get("version", "wc/v3") 29 | self.is_ssl = self.__is_ssl() 30 | self.timeout = kwargs.get("timeout", 5) 31 | self.verify_ssl = kwargs.get("verify_ssl", True) 32 | self.query_string_auth = kwargs.get("query_string_auth", False) 33 | self.user_agent = kwargs.get("user_agent", f"WooCommerce-Python-REST-API/{__version__}") 34 | 35 | def __is_ssl(self): 36 | """ Check if url use HTTPS """ 37 | return self.url.startswith("https") 38 | 39 | def __get_url(self, endpoint): 40 | """ Get URL for requests """ 41 | url = self.url 42 | api = "wc-api" 43 | 44 | if url.endswith("/") is False: 45 | url = f"{url}/" 46 | 47 | if self.wp_api: 48 | api = "wp-json" 49 | 50 | return f"{url}{api}/{self.version}/{endpoint}" 51 | 52 | def __get_oauth_url(self, url, method, **kwargs): 53 | """ Generate oAuth1.0a URL """ 54 | oauth = OAuth( 55 | url=url, 56 | consumer_key=self.consumer_key, 57 | consumer_secret=self.consumer_secret, 58 | version=self.version, 59 | method=method, 60 | oauth_timestamp=kwargs.get("oauth_timestamp", int(time())) 61 | ) 62 | 63 | return oauth.get_oauth_url() 64 | 65 | def __request(self, method, endpoint, data, params=None, **kwargs): 66 | """ Do requests """ 67 | if params is None: 68 | params = {} 69 | url = self.__get_url(endpoint) 70 | auth = None 71 | headers = { 72 | "user-agent": f"{self.user_agent}", 73 | "accept": "application/json" 74 | } 75 | 76 | if self.is_ssl is True and self.query_string_auth is False: 77 | auth = HTTPBasicAuth(self.consumer_key, self.consumer_secret) 78 | elif self.is_ssl is True and self.query_string_auth is True: 79 | params.update({ 80 | "consumer_key": self.consumer_key, 81 | "consumer_secret": self.consumer_secret 82 | }) 83 | else: 84 | encoded_params = urlencode(params) 85 | url = f"{url}?{encoded_params}" 86 | url = self.__get_oauth_url(url, method, **kwargs) 87 | 88 | if data is not None: 89 | data = jsonencode(data, ensure_ascii=False).encode('utf-8') 90 | headers["content-type"] = "application/json;charset=utf-8" 91 | 92 | return request( 93 | method=method, 94 | url=url, 95 | verify=self.verify_ssl, 96 | auth=auth, 97 | params=params, 98 | data=data, 99 | timeout=self.timeout, 100 | headers=headers, 101 | **kwargs 102 | ) 103 | 104 | def get(self, endpoint, **kwargs): 105 | """ Get requests """ 106 | return self.__request("GET", endpoint, None, **kwargs) 107 | 108 | def post(self, endpoint, data, **kwargs): 109 | """ POST requests """ 110 | return self.__request("POST", endpoint, data, **kwargs) 111 | 112 | def put(self, endpoint, data, **kwargs): 113 | """ PUT requests """ 114 | return self.__request("PUT", endpoint, data, **kwargs) 115 | 116 | def delete(self, endpoint, **kwargs): 117 | """ DELETE requests """ 118 | return self.__request("DELETE", endpoint, None, **kwargs) 119 | 120 | def options(self, endpoint, **kwargs): 121 | """ OPTIONS requests """ 122 | return self.__request("OPTIONS", endpoint, None, **kwargs) 123 | -------------------------------------------------------------------------------- /woocommerce/oauth.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | WooCommerce OAuth1.0a Class 5 | """ 6 | 7 | __title__ = "woocommerce-oauth" 8 | __version__ = "3.0.0" 9 | __author__ = "Claudio Sanches @ Automattic" 10 | __license__ = "MIT" 11 | 12 | from time import time 13 | from random import randint 14 | from hmac import new as HMAC 15 | from hashlib import sha1, sha256 16 | from base64 import b64encode 17 | from collections import OrderedDict 18 | from urllib.parse import urlencode, quote, unquote, parse_qsl, urlparse 19 | 20 | 21 | class OAuth(object): 22 | """ API Class """ 23 | 24 | def __init__(self, url, consumer_key, consumer_secret, **kwargs): 25 | self.url = url 26 | self.consumer_key = consumer_key 27 | self.consumer_secret = consumer_secret 28 | self.version = kwargs.get("version", "v3") 29 | self.method = kwargs.get("method", "GET") 30 | self.timestamp = kwargs.get("oauth_timestamp", int(time())) 31 | 32 | def get_oauth_url(self): 33 | """ Returns the URL with OAuth params """ 34 | params = OrderedDict() 35 | 36 | if "?" in self.url: 37 | url = self.url[:self.url.find("?")] 38 | for key, value in parse_qsl(urlparse(self.url).query): 39 | params[key] = value 40 | else: 41 | url = self.url 42 | 43 | params["oauth_consumer_key"] = self.consumer_key 44 | params["oauth_timestamp"] = self.timestamp 45 | params["oauth_nonce"] = self.generate_nonce() 46 | params["oauth_signature_method"] = "HMAC-SHA256" 47 | params["oauth_signature"] = self.generate_oauth_signature(params, url) 48 | 49 | query_string = urlencode(params) 50 | 51 | return f"{url}?{query_string}" 52 | 53 | def generate_oauth_signature(self, params, url): 54 | """ Generate OAuth Signature """ 55 | if "oauth_signature" in params.keys(): 56 | del params["oauth_signature"] 57 | 58 | base_request_uri = quote(url, "") 59 | params = self.sorted_params(params) 60 | params = self.normalize_parameters(params) 61 | query_params = ["{param_key}%3D{param_value}".format(param_key=key, param_value=value) 62 | for key, value in params.items()] 63 | 64 | query_string = "%26".join(query_params) 65 | string_to_sign = f"{self.method}&{base_request_uri}&{query_string}" 66 | 67 | consumer_secret = str(self.consumer_secret) 68 | if self.version not in ["v1", "v2"]: 69 | consumer_secret += "&" 70 | 71 | hash_signature = HMAC( 72 | consumer_secret.encode(), 73 | str(string_to_sign).encode(), 74 | sha256 75 | ).digest() 76 | 77 | return b64encode(hash_signature).decode("utf-8").replace("\n", "") 78 | 79 | @staticmethod 80 | def sorted_params(params): 81 | ordered = OrderedDict() 82 | base_keys = sorted(set(k.split('[')[0] for k in params.keys())) 83 | 84 | for base in base_keys: 85 | for key in params.keys(): 86 | if key == base or key.startswith(base + '['): 87 | ordered[key] = params[key] 88 | 89 | return ordered 90 | 91 | @staticmethod 92 | def normalize_parameters(params): 93 | """ Normalize parameters """ 94 | params = params or {} 95 | normalized_parameters = OrderedDict() 96 | 97 | def get_value_like_as_php(val): 98 | """ Prepare value for quote """ 99 | try: 100 | base = basestring 101 | except NameError: 102 | base = (str, bytes) 103 | 104 | if isinstance(val, base): 105 | return val 106 | elif isinstance(val, bool): 107 | return "1" if val else "" 108 | elif isinstance(val, int): 109 | return str(val) 110 | elif isinstance(val, float): 111 | return str(int(val)) if val % 1 == 0 else str(val) 112 | else: 113 | return "" 114 | 115 | for key, value in params.items(): 116 | value = get_value_like_as_php(value) 117 | key = quote(unquote(str(key))).replace("%", "%25") 118 | value = quote(unquote(str(value))).replace("%", "%25") 119 | normalized_parameters[key] = value 120 | 121 | return normalized_parameters 122 | 123 | @staticmethod 124 | def generate_nonce(): 125 | """ Generate nonce number """ 126 | nonce = ''.join([str(randint(0, 9)) for i in range(8)]) 127 | return HMAC( 128 | nonce.encode(), 129 | "secret".encode(), 130 | sha1 131 | ).hexdigest() 132 | -------------------------------------------------------------------------------- /test_api.py: -------------------------------------------------------------------------------- 1 | """ API Tests """ 2 | import unittest 3 | import woocommerce 4 | from woocommerce import oauth 5 | from httmock import all_requests, HTTMock 6 | 7 | 8 | class WooCommerceTestCase(unittest.TestCase): 9 | """Test case for the client methods.""" 10 | 11 | def setUp(self): 12 | self.consumer_key = "ck_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" 13 | self.consumer_secret = "cs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" 14 | self.api = woocommerce.API( 15 | url="http://woo.test", 16 | consumer_key=self.consumer_key, 17 | consumer_secret=self.consumer_secret 18 | ) 19 | 20 | def test_version(self): 21 | """ Test default version """ 22 | api = woocommerce.API( 23 | url="https://woo.test", 24 | consumer_key=self.consumer_key, 25 | consumer_secret=self.consumer_secret 26 | ) 27 | 28 | self.assertEqual(api.version, "wc/v3") 29 | 30 | def test_non_ssl(self): 31 | """ Test non-ssl """ 32 | api = woocommerce.API( 33 | url="http://woo.test", 34 | consumer_key=self.consumer_key, 35 | consumer_secret=self.consumer_secret 36 | ) 37 | self.assertFalse(api.is_ssl) 38 | 39 | def test_with_ssl(self): 40 | """ Test ssl """ 41 | api = woocommerce.API( 42 | url="https://woo.test", 43 | consumer_key=self.consumer_key, 44 | consumer_secret=self.consumer_secret 45 | ) 46 | self.assertTrue(api.is_ssl, True) 47 | 48 | def test_with_timeout(self): 49 | """ Test timeout """ 50 | api = woocommerce.API( 51 | url="https://woo.test", 52 | consumer_key=self.consumer_key, 53 | consumer_secret=self.consumer_secret, 54 | timeout=10, 55 | ) 56 | self.assertEqual(api.timeout, 10) 57 | 58 | @all_requests 59 | def woo_test_mock(*args, **kwargs): 60 | """ URL Mock """ 61 | return {'status_code': 200, 62 | 'content': 'OK'} 63 | 64 | with HTTMock(woo_test_mock): 65 | # call requests 66 | status = api.get("products").status_code 67 | self.assertEqual(status, 200) 68 | 69 | def test_get(self): 70 | """ Test GET requests """ 71 | @all_requests 72 | def woo_test_mock(*args, **kwargs): 73 | """ URL Mock """ 74 | return {'status_code': 200, 75 | 'content': 'OK'} 76 | 77 | with HTTMock(woo_test_mock): 78 | # call requests 79 | status = self.api.get("products").status_code 80 | self.assertEqual(status, 200) 81 | 82 | def test_get_with_parameters(self): 83 | """ Test GET requests w/ url params """ 84 | @all_requests 85 | def woo_test_mock(*args, **kwargs): 86 | return {'status_code': 200, 87 | 'content': 'OK'} 88 | 89 | with HTTMock(woo_test_mock): 90 | # call requests 91 | status = self.api.get("products", params={"per_page": 10, "page": 1, "offset": 0}).status_code 92 | self.assertEqual(status, 200) 93 | 94 | def test_get_with_requests_kwargs(self): 95 | """ Test GET requests w/ optional requests-module kwargs """ 96 | 97 | @all_requests 98 | def woo_test_mock(*args, **kwargs): 99 | return {'status_code': 200, 100 | 'content': 'OK'} 101 | 102 | with HTTMock(woo_test_mock): 103 | # call requests 104 | status = self.api.get("products", allow_redirects=True).status_code 105 | self.assertEqual(status, 200) 106 | 107 | def test_post(self): 108 | """ Test POST requests """ 109 | @all_requests 110 | def woo_test_mock(*args, **kwargs): 111 | """ URL Mock """ 112 | return {'status_code': 201, 113 | 'content': 'OK'} 114 | 115 | with HTTMock(woo_test_mock): 116 | # call requests 117 | status = self.api.post("products", {}).status_code 118 | self.assertEqual(status, 201) 119 | 120 | def test_put(self): 121 | """ Test PUT requests """ 122 | @all_requests 123 | def woo_test_mock(*args, **kwargs): 124 | """ URL Mock """ 125 | return {'status_code': 200, 126 | 'content': 'OK'} 127 | 128 | with HTTMock(woo_test_mock): 129 | # call requests 130 | status = self.api.put("products", {}).status_code 131 | self.assertEqual(status, 200) 132 | 133 | def test_delete(self): 134 | """ Test DELETE requests """ 135 | @all_requests 136 | def woo_test_mock(*args, **kwargs): 137 | """ URL Mock """ 138 | return {'status_code': 200, 139 | 'content': 'OK'} 140 | 141 | with HTTMock(woo_test_mock): 142 | # call requests 143 | status = self.api.delete("products").status_code 144 | self.assertEqual(status, 200) 145 | 146 | def test_oauth_sorted_params(self): 147 | """ Test order of parameters for OAuth signature """ 148 | def check_sorted(keys, expected): 149 | params = oauth.OrderedDict() 150 | for key in keys: 151 | params[key] = '' 152 | 153 | ordered = list(oauth.OAuth.sorted_params(params).keys()) 154 | self.assertEqual(ordered, expected) 155 | 156 | check_sorted(['a', 'b'], ['a', 'b']) 157 | check_sorted(['b', 'a'], ['a', 'b']) 158 | check_sorted(['a', 'b[a]', 'b[b]', 'b[c]', 'c'], ['a', 'b[a]', 'b[b]', 'b[c]', 'c']) 159 | check_sorted(['a', 'b[c]', 'b[a]', 'b[b]', 'c'], ['a', 'b[c]', 'b[a]', 'b[b]', 'c']) 160 | check_sorted(['d', 'b[c]', 'b[a]', 'b[b]', 'c'], ['b[c]', 'b[a]', 'b[b]', 'c', 'd']) 161 | check_sorted(['a1', 'b[c]', 'b[a]', 'b[b]', 'a2'], ['a1', 'a2', 'b[c]', 'b[a]', 'b[b]']) 162 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | WooCommerce API - Python Client 2 | =============================== 3 | 4 | A Python wrapper for the WooCommerce REST API. Easily interact with the WooCommerce REST API using this library. 5 | 6 | .. image:: https://github.com/woocommerce/wc-api-python/actions/workflows/ci.yml/badge.svg?branch=trunk 7 | :target: https://github.com/woocommerce/wc-api-python/actions/workflows/ci.yml 8 | 9 | .. image:: https://img.shields.io/pypi/v/woocommerce.svg 10 | :target: https://pypi.python.org/pypi/WooCommerce 11 | 12 | 13 | Installation 14 | ------------ 15 | 16 | .. code-block:: bash 17 | 18 | pip install woocommerce 19 | 20 | Getting started 21 | --------------- 22 | 23 | Generate API credentials (Consumer Key & Consumer Secret) following this instructions http://woocommerce.github.io/woocommerce-rest-api-docs/#rest-api-keys. 24 | 25 | Check out the WooCommerce API endpoints and data that can be manipulated in http://woocommerce.github.io/woocommerce-rest-api-docs/. 26 | 27 | Setup 28 | ----- 29 | 30 | .. code-block:: python 31 | 32 | from woocommerce import API 33 | 34 | wcapi = API( 35 | url="http://example.com", 36 | consumer_key="ck_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 37 | consumer_secret="cs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 38 | version="wc/v3" 39 | ) 40 | 41 | Options 42 | ~~~~~~~ 43 | 44 | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ 45 | | Option | Type | Required | Description | 46 | +=======================+=============+==========+=======================================================================================================+ 47 | | ``url`` | ``string`` | yes | Your Store URL, example: http://woo.dev/ | 48 | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ 49 | | ``consumer_key`` | ``string`` | yes | Your API consumer key | 50 | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ 51 | | ``consumer_secret`` | ``string`` | yes | Your API consumer secret | 52 | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ 53 | | ``version`` | ``string`` | no | API version, default is ``wc/v3`` | 54 | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ 55 | | ``timeout`` | ``integer`` | no | Connection timeout, default is ``5`` | 56 | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ 57 | | ``verify_ssl`` | ``bool`` | no | Verify SSL when connect, use this option as ``False`` when need to test with self-signed certificates | 58 | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ 59 | | ``query_string_auth`` | ``bool`` | no | Force Basic Authentication as query string when ``True`` and using under HTTPS, default is ``False`` | 60 | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ 61 | | ``user_agent`` | ``string`` | no | Set a custom User-Agent, default is ``WooCommerce-Python-REST-API/3.0.0`` | 62 | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ 63 | | ``oauth_timestamp`` | ``integer`` | no | Custom timestamp for requests made with oAuth1.0a | 64 | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ 65 | | ``wp_api`` | ``bool`` | no | Set to ``False`` in order to use the legacy WooCommerce API (deprecated) | 66 | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ 67 | 68 | Methods 69 | ------- 70 | 71 | +--------------+----------------+------------------------------------------------------------------+ 72 | | Params | Type | Description | 73 | +==============+================+==================================================================+ 74 | | ``endpoint`` | ``string`` | WooCommerce API endpoint, example: ``customers`` or ``order/12`` | 75 | +--------------+----------------+------------------------------------------------------------------+ 76 | | ``data`` | ``dictionary`` | Data that will be converted to JSON | 77 | +--------------+----------------+------------------------------------------------------------------+ 78 | | ``**kwargs`` | ``dictionary`` | Accepts ``params``, also other Requests arguments | 79 | +--------------+----------------+------------------------------------------------------------------+ 80 | 81 | GET 82 | ~~~ 83 | 84 | - ``.get(endpoint, **kwargs)`` 85 | 86 | POST 87 | ~~~~ 88 | 89 | - ``.post(endpoint, data, **kwargs)`` 90 | 91 | PUT 92 | ~~~ 93 | 94 | - ``.put(endpoint, data), **kwargs`` 95 | 96 | DELETE 97 | ~~~~~~ 98 | 99 | - ``.delete(endpoint, **kwargs)`` 100 | 101 | OPTIONS 102 | ~~~~~~~ 103 | 104 | - ``.options(endpoint, **kwargs)`` 105 | 106 | Response 107 | -------- 108 | 109 | All methods will return `Response `_ object. 110 | 111 | Example of returned data: 112 | 113 | .. code-block:: bash 114 | 115 | >>> r = wcapi.get("products") 116 | >>> r.status_code 117 | 200 118 | >>> r.headers['content-type'] 119 | 'application/json; charset=UTF-8' 120 | >>> r.encoding 121 | 'UTF-8' 122 | >>> r.text 123 | u'{"products":[{"title":"Flying Ninja","id":70,...' // Json text 124 | >>> r.json() 125 | {u'products': [{u'sold_individually': False,... // Dictionary data 126 | 127 | Request with `params` example 128 | ----------------------------- 129 | 130 | .. code-block:: python 131 | 132 | from woocommerce import API 133 | 134 | wcapi = API( 135 | url="http://example.com", 136 | consumer_key="ck_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 137 | consumer_secret="cs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 138 | version="wc/v3" 139 | ) 140 | 141 | # Force delete example. 142 | print(wcapi.delete("products/100", params={"force": True}).json()) 143 | 144 | # Query example. 145 | print(wcapi.get("products", params={"per_page": 20}).json()) 146 | 147 | 148 | Changelog 149 | --------- 150 | 151 | See `CHANGELOG.md `_. 152 | --------------------------------------------------------------------------------