diff --git a/examples/authentication.py b/examples/authentication.py index cb1a22b..397aad8 100644 --- a/examples/authentication.py +++ b/examples/authentication.py @@ -1,3 +1,4 @@ +from __future__ import print_function from linkedin.linkedin import (LinkedInAuthentication, LinkedInApplication, PERMISSIONS) @@ -7,6 +8,6 @@ API_SECRET = 'daJDa6_8UcnGMw1yuq9TjoO_PMKukXMo8vEMo7Qv5J-G3SPgrAV0FqFCd0TNjQyG' RETURN_URL = 'http://localhost:8000' authentication = LinkedInAuthentication(API_KEY, API_SECRET, RETURN_URL, - PERMISSIONS.enums.values()) - print authentication.authorization_url + list(PERMISSIONS.enums.values())) + print(authentication.authorization_url) application = LinkedInApplication(authentication) diff --git a/linkedin/__init__.py b/linkedin/__init__.py index 5d5e187..390b861 100644 --- a/linkedin/__init__.py +++ b/linkedin/__init__.py @@ -1,2 +1,2 @@ __version__ = '4.1' -VERSION = tuple(map(int, __version__.split('.'))) +VERSION = tuple(int(version_part) for version_part in __version__.split('.')) diff --git a/linkedin/linkedin.py b/linkedin/linkedin.py index 9f5a2ee..ee12c39 100644 --- a/linkedin/linkedin.py +++ b/linkedin/linkedin.py @@ -1,15 +1,23 @@ # -*- coding: utf-8 -*- +from future.builtins import str +from future.builtins import map +from future import standard_library +standard_library.install_hooks() +from future.builtins import object import contextlib import hashlib import random -import urllib +import urllib.request +import urllib.parse +import urllib.error +from io import StringIO import requests from requests_oauthlib import OAuth1 from .exceptions import LinkedInError from .models import AccessToken, LinkedInInvitation, LinkedInMessage -from .utils import enum, to_utf8, raise_for_error, json, StringIO +from .utils import enum, raise_for_error, json __all__ = ['LinkedInAuthentication', 'LinkedInApplication', 'PERMISSIONS'] @@ -94,7 +102,7 @@ def authorization_url(self): 'redirect_uri': self.redirect_uri} # urlencode uses quote_plus when encoding the query string so, # we ought to be encoding the qs by on our own. - qsl = ['%s=%s' % (urllib.quote(k), urllib.quote(v)) for k, v in qd.items()] + qsl = ['%s=%s' % (urllib.parse.quote(k), urllib.parse.quote(v)) for k, v in qd.items()] return '%s?%s' % (self.AUTHORIZATION_URL, '&'.join(qsl)) @property @@ -123,13 +131,13 @@ class LinkedInSelector(object): @classmethod def parse(cls, selector): with contextlib.closing(StringIO()) as result: - if type(selector) == dict: + if isinstance(selector, dict): for k, v in selector.items(): - result.write('%s:(%s)' % (to_utf8(k), cls.parse(v))) - elif type(selector) in (list, tuple): + result.write('%s:(%s)' % (str(k), cls.parse(v))) + elif isinstance(selector, (list, tuple)): result.write(','.join(map(cls.parse, selector))) else: - result.write(to_utf8(selector)) + result.write(str(selector)) return result.getvalue() @@ -137,7 +145,8 @@ class LinkedInApplication(object): BASE_URL = 'https://api.linkedin.com' def __init__(self, authentication=None, token=None): - assert authentication or token, 'Either authentication instance or access token is required' + assert authentication or token, ( + 'Either authentication instance or access token is required') self.authentication = authentication if not self.authentication: self.authentication = LinkedInAuthentication('', '', '') @@ -145,6 +154,7 @@ def __init__(self, authentication=None, token=None): def make_request(self, method, url, data=None, params=None, headers=None, timeout=60): + print(url) if headers is None: headers = {'x-li-format': 'json', 'Content-Type': 'application/json'} else: @@ -170,7 +180,7 @@ def get_profile(self, member_id=None, member_url=None, selectors=None, if member_id: url = '%s/id=%s' % (ENDPOINTS.PEOPLE, str(member_id)) elif member_url: - url = '%s/url=%s' % (ENDPOINTS.PEOPLE, urllib.quote_plus(member_url)) + url = '%s/url=%s' % (ENDPOINTS.PEOPLE, urllib.parse.quote_plus(member_url)) else: url = '%s/~' % ENDPOINTS.PEOPLE if selectors: @@ -196,7 +206,7 @@ def get_picture_urls(self, member_id=None, member_url=None, url = '%s/id=%s/picture-urls::(original)' % (ENDPOINTS.PEOPLE, str(member_id)) elif member_url: url = '%s/url=%s/picture-urls::(original)' % (ENDPOINTS.PEOPLE, - urllib.quote_plus(member_url)) + urllib.parse.quote_plus(member_url)) else: url = '%s/~/picture-urls::(original)' % ENDPOINTS.PEOPLE @@ -210,7 +220,7 @@ def get_connections(self, member_id=None, member_url=None, selectors=None, url = '%s/id=%s/connections' % (ENDPOINTS.PEOPLE, str(member_id)) elif member_url: url = '%s/url=%s/connections' % (ENDPOINTS.PEOPLE, - urllib.quote_plus(member_url)) + urllib.parse.quote_plus(member_url)) else: url = '%s/~/connections' % ENDPOINTS.PEOPLE if selectors: @@ -226,7 +236,7 @@ def get_memberships(self, member_id=None, member_url=None, group_id=None, url = '%s/id=%s/group-memberships' % (ENDPOINTS.PEOPLE, str(member_id)) elif member_url: url = '%s/url=%s/group-memberships' % (ENDPOINTS.PEOPLE, - urllib.quote_plus(member_url)) + urllib.parse.quote_plus(member_url)) else: url = '%s/~/group-memberships' % ENDPOINTS.PEOPLE @@ -292,7 +302,7 @@ def like_post(self, post_id, action): url = '%s/%s/relation-to-viewer/is-liked' % (ENDPOINTS.POSTS, str(post_id)) try: self.make_request('PUT', url, data=json.dumps(action)) - except (requests.ConnectionError, requests.HTTPError), error: + except (requests.ConnectionError, requests.HTTPError) as error: raise LinkedInError(error.message) else: return True @@ -304,7 +314,7 @@ def comment_post(self, post_id, comment): url = '%s/%s/comments' % (ENDPOINTS.POSTS, str(post_id)) try: self.make_request('POST', url, data=json.dumps(post)) - except (requests.ConnectionError, requests.HTTPError), error: + except (requests.ConnectionError, requests.HTTPError) as error: raise LinkedInError(error.message) else: return True @@ -321,7 +331,7 @@ def get_companies(self, company_ids=None, universal_names=None, selectors=None, identifiers = [] url = ENDPOINTS.COMPANIES if company_ids: - identifiers += map(str, company_ids) + identifiers.extend(map(str, company_ids)) if universal_names: identifiers += ['universal-name=%s' % un for un in universal_names] diff --git a/linkedin/models.py b/linkedin/models.py index 613a105..a5e571d 100644 --- a/linkedin/models.py +++ b/linkedin/models.py @@ -1,94 +1,96 @@ -# -*- coding: utf-8 -*- -import collections - -AccessToken = collections.namedtuple('AccessToken', ['access_token', 'expires_in']) - - -class LinkedInRecipient(object): - def __init__(self, member_id, email, first_name, last_name): - assert member_id or email, 'Either member ID or email must be given' - if member_id: - self.member_id = str(member_id) - else: - self.member_id = None - self.email = email - self.first_name = first_name - self.last_name = last_name - - @property - def json(self): - result = {'person': None} - if self.member_id: - result['person'] = {'_path': '/people/id=%s' % self.member_id} - else: - result['person'] = {'_path': '/people/email=%s' % self.email} - - if self.first_name: - result['person']['first-name'] = self.first_name - - if self.last_name: - result['person']['last-name'] = self.last_name - - return result - - -class LinkedInInvitation(object): - def __init__(self, subject, body, recipients, connect_type, auth_name=None, - auth_value=None): - self.subject = subject - self.body = body - self.recipients = recipients - self.connect_type = connect_type - self.auth_name = auth_name - self.auth_value = auth_value - - @property - def json(self): - result = { - 'recipients': { - 'values': [] - }, - 'subject': self.subject, - 'body': self.body, - 'item-content': { - 'invitation-request': { - 'connect-type': self.connect_type - } - } - } - for recipient in self.recipients: - result['recipients']['values'].append(recipient.json) - - if self.auth_name and self.auth_value: - auth = {'name': self.auth_name, 'value': self.auth_value} - result['item-content']['invitation-request']['authorization'] = auth - - return result - - -class LinkedInMessage(object): - def __init__(self, subject, body, recipients, auth_name=None, - auth_value=None): - self.subject = subject - self.body = body - self.recipients = recipients - self.auth_name = auth_name - self.auth_value = auth_value - - @property - def json(self): - result = { - 'recipients': { - 'values': [] - }, - 'subject': self.subject, - 'body': self.body, - } - for recipient in self.recipients: - result['recipients']['values'].append(recipient.json) - - if self.auth_name and self.auth_value: - auth = {'name': self.auth_name, 'value': self.auth_value} - result['item-content']['invitation-request']['authorization'] = auth - - return result +# -*- coding: utf-8 -*- +from future.builtins import str +from future.builtins import object +import collections + +AccessToken = collections.namedtuple('AccessToken', ['access_token', 'expires_in']) + + +class LinkedInRecipient(object): + def __init__(self, member_id, email, first_name, last_name): + assert member_id or email, 'Either member ID or email must be given' + if member_id: + self.member_id = str(member_id) + else: + self.member_id = None + self.email = email + self.first_name = first_name + self.last_name = last_name + + @property + def json(self): + result = {'person': None} + if self.member_id: + result['person'] = {'_path': '/people/id=%s' % self.member_id} + else: + result['person'] = {'_path': '/people/email=%s' % self.email} + + if self.first_name: + result['person']['first-name'] = self.first_name + + if self.last_name: + result['person']['last-name'] = self.last_name + + return result + + +class LinkedInInvitation(object): + def __init__(self, subject, body, recipients, connect_type, auth_name=None, + auth_value=None): + self.subject = subject + self.body = body + self.recipients = recipients + self.connect_type = connect_type + self.auth_name = auth_name + self.auth_value = auth_value + + @property + def json(self): + result = { + 'recipients': { + 'values': [] + }, + 'subject': self.subject, + 'body': self.body, + 'item-content': { + 'invitation-request': { + 'connect-type': self.connect_type + } + } + } + for recipient in self.recipients: + result['recipients']['values'].append(recipient.json) + + if self.auth_name and self.auth_value: + auth = {'name': self.auth_name, 'value': self.auth_value} + result['item-content']['invitation-request']['authorization'] = auth + + return result + + +class LinkedInMessage(object): + def __init__(self, subject, body, recipients, auth_name=None, + auth_value=None): + self.subject = subject + self.body = body + self.recipients = recipients + self.auth_name = auth_name + self.auth_value = auth_value + + @property + def json(self): + result = { + 'recipients': { + 'values': [] + }, + 'subject': self.subject, + 'body': self.body, + } + for recipient in self.recipients: + result['recipients']['values'].append(recipient.json) + + if self.auth_name and self.auth_value: + auth = {'name': self.auth_name, 'value': self.auth_value} + result['item-content']['invitation-request']['authorization'] = auth + + return result diff --git a/linkedin/server.py b/linkedin/server.py index 91e5185..7548eac 100644 --- a/linkedin/server.py +++ b/linkedin/server.py @@ -1,40 +1,41 @@ -# -*- coding: utf-8 -*- -import BaseHTTPServer -import urlparse - -from .linkedin import LinkedInApplication, LinkedInAuthentication, PERMISSIONS - - -def quick_api(api_key, secret_key): - """ - This method helps you get access to linkedin api quickly when using it - from the interpreter. - Notice that this method creates http server and wait for a request, so it - shouldn't be used in real production code - it's just an helper for debugging - - The usage is basically: - api = quick_api(KEY, SECRET) - After you do that, it will print a URL to the screen which you must go in - and allow the access, after you do that, the method will return with the api - object. - """ - auth = LinkedInAuthentication(api_key, secret_key, 'http://localhost:8000/', - PERMISSIONS.enums.values()) - app = LinkedInApplication(authentication=auth) - print auth.authorization_url - _wait_for_user_to_enter_browser(app) - return app - - -def _wait_for_user_to_enter_browser(app): - class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler): - def do_GET(self): - p = self.path.split('?') - if len(p) > 1: - params = urlparse.parse_qs(p[1], True, True) - app.authentication.authorization_code = params['code'][0] - app.authentication.get_access_token() - - server_address = ('', 8000) - httpd = BaseHTTPServer.HTTPServer(server_address, MyHandler) - httpd.handle_request() +# -*- coding: utf-8 -*- +from __future__ import print_function +import future.moves.http.server as http_server +import future.moves.urllib.parse as urllib_parse + +from .linkedin import LinkedInApplication, LinkedInAuthentication, PERMISSIONS + + +def quick_api(api_key, secret_key): + """ + This method helps you get access to linkedin api quickly when using it + from the interpreter. + Notice that this method creates http server and wait for a request, so it + shouldn't be used in real production code - it's just an helper for debugging + + The usage is basically: + api = quick_api(KEY, SECRET) + After you do that, it will print a URL to the screen which you must go in + and allow the access, after you do that, the method will return with the api + object. + """ + auth = LinkedInAuthentication(api_key, secret_key, 'http://localhost:8000/', + list(PERMISSIONS.enums.values())) + app = LinkedInApplication(authentication=auth) + print(auth.authorization_url) + _wait_for_user_to_enter_browser(app) + return app + + +def _wait_for_user_to_enter_browser(app): + class MyHandler(http_server.BaseHTTPRequestHandler): + def do_GET(self): + p = self.path.split('?') + if len(p) > 1: + params = urllib_parse.parse_qs(p[1], True, True) + app.authentication.authorization_code = params['code'][0] + app.authentication.get_access_token() + + server_address = ('', 8000) + httpd = http_server.HTTPServer(server_address, MyHandler) + httpd.handle_request() diff --git a/linkedin/utils.py b/linkedin/utils.py index 5f93f0a..79f89a8 100644 --- a/linkedin/utils.py +++ b/linkedin/utils.py @@ -2,10 +2,6 @@ import requests from .exceptions import LinkedInError, get_exception_for_error_code -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO try: import simplejson as json @@ -31,7 +27,7 @@ def __init__(instance, *args, **kwargs): methods = {} base_classes = base_classes + (object,) - for k, v in methods.iteritems(): + for k, v in methods.items(): methods[k] = classmethod(v) attrs['enums'] = attrs.copy() @@ -40,17 +36,10 @@ def __init__(instance, *args, **kwargs): return type(enum_type, base_classes, methods) -def to_utf8(st): - if isinstance(st, unicode): - return st.encode('utf-8') - else: - return bytes(st) - - def raise_for_error(response): try: response.raise_for_status() - except (requests.HTTPError, requests.ConnectionError), error: + except (requests.HTTPError, requests.ConnectionError) as error: try: if len(response.content) == 0: # There is nothing we can do here since LinkedIn has neither sent diff --git a/setup.py b/setup.py index 3b04d4d..c38bd7e 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,5 @@ url='http://ozgur.github.com/python-linkedin/', license='MIT', packages=['linkedin'], - install_requires=['requests>=1.1.0', 'requests-oauthlib>=0.3'], - zip_safe=False -) + install_requires=['requests>=1.1.0', 'requests-oauthlib>=0.3', 'future>=0.12.4'], + zip_safe=False)