diff --git a/CHANGELOG.md b/CHANGELOG.md index c3ff357..30f901a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ -# 0.5.0 +# 0.5.1 +## Added +* Use six module to handle version compatibility +## Fixed +* Fixed the error that occured when calling extract_cursor in python2 +# 0.5.0 * Make URL query param ordering consistent * Use a first-party JSONAPIParser * README improvements @@ -7,4 +12,4 @@ * Set User-Agent for proper traffic attribution * Make currently-unused parameter optional * Improve flask example -* Thanks to: @21echoes, @tanabi, @phildini \ No newline at end of file +* Thanks to: @21echoes, @tanabi, @phildini diff --git a/patreon/api.py b/patreon/api.py index b2072b9..c4aa18b 100644 --- a/patreon/api.py +++ b/patreon/api.py @@ -1,11 +1,12 @@ import requests +import six from patreon.jsonapi.parser import JSONAPIParser from patreon.jsonapi.url_util import build_url from patreon.schemas import campaign from patreon.utils import user_agent_string from patreon.version_compatibility.utc_timezone import utc_timezone -from patreon.version_compatibility.urllib_parse import urlencode, urlparse, parse_qs +from six.moves.urllib.parse import urlparse, parse_qs, urlencode class API(object): @@ -69,7 +70,7 @@ def head_and_tail(path): if current_dict is None or (head is not None and tail is None): return None # Path stopped before leaf was reached - elif current_dict and type(current_dict) != str: + elif current_dict and type(current_dict) != six.text_type: raise Exception( 'Provided cursor path did not result in a link', current_dict ) diff --git a/patreon/api_spec.py b/patreon/api_spec.py index 7a3ef85..caec1a0 100644 --- a/patreon/api_spec.py +++ b/patreon/api_spec.py @@ -1,18 +1,20 @@ import datetime import functools import mock +import six from patreon import api from patreon.jsonapi import url_util from patreon.jsonapi.parser import JSONAPIParser from patreon.utils import user_agent_string -from patreon.version_compatibility import urllib_parse from patreon.version_compatibility.utc_timezone import utc_timezone +from six.moves.urllib.parse import urlencode MOCK_CAMPAIGN_ID = 12 -API_ROOT_ENDPOINT = 'https://www.patreon.com/api/oauth2/api/' -MOCK_ACCESS_TOKEN = 'mock token' -MOCK_CURSOR_VALUE = 'Mock Cursor Value' +API_ROOT_ENDPOINT = 'https://www.patreon.com/api/oauth2/api/' +MOCK_ACCESS_TOKEN = 'mock token' +MOCK_CURSOR_VALUE = 'Mock Cursor Value' + DEFAULT_API_HEADERS = { 'Authorization': 'Bearer ' + MOCK_ACCESS_TOKEN, @@ -35,7 +37,7 @@ def api_url(*segments, **query): del query['includes'] if query: - path += '?' + urllib_parse.urlencode(query) + path += '?' + urlencode(query) return url_util.build_url( API_ROOT_ENDPOINT + path, @@ -85,10 +87,10 @@ def execute_test(method_func, *args, **kwargs): def test_extract_cursor_returns_cursor_when_provided(): assert MOCK_CURSOR_VALUE == api.API.extract_cursor( { - 'links': + six.text_type('links'): { - 'next': - 'https://patreon.com/members?page[cursor]=' + + six.text_type('next'): + six.text_type('https://patreon.com/members?page[cursor]=') + MOCK_CURSOR_VALUE, }, } @@ -98,8 +100,8 @@ def test_extract_cursor_returns_cursor_when_provided(): def test_extract_cursor_returns_None_when_no_cursor_provided(): assert None is api.API.extract_cursor( { - 'links': { - 'next': 'https://patreon.com/members?page[offset]=25', + six.text_type('links'): { + six.text_type('next'): six.text_type('https://patreon.com/members?page[offset]=25'), }, } ) @@ -107,8 +109,8 @@ def test_extract_cursor_returns_None_when_no_cursor_provided(): def test_extract_cursor_returns_None_when_link_is_not_a_string(): assert None is api.API.extract_cursor({ - 'links': { - 'next': None, + 'links': { + 'next': None, }, }) @@ -118,8 +120,8 @@ def test_extract_cursor_returns_None_when_link_is_malformed(): try: api.API.extract_cursor({ - 'links': { - 'next': 12, + 'links': { + 'next': 12, }, }) @@ -132,12 +134,12 @@ def test_extract_cursor_returns_None_when_link_is_malformed(): @api_test() def test_can_fetch_user(): - return api_url('current_user'), client.fetch_user() + return api_url( 'current_user'), client.fetch_user() @api_test() def test_can_fetch_campaign(): - expected_url = api_url('current_user', 'campaigns') + expected_url = api_url( 'current_user', 'campaigns') response = client.fetch_campaign() return expected_url, response @@ -147,9 +149,9 @@ def test_can_fetch_api_and_patrons(): response = client.fetch_campaign_and_patrons() expected_url = api_url( - 'current_user', - 'campaigns', - includes=['rewards', 'creator', 'goals', 'pledges'], + 'current_user', + 'campaigns', + includes=[ 'rewards', 'creator', 'goals', 'pledges'], ) return expected_url, response @@ -158,8 +160,8 @@ def test_can_fetch_api_and_patrons(): @api_test() def test_can_fetch_api_and_patrons_with_custom_includes(): expected_url = api_url( - 'current_user', - 'campaigns', + 'current_user', + 'campaigns', includes=['creator'], ) @@ -179,7 +181,7 @@ def test_can_fetch_page_of_pledges(): query_params = {'page[count]': PAGE_COUNT} expected_url = api_url( - 'campaigns', MOCK_CAMPAIGN_ID, 'pledges', **query_params + 'campaigns', MOCK_CAMPAIGN_ID, 'pledges', **query_params ) return expected_url, response @@ -202,7 +204,7 @@ def test_can_fetch_page_of_pledges_with_arbitrary_cursor(): } expected_url = api_url( - 'campaigns', MOCK_CAMPAIGN_ID, 'pledges', **query_params + 'campaigns', MOCK_CAMPAIGN_ID, 'pledges', **query_params ) return expected_url, response @@ -233,7 +235,7 @@ def test_can_fetch_page_of_pledges_with_custom_options_without_tzinfo(): } expected_url = api_url( - 'campaigns', MOCK_CAMPAIGN_ID, 'pledges', **query_params + 'campaigns', MOCK_CAMPAIGN_ID, 'pledges', **query_params ) return expected_url, response @@ -264,7 +266,7 @@ def test_can_fetch_page_of_pledges_with_custom_options_with_tzinfo(): } expected_url = api_url( - 'campaigns', MOCK_CAMPAIGN_ID, 'pledges', **query_params + 'campaigns', MOCK_CAMPAIGN_ID, 'pledges', **query_params ) return expected_url, response diff --git a/patreon/jsonapi/url_util.py b/patreon/jsonapi/url_util.py index 5d97e3f..4cb41c5 100644 --- a/patreon/jsonapi/url_util.py +++ b/patreon/jsonapi/url_util.py @@ -1,6 +1,5 @@ from collections import OrderedDict - -from patreon.version_compatibility.urllib_parse import urlencode +from six.moves.urllib.parse import urlencode def joined_or_null(arr): diff --git a/patreon/version_compatibility/urllib_parse.py b/patreon/version_compatibility/urllib_parse.py deleted file mode 100644 index b956d97..0000000 --- a/patreon/version_compatibility/urllib_parse.py +++ /dev/null @@ -1,7 +0,0 @@ -try: - # python2 - from urllib import urlencode - from urlparse import urlparse, parse_qs -except ImportError: - # python3 - from urllib.parse import urlencode, urlparse, parse_qs diff --git a/setup.py b/setup.py index 3a1c25d..aa70695 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ def is_running_tests(): setup( name='patreon', - version='0.5.0', + version='0.5.1', description=DESCRIPTION, url='http://github.com/Patreon/patreon-python', author='Patreon', @@ -29,6 +29,7 @@ def is_running_tests(): setup_requires=setup_requires, install_requires=[ 'requests', + 'six>=1.10.0', ], tests_require=[ 'pytest', @@ -37,7 +38,7 @@ def is_running_tests(): ], zip_safe=True, classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', @@ -50,5 +51,6 @@ def is_running_tests(): 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', ] )