diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..80676bc --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +per-file-ignores = __init__.py:F401 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..82f9239 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions +name: Run CI +on: + push: + branches: [trunk] + pull_request: + branches: [trunk] +jobs: + build: + name: Build + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.6, 3.7, 3.8, 3.9] + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -r requirements-test.txt + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..d9562ac --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,28 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries +name: Publish package to PyPI +on: + release: + types: [created] +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/.gitignore b/.gitignore index 5b78e25..2b50e6d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,7 @@ __pycache__ build/ dist/ *.egg-info/ -run.py -run3.py +sample.py +.vscode/ +env/ +.pytest_cache/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0a4416f..0000000 --- a/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -language: python -python: - - "2.6" - - "2.7" - - "3.2" - - "3.3" - - "3.4" - - "nightly" -# command to install dependencies -install: - - pip install . - - pip install -r requirements-test.txt -# command to run tests -script: nosetests diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d08df1d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,94 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [3.0.0] - 2021-03-13 +### Removed +- Removed support to legacy Python versions, now supports Python 3.6+. +- Removed ordereddict package dependency. +### Added +- Added support for Python 3.8 and Python 3.9. +- Added option to set custom `user_agent`. +### Changed +- Updated default "User-Agent" to `WooCommerce-Python-REST-API/3.0.0`. +- Updated Request library to 2.25.1. +### Fixed +- Fixed Basic Auth in Python 3.8. + +## [2.1.1] - 2019-07-22 +### Changed +- Updated Request library to 2.22.0. +- Updated examples. + +## [2.1.0] - 2019-01-15 +### Changed +- Uses WP REST API by default, need to set `wp_api` as `False` in order to use the legacy WooCommerce API. +- Updated default REST API version to `wc/v3`. + +## [2.0.0] - 2019-01-15 +### Added +- Added support for custom timestamps in oAuth1.0a requests with `oauth_timestamp`. +- Allow pass custom arguments to "Requests" library.. +### Changed +- Updated "Requests" library to version 2.20.0. + +## [1.2.1] - 2016-12-14 +### Fixed +- Fixed use of `content-type` to fix issues with WordPress 4.7. + +## [1.2.0] - 2016-06-22 +### Added +- Added option `query_string_auth` to allow Basic Auth as query strings. + +## [1.1.1] - 2016-06-03 +### Fixed +- Fixed oAuth signature for WP REST API. + +## [1.1.0] - 2016-05-09 +### Added +- Added support for WP REST API. +- Added method to handle HTTP OPTIONS requests. + +## [1.0.5] - 2015-12-07 +### Fixed +- Fixed oAuth filters sorting. + +## [1.0.4] - 2015-09-25 +### Added +- Adds `timeout` argument for `API` class. + +## [1.0.3] - 2015-08-07 +### Changed +- Forced utf-8 encoding on `API.__request()` to avoid `UnicodeDecodeError`. + +## [1.0.2] - 2015-08-05 +### Fixed +- Fixed handler for query strings. + +## [1.0.1] - 2015-07-13 +### Fixed +- Fixed support for Python 2.6. + +## [1.0.0] - 2015-07-12 +### Added +- Initial release. + +[Unreleased]: https://github.com/woocommerce/wc-api-python/compare/3.0.0...HEAD +[3.0.0]: https://github.com/woocommerce/wc-api-python/compare/2.1.1...3.0.0 +[2.1.1]: https://github.com/woocommerce/wc-api-python/compare/2.0.1...2.1.1 +[2.1.0]: https://github.com/woocommerce/wc-api-python/compare/2.0.0...2.1.0 +[2.0.0]: https://github.com/woocommerce/wc-api-python/compare/1.2.1...2.0.0 +[1.2.1]: https://github.com/woocommerce/wc-api-python/compare/1.2.0...1.2.1 +[1.2.0]: https://github.com/woocommerce/wc-api-python/compare/1.1.1...1.2.0 +[1.1.1]: https://github.com/woocommerce/wc-api-python/compare/1.1.0...1.1.1 +[1.1.0]: https://github.com/woocommerce/wc-api-python/compare/1.0.5...1.1.0 +[1.0.5]: https://github.com/woocommerce/wc-api-python/compare/1.0.4...1.0.5 +[1.0.4]: https://github.com/woocommerce/wc-api-python/compare/1.0.3...1.0.4 +[1.0.3]: https://github.com/woocommerce/wc-api-python/compare/1.0.2...1.0.3 +[1.0.2]: https://github.com/woocommerce/wc-api-python/compare/1.0.1...1.0.2 +[1.0.1]: https://github.com/woocommerce/wc-api-python/compare/1.0.0...1.0.1 +[1.0.0]: https://github.com/woocommerce/wc-api-python/releases/tag/1.0.0 diff --git a/LICENSE.txt b/LICENSE.txt index 8b3652e..73248e1 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015, WooThemes (https://woocommerce.com/) +Copyright (c) 2021, Automattic (https://automattic.com/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.rst b/README.rst index eed4df7..38d507b 100644 --- a/README.rst +++ b/README.rst @@ -3,8 +3,8 @@ WooCommerce API - Python Client A Python wrapper for the WooCommerce REST API. Easily interact with the WooCommerce REST API using this library. -.. image:: https://secure.travis-ci.org/woocommerce/wc-api-python.svg - :target: http://travis-ci.org/woocommerce/wc-api-python +.. image:: https://github.com/woocommerce/wc-api-python/actions/workflows/ci.yml/badge.svg?branch=trunk + :target: https://github.com/woocommerce/wc-api-python/actions/workflows/ci.yml .. image:: https://img.shields.io/pypi/v/woocommerce.svg :target: https://pypi.python.org/pypi/WooCommerce @@ -20,27 +20,13 @@ Installation Getting started --------------- -Generate API credentials (Consumer Key & Consumer Secret) following this instructions http://docs.woocommerce.com/document/woocommerce-rest-api/. +Generate API credentials (Consumer Key & Consumer Secret) following this instructions http://woocommerce.github.io/woocommerce-rest-api-docs/#rest-api-keys. Check out the WooCommerce API endpoints and data that can be manipulated in http://woocommerce.github.io/woocommerce-rest-api-docs/. Setup ----- -Setup for the old WooCommerce API v3: - -.. code-block:: python - - from woocommerce import API - - wcapi = API( - url="http://example.com", - consumer_key="ck_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", - consumer_secret="cs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" - ) - -Setup for the new WP REST API integration (WooCommerce 2.6 or later): - .. code-block:: python from woocommerce import API @@ -49,8 +35,7 @@ Setup for the new WP REST API integration (WooCommerce 2.6 or later): url="http://example.com", consumer_key="ck_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", consumer_secret="cs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", - wp_api=True, - version="wc/v1" + version="wc/v3" ) Options @@ -61,13 +46,11 @@ Options +=======================+=============+==========+=======================================================================================================+ | ``url`` | ``string`` | yes | Your Store URL, example: http://woo.dev/ | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ -| ``consumerKey`` | ``string`` | yes | Your API consumer key | -+-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ -| ``consumerSecret`` | ``string`` | yes | Your API consumer secret | +| ``consumer_key`` | ``string`` | yes | Your API consumer key | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ -| ``wp_api`` | ``bool`` | no | Allow requests to the WP REST API (WooCommerce 2.6 or later) | +| ``consumer_secret`` | ``string`` | yes | Your API consumer secret | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ -| ``version`` | ``string`` | no | API version, default is ``v3`` | +| ``version`` | ``string`` | no | API version, default is ``wc/v3`` | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ | ``timeout`` | ``integer`` | no | Connection timeout, default is ``5`` | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ @@ -75,6 +58,12 @@ Options +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ | ``query_string_auth`` | ``bool`` | no | Force Basic Authentication as query string when ``True`` and using under HTTPS, default is ``False`` | +-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ +| ``user_agent`` | ``string`` | no | Set a custom User-Agent, default is ``WooCommerce-Python-REST-API/3.0.0`` | ++-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ +| ``oauth_timestamp`` | ``integer`` | no | Custom timestamp for requests made with oAuth1.0a | ++-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ +| ``wp_api`` | ``bool`` | no | Set to ``False`` in order to use the legacy WooCommerce API (deprecated) | ++-----------------------+-------------+----------+-------------------------------------------------------------------------------------------------------+ Methods ------- @@ -86,31 +75,33 @@ Methods +--------------+----------------+------------------------------------------------------------------+ | ``data`` | ``dictionary`` | Data that will be converted to JSON | +--------------+----------------+------------------------------------------------------------------+ +| ``**kwargs`` | ``dictionary`` | Accepts ``params``, also other Requests arguments | ++--------------+----------------+------------------------------------------------------------------+ GET ~~~ -- ``.get(endpoint)`` +- ``.get(endpoint, **kwargs)`` POST ~~~~ -- ``.post(endpoint, data)`` +- ``.post(endpoint, data, **kwargs)`` PUT ~~~ -- ``.put(endpoint, data)`` +- ``.put(endpoint, data), **kwargs`` DELETE ~~~~~~ -- ``.delete(endpoint)`` +- ``.delete(endpoint, **kwargs)`` OPTIONS ~~~~~~~ -- ``.options(endpoint)`` +- ``.options(endpoint, **kwargs)`` Response -------- @@ -133,52 +124,28 @@ Example of returned data: >>> r.json() {u'products': [{u'sold_individually': False,... // Dictionary data +Request with `params` example +----------------------------- -Changelog ---------- - -1.2.0 - 2016/06/22 -~~~~~~~~~~~~~~~~~~ - -- Added option ``query_string_auth`` to allow Basic Auth as query strings. - -1.1.1 - 2016/06/03 -~~~~~~~~~~~~~~~~~~ - -- Fixed oAuth signature for WP REST API. - -1.1.0 - 2016/05/09 -~~~~~~~~~~~~~~~~~~ - -- Added support for WP REST API. -- Added method to do HTTP OPTIONS requests. - -1.0.5 - 2015/12/07 -~~~~~~~~~~~~~~~~~~ - -- Fixed oAuth filters sorting. - -1.0.4 - 2015/09/25 -~~~~~~~~~~~~~~~~~~ - -- Implemented ``timeout`` argument for ``API`` class. - -1.0.3 - 2015/08/07 -~~~~~~~~~~~~~~~~~~ +.. code-block:: python -- Forced utf-8 encoding on ``API.__request()`` to avoid ``UnicodeDecodeError`` + from woocommerce import API -1.0.2 - 2015/08/05 -~~~~~~~~~~~~~~~~~~ + wcapi = API( + url="http://example.com", + consumer_key="ck_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + consumer_secret="cs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + version="wc/v3" + ) -- Fixed handler for query strings + # Force delete example. + print(wcapi.delete("products/100", params={"force": True}).json()) -1.0.1 - 2015/07/13 -~~~~~~~~~~~~~~~~~~ + # Query example. + print(wcapi.get("products", params={"per_page": 20}).json()) -- Fixed support for Python 2.6 -1.0.1 - 2015/07/12 -~~~~~~~~~~~~~~~~~~ +Changelog +--------- -- Initial version +See `CHANGELOG.md `_. diff --git a/requirements-test.txt b/requirements-test.txt index 5f4dc7e..950b2c5 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,3 +1,4 @@ -r requirements.txt -httmock==1.2.3 -nose==1.3.7 +httmock==1.4.0 +pytest==6.2.2 +flake8==3.8.4 diff --git a/requirements.txt b/requirements.txt index d090df9..9d84d35 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ -requests==2.7.0 -ordereddict==1.1 +requests==2.25.1 diff --git a/setup.py b/setup.py index 5901b0b..9dae917 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,8 @@ version=VERSION, description="A Python wrapper for the WooCommerce REST API", long_description=README, - author="Claudio Sanches @ WooThemes", + author="Claudio Sanches @ Automattic", + author_email="claudio+pypi@automattic.com", url="https://github.com/woocommerce/wc-api-python", license="MIT License", packages=[ @@ -35,20 +36,25 @@ include_package_data=True, platforms=['any'], install_requires=[ - "requests", - "ordereddict" + "requests" ], - classifiers=( + python_requires=">=3.6", + classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Natural Language :: English", "License :: OSI Approved :: MIT License", "Programming Language :: Python", - "Programming Language :: Python :: 2.6", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.2", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules" - ), + ], + keywords='woocommerce rest api', + project_urls={ + 'Documentation': 'https://woocommerce.github.io/woocommerce-rest-api-docs/?python#libraries-and-tools', + 'Source': 'https://github.com/woocommerce/wc-api-python', + 'Tracker': 'https://github.com/woocommerce/wc-api-python/issues', + }, ) diff --git a/tests.py b/test_api.py similarity index 81% rename from tests.py rename to test_api.py index ddae9df..c11759e 100644 --- a/tests.py +++ b/test_api.py @@ -25,7 +25,7 @@ def test_version(self): consumer_secret=self.consumer_secret ) - self.assertEqual(api.version, "v3") + self.assertEqual(api.version, "wc/v3") def test_non_ssl(self): """ Test non-ssl """ @@ -37,7 +37,7 @@ def test_non_ssl(self): self.assertFalse(api.is_ssl) def test_with_ssl(self): - """ Test non-ssl """ + """ Test ssl """ api = woocommerce.API( url="https://woo.test", consumer_key=self.consumer_key, @@ -46,7 +46,7 @@ def test_with_ssl(self): self.assertTrue(api.is_ssl, True) def test_with_timeout(self): - """ Test non-ssl """ + """ Test timeout """ api = woocommerce.API( url="https://woo.test", consumer_key=self.consumer_key, @@ -79,6 +79,31 @@ def woo_test_mock(*args, **kwargs): status = self.api.get("products").status_code self.assertEqual(status, 200) + def test_get_with_parameters(self): + """ Test GET requests w/ url params """ + @all_requests + def woo_test_mock(*args, **kwargs): + return {'status_code': 200, + 'content': 'OK'} + + with HTTMock(woo_test_mock): + # call requests + status = self.api.get("products", params={"per_page": 10, "page": 1, "offset": 0}).status_code + self.assertEqual(status, 200) + + def test_get_with_requests_kwargs(self): + """ Test GET requests w/ optional requests-module kwargs """ + + @all_requests + def woo_test_mock(*args, **kwargs): + return {'status_code': 200, + 'content': 'OK'} + + with HTTMock(woo_test_mock): + # call requests + status = self.api.get("products", allow_redirects=True).status_code + self.assertEqual(status, 200) + def test_post(self): """ Test POST requests """ @all_requests diff --git a/woocommerce/__init__.py b/woocommerce/__init__.py index 76fd0f3..15edcc8 100644 --- a/woocommerce/__init__.py +++ b/woocommerce/__init__.py @@ -5,13 +5,13 @@ ~~~~~~~~~~~~~~~ A Python wrapper for WooCommerce API. -:copyright: (c) 2015 by WooThemes. +:copyright: (c) 2019 by Automattic. :license: MIT, see LICENSE for details. """ __title__ = "woocommerce" -__version__ = "1.2.0" -__author__ = "Claudio Sanches @ WooThemes" +__version__ = "3.0.0" +__author__ = "Claudio Sanches @ Automattic" __license__ = "MIT" from woocommerce.api import API diff --git a/woocommerce/api.py b/woocommerce/api.py index 31d0e49..a97c901 100644 --- a/woocommerce/api.py +++ b/woocommerce/api.py @@ -5,13 +5,16 @@ """ __title__ = "woocommerce-api" -__version__ = "1.2.0" -__author__ = "Claudio Sanches @ WooThemes" +__version__ = "3.0.0" +__author__ = "Claudio Sanches @ Automattic" __license__ = "MIT" from requests import request from json import dumps as jsonencode +from time import time from woocommerce.oauth import OAuth +from requests.auth import HTTPBasicAuth +from urllib.parse import urlencode class API(object): @@ -21,12 +24,13 @@ def __init__(self, url, consumer_key, consumer_secret, **kwargs): self.url = url self.consumer_key = consumer_key self.consumer_secret = consumer_secret - self.wp_api = kwargs.get("wp_api", False) - self.version = kwargs.get("version", "v3") + self.wp_api = kwargs.get("wp_api", True) + self.version = kwargs.get("version", "wc/v3") self.is_ssl = self.__is_ssl() self.timeout = kwargs.get("timeout", 5) self.verify_ssl = kwargs.get("verify_ssl", True) self.query_string_auth = kwargs.get("query_string_auth", False) + self.user_agent = kwargs.get("user_agent", f"WooCommerce-Python-REST-API/{__version__}") def __is_ssl(self): """ Check if url use HTTPS """ @@ -38,48 +42,52 @@ def __get_url(self, endpoint): api = "wc-api" if url.endswith("/") is False: - url = "%s/" % url + url = f"{url}/" if self.wp_api: api = "wp-json" - return "%s%s/%s/%s" % (url, api, self.version, endpoint) + return f"{url}{api}/{self.version}/{endpoint}" - def __get_oauth_url(self, url, method): + def __get_oauth_url(self, url, method, **kwargs): """ Generate oAuth1.0a URL """ oauth = OAuth( url=url, consumer_key=self.consumer_key, consumer_secret=self.consumer_secret, version=self.version, - method=method + method=method, + oauth_timestamp=kwargs.get("oauth_timestamp", int(time())) ) return oauth.get_oauth_url() - def __request(self, method, endpoint, data): + def __request(self, method, endpoint, data, params=None, **kwargs): """ Do requests """ + if params is None: + params = {} url = self.__get_url(endpoint) auth = None - params = {} headers = { - "user-agent": "WooCommerce API Client-Python/%s" % __version__, - "content-type": "application/json;charset=utf-8", + "user-agent": f"{self.user_agent}", "accept": "application/json" } if self.is_ssl is True and self.query_string_auth is False: - auth = (self.consumer_key, self.consumer_secret) + auth = HTTPBasicAuth(self.consumer_key, self.consumer_secret) elif self.is_ssl is True and self.query_string_auth is True: - params = { + params.update({ "consumer_key": self.consumer_key, "consumer_secret": self.consumer_secret - } + }) else: - url = self.__get_oauth_url(url, method) + encoded_params = urlencode(params) + url = f"{url}?{encoded_params}" + url = self.__get_oauth_url(url, method, **kwargs) if data is not None: data = jsonencode(data, ensure_ascii=False).encode('utf-8') + headers["content-type"] = "application/json;charset=utf-8" return request( method=method, @@ -89,25 +97,26 @@ def __request(self, method, endpoint, data): params=params, data=data, timeout=self.timeout, - headers=headers + headers=headers, + **kwargs ) - def get(self, endpoint): + def get(self, endpoint, **kwargs): """ Get requests """ - return self.__request("GET", endpoint, None) + return self.__request("GET", endpoint, None, **kwargs) - def post(self, endpoint, data): + def post(self, endpoint, data, **kwargs): """ POST requests """ - return self.__request("POST", endpoint, data) + return self.__request("POST", endpoint, data, **kwargs) - def put(self, endpoint, data): + def put(self, endpoint, data, **kwargs): """ PUT requests """ - return self.__request("PUT", endpoint, data) + return self.__request("PUT", endpoint, data, **kwargs) - def delete(self, endpoint): + def delete(self, endpoint, **kwargs): """ DELETE requests """ - return self.__request("DELETE", endpoint, None) + return self.__request("DELETE", endpoint, None, **kwargs) - def options(self, endpoint): + def options(self, endpoint, **kwargs): """ OPTIONS requests """ - return self.__request("OPTIONS", endpoint, None) + return self.__request("OPTIONS", endpoint, None, **kwargs) diff --git a/woocommerce/oauth.py b/woocommerce/oauth.py index a53739f..62557c0 100644 --- a/woocommerce/oauth.py +++ b/woocommerce/oauth.py @@ -5,8 +5,8 @@ """ __title__ = "woocommerce-oauth" -__version__ = "1.2.0" -__author__ = "Claudio Sanches @ WooThemes" +__version__ = "3.0.0" +__author__ = "Claudio Sanches @ Automattic" __license__ = "MIT" from time import time @@ -14,17 +14,8 @@ from hmac import new as HMAC from hashlib import sha1, sha256 from base64 import b64encode - -try: - from urllib.parse import urlencode, quote, unquote, parse_qsl, urlparse -except ImportError: - from urllib import urlencode, quote, unquote - from urlparse import parse_qsl, urlparse - -try: - from collections import OrderedDict -except ImportError: - from ordereddict import OrderedDict +from collections import OrderedDict +from urllib.parse import urlencode, quote, unquote, parse_qsl, urlparse class OAuth(object): @@ -36,6 +27,7 @@ def __init__(self, url, consumer_key, consumer_secret, **kwargs): self.consumer_secret = consumer_secret self.version = kwargs.get("version", "v3") self.method = kwargs.get("method", "GET") + self.timestamp = kwargs.get("oauth_timestamp", int(time())) def get_oauth_url(self): """ Returns the URL with OAuth params """ @@ -49,14 +41,14 @@ def get_oauth_url(self): url = self.url params["oauth_consumer_key"] = self.consumer_key - params["oauth_timestamp"] = int(time()) + params["oauth_timestamp"] = self.timestamp params["oauth_nonce"] = self.generate_nonce() params["oauth_signature_method"] = "HMAC-SHA256" params["oauth_signature"] = self.generate_oauth_signature(params, url) query_string = urlencode(params) - return "%s?%s" % (url, query_string) + return f"{url}?{query_string}" def generate_oauth_signature(self, params, url): """ Generate OAuth Signature """ @@ -70,7 +62,7 @@ def generate_oauth_signature(self, params, url): for key, value in params.items()] query_string = "%26".join(query_params) - string_to_sign = "%s&%s&%s" % (self.method, base_request_uri, query_string) + string_to_sign = f"{self.method}&{base_request_uri}&{query_string}" consumer_secret = str(self.consumer_secret) if self.version not in ["v1", "v2"]: