Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

2 minor fixes #8

Open
wants to merge 7 commits into from

1 participant

Commits on Oct 12, 2011
  1. @andreisoare

    Fix Profile API urls

    andreisoare authored
  2. @andreisoare

    Fix people-search parser

    andreisoare authored
Commits on Oct 13, 2011
  1. @andreisoare

    Add company search

    andreisoare authored
Commits on Oct 22, 2011
  1. @andreisoare

    Add company search by name

    andreisoare authored
Commits on Oct 24, 2011
  1. @andreisoare

    Fix company parser

    andreisoare authored
Commits on Oct 26, 2011
  1. @andreisoare
Commits on Mar 13, 2012
  1. @andreisoare

    Fix get_user for self

    andreisoare authored
This page is out of date. Refresh to see the latest.
View
79 liclient/__init__.py
@@ -13,7 +13,7 @@ def __init__(self, ck, cs):
self.consumer_key = ck
self.consumer_secret = cs
- self.api_profile_url = 'http://api.linkedin.com/v1/people/~'
+ self.api_profile_url = 'http://api.linkedin.com/v1/people/'
self.api_profile_connections_url = 'http://api.linkedin.com/v1/people/~/connections'
self.api_network_update_url = 'http://api.linkedin.com/v1/people/~/network'
self.api_comment_feed_url = 'http://api.linkedin.com/v1/people/~/network/updates/' + \
@@ -83,7 +83,7 @@ def get_user_profile(self, access_token, selectors=None, **kwargs):
content = self.clean_dates(content)
return LinkedInXMLParser(content).results
-
+
def get_user_connections(self, access_token, selectors=None, **kwargs):
"""
Get the connections of the current user. Valid keyword arguments are
@@ -176,6 +176,20 @@ def search(self, access_token, data, field_selector_string=None):
# print content # useful for debugging...
return LinkedInXMLParser(content).results
+ def company_search(self, access_token, data, field_selector_string=None):
+ """
+ Use the LinkedIn Search API to find users. The criteria for your search
+ should be passed as the 2nd positional argument as a dictionary of key-
+ value pairs corresponding to the paramters allowed by the API. Formatting
+ of arguments will be done for you (i.e. lists of keywords will be joined
+ with "+")
+ """
+ srch = LinkedInCompanySearchAPI(data, access_token, field_selector_string)
+ client = oauth.Client(self.consumer, srch.user_token)
+ rest, content = client.request(srch.generated_url, method='GET')
+ # print content # useful for debugging...
+ return LinkedInXMLParser(content).results
+
def send_message(self, access_token, recipients, subject, body):
"""
Send a message to a connection. "Recipients" is a list of ID numbers,
@@ -227,6 +241,7 @@ def prepare_request(self, access_token, url, kws=[]):
prep_url = self.append_initial_arg(k, kws[k], prep_url)
else:
prep_url = self.append_sequential_arg(k, kws[k], prep_url)
+ if not kws: prep_url += '~'
prep_url = re.sub('&&', '&', prep_url)
print prep_url
return user_token, prep_url
@@ -243,13 +258,12 @@ def append_id_args(self, ids, prep_url):
return prep_url
def append_initial_arg(self, key, args, prep_url):
- assert '?' not in prep_url, 'Initial argument has already been applied to %s' % prep_url
if type(args) == type([]):
- prep_url += '?' + key + '=' + str(args[0])
+ prep_url += key + '=' + str(args[0])
if len(args) > 1:
prep_url += ''.join(['&' + key + '=' + str(arg) for arg in args[1:]])
else:
- prep_url += '?' + key + '=' + str(args)
+ prep_url += key + '=' + str(args)
return prep_url
def append_sequential_arg(self, key, args, prep_url):
@@ -358,12 +372,65 @@ def invitation_factory(self, recipient, subject, body, **kwargs):
auth
)
return re.sub('_', '-', etree.tostring(mxml))
-
+
+class LinkedInCompanySearchAPI(LinkedInAPI):
+ def __init__(self, params, access_token, field_selector_string=None):
+ self.api_search_url = 'http://api.linkedin.com/v1/companies'
+ self.field_selector_string = field_selector_string
+ self.routing = {
+ 'email-domain': self.email_domain,
+ 'universal-name': self.universal_domain,
+ }
+ self.user_token, self.generated_url = self.do_process(access_token, params)
+ print "url:", self.generated_url
+
+ def do_process(self, access_token, params):
+ assert type(params) == type(dict()), 'The passed parameters to the Search API must be a dictionary.'
+ user_token = oauth.Token(access_token['oauth_token'], access_token['oauth_token_secret'])
+ url = self.api_search_url
+ for p in params:
+ try:
+ url = self.routing[p](url, params[p])
+ params[p] = None
+ except KeyError:
+ continue
+ remaining_params = {}
+ for p in params:
+ if params[p]:
+ remaining_params[p] = params[p]
+ url = self.process_remaining_params(url, remaining_params)
+ return user_token, url
+
+ def process_remaining_params(self, url, remaining_params):
+ return url
+
+ def email_domain(self, url, val):
+ prep_url = url
+ if self.field_selector_string:
+ prep_url += ':' + self.field_selector_string
+ prep_url += '?'
+ try:
+ prep_url = self.append_initial_arg('email-domain', val, prep_url)
+ except AssertionError:
+ prep_url = self.append_sequential_arg('email-domain', val, prep_url)
+ return prep_url
+
+ def universal_domain(self, url, val):
+ prep_url = url + '/'
+ try:
+ prep_url = self.append_initial_arg('universal-name', val, prep_url)
+ except AssertionError:
+ prep_url = self.append_sequential_arg('universal-name', val, prep_url)
+ if self.field_selector_string:
+ prep_url += ':' + self.field_selector_string
+ return prep_url
+
class LinkedInSearchAPI(LinkedInAPI):
def __init__(self, params, access_token, field_selector_string=None):
self.api_search_url = 'http://api.linkedin.com/v1/people-search'
if field_selector_string:
self.api_search_url += ':' + field_selector_string
+ self.api_search_url += '?'
self.routing = {
'keywords': self.keywords,
'name': self.name,
View
109 liclient/parsers/lixml.py
@@ -14,9 +14,14 @@ def __init__(self, content):
'position': self.__parse_position,
'skill': self.__parse_skills,
'education': self.__parse_education,
- 'people': self.__parse_people_collection,
+ 'people-search': self.__parse_people_collection,
'twitter-account': self.__parse_twitter_accounts,
- 'member-url': self.__parse_member_url_resources
+ 'member-url': self.__parse_member_url_resources,
+ 'companies': self.__parse_company_collection,
+ 'company': self.__parse_single_company,
+ 'location': self.__parse_company_location,
+ 'specialty': self.__parse_company_specialty,
+ 'email-domain': self.__parse_company_email_domain,
}
self.tree = etree.fromstring(content)
self.root = self.tree.tag
@@ -71,10 +76,33 @@ def __parse_people_collection(self, tree):
result_count = int(n.text)
content = []
for p in ppl:
+ print p.getchildren()
rslts = LinkedInProfileParser(p).results
content.append(rslts)
return content
-
+
+ def __parse_company_collection(self, tree):
+ companies = tree.getchildren()
+ content = []
+ for c in companies:
+ rslts = LinkedInCompanyParser(c).results
+ content.append(rslts)
+ return content
+
+ def __parse_single_company(self, tree):
+ company = tree
+ rslts = LinkedInCompanyParser(company).results
+ return [rslts]
+
+ def __parse_company_location(self, tree):
+ return LinkedInLocationParser(tree).results
+
+ def __parse_company_specialty(self, tree):
+ return tree.text
+
+ def __parse_company_email_domain(self, tree):
+ return tree.text
+
class LinkedInNetworkUpdateParser(LinkedInXMLParser):
def __init__(self, content):
self.xpath_collection = {
@@ -167,6 +195,81 @@ def __objectify(self, data, u_type, u):
obj = mappers.NetworkUpdate(data, u)
return obj
+class LinkedInCompanyParser(LinkedInXMLParser):
+ def __init__(self, content):
+ self.tree = content
+ self.results = self.__build_data(self.tree)
+
+ def __build_data(self, tree):
+ results = []
+ for c in tree.xpath('/company'):
+ company = {}
+ for item in c.getchildren():
+ company[re.sub(r'-', '_', item.tag)] = item.text
+ obj = mappers.Company(company, c)
+ results.append(obj)
+
+ # deal with hierarchical results in a somewhat kludgy way
+ def fix(s):
+ return re.sub(r'-', '_', s)
+ def build_name(parent, item):
+ s = ''
+ p = item.getparent()
+ while p != parent:
+ s = fix(p.tag) + '_' + s
+ p = p.getparent()
+ s += fix(item.tag)
+ return s
+ if not results:
+ company = {}
+ for item in tree.iterdescendants():
+ clean = item.text and item.text.strip()
+ if clean:
+ name = build_name(tree, item)
+ if name in company:
+ value = company[name]
+ if type(value) != list:
+ company[name] = [value, clean]
+ else:
+ company[name].append(clean)
+ else:
+ company[name] = clean
+ obj = mappers.Company(company, tree)
+ results.append(obj)
+ return results
+
+class LinkedInLocationParser(LinkedInXMLParser):
+ def __init__(self, content):
+ self.tree = content
+ self.results = self.__build_data(self.tree)
+
+ def __build_data(self, tree):
+ def fix(s):
+ return re.sub(r'-', '_', s)
+ def build_name(parent, item):
+ s = ''
+ p = item.getparent()
+ while p != parent:
+ s = fix(p.tag) + '_' + s
+ p = p.getparent()
+ s += fix(item.tag)
+ return s
+ location = {}
+ for item in tree.iterdescendants():
+ clean = item.text and item.text.strip()
+ if clean:
+ name = build_name(tree, item)
+ if name in location:
+ value = location[name]
+ if type(value) != list:
+ location[name] = [value, clean]
+ else:
+ location[name].append(clean)
+ else:
+ location[name] = clean
+ obj = mappers.Location(location, tree)
+ return obj
+
class LinkedInProfileParser(LinkedInXMLParser):
def __init__(self, content):
self.tree = content
View
147 liclient/parsers/mappers.py
@@ -1,23 +1,23 @@
from lxml import etree
import datetime, re
import lixml
-
+
class LinkedInData(object):
def __init__(self, data, xml):
self.xml = xml
self.parse_data(data)
-
+
def parse_data(self, data):
for k in data.keys():
self.__dict__[k] = data[k]
-
+
def jsonify(self):
json = {}
for k in self.__dict__.keys():
if type(self.__dict__[k]) == type(''):
json[k] = self.__dict__[k]
return json
-
+
def xmlify(self):
converted = [re.sub('_', '-', k) for k in self.__dict__.keys()]
for d in self.xml.iter(tag=etree.Element):
@@ -27,20 +27,20 @@ def xmlify(self):
except:
continue
return etree.tostring(self.xml)
-
+
def __str__(self):
return self.update_content if hasattr(self, 'update_content') and self.update_content else '<No Content>'
-
+
class LinkedInError(LinkedInData):
def __repr__(self):
return '<LinkedIn Error code %s>'.encode('utf-8') % self.status
-
+
class NetworkUpdate(LinkedInData):
def __init__(self, data, xml):
self.xml = xml
self.update_key = None
self.parse_data(data)
-
+
def jsonify(self):
jsondict = {'first_name': self.first_name,
'last_name': self.last_name,
@@ -49,7 +49,7 @@ def jsonify(self):
'update_key': self.update_key,
'profile_url': self.profile_url}
return jsondict
-
+
class NetworkStatusUpdate(NetworkUpdate):
def __init__(self, data, xml):
self.status_xpath = etree.XPath('update-content/person/current-status')
@@ -66,7 +66,7 @@ def get_comments(self):
comment = NetworkUpdateComment(c)
self.comments.append(comment)
return
-
+
class NetworkConnectionUpdate(NetworkUpdate):
def __init__(self, data, xml):
self.xml = xml
@@ -76,12 +76,12 @@ def __init__(self, data, xml):
self.targets = []
self.get_targets()
self.set_update_content(self.targets)
-
+
def get_targets(self):
for p in self.connection_target(self.xml):
obj = lixml.LinkedInProfileParser(p).results
self.targets = obj
-
+
def set_update_content(self, targets):
update_str = self.first_name + ' ' + self.last_name + ' is now connected with '
if len(targets) == 1:
@@ -92,27 +92,27 @@ def set_update_content(self, targets):
update_str = re.sub(', and $', '', update_str)
self.update_content = update_str
return
-
+
class NetworkNewConnectionUpdate(NetworkConnectionUpdate):
def get_targets(self):
self.connection_target = etree.XPath('update-content/person/')
for p in self.connection_target(self.xml):
obj = LinkedInProfileParser(p).results
self.targets = obj
-
+
def set_update_content(self, target):
update_str = ' is now connected with you.'
update_str = targets[0].first_name + ' ' + targets[0].last_name + update_str
self.update_content = update_str
return
-
+
class NetworkAddressBookUpdate(NetworkNewConnectionUpdate):
def set_update_content(self, target):
update_str = ' just joined LinkedIn.'
update_str = self.targets[0].first_name + ' ' + self.targets[0].last_name + update_str
self.update_content = update_str
return
-
+
class NetworkGroupUpdate(NetworkUpdate):
def __init__(self, data, xml):
self.update_key = None
@@ -124,7 +124,7 @@ def __init__(self, data, xml):
self.targets = []
self.get_targets()
self.set_update_content(self.targets)
-
+
def get_targets(self):
for g in self.group_target(self.xml):
target_dict = {}
@@ -133,7 +133,7 @@ def get_targets(self):
target_dict[k] = v
self.targets.append(target_dict)
return
-
+
def set_update_content(self, targets):
update_str = self.first_name + ' ' + self.last_name + ' joined '
if len(targets) == 1:
@@ -144,7 +144,7 @@ def set_update_content(self, targets):
update_str = re.sub(', and $', '', update_str)
self.update_content = update_str
return
-
+
class NetworkQuestionUpdate(NetworkUpdate):
def __init__(self, data, xml):
self.xml = xml
@@ -152,14 +152,14 @@ def __init__(self, data, xml):
self.parse_data(data)
self.question_title_xpath = etree.XPath('update-content/question/title')
self.set_update_content()
-
+
def set_update_content(self):
update_str = self.first_name + ' ' + self.last_name + ' asked a question: '
qstn_text = self.question_title_xpath(self.xml)[0].text.strip()
update_str += qstn_text
self.update_content = update_str
return
-
+
class NetworkAnswerUpdate(NetworkUpdate):
def __init__(self, data, xml):
self.update_key = None
@@ -169,32 +169,32 @@ def __init__(self, data, xml):
self.answer_xpath = etree.XPath('update-content/question/answers/answer')
self.get_answers()
self.set_update_content()
-
+
def get_answers(self):
for a in self.answer_xpath(self.xml):
self.profile_url = a.xpath('web-url')[0].text.strip()
self.first_name = a.xpath('author/first-name')[0].text.strip()
self.last_name = a.xpath('author/last-name')[0].text.strip()
-
+
def set_update_content(self):
update_str = self.first_name + ' ' + self.last_name + ' answered: '
qstn_text = self.question_title_xpath(self.xml)[0].text.strip()
update_str += qstn_text
self.update_content = update_str
return
-
+
class NetworkJobPostingUpdate(NetworkUpdate):
def __init__(self, data, xml):
self.xml = xml
self.parse_data(data)
self.set_update_content()
self.poster = lixml.LinkedInXMLParser(xml.xpath('job-poster')[0])
-
+
def set_update_content(self):
update_str = self.poster.first_name + ' ' + self.poster.last_name + ' posted a job: ' + self.job_title
self.update_content = update_str
return
-
+
class NetworkUpdateComment(LinkedInData):
def __init__(self, xml):
self.xml = xml
@@ -205,14 +205,75 @@ def __init__(self, xml):
self.last_name = self.__content.last_name
self.profile_url = self.__content.profile_url
self.update_content = self.comment_xpath(xml)[0].text
-
+
def jsonify(self):
jsondict = {'first_name': self.first_name,
'last_name': self.last_name,
'update_content': self.update_content,
'profile_url': self.profile_url}
return jsondict
-
+
+class Company(LinkedInData):
+ def __init__(self, data, xml):
+ self.xml = xml
+ self.parse_data(data)
+ self.locations = []
+ self.specialties = []
+ self.email_domains = []
+ self.employee_count_range = ''
+ self.status = ''
+ self.company_type = ''
+ self.get_locations()
+ self.get_specialties()
+ self.get_employee_count()
+ self.get_domains()
+ self.get_company_status()
+ self.get_company_type()
+
+ def get_locations(self):
+ location_xpath = etree.XPath('locations/location')
+ loc = location_xpath(self.xml)
+ for l in loc:
+ obj = lixml.LinkedInXMLParser(etree.tostring(l)).results
+ self.locations.append(obj)
+
+ def get_specialties(self):
+ specialty_xpath = etree.XPath('specialties/specialty')
+ specialties = specialty_xpath(self.xml)
+ for s in specialties:
+ obj = lixml.LinkedInXMLParser(etree.tostring(s)).results
+ self.specialties.append(obj)
+
+ def get_domains(self):
+ domain_xpath = etree.XPath('email-domains/email-domain')
+ domains = domain_xpath(self.xml)
+ for d in domains:
+ obj = lixml.LinkedInXMLParser(etree.tostring(d)).results
+ self.email_domains.append(obj)
+
+ def get_employee_count(self):
+ emp_count_xpath = etree.XPath('employee-count-range/code')
+ emp_count = emp_count_xpath(self.xml)
+ try: self.employee_count_range = emp_count[0].text
+ except: pass
+
+ def get_company_type(self):
+ xpath = etree.XPath('company-type/code')
+ company_type = xpath(self.xml)
+ try: self.company_type = company_type[0].text
+ except: pass
+
+ def get_company_status(self):
+ xpath = etree.XPath('status/code')
+ status = xpath(self.xml)
+ try: self.status = status[0].text
+ except: pass
+
+class Location(LinkedInData):
+ def __init__(self, data, xml):
+ self.xml = xml
+ self.parse_data(data)
+
class Profile(LinkedInData):
def __init__(self, data, xml):
self.profile_url = ''
@@ -231,14 +292,14 @@ def __init__(self, data, xml):
self.get_educations()
self.get_twitter_accounts()
self.get_member_url_resources()
-
+
def set_profile_url(self):
try:
profile_url_xpath = etree.XPath('site-standard-profile-request/url')
self.profile_url = profile_url_xpath(self.xml)[0].text.strip()
except:
pass
-
+
def get_location(self):
try:
location_name_xpath = etree.XPath('location/name')
@@ -247,36 +308,36 @@ def get_location(self):
self.country = country_code_xpath(self.xml)[0].text.strip()
except:
pass
-
+
def get_positions(self):
profile_position_xpath = etree.XPath('positions/position')
pos = profile_position_xpath(self.xml)
for p in pos:
obj = lixml.LinkedInXMLParser(etree.tostring(p)).results
self.positions.append(obj)
-
+
def get_skills(self):
-
+
profile_skills_xpath = etree.XPath('skills/skill')
skills = profile_skills_xpath(self.xml)
for s in skills:
obj = lixml.LinkedInXMLParser(etree.tostring(s)).results
self.skills.append(obj)
-
+
def get_educations(self):
profile_education_xpath = etree.XPath('educations/education')
eds = profile_education_xpath(self.xml)
for e in eds:
obj = lixml.LinkedInXMLParser(etree.tostring(e)).results
self.educations.append(obj)
-
+
def get_twitter_accounts(self):
twitter_accounts_xpath = etree.XPath('twitter-accounts/twitter-account')
accounts = twitter_accounts_xpath(self.xml)
for account in accounts:
obj = lixml.LinkedInXMLParser(etree.tostring(account)).results
self.twitter_accounts.append(obj)
-
+
def get_member_url_resources(self):
url_resources_xpath = etree.XPath('member-url-resources/member-url')
urls = url_resources_xpath(self.xml)
@@ -284,19 +345,19 @@ def get_member_url_resources(self):
obj = lixml.LinkedInXMLParser(etree.tostring(url)).results
self.member_url_resources.append(obj)
-
-
+
+
class Position(LinkedInData):
pass
-
+
class Education(LinkedInData):
pass
-
+
class TwitterAccount(LinkedInData):
pass
-
+
class Skills(LinkedInData):
pass
-
+
class MemberUrlResource(LinkedInData):
- pass
+ pass
Something went wrong with that request. Please try again.