From c0b234ce4633be3de0a719c111d2f98329af4998 Mon Sep 17 00:00:00 2001 From: Lynnea Tan Date: Mon, 14 May 2018 10:06:28 -0700 Subject: [PATCH 1/7] Add python six module for python compatibility --- patreon/api.py | 5 +++-- patreon/api_spec.py | 4 ++-- patreon/jsonapi/url_util.py | 3 +-- patreon/version_compatibility/urllib_parse.py | 7 ------- 4 files changed, 6 insertions(+), 13 deletions(-) delete mode 100644 patreon/version_compatibility/urllib_parse.py 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..4d46d53 100644 --- a/patreon/api_spec.py +++ b/patreon/api_spec.py @@ -6,8 +6,8 @@ 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/' @@ -35,7 +35,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, 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 From 363c3170105ce077d1a75bba8c4cdd5aa78e8f7b Mon Sep 17 00:00:00 2001 From: Lynnea Tan Date: Mon, 14 May 2018 11:19:20 -0700 Subject: [PATCH 2/7] Add string compatibility for python 2.7 --- patreon/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patreon/api.py b/patreon/api.py index c4aa18b..f2ccec6 100644 --- a/patreon/api.py +++ b/patreon/api.py @@ -70,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) != six.text_type: + elif current_dict and (type(current_dict) != six.text_type) or type(current_dict != str): raise Exception( 'Provided cursor path did not result in a link', current_dict ) From 67bc75add3a5cfd035cd3b23ba934f6aff9f8882 Mon Sep 17 00:00:00 2001 From: Lynnea Tan Date: Mon, 14 May 2018 11:26:45 -0700 Subject: [PATCH 3/7] Fix test error --- patreon/api.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/patreon/api.py b/patreon/api.py index f2ccec6..63a8e94 100644 --- a/patreon/api.py +++ b/patreon/api.py @@ -70,10 +70,11 @@ 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) != six.text_type) or type(current_dict != str): - raise Exception( - 'Provided cursor path did not result in a link', current_dict - ) + elif current_dict and type(current_dict) != str: + if type(current_dict) != six.text_type: + raise Exception( + 'Provided cursor path did not result in a link', current_dict + ) link = current_dict query_string = urlparse(link).query From a013543244e00246275557d3266fbc03aa79f62f Mon Sep 17 00:00:00 2001 From: Lynnea Tan Date: Tue, 15 May 2018 17:14:05 -0700 Subject: [PATCH 4/7] Fix tests --- patreon/api.py | 9 ++++----- patreon/api_spec.py | 38 ++++++++++++++++++++------------------ 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/patreon/api.py b/patreon/api.py index 63a8e94..c4aa18b 100644 --- a/patreon/api.py +++ b/patreon/api.py @@ -70,11 +70,10 @@ 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: - if type(current_dict) != six.text_type: - raise Exception( - 'Provided cursor path did not result in a link', current_dict - ) + elif current_dict and type(current_dict) != six.text_type: + raise Exception( + 'Provided cursor path did not result in a link', current_dict + ) link = current_dict query_string = urlparse(link).query diff --git a/patreon/api_spec.py b/patreon/api_spec.py index 4d46d53..f4f235a 100644 --- a/patreon/api_spec.py +++ b/patreon/api_spec.py @@ -1,6 +1,7 @@ import datetime import functools import mock +import six from patreon import api from patreon.jsonapi import url_util @@ -10,9 +11,10 @@ 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 = six.text_type('https://www.patreon.com/api/oauth2/api/') +MOCK_ACCESS_TOKEN = six.text_type('mock token') +MOCK_CURSOR_VALUE = six.text_type('Mock Cursor Value') + DEFAULT_API_HEADERS = { 'Authorization': 'Bearer ' + MOCK_ACCESS_TOKEN, @@ -23,7 +25,7 @@ def api_url(*segments, **query): - path = '/'.join(map(str, segments)) + path = six.text_type('/').join(map(six.text_type, segments)) fields = query.get('fields', None) includes = query.get('includes', None) @@ -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, + six.text_type('links'): { + six.text_type('next'): None, }, }) @@ -118,8 +120,8 @@ def test_extract_cursor_returns_None_when_link_is_malformed(): try: api.API.extract_cursor({ - 'links': { - 'next': 12, + six.text_type('links'): { + six.text_type('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(six.text_type('current_user')), client.fetch_user() @api_test() def test_can_fetch_campaign(): - expected_url = api_url('current_user', 'campaigns') + expected_url = api_url(six.text_type('current_user'), six.text_type('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'], + six.text_type('current_user'), + six.text_type('campaigns'), + includes=[six.text_type('rewards'), six.text_type('creator'), six.text_type('goals'), six.text_type('pledges')], ) return expected_url, response From 20c66000b2a21ab408ccbdc484dec1402b0f3b27 Mon Sep 17 00:00:00 2001 From: Lynnea Tan Date: Wed, 16 May 2018 12:22:48 -0700 Subject: [PATCH 5/7] Fix versioning --- CHANGELOG.md | 3 ++- patreon/api_spec.py | 12 ++++++------ setup.py | 6 ++++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3ff357..b2e1f3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ -# 0.5.0 +# 0.5.1 +* Use six module to handle version compatibility * Make URL query param ordering consistent * Use a first-party JSONAPIParser * README improvements diff --git a/patreon/api_spec.py b/patreon/api_spec.py index f4f235a..f87a617 100644 --- a/patreon/api_spec.py +++ b/patreon/api_spec.py @@ -160,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', + six.text_type('current_user'), + six.text_type('campaigns'), includes=['creator'], ) @@ -181,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 + six.text_type('campaigns'), MOCK_CAMPAIGN_ID, six.text_type('pledges'), **query_params ) return expected_url, response @@ -204,7 +204,7 @@ def test_can_fetch_page_of_pledges_with_arbitrary_cursor(): } expected_url = api_url( - 'campaigns', MOCK_CAMPAIGN_ID, 'pledges', **query_params + six.text_type('campaigns'), MOCK_CAMPAIGN_ID, six.text_type('pledges'), **query_params ) return expected_url, response @@ -235,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 + six.text_type('campaigns'), MOCK_CAMPAIGN_ID, six.text_type('pledges'), **query_params ) return expected_url, response @@ -266,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 + six.text_type('campaigns'), MOCK_CAMPAIGN_ID, six.text_type('pledges'), **query_params ) return expected_url, response diff --git a/setup.py b/setup.py index 3a1c25d..7f9855a 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', ] ) From 1cf42517f3e0e8ec262ad8fac1a3330aa5b7a14a Mon Sep 17 00:00:00 2001 From: Lynnea Tan Date: Wed, 16 May 2018 12:26:28 -0700 Subject: [PATCH 6/7] Add six version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7f9855a..aa70695 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ def is_running_tests(): setup_requires=setup_requires, install_requires=[ 'requests', - 'six==1.10.0', + 'six>=1.10.0', ], tests_require=[ 'pytest', From fbd17ee88fc0d9536fdf2509c354bcd32fae06cf Mon Sep 17 00:00:00 2001 From: Lynnea Tan Date: Wed, 16 May 2018 16:57:37 -0700 Subject: [PATCH 7/7] Remove unnecessary text_type conversions --- CHANGELOG.md | 8 ++++++-- patreon/api_spec.py | 48 ++++++++++++++++++++++----------------------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2e1f3b..30f901a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # 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 @@ -8,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_spec.py b/patreon/api_spec.py index f87a617..caec1a0 100644 --- a/patreon/api_spec.py +++ b/patreon/api_spec.py @@ -11,9 +11,9 @@ from six.moves.urllib.parse import urlencode MOCK_CAMPAIGN_ID = 12 -API_ROOT_ENDPOINT = six.text_type('https://www.patreon.com/api/oauth2/api/') -MOCK_ACCESS_TOKEN = six.text_type('mock token') -MOCK_CURSOR_VALUE = six.text_type('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 = { @@ -25,7 +25,7 @@ def api_url(*segments, **query): - path = six.text_type('/').join(map(six.text_type, segments)) + path = '/'.join(map(str, segments)) fields = query.get('fields', None) includes = query.get('includes', None) @@ -87,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( { - six.text_type('links'): + six.text_type('links'): { - six.text_type('next'): - six.text_type('https://patreon.com/members?page[cursor]=') + + six.text_type('next'): + six.text_type('https://patreon.com/members?page[cursor]=') + MOCK_CURSOR_VALUE, }, } @@ -100,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( { - six.text_type('links'): { - six.text_type('next'): six.text_type('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'), }, } ) @@ -109,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({ - six.text_type('links'): { - six.text_type('next'): None, + 'links': { + 'next': None, }, }) @@ -120,8 +120,8 @@ def test_extract_cursor_returns_None_when_link_is_malformed(): try: api.API.extract_cursor({ - six.text_type('links'): { - six.text_type('next'): 12, + 'links': { + 'next': 12, }, }) @@ -134,12 +134,12 @@ def test_extract_cursor_returns_None_when_link_is_malformed(): @api_test() def test_can_fetch_user(): - return api_url(six.text_type('current_user')), client.fetch_user() + return api_url( 'current_user'), client.fetch_user() @api_test() def test_can_fetch_campaign(): - expected_url = api_url(six.text_type('current_user'), six.text_type('campaigns')) + expected_url = api_url( 'current_user', 'campaigns') response = client.fetch_campaign() return expected_url, response @@ -149,9 +149,9 @@ def test_can_fetch_api_and_patrons(): response = client.fetch_campaign_and_patrons() expected_url = api_url( - six.text_type('current_user'), - six.text_type('campaigns'), - includes=[six.text_type('rewards'), six.text_type('creator'), six.text_type('goals'), six.text_type('pledges')], + 'current_user', + 'campaigns', + includes=[ 'rewards', 'creator', 'goals', 'pledges'], ) return expected_url, response @@ -160,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( - six.text_type('current_user'), - six.text_type('campaigns'), + 'current_user', + 'campaigns', includes=['creator'], ) @@ -181,7 +181,7 @@ def test_can_fetch_page_of_pledges(): query_params = {'page[count]': PAGE_COUNT} expected_url = api_url( - six.text_type('campaigns'), MOCK_CAMPAIGN_ID, six.text_type('pledges'), **query_params + 'campaigns', MOCK_CAMPAIGN_ID, 'pledges', **query_params ) return expected_url, response @@ -204,7 +204,7 @@ def test_can_fetch_page_of_pledges_with_arbitrary_cursor(): } expected_url = api_url( - six.text_type('campaigns'), MOCK_CAMPAIGN_ID, six.text_type('pledges'), **query_params + 'campaigns', MOCK_CAMPAIGN_ID, 'pledges', **query_params ) return expected_url, response @@ -235,7 +235,7 @@ def test_can_fetch_page_of_pledges_with_custom_options_without_tzinfo(): } expected_url = api_url( - six.text_type('campaigns'), MOCK_CAMPAIGN_ID, six.text_type('pledges'), **query_params + 'campaigns', MOCK_CAMPAIGN_ID, 'pledges', **query_params ) return expected_url, response @@ -266,7 +266,7 @@ def test_can_fetch_page_of_pledges_with_custom_options_with_tzinfo(): } expected_url = api_url( - six.text_type('campaigns'), MOCK_CAMPAIGN_ID, six.text_type('pledges'), **query_params + 'campaigns', MOCK_CAMPAIGN_ID, 'pledges', **query_params ) return expected_url, response