Skip to content

Commit 797d168

Browse files
author
Jack Zabolotnyi
committed
Merge branch 'master' of github.com:ozgur/python-linkedin
2 parents 5800975 + 0336879 commit 797d168

File tree

8 files changed

+159
-49
lines changed

8 files changed

+159
-49
lines changed

README.md

+21-4
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,20 @@ This library provides a pure Python interface to the LinkedIn **Profile**, **Gro
1515
You can install **python-linkedin** library via pip:
1616

1717
$ pip install python-linkedin
18-
$ pip install requests
19-
$ pip install requests_oauthlib
20-
2118

2219
## Authentication
2320

24-
The LinkedIn REST API now supports the **Oauth 2.0** protocol for authentication. This package provides a full OAuth 2.0 implementation for connecting to LinkedIn as well as an option for using an OAuth 1.0a flow that can be helpful for development purposes or just accessing your own data.
21+
The LinkedIn REST API now supports the **OAuth 2.0** protocol for authentication. This package provides a full OAuth 2.0 implementation for connecting to LinkedIn as well as an option for using an OAuth 1.0a flow that can be helpful for development purposes or just accessing your own data.
22+
23+
### HTTP API example
24+
25+
Set `LINKEDIN_API_KEY` and `LINKEDIN_API_SECRET`, configure your app to redirect to `http://localhost:8080/code`, then execute:
26+
27+
0. `http_api.py`
28+
1. Visit `http://localhost:8080` in your browser, curl or similar
29+
2. A tab in your browser will open up, give LinkedIn permission there
30+
3. You'll then be presented with a list of available routes, hit any, e.g.:
31+
4. `curl -XGET http://localhost:8080/get_profile`
2532

2633
### Developer Authentication
2734

@@ -252,6 +259,16 @@ application.get_memberships(params={'count': 20})
252259
u'membershipState': {u'code': u'member'}}]}
253260

254261
application.get_posts(41001)
262+
263+
application.get_post_comments(
264+
%POST_ID%,
265+
selectors=[
266+
{"creator": ["first-name", "last-name"]},
267+
"creation-timestamp",
268+
"text"
269+
],
270+
params={"start": 0, "count": 20}
271+
)
255272
```
256273

257274
You can also submit a new post into a specific group.

examples/http_api.py

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
__author__ = 'Samuel Marks <samuelmarks@gmail.com>'
2+
__version__ = '0.1.0'
3+
4+
from SocketServer import ThreadingTCPServer
5+
from SimpleHTTPServer import SimpleHTTPRequestHandler
6+
from webbrowser import open_new_tab
7+
from json import dumps
8+
from urlparse import urlparse
9+
from os import environ
10+
from types import NoneType
11+
12+
from linkedin.linkedin import LinkedInAuthentication, LinkedInApplication, PERMISSIONS
13+
14+
PORT = 8080
15+
16+
17+
class LinkedInWrapper(object):
18+
""" Simple namespacing """
19+
API_KEY = environ.get('LINKEDIN_API_KEY')
20+
API_SECRET = environ.get('LINKEDIN_API_SECRET')
21+
RETURN_URL = 'http://localhost:{0}/code'.format(globals()['PORT'])
22+
authentication = LinkedInAuthentication(API_KEY, API_SECRET, RETURN_URL, PERMISSIONS.enums.values())
23+
application = LinkedInApplication(authentication)
24+
25+
26+
liw = LinkedInWrapper()
27+
run_already = False
28+
params_to_d = lambda params: {
29+
l[0]: l[1] for l in map(lambda j: j.split('='), urlparse(params).query.split('&'))
30+
}
31+
32+
33+
class CustomHandler(SimpleHTTPRequestHandler):
34+
def json_headers(self, status_code=200):
35+
self.send_response(status_code)
36+
self.send_header('Content-type', 'application/json')
37+
self.end_headers()
38+
39+
def do_GET(self):
40+
parsedurl = urlparse(self.path)
41+
authed = type(liw.authentication.token) is not NoneType
42+
43+
if parsedurl.path == '/code':
44+
self.json_headers()
45+
46+
liw.authentication.authorization_code = params_to_d(self.path).get('code')
47+
self.wfile.write(dumps({'access_token': liw.authentication.get_access_token(),
48+
'routes': filter(lambda d: not d.startswith('_'), dir(liw.application))}))
49+
elif parsedurl.path == '/routes':
50+
self.json_headers()
51+
52+
self.wfile.write(dumps({'routes': filter(lambda d: not d.startswith('_'), dir(liw.application))}))
53+
elif not authed:
54+
self.json_headers()
55+
56+
if not globals()['run_already']:
57+
open_new_tab(liw.authentication.authorization_url)
58+
globals()['run_already'] = True
59+
self.wfile.write(dumps({'path': self.path, 'authed': type(liw.authentication.token) is NoneType}))
60+
elif authed and len(parsedurl.path) and parsedurl.path[1:] in dir(liw.application):
61+
self.json_headers()
62+
self.wfile.write(dumps(getattr(liw.application, parsedurl.path[1:])()))
63+
else:
64+
self.json_headers(501)
65+
self.wfile.write(dumps({'error': 'NotImplemented'}))
66+
67+
68+
if __name__ == '__main__':
69+
httpd = ThreadingTCPServer(('localhost', PORT), CustomHandler)
70+
71+
print 'Server started on port:', PORT
72+
httpd.serve_forever()

linkedin/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
__version__ = '4.1'
1+
__version__ = '4.2'
22
VERSION = tuple(map(int, __version__.split('.')))

linkedin/exceptions.py

+16-7
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,44 @@
33
class LinkedInError(Exception):
44
pass
55

6+
67
class LinkedInBadRequestError(LinkedInError):
78
pass
89

10+
911
class LinkedInUnauthorizedError(LinkedInError):
1012
pass
1113

14+
1215
class LinkedInPaymentRequiredError(LinkedInError):
1316
pass
1417

18+
1519
class LinkedInNotFoundError(LinkedInError):
1620
pass
1721

22+
1823
class LinkedInConflictError(LinkedInError):
1924
pass
2025

26+
2127
class LinkedInForbiddenError(LinkedInError):
2228
pass
2329

30+
2431
class LinkedInInternalServiceError(LinkedInError):
2532
pass
2633

34+
2735
ERROR_CODE_EXCEPTION_MAPPING = {
28-
400: LinkedInBadRequestError,
29-
401: LinkedInUnauthorizedError,
30-
402: LinkedInPaymentRequiredError,
31-
403: LinkedInForbiddenError,
32-
404: LinkedInNotFoundError,
33-
409: LinkedInForbiddenError,
34-
500: LinkedInInternalServiceError}
36+
400: LinkedInBadRequestError,
37+
401: LinkedInUnauthorizedError,
38+
402: LinkedInPaymentRequiredError,
39+
403: LinkedInForbiddenError,
40+
404: LinkedInNotFoundError,
41+
409: LinkedInForbiddenError,
42+
500: LinkedInInternalServiceError}
43+
3544

3645
def get_exception_for_error_code(error_code):
3746
return ERROR_CODE_EXCEPTION_MAPPING.get(error_code, LinkedInError)

linkedin/linkedin.py

+43-33
Original file line numberDiff line numberDiff line change
@@ -15,38 +15,37 @@
1515
__all__ = ['LinkedInAuthentication', 'LinkedInApplication', 'PERMISSIONS']
1616

1717
PERMISSIONS = enum('Permission',
18-
BASIC_PROFILE='r_basicprofile',
19-
FULL_PROFILE='r_fullprofile',
20-
EMAIL_ADDRESS='r_emailaddress',
21-
NETWORK='r_network',
22-
CONTACT_INFO='r_contactinfo',
23-
NETWORK_UPDATES='rw_nus',
24-
GROUPS='rw_groups',
25-
MESSAGES='w_messages')
26-
18+
COMPANY_ADMIN='rw_company_admin',
19+
BASIC_PROFILE='r_basicprofile',
20+
FULL_PROFILE='r_fullprofile',
21+
EMAIL_ADDRESS='r_emailaddress',
22+
NETWORK='r_network',
23+
CONTACT_INFO='r_contactinfo',
24+
NETWORK_UPDATES='rw_nus',
25+
GROUPS='rw_groups',
26+
MESSAGES='w_messages')
2727

2828
ENDPOINTS = enum('LinkedInURL',
29-
PEOPLE='https://api.linkedin.com/v1/people',
30-
PEOPLE_SEARCH='https://api.linkedin.com/v1/people-search',
31-
GROUPS='https://api.linkedin.com/v1/groups',
32-
POSTS='https://api.linkedin.com/v1/posts',
33-
COMPANIES='https://api.linkedin.com/v1/companies',
34-
COMPANY_SEARCH='https://api.linkedin.com/v1/company-search',
35-
JOBS='https://api.linkedin.com/v1/jobs',
36-
JOB_SEARCH='https://api.linkedin.com/v1/job-search')
37-
29+
PEOPLE='https://api.linkedin.com/v1/people',
30+
PEOPLE_SEARCH='https://api.linkedin.com/v1/people-search',
31+
GROUPS='https://api.linkedin.com/v1/groups',
32+
POSTS='https://api.linkedin.com/v1/posts',
33+
COMPANIES='https://api.linkedin.com/v1/companies',
34+
COMPANY_SEARCH='https://api.linkedin.com/v1/company-search',
35+
JOBS='https://api.linkedin.com/v1/jobs',
36+
JOB_SEARCH='https://api.linkedin.com/v1/job-search')
3837

3938
NETWORK_UPDATES = enum('NetworkUpdate',
40-
APPLICATION='APPS',
41-
COMPANY='CMPY',
42-
CONNECTION='CONN',
43-
JOB='JOBS',
44-
GROUP='JGRP',
45-
PICTURE='PICT',
46-
EXTENDED_PROFILE='PRFX',
47-
CHANGED_PROFILE='PRFU',
48-
SHARED='SHAR',
49-
VIRAL='VIRL')
39+
APPLICATION='APPS',
40+
COMPANY='CMPY',
41+
CONNECTION='CONN',
42+
JOB='JOBS',
43+
GROUP='JGRP',
44+
PICTURE='PICT',
45+
EXTENDED_PROFILE='PRFX',
46+
CHANGED_PROFILE='PRFU',
47+
SHARED='SHAR',
48+
VIRAL='VIRL')
5049

5150

5251
class LinkedInDeveloperAuthentication(object):
@@ -56,6 +55,7 @@ class LinkedInDeveloperAuthentication(object):
5655
Useful for situations in which users would like to access their own data or
5756
during the development process.
5857
"""
58+
5959
def __init__(self, consumer_key, consumer_secret, user_token, user_secret,
6060
redirect_uri, permissions=[]):
6161
self.consumer_key = consumer_key
@@ -195,9 +195,9 @@ def search_profile(self, selectors=None, params=None, headers=None):
195195
return response.json()
196196

197197
def get_picture_urls(self, member_id=None, member_url=None,
198-
params=None, headers=None):
198+
params=None, headers=None):
199199
if member_id:
200-
url = '%s/id=%s/picture-urls::(original)' % (ENDPOINTS.PEOPLE, str(member_id))
200+
url = '%s/id=%s/picture-urls::(original)' % (ENDPOINTS.PEOPLE, str(member_id))
201201
elif member_url:
202202
url = '%s/url=%s/picture-urls::(original)' % (ENDPOINTS.PEOPLE,
203203
urllib.quote_plus(member_url))
@@ -262,11 +262,21 @@ def get_posts(self, group_id, post_ids=None, selectors=None, params=None,
262262
response = self.make_request('GET', url, params=params, headers=headers)
263263
raise_for_error(response)
264264
return response.json()
265+
266+
def get_post_comments(self, post_id, selectors=None, params=None,
267+
headers=None):
268+
url = '%s/%s/comments' % (ENDPOINTS.POSTS, post_id)
269+
if selectors:
270+
url = '%s:(%s)' % (url, LinkedInSelector.parse(selectors))
271+
272+
response = self.make_request('GET', url, params=params, headers=headers)
273+
raise_for_error(response)
274+
return response.json()
265275

266276
def join_group(self, group_id):
267277
url = '%s/~/group-memberships/%s' % (ENDPOINTS.PEOPLE, str(group_id))
268278
response = self.make_request('PUT', url,
269-
data=json.dumps({'membershipState': {'code': 'member'}}))
279+
data=json.dumps({'membershipState': {'code': 'member'}}))
270280
raise_for_error(response)
271281
return True
272282

@@ -429,7 +439,7 @@ def get_network_updates(self, types, member_id=None,
429439
self_scope=True, params=None, headers=None):
430440
if member_id:
431441
url = '%s/id=%s/network/updates' % (ENDPOINTS.PEOPLE,
432-
str(member_id))
442+
str(member_id))
433443
else:
434444
url = '%s/~/network/updates' % ENDPOINTS.PEOPLE
435445

@@ -447,7 +457,7 @@ def get_network_updates(self, types, member_id=None,
447457
return response.json()
448458

449459
def get_network_update(self, types, update_key,
450-
self_scope=True, params=None, headers=None):
460+
self_scope=True, params=None, headers=None):
451461
url = '%s/~/network/updates/key=%s' % (ENDPOINTS.PEOPLE, str(update_key))
452462

453463
if not params:

linkedin/utils.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,16 @@ def to_utf8(st):
5050
def raise_for_error(response):
5151
try:
5252
response.raise_for_status()
53-
except (requests.HTTPError, requests.ConnectionError), error:
53+
except (requests.HTTPError, requests.ConnectionError) as error:
5454
try:
5555
if len(response.content) == 0:
5656
# There is nothing we can do here since LinkedIn has neither sent
5757
# us a 2xx response nor a response content.
5858
return
5959
response = response.json()
6060
if ('error' in response) or ('errorCode' in response):
61-
message = '%s: %s' % (response.get('error', error.message),
62-
response.get('error_description', 'Unknown Error'))
61+
message = '%s: %s' % (response.get('error', str(error)),
62+
response.get('message', 'Unknown Error'))
6363
error_code = response.get('status')
6464
ex = get_exception_for_error_code(error_code)
6565
raise ex(message)
@@ -68,5 +68,6 @@ def raise_for_error(response):
6868
except (ValueError, TypeError):
6969
raise LinkedInError(error.message)
7070

71+
7172
HTTP_METHODS = enum('HTTPMethod', GET='GET', POST='POST',
7273
PUT='PUT', DELETE='DELETE', PATCH='PATCH')

requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
requests
2+
requests_oauthlib

setup.py

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme:
1313
long_description = readme.read()
1414

15-
1615
setup(name='python-linkedin',
1716
version=__version__,
1817
description='Python Interface to the LinkedIn API',

0 commit comments

Comments
 (0)