Skip to content

Commit

Permalink
Merge branch 'master' of github.com:seajosh/python-instagram into sea…
Browse files Browse the repository at this point in the history
…josh-master
  • Loading branch information
Jon Heaton committed Oct 20, 2014
2 parents 7194301 + 16412bd commit dee95d8
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 45 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

python-instagram
======
A Python client for the Instagram REST and Search APIs
A Python 2/3 client for the Instagram REST and Search APIs

Installation
-----
Expand All @@ -12,6 +12,7 @@ Requires
-----
* httplib2
* simplejson
* six


Instagram REST and Search APIs
Expand Down
4 changes: 2 additions & 2 deletions instagram/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from bind import InstagramAPIError, InstagramClientError
from client import InstagramAPI
from .bind import InstagramAPIError, InstagramClientError
from .client import InstagramAPI
17 changes: 10 additions & 7 deletions instagram/bind.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import urllib
from oauth2 import OAuth2Request
from .oauth2 import OAuth2Request
import re
from json_import import simplejson
from .json_import import simplejson
import hmac
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, unicode) else str(value)
if isinstance(value, six.text_type) else str(value)


class InstagramClientError(Exception):
Expand Down Expand Up @@ -76,7 +79,7 @@ def _build_parameters(self, args, kwargs):
except IndexError:
raise InstagramClientError("Too many arguments supplied")

for key, value in kwargs.iteritems():
for key, value in six.iteritems(kwargs):
if value is None:
continue
if key in self.parameters:
Expand All @@ -91,7 +94,7 @@ def _build_path(self):
name = variable.strip('{}')

try:
value = urllib.quote(self.parameters[name])
value = quote(self.parameters[name])
except KeyError:
raise Exception('No parameter value found for path variable: %s' % name)
del self.parameters[name]
Expand Down Expand Up @@ -130,7 +133,7 @@ def _do_api_request(self, url, method="GET", body=None, headers=None):
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 not content_obj.has_key('meta'):
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)
Expand Down Expand Up @@ -166,7 +169,7 @@ 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 (pages_read < self.max_pages or self.max_pages is None):
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
Expand Down
6 changes: 3 additions & 3 deletions instagram/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import oauth2
from bind import bind_method
from models import MediaShortcode, Media, User, Location, Tag, Comment, Relationship
from . import oauth2
from .bind import bind_method
from .models import MediaShortcode, Media, User, Location, Tag, Comment, Relationship

MEDIA_ACCEPT_PARAMETERS = ["count", "max_id"]
SEARCH_ACCEPT_PARAMETERS = ["q", "count"]
Expand Down
31 changes: 21 additions & 10 deletions instagram/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from helper import timestamp_to_datetime
from .helper import timestamp_to_datetime
import six


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

def __repr__(self):
return unicode(self).encode('utf8')
return str(self)
# if six.PY2:
# return six.text_type(self).encode('utf8')
# else:
# return self.encode('utf8')

def __str__(self):
if six.PY3:
return self.__unicode__()
else:
return unicode(self).encode('utf-8')


class Image(ApiModel):
Expand All @@ -36,7 +47,7 @@ class Media(ApiModel):

def __init__(self, id=None, **kwargs):
self.id = id
for key, value in kwargs.iteritems():
for key, value in six.iteritems(kwargs):
setattr(self, key, value)

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

new_media.images = {}
for version, version_info in entry['images'].iteritems():
for version, version_info in six.iteritems(entry['images']):
new_media.images[version] = Image.object_from_dictionary(version_info)

if new_media.type == 'video':
new_media.videos = {}
for version, version_info in entry['videos'].iteritems():
for version, version_info in six.iteritems(entry['videos']):
new_media.videos[version] = Video.object_from_dictionary(version_info)

if 'user_has_liked' in entry:
Expand Down Expand Up @@ -113,14 +124,14 @@ class MediaShortcode(Media):

def __init__(self, shortcode=None, **kwargs):
self.shortcode = shortcode
for key, value in kwargs.iteritems():
for key, value in six.iteritems(kwargs):
setattr(self, key, value)


class Tag(ApiModel):
def __init__(self, name, **kwargs):
self.name = name
for key, value in kwargs.iteritems():
for key, value in six.iteritems(kwargs):
setattr(self, key, value)

def __unicode__(self):
Expand All @@ -129,7 +140,7 @@ def __unicode__(self):

class Comment(ApiModel):
def __init__(self, *args, **kwargs):
for key, value in kwargs.iteritems():
for key, value in six.iteritems(kwargs):
setattr(self, key, value)

@classmethod
Expand All @@ -156,7 +167,7 @@ def __unicode__(self):
class Location(ApiModel):
def __init__(self, id, *args, **kwargs):
self.id = str(id)
for key, value in kwargs.iteritems():
for key, value in six.iteritems(kwargs):
setattr(self, key, value)

@classmethod
Expand All @@ -178,7 +189,7 @@ class User(ApiModel):

def __init__(self, id, *args, **kwargs):
self.id = id
for key, value in kwargs.iteritems():
for key, value in six.iteritems(kwargs):
setattr(self, key, value)

def __unicode__(self):
Expand Down
17 changes: 10 additions & 7 deletions instagram/oauth2.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from json_import import simplejson
import urllib
from .json_import import simplejson
from six.moves.urllib.parse import urlencode
from httplib2 import Http
import mimetypes
import six


class OAuth2AuthExchangeError(Exception):
Expand Down Expand Up @@ -67,7 +68,7 @@ def _url_for_authorize(self, scope=None):
}
if scope:
client_params.update(scope=' '.join(scope))
url_params = urllib.urlencode(client_params)
url_params = urlencode(client_params)
return "%s?%s" % (self.api.authorize_url, url_params)

def _data_for_exchange(self, code=None, username=None, password=None, scope=None, user_id=None):
Expand All @@ -87,7 +88,7 @@ def _data_for_exchange(self, code=None, username=None, password=None, scope=None
client_params.update(scope=' '.join(scope))
elif user_id:
client_params.update(user_id=user_id)
return urllib.urlencode(client_params)
return urlencode(client_params)

def get_authorize_url(self, scope=None):
return self._url_for_authorize(scope=scope)
Expand Down Expand Up @@ -137,7 +138,7 @@ def _full_url_with_params(self, path, params, include_secret=False):
return (self._full_url(path, include_secret) + self._full_query_with_params(params))

def _full_query_with_params(self, params):
params = ("&" + urllib.urlencode(params)) if params else ""
params = ("&" + urlencode(params)) if params else ""
return params

def _auth_query(self, include_secret=False):
Expand All @@ -150,7 +151,7 @@ def _auth_query(self, include_secret=False):
return base

def _post_body(self, params):
return urllib.urlencode(params)
return urlencode(params)

def _encode_multipart(params, files):
boundary = "MuL7Ip4rt80uND4rYF0o"
Expand Down Expand Up @@ -208,5 +209,7 @@ def make_request(self, url, method="GET", body=None, headers=None):
headers = headers or {}
if not 'User-Agent' in headers:
headers.update({"User-Agent": "%s Python Client" % self.api.api_name})
http_obj = Http(disable_ssl_certificate_validation=True)
# https://github.com/jcgregorio/httplib2/issues/173
# bug in httplib2 w/ Python 3 and disable_ssl_certificate_validation=True
http_obj = Http() if six.PY3 else Http(disable_ssl_certificate_validation=True)
return http_obj.request(url, method, body=body, headers=headers)
2 changes: 1 addition & 1 deletion instagram/subscriptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import hmac
import hashlib
from json_import import simplejson
from .json_import import simplejson

class SubscriptionType:
TAG = 'tag'
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
version="1.1.4",
description="Instagram API client",
license="MIT",
install_requires=["simplejson","httplib2"],
install_requires=["simplejson","httplib2","six"],
author="Instagram, Inc",
author_email="[email protected]",
url="http://github.com/Instagram/python-instagram",
Expand Down
28 changes: 15 additions & 13 deletions tests.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#!/usr/bin/env python

import types
import six
try:
import simplejson as json
except ImportError:
import json
import getpass
import unittest
import urlparse
from six.moves.urllib.parse import urlparse, parse_qs
from instagram import client, oauth2, InstagramAPIError

TEST_AUTH = False
Expand All @@ -26,8 +27,8 @@ def request(self, url, method="GET", body=None, headers={}):
'status':'400'
}, "{}"

parsed = urlparse.urlparse(url)
options = urlparse.parse_qs(parsed.query)
parsed = urlparse(url)
options = parse_qs(parsed.query)

fn_name = str(active_call)
if fn_name == 'get_authorize_login_url':
Expand All @@ -43,6 +44,7 @@ def request(self, url, method="GET", body=None, headers={}):

fl = open('fixtures/%s.json' % fn_name)
content = fl.read()
fl.close()
json_content = json.loads(content)
status = json_content['meta']['code']
return {
Expand All @@ -67,7 +69,7 @@ def setUp(self):
def test_authorize_login_url(self):
redirect_uri = self.unauthenticated_api.get_authorize_login_url()
assert redirect_uri
print "Please visit and authorize at:\n%s" % redirect_uri
print("Please visit and authorize at:\n%s" % redirect_uri)
code = raw_input("Paste received code (blank to skip): ").strip()
if not code:
return
Expand Down Expand Up @@ -129,7 +131,7 @@ def test_generator_user_feed(self):
def test_generator_user_feed_all(self):
generator = self.api.user_media_feed(as_generator=True, max_pages=None)
for i in range(10):
page = generator.next()
page = six.advance_iterator(generator)
str(generator)

generator = self.api.user_media_feed(as_generator=True, max_pages=0)
Expand All @@ -145,34 +147,34 @@ def test_user_recent_media(self):
self.assertTrue( all( [hasattr(obj, 'type') for obj in media] ) )

image = media[0]
self.assertEquals(
self.assertEqual(
image.get_standard_resolution_url(),
"http://distillery-dev.s3.amazonaws.com/media/2011/02/02/1ce5f3f490a640ca9068e6000c91adc5_7.jpg")

self.assertEquals(
self.assertEqual(
image.get_low_resolution_url(),
"http://distillery-dev.s3.amazonaws.com/media/2011/02/02/1ce5f3f490a640ca9068e6000c91adc5_6.jpg")

self.assertEquals(
self.assertEqual(
image.get_thumbnail_url(),
"http://distillery-dev.s3.amazonaws.com/media/2011/02/02/1ce5f3f490a640ca9068e6000c91adc5_5.jpg")

self.assertEquals( False, hasattr(image, 'videos') )
self.assertEqual( False, hasattr(image, 'videos') )

video = media[1]
self.assertEquals(
self.assertEqual(
video.get_standard_resolution_url(),
video.videos['standard_resolution'].url)

self.assertEquals(
self.assertEqual(
video.get_standard_resolution_url(),
"http://distilleryvesper9-13.ak.instagram.com/090d06dad9cd11e2aa0912313817975d_101.mp4")

self.assertEquals(
self.assertEqual(
video.get_low_resolution_url(),
"http://distilleryvesper9-13.ak.instagram.com/090d06dad9cd11e2aa0912313817975d_102.mp4")

self.assertEquals(
self.assertEqual(
video.get_thumbnail_url(),
"http://distilleryimage2.ak.instagram.com/11f75f1cd9cc11e2a0fd22000aa8039a_5.jpg")

Expand Down

0 comments on commit dee95d8

Please sign in to comment.