From 1b0b2a717e84980defa2cd1f42b5d5eadde36449 Mon Sep 17 00:00:00 2001 From: wkoot Date: Tue, 28 Feb 2017 13:27:38 +0100 Subject: [PATCH 1/5] Remove dupe tests --- tests.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests.py b/tests.py index d18e26fa..8e0bfded 100755 --- a/tests.py +++ b/tests.py @@ -189,7 +189,7 @@ def test_user_search(self): self.api.user_search('mikeyk', 10) def test_user_follows(self): - for page in self.api.user_followed_by(as_generator=True): + for page in self.api.user_follows(as_generator=True): str(page) def test_user_followed_by(self): @@ -225,12 +225,6 @@ def test_tag_search(self): def test_tag(self): self.api.tag("coffee") - def test_user_follows(self): - self.api.user_follows() - - def test_user_requested_by(self): - self.api.user_followed_by() - def test_user_incoming_requests(self): self.api.user_incoming_requests() From 51cb414dc625c57d9b092e07f78f188ab6bf9285 Mon Sep 17 00:00:00 2001 From: wkoot Date: Tue, 28 Feb 2017 13:28:10 +0100 Subject: [PATCH 2/5] pep8 formatting --- instagram/client.py | 240 ++++++++++++++++++++++---------------------- 1 file changed, 120 insertions(+), 120 deletions(-) diff --git a/instagram/client.py b/instagram/client.py index c30c7eef..e397442c 100644 --- a/instagram/client.py +++ b/instagram/client.py @@ -9,7 +9,6 @@ class InstagramAPI(oauth2.OAuth2API): - host = "api.instagram.com" base_path = "/v1" access_token_field = "access_token" @@ -17,7 +16,7 @@ class InstagramAPI(oauth2.OAuth2API): access_token_url = "https://api.instagram.com/oauth/access_token" protocol = "https" api_name = "Instagram" - x_ratelimit_remaining = None + x_ratelimit_remaining = None x_ratelimit = None def __init__(self, *args, **kwargs): @@ -29,179 +28,178 @@ def __init__(self, *args, **kwargs): super(InstagramAPI, self).__init__(**kwargs) media_popular = bind_method( - path="/media/popular", - accepts_parameters=MEDIA_ACCEPT_PARAMETERS, - root_class=Media) + path="/media/popular", + accepts_parameters=MEDIA_ACCEPT_PARAMETERS, + root_class=Media) media_search = bind_method( - path="/media/search", - accepts_parameters=SEARCH_ACCEPT_PARAMETERS + ['lat', 'lng', 'min_timestamp', 'max_timestamp', 'distance'], - root_class=Media) + path="/media/search", + accepts_parameters=SEARCH_ACCEPT_PARAMETERS + ['lat', 'lng', 'min_timestamp', 'max_timestamp', 'distance'], + root_class=Media) media_shortcode = bind_method( - path="/media/shortcode/{shortcode}", - accepts_parameters=['shortcode'], - response_type="entry", - root_class=MediaShortcode, - exclude_format=True) - + path="/media/shortcode/{shortcode}", + accepts_parameters=['shortcode'], + response_type="entry", + root_class=MediaShortcode, + exclude_format=True) media_likes = bind_method( - path="/media/{media_id}/likes", - accepts_parameters=['media_id'], - root_class=User) + path="/media/{media_id}/likes", + accepts_parameters=['media_id'], + root_class=User) like_media = bind_method( - path="/media/{media_id}/likes", - method="POST", - signature=True, - accepts_parameters=['media_id'], - response_type="empty") + path="/media/{media_id}/likes", + method="POST", + signature=True, + accepts_parameters=['media_id'], + response_type="empty") unlike_media = bind_method( - path="/media/{media_id}/likes", - method="DELETE", - signature=True, - accepts_parameters=['media_id'], - response_type="empty") + path="/media/{media_id}/likes", + method="DELETE", + signature=True, + accepts_parameters=['media_id'], + response_type="empty") create_media_comment = bind_method( - path="/media/{media_id}/comments", - method="POST", - signature=True, - accepts_parameters=['media_id', 'text'], - response_type="empty", - root_class=Comment) + path="/media/{media_id}/comments", + method="POST", + signature=True, + accepts_parameters=['media_id', 'text'], + response_type="empty", + root_class=Comment) delete_comment = bind_method( - path="/media/{media_id}/comments/{comment_id}", - method="DELETE", - signature=True, - accepts_parameters=['media_id', 'comment_id'], - response_type="empty") + path="/media/{media_id}/comments/{comment_id}", + method="DELETE", + signature=True, + accepts_parameters=['media_id', 'comment_id'], + response_type="empty") media_comments = bind_method( - path="/media/{media_id}/comments", - method="GET", - accepts_parameters=['media_id'], - response_type="list", - root_class=Comment) + path="/media/{media_id}/comments", + method="GET", + accepts_parameters=['media_id'], + response_type="list", + root_class=Comment) media = bind_method( - path="/media/{media_id}", - accepts_parameters=['media_id'], - response_type="entry", - root_class=Media) + path="/media/{media_id}", + accepts_parameters=['media_id'], + response_type="entry", + root_class=Media) user_media_feed = bind_method( - path="/users/self/media/recent", - accepts_parameters=MEDIA_ACCEPT_PARAMETERS, - root_class=Media, - paginates=True) + path="/users/self/media/recent", + accepts_parameters=MEDIA_ACCEPT_PARAMETERS, + root_class=Media, + paginates=True) user_liked_media = bind_method( - path="/users/self/media/liked", - accepts_parameters=MEDIA_ACCEPT_PARAMETERS, - root_class=Media, - paginates=True) + path="/users/self/media/liked", + accepts_parameters=MEDIA_ACCEPT_PARAMETERS, + root_class=Media, + paginates=True) user_recent_media = bind_method( - path="/users/{user_id}/media/recent", - accepts_parameters=MEDIA_ACCEPT_PARAMETERS + ['user_id', 'min_id', 'max_timestamp', 'min_timestamp'], - root_class=Media, - paginates=True) + path="/users/{user_id}/media/recent", + accepts_parameters=MEDIA_ACCEPT_PARAMETERS + ['user_id', 'min_id', 'max_timestamp', 'min_timestamp'], + root_class=Media, + paginates=True) user_search = bind_method( - path="/users/search", - accepts_parameters=SEARCH_ACCEPT_PARAMETERS, - root_class=User) + path="/users/search", + accepts_parameters=SEARCH_ACCEPT_PARAMETERS, + root_class=User) user_follows = bind_method( - path="/users/{user_id}/follows", - accepts_parameters=["user_id"], - paginates=True, - root_class=User) + path="/users/{user_id}/follows", + accepts_parameters=["user_id"], + paginates=True, + root_class=User) user_followed_by = bind_method( - path="/users/{user_id}/followed-by", - accepts_parameters=["user_id"], - paginates=True, - root_class=User) + path="/users/{user_id}/followed-by", + accepts_parameters=["user_id"], + paginates=True, + root_class=User) user = bind_method( - path="/users/{user_id}", - accepts_parameters=["user_id"], - root_class=User, - response_type="entry") + path="/users/{user_id}", + accepts_parameters=["user_id"], + root_class=User, + response_type="entry") location_recent_media = bind_method( - path="/locations/{location_id}/media/recent", - accepts_parameters=MEDIA_ACCEPT_PARAMETERS + ['location_id'], - root_class=Media, - paginates=True) + path="/locations/{location_id}/media/recent", + accepts_parameters=MEDIA_ACCEPT_PARAMETERS + ['location_id'], + root_class=Media, + paginates=True) location_search = bind_method( - path="/locations/search", - accepts_parameters=SEARCH_ACCEPT_PARAMETERS + ['lat', 'lng', 'foursquare_id', 'foursquare_v2_id'], - root_class=Location) + path="/locations/search", + accepts_parameters=SEARCH_ACCEPT_PARAMETERS + ['lat', 'lng', 'foursquare_id', 'foursquare_v2_id'], + root_class=Location) location = bind_method( - path="/locations/{location_id}", - accepts_parameters=["location_id"], - root_class=Location, - response_type="entry") + path="/locations/{location_id}", + accepts_parameters=["location_id"], + root_class=Location, + response_type="entry") geography_recent_media = bind_method( - path="/geographies/{geography_id}/media/recent", - accepts_parameters=MEDIA_ACCEPT_PARAMETERS + ["geography_id"], - root_class=Media, - paginates=True) + path="/geographies/{geography_id}/media/recent", + accepts_parameters=MEDIA_ACCEPT_PARAMETERS + ["geography_id"], + root_class=Media, + paginates=True) tag_recent_media = bind_method( - path="/tags/{tag_name}/media/recent", - accepts_parameters=['count', 'max_tag_id', 'tag_name'], - root_class=Media, - paginates=True) + path="/tags/{tag_name}/media/recent", + accepts_parameters=['count', 'max_tag_id', 'tag_name'], + root_class=Media, + paginates=True) tag_search = bind_method( - path="/tags/search", - accepts_parameters=SEARCH_ACCEPT_PARAMETERS, - root_class=Tag, - paginates=True) + path="/tags/search", + accepts_parameters=SEARCH_ACCEPT_PARAMETERS, + root_class=Tag, + paginates=True) tag = bind_method( - path="/tags/{tag_name}", - accepts_parameters=["tag_name"], - root_class=Tag, - response_type="entry") + path="/tags/{tag_name}", + accepts_parameters=["tag_name"], + root_class=Tag, + response_type="entry") user_incoming_requests = bind_method( - path="/users/self/requested-by", - root_class=User) + path="/users/self/requested-by", + root_class=User) change_user_relationship = bind_method( - method="POST", - path="/users/{user_id}/relationship", - signature=True, - root_class=Relationship, - accepts_parameters=["user_id", "action"], - paginates=True, - requires_target_user=True, - response_type="entry") + method="POST", + path="/users/{user_id}/relationship", + signature=True, + root_class=Relationship, + accepts_parameters=["user_id", "action"], + paginates=True, + requires_target_user=True, + response_type="entry") user_relationship = bind_method( - method="GET", - path="/users/{user_id}/relationship", - root_class=Relationship, - accepts_parameters=["user_id"], - paginates=False, - requires_target_user=True, - response_type="entry") + method="GET", + path="/users/{user_id}/relationship", + root_class=Relationship, + accepts_parameters=["user_id"], + paginates=False, + requires_target_user=True, + response_type="entry") def _make_relationship_shortcut(action): def _inner(self, *args, **kwargs): - return self.change_user_relationship(user_id=kwargs.get("user_id"), - action=action) + return self.change_user_relationship(user_id=kwargs.get("user_id"), action=action) + return _inner follow_user = _make_relationship_shortcut('follow') @@ -225,7 +223,9 @@ def _make_subscription_action(method, include=None, exclude=None): accepts_parameters.extend(include) if exclude: accepts_parameters = [x for x in accepts_parameters if x not in exclude] + signature = False if method == 'GET' else True + return bind_method( path="/subscriptions", method=method, From 1de32e07be3e1bf721cba6ccdc5c2ee593938281 Mon Sep 17 00:00:00 2001 From: wkoot Date: Tue, 28 Feb 2017 13:40:13 +0100 Subject: [PATCH 3/5] Move api client helper methods outside class --- instagram/client.py | 68 +++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/instagram/client.py b/instagram/client.py index e397442c..df283eea 100644 --- a/instagram/client.py +++ b/instagram/client.py @@ -8,6 +8,40 @@ SUPPORTED_FORMATS = ['json'] +def _make_relationship_shortcut(action): + def _inner(api, *args, **kwargs): + return api.change_user_relationship(user_id=kwargs.get("user_id"), action=action) + + return _inner + + +def _make_subscription_action(method, include=None, exclude=None): + accepts_parameters = ["object", + "aspect", + "object_id", # Optional if subscribing to all users + "callback_url", + "lat", # Geography + "lng", # Geography + "radius", # Geography + "verify_token"] + + if include: + accepts_parameters.extend(include) + if exclude: + accepts_parameters = [x for x in accepts_parameters if x not in exclude] + + signature = False if method == 'GET' else True + + return bind_method( + path="/subscriptions", + method=method, + accepts_parameters=accepts_parameters, + include_secret=True, + objectify_response=False, + signature=signature, + ) + + class InstagramAPI(oauth2.OAuth2API): host = "api.instagram.com" base_path = "/v1" @@ -196,12 +230,6 @@ def __init__(self, *args, **kwargs): requires_target_user=True, response_type="entry") - def _make_relationship_shortcut(action): - def _inner(self, *args, **kwargs): - return self.change_user_relationship(user_id=kwargs.get("user_id"), action=action) - - return _inner - follow_user = _make_relationship_shortcut('follow') unfollow_user = _make_relationship_shortcut('unfollow') block_user = _make_relationship_shortcut('block') @@ -209,32 +237,6 @@ def _inner(self, *args, **kwargs): approve_user_request = _make_relationship_shortcut('approve') ignore_user_request = _make_relationship_shortcut('ignore') - def _make_subscription_action(method, include=None, exclude=None): - accepts_parameters = ["object", - "aspect", - "object_id", # Optional if subscribing to all users - "callback_url", - "lat", # Geography - "lng", # Geography - "radius", # Geography - "verify_token"] - - if include: - accepts_parameters.extend(include) - if exclude: - accepts_parameters = [x for x in accepts_parameters if x not in exclude] - - signature = False if method == 'GET' else True - - return bind_method( - path="/subscriptions", - method=method, - accepts_parameters=accepts_parameters, - include_secret=True, - objectify_response=False, - signature=signature, - ) - create_subscription = _make_subscription_action('POST') list_subscriptions = _make_subscription_action('GET') - delete_subscriptions = _make_subscription_action('DELETE', exclude=['object_id'], include=['id']) + delete_subscriptions = _make_subscription_action('DELETE', include=['id'], exclude=['object_id']) From ce571471e01b5564bd000cafea17bf42ba96c311 Mon Sep 17 00:00:00 2001 From: wkoot Date: Tue, 28 Feb 2017 13:45:08 +0100 Subject: [PATCH 4/5] Clean up warnings --- instagram/bind.py | 50 +++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/instagram/bind.py b/instagram/bind.py index 452cf3a1..2288e9e1 100644 --- a/instagram/bind.py +++ b/instagram/bind.py @@ -1,4 +1,3 @@ -import urllib from .oauth2 import OAuth2Request import re from .json_import import simplejson @@ -6,14 +5,12 @@ from hashlib import sha256 import six from six.moves.urllib.parse import quote -import sys re_path_template = re.compile('{\w+}') def encode_string(value): - return value.encode('utf-8') \ - if isinstance(value, six.text_type) else str(value) + return value.encode('utf-8') if isinstance(value, six.text_type) else str(value) class InstagramClientError(Exception): @@ -29,7 +26,6 @@ def __str__(self): class InstagramAPIError(Exception): - def __init__(self, status_code, error_type, error_message, *args, **kwargs): self.status_code = status_code self.error_type = error_type @@ -40,7 +36,6 @@ def __str__(self): def bind_method(**config): - class InstagramAPIMethod(object): path = config['path'] @@ -62,6 +57,7 @@ def __init__(self, api, *args, **kwargs): self.pagination_format = 'next_url' else: self.pagination_format = kwargs.pop('pagination_format', 'next_url') + self.return_json = kwargs.pop("return_json", False) self.max_pages = kwargs.pop("max_pages", 3) self.with_next_url = kwargs.pop("with_next_url", None) @@ -85,9 +81,10 @@ def _build_parameters(self, args, kwargs): continue if key in self.parameters: raise InstagramClientError("Parameter %s already supplied" % key) + self.parameters[key] = encode_string(value) - if 'user_id' in self.accepts_parameters and not 'user_id' in self.parameters \ - and not self.requires_target_user: + + if 'user_id' in self.accepts_parameters and 'user_id' not in self.parameters and not self.requires_target_user: self.parameters['user_id'] = 'self' def _build_path(self): @@ -103,7 +100,7 @@ def _build_path(self): self.path = self.path.replace(variable, value) if self.api.format and not self.exclude_format: - self.path = self.path + '.%s' % self.api.format + self.path += '.%s' % self.api.format def _build_pagination_info(self, content_obj): """Extract pagination information in the desired format.""" @@ -112,11 +109,12 @@ def _build_pagination_info(self, content_obj): return pagination.get('next_url') if self.pagination_format == 'dict': return pagination + raise Exception('Invalid value for pagination_format: %s' % self.pagination_format) - + def _do_api_request(self, url, method="GET", body=None, headers=None): headers = headers or {} - if self.signature and self.api.client_ips != None and self.api.client_secret != None: + if self.signature and self.api.client_ips is not None and self.api.client_secret is not None: secret = self.api.client_secret ips = self.api.client_ips signature = hmac.new(secret, ips, sha256).hexdigest() @@ -127,20 +125,24 @@ def _do_api_request(self, url, method="GET", body=None, headers=None): raise InstagramAPIError(response['status'], "Rate limited", "Your client is making too many request per second") if hasattr(content, "decode"): content = content.decode('utf-8') + try: content_obj = simplejson.loads(content) except ValueError: raise InstagramClientError('Unable to parse response, not valid JSON.', status_code=response['status']) + # Handle OAuthRateLimitExceeded from Instagram's Nginx which uses different format to documented api responses if 'meta' not in content_obj: if content_obj.get('code') == 420 or content_obj.get('code') == 429: error_message = content_obj.get('error_message') or "Your client is making too many request per second" raise InstagramAPIError(content_obj.get('code'), "Rate limited", error_message) + raise InstagramAPIError(content_obj.get('code'), content_obj.get('error_type'), content_obj.get('error_message')) + api_responses = [] status_code = content_obj['meta']['code'] - self.api.x_ratelimit_remaining = response.get("x-ratelimit-remaining",None) - self.api.x_ratelimit = response.get("x-ratelimit-limit",None) + self.api.x_ratelimit_remaining = response.get("x-ratelimit-remaining", None) + self.api.x_ratelimit = response.get("x-ratelimit-limit", None) if status_code == 200: if not self.objectify_response: return content_obj, None @@ -152,14 +154,17 @@ def _do_api_request(self, url, method="GET", body=None, headers=None): else: obj = self.root_class.object_from_dictionary(entry) api_responses.append(obj) + elif self.response_type == 'entry': data = content_obj['data'] if self.return_json: api_responses = data else: api_responses = self.root_class.object_from_dictionary(data) + elif self.response_type == 'empty': pass + return api_responses, self._build_pagination_info(content_obj) else: raise InstagramAPIError(status_code, content_obj['meta']['error_type'], content_obj['meta']['error_message']) @@ -167,30 +172,33 @@ def _do_api_request(self, url, method="GET", body=None, headers=None): def _paginator_with_url(self, url, method="GET", body=None, headers=None): headers = headers or {} pages_read = 0 + while url and (self.max_pages is None or pages_read < self.max_pages): api_responses, url = self._do_api_request(url, method, body, headers) pages_read += 1 yield api_responses, url + return def _get_with_next_url(self, url, method="GET", body=None, headers=None): headers = headers or {} - content, next = self._do_api_request(url, method, body, headers) - return content, next + content, next_url = self._do_api_request(url, method, body, headers) + + return content, next_url def execute(self): - url, method, body, headers = OAuth2Request(self.api).prepare_request(self.method, - self.path, - self.parameters, - include_secret=self.include_secret) + url, method, body, headers = OAuth2Request(self.api).prepare_request(self.method, self.path, self.parameters, include_secret=self.include_secret) + if self.with_next_url: return self._get_with_next_url(self.with_next_url, method, body, headers) + if self.as_generator: return self._paginator_with_url(url, method, body, headers) else: - content, next = self._do_api_request(url, method, body, headers) + content, next_url = self._do_api_request(url, method, body, headers) + if self.paginates: - return content, next + return content, next_url else: return content From 5f41b33d672465bfb8f56d8d1d4aa02c33e32867 Mon Sep 17 00:00:00 2001 From: wkoot Date: Tue, 28 Feb 2017 13:47:29 +0100 Subject: [PATCH 5/5] pep8 --- instagram/subscriptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/instagram/subscriptions.py b/instagram/subscriptions.py index d84d1fe9..22ba5e77 100644 --- a/instagram/subscriptions.py +++ b/instagram/subscriptions.py @@ -2,6 +2,7 @@ import hashlib from .json_import import simplejson + class SubscriptionType: TAG = 'tag' USER = 'user'