Skip to content
This repository was archived by the owner on Jan 13, 2022. It is now read-only.

Commit 8ddb0d6

Browse files
author
Jon Heaton
committed
Merge branch 'seajosh-master'
2 parents 7194301 + 82e65c7 commit 8ddb0d6

12 files changed

Lines changed: 147 additions & 115 deletions

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
python-instagram
44
======
5-
A Python client for the Instagram REST and Search APIs
5+
A Python 2/3 client for the Instagram REST and Search APIs
66

77
Installation
88
-----
@@ -12,6 +12,7 @@ Requires
1212
-----
1313
* httplib2
1414
* simplejson
15+
* six
1516

1617

1718
Instagram REST and Search APIs

get_access_token.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,17 @@
1414
except Exception:
1515
pass
1616

17-
client_id = raw_input("Client ID: ").strip()
18-
client_secret = raw_input("Client Secret: ").strip()
19-
redirect_uri = raw_input("Redirect URI: ").strip()
20-
raw_scope = raw_input("Requested scope (separated by spaces, blank for just basic read): ").strip()
17+
# Fix Python 2.x.
18+
try:
19+
import __builtin__
20+
input = getattr(__builtin__, 'raw_input')
21+
except (ImportError, AttributeError):
22+
pass
23+
24+
client_id = input("Client ID: ").strip()
25+
client_secret = input("Client Secret: ").strip()
26+
redirect_uri = input("Redirect URI: ").strip()
27+
raw_scope = input("Requested scope (separated by spaces, blank for just basic read): ").strip()
2128
scope = raw_scope.split(' ')
2229
# For basic, API seems to need to be set explicitly
2330
if not scope or scope == [""]:
@@ -26,10 +33,11 @@
2633
api = InstagramAPI(client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri)
2734
redirect_uri = api.get_authorize_login_url(scope = scope)
2835

29-
print "Visit this page and authorize access in your browser:\n", redirect_uri
36+
print ("Visit this page and authorize access in your browser: "+ redirect_uri)
3037

31-
code = raw_input("Paste in code in query string after redirect: ").strip()
38+
code = (str(input("Paste in code in query string after redirect: ").strip()))
3239

3340
access_token = api.exchange_code_for_access_token(code)
34-
print "access token:\n", access_token
41+
print ("access token: " )
42+
print (access_token)
3543

instagram/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
from bind import InstagramAPIError, InstagramClientError
2-
from client import InstagramAPI
1+
from .bind import InstagramAPIError, InstagramClientError
2+
from .client import InstagramAPI

instagram/bind.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import urllib
2-
from oauth2 import OAuth2Request
2+
from .oauth2 import OAuth2Request
33
import re
4-
from json_import import simplejson
4+
from .json_import import simplejson
55
import hmac
66
from hashlib import sha256
7+
import six
8+
from six.moves.urllib.parse import quote
9+
import sys
710

811
re_path_template = re.compile('{\w+}')
912

1013

1114
def encode_string(value):
1215
return value.encode('utf-8') \
13-
if isinstance(value, unicode) else str(value)
16+
if isinstance(value, six.text_type) else str(value)
1417

1518

1619
class InstagramClientError(Exception):
@@ -76,7 +79,7 @@ def _build_parameters(self, args, kwargs):
7679
except IndexError:
7780
raise InstagramClientError("Too many arguments supplied")
7881

79-
for key, value in kwargs.iteritems():
82+
for key, value in six.iteritems(kwargs):
8083
if value is None:
8184
continue
8285
if key in self.parameters:
@@ -91,7 +94,7 @@ def _build_path(self):
9194
name = variable.strip('{}')
9295

9396
try:
94-
value = urllib.quote(self.parameters[name])
97+
value = quote(self.parameters[name])
9598
except KeyError:
9699
raise Exception('No parameter value found for path variable: %s' % name)
97100
del self.parameters[name]
@@ -119,23 +122,19 @@ def _do_api_request(self, url, method="GET", body=None, headers=None):
119122
ips = self.api.client_ips
120123
signature = hmac.new(secret, ips, sha256).hexdigest()
121124
headers['X-Insta-Forwarded-For'] = '|'.join([ips, signature])
122-
123125
response, content = OAuth2Request(self.api).make_request(url, method=method, body=body, headers=headers)
124126
if response['status'] == '503' or response['status'] == '429':
125127
raise InstagramAPIError(response['status'], "Rate limited", "Your client is making too many request per second")
126-
127128
try:
128-
content_obj = simplejson.loads(content)
129+
content_obj = simplejson.loads(content.decode())
129130
except ValueError:
130131
raise InstagramClientError('Unable to parse response, not valid JSON.', status_code=response['status'])
131-
132132
# Handle OAuthRateLimitExceeded from Instagram's Nginx which uses different format to documented api responses
133-
if not content_obj.has_key('meta'):
133+
if 'meta' not in content_obj:
134134
if content_obj.get('code') == 420 or content_obj.get('code') == 429:
135135
error_message = content_obj.get('error_message') or "Your client is making too many request per second"
136136
raise InstagramAPIError(content_obj.get('code'), "Rate limited", error_message)
137137
raise InstagramAPIError(content_obj.get('code'), content_obj.get('error_type'), content_obj.get('error_message'))
138-
139138
api_responses = []
140139
status_code = content_obj['meta']['code']
141140
self.api.x_ratelimit_remaining = response.get("x-ratelimit-remaining",None)
@@ -166,7 +165,7 @@ def _do_api_request(self, url, method="GET", body=None, headers=None):
166165
def _paginator_with_url(self, url, method="GET", body=None, headers=None):
167166
headers = headers or {}
168167
pages_read = 0
169-
while url and (pages_read < self.max_pages or self.max_pages is None):
168+
while url and (self.max_pages is None or pages_read < self.max_pages):
170169
api_responses, url = self._do_api_request(url, method, body, headers)
171170
pages_read += 1
172171
yield api_responses, url

instagram/client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import oauth2
2-
from bind import bind_method
3-
from models import MediaShortcode, Media, User, Location, Tag, Comment, Relationship
1+
from . import oauth2
2+
from .bind import bind_method
3+
from .models import MediaShortcode, Media, User, Location, Tag, Comment, Relationship
44

55
MEDIA_ACCEPT_PARAMETERS = ["count", "max_id"]
66
SEARCH_ACCEPT_PARAMETERS = ["q", "count"]

instagram/models.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from helper import timestamp_to_datetime
1+
from .helper import timestamp_to_datetime
2+
import six
23

34

45
class ApiModel(object):
@@ -12,7 +13,17 @@ def object_from_dictionary(cls, entry):
1213
return cls(**entry_str_dict)
1314

1415
def __repr__(self):
15-
return unicode(self).encode('utf8')
16+
return str(self)
17+
# if six.PY2:
18+
# return six.text_type(self).encode('utf8')
19+
# else:
20+
# return self.encode('utf8')
21+
22+
def __str__(self):
23+
if six.PY3:
24+
return self.__unicode__()
25+
else:
26+
return unicode(self).encode('utf-8')
1627

1728

1829
class Image(ApiModel):
@@ -36,7 +47,7 @@ class Media(ApiModel):
3647

3748
def __init__(self, id=None, **kwargs):
3849
self.id = id
39-
for key, value in kwargs.iteritems():
50+
for key, value in six.iteritems(kwargs):
4051
setattr(self, key, value)
4152

4253
def get_standard_resolution_url(self):
@@ -67,12 +78,12 @@ def object_from_dictionary(cls, entry):
6778
new_media.user = User.object_from_dictionary(entry['user'])
6879

6980
new_media.images = {}
70-
for version, version_info in entry['images'].iteritems():
81+
for version, version_info in six.iteritems(entry['images']):
7182
new_media.images[version] = Image.object_from_dictionary(version_info)
7283

7384
if new_media.type == 'video':
7485
new_media.videos = {}
75-
for version, version_info in entry['videos'].iteritems():
86+
for version, version_info in six.iteritems(entry['videos']):
7687
new_media.videos[version] = Video.object_from_dictionary(version_info)
7788

7889
if 'user_has_liked' in entry:
@@ -113,14 +124,14 @@ class MediaShortcode(Media):
113124

114125
def __init__(self, shortcode=None, **kwargs):
115126
self.shortcode = shortcode
116-
for key, value in kwargs.iteritems():
127+
for key, value in six.iteritems(kwargs):
117128
setattr(self, key, value)
118129

119130

120131
class Tag(ApiModel):
121132
def __init__(self, name, **kwargs):
122133
self.name = name
123-
for key, value in kwargs.iteritems():
134+
for key, value in six.iteritems(kwargs):
124135
setattr(self, key, value)
125136

126137
def __unicode__(self):
@@ -129,7 +140,7 @@ def __unicode__(self):
129140

130141
class Comment(ApiModel):
131142
def __init__(self, *args, **kwargs):
132-
for key, value in kwargs.iteritems():
143+
for key, value in six.iteritems(kwargs):
133144
setattr(self, key, value)
134145

135146
@classmethod
@@ -156,7 +167,7 @@ def __unicode__(self):
156167
class Location(ApiModel):
157168
def __init__(self, id, *args, **kwargs):
158169
self.id = str(id)
159-
for key, value in kwargs.iteritems():
170+
for key, value in six.iteritems(kwargs):
160171
setattr(self, key, value)
161172

162173
@classmethod
@@ -178,7 +189,7 @@ class User(ApiModel):
178189

179190
def __init__(self, id, *args, **kwargs):
180191
self.id = id
181-
for key, value in kwargs.iteritems():
192+
for key, value in six.iteritems(kwargs):
182193
setattr(self, key, value)
183194

184195
def __unicode__(self):

instagram/oauth2.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from json_import import simplejson
2-
import urllib
1+
from .json_import import simplejson
2+
from six.moves.urllib.parse import urlencode
33
from httplib2 import Http
44
import mimetypes
5+
import six
56

67

78
class OAuth2AuthExchangeError(Exception):
@@ -67,7 +68,7 @@ def _url_for_authorize(self, scope=None):
6768
}
6869
if scope:
6970
client_params.update(scope=' '.join(scope))
70-
url_params = urllib.urlencode(client_params)
71+
url_params = urlencode(client_params)
7172
return "%s?%s" % (self.api.authorize_url, url_params)
7273

7374
def _data_for_exchange(self, code=None, username=None, password=None, scope=None, user_id=None):
@@ -87,7 +88,7 @@ def _data_for_exchange(self, code=None, username=None, password=None, scope=None
8788
client_params.update(scope=' '.join(scope))
8889
elif user_id:
8990
client_params.update(user_id=user_id)
90-
return urllib.urlencode(client_params)
91+
return urlencode(client_params)
9192

9293
def get_authorize_url(self, scope=None):
9394
return self._url_for_authorize(scope=scope)
@@ -107,7 +108,7 @@ def exchange_for_access_token(self, code=None, username=None, password=None, sco
107108
http_object = Http(disable_ssl_certificate_validation=True)
108109
url = self.api.access_token_url
109110
response, content = http_object.request(url, method="POST", body=data)
110-
parsed_content = simplejson.loads(content)
111+
parsed_content = simplejson.loads(content.decode())
111112
if int(response['status']) != 200:
112113
raise OAuth2AuthExchangeError(parsed_content.get("error_message", ""))
113114
return parsed_content['access_token'], parsed_content['user']
@@ -137,7 +138,7 @@ def _full_url_with_params(self, path, params, include_secret=False):
137138
return (self._full_url(path, include_secret) + self._full_query_with_params(params))
138139

139140
def _full_query_with_params(self, params):
140-
params = ("&" + urllib.urlencode(params)) if params else ""
141+
params = ("&" + urlencode(params)) if params else ""
141142
return params
142143

143144
def _auth_query(self, include_secret=False):
@@ -150,7 +151,7 @@ def _auth_query(self, include_secret=False):
150151
return base
151152

152153
def _post_body(self, params):
153-
return urllib.urlencode(params)
154+
return urlencode(params)
154155

155156
def _encode_multipart(params, files):
156157
boundary = "MuL7Ip4rt80uND4rYF0o"
@@ -208,5 +209,7 @@ def make_request(self, url, method="GET", body=None, headers=None):
208209
headers = headers or {}
209210
if not 'User-Agent' in headers:
210211
headers.update({"User-Agent": "%s Python Client" % self.api.api_name})
211-
http_obj = Http(disable_ssl_certificate_validation=True)
212+
# https://github.com/jcgregorio/httplib2/issues/173
213+
# bug in httplib2 w/ Python 3 and disable_ssl_certificate_validation=True
214+
http_obj = Http() if six.PY3 else Http(disable_ssl_certificate_validation=True)
212215
return http_obj.request(url, method, body=body, headers=headers)

instagram/subscriptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import hmac
22
import hashlib
3-
from json_import import simplejson
3+
from .json_import import simplejson
44

55
class SubscriptionType:
66
TAG = 'tag'

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
bottle==0.12.7
2-
bottle-session==0.3
32
httplib2==0.9
43
python-instagram==1.1.3
54
redis==2.10.3
65
simplejson==3.6.3
76
wsgiref==0.1.2
7+
beaker==1.6.4

0 commit comments

Comments
 (0)