Skip to content

Instantly share code, notes, and snippets.

@skion
Created January 19, 2018 00:06
Show Gist options
  • Save skion/0d4ca4c4c2e97a6a549207c55fb925d0 to your computer and use it in GitHub Desktop.
Save skion/0d4ca4c4c2e97a6a549207c55fb925d0 to your computer and use it in GitHub Desktop.
A Hunter.io client optimised for interactive usage, written in Python 3.
import collections.abc
import urllib.parse
from typing import Dict, List
from requests_cache import CachedSession
class DictView(collections.abc.Mapping):
"""
Provides a viw into another dictionary.
"""
MAPPING = NotImplemented
def __init__(self, data: Dict) -> None:
self._data = data
def __getitem__(self, key):
key = self.MAPPING[key]
return self._data[key]
def __len__(self) -> int:
return len(self.MAPPING.keys() & self._data.keys())
def __iter__(self):
return iter(self.MAPPING.keys() & self._data.keys())
def __getattr__(self, key):
try:
key = self.MAPPING[key]
except KeyError as exc:
raise AttributeError(exc)
return self._data[key]
def get_original(self) -> Dict:
return self._data
class Email(DictView):
"""
Represents an email address found for a domain.
"""
MAPPING = dict(
first_name="first_name",
last_name="last_name",
email="email",
score="score",
domain="domain",
position="position",
twitter="twitter",
linkedin_url="linkedin_url",
phone_number="phone_number",
company="company",
)
def __bool__(self):
return bool(self.email)
def __str__(self):
if self.first_name and self.last_name:
return f"\"{self.first_name} {self.last_name}\" " \
f"<{self.email}> ({self.score}%)"
return f"<{self.email}> ({self.score}%)"
def __repr__(self):
return f"{self.__class__.__name__}({self.email}, {self.score}%)"
class DomainEmail(DictView):
"""
Represents an email address found for a domain.
"""
MAPPING = dict(
first_name="first_name",
last_name="last_name",
email="value",
score="confidence",
type="type",
position="position",
twitter="twitter",
linkedin_url="linkedin",
phone_number="phone_number",
company="company",
)
def __bool__(self):
return bool(self.email)
def __str__(self):
if self.first_name and self.last_name:
return f"\"{self.first_name} {self.last_name}\" " \
f"<{self.email}> ({self.score}%)"
return f"<{self.email}> ({self.score}%)"
def __repr__(self):
return f"{self.__class__.__name__}({self.email}, {self.score}%)"
class Domain(DictView):
"""
Represents email addresses found for a domain.
"""
MAPPING = dict(
domain="domain",
company="organization",
)
@property
def all_emails(self) -> List[DomainEmail]:
return [DomainEmail(e) for e in self._data["emails"]]
@property
def generic_emails(self) -> List[DomainEmail]:
return [DomainEmail(e) for e in self._data["emails"]
if e["type"] == "generic"]
@property
def personal_emails(self) -> List[DomainEmail]:
return [DomainEmail(e) for e in self._data["emails"]
if e["type"] == "personal"]
def __repr__(self):
return f"{self.__class__.__name__}" \
f"({self.domain}, {len(self.emails)} addresses)"
class HunterClient:
BASE = "https://api.hunter.io/v2/"
SESSION_CLS = CachedSession
def __init__(self, api_key):
self.api_key = api_key
self.session = self.SESSION_CLS()
def domain_search(self, domain: str=None, company: str=None,
limit: int=None, offset: int=None, type: str=None) \
-> Domain:
"""
One key feature of Hunter is to search all the email addresses
corresponding to one website. You give one domain name and it
returns all the email addresses using this domain name found
on the internet.
"""
self.validate_domain_or_company(domain, company)
result = self.request("domain-search", domain=domain, company=company,
limit=limit, offset=offset, type=type)
return Domain(result)
def email_finder(self, domain: str=None, company: str=None,
first_name: str=None, last_name: str=None,
full_name: str=None) -> Email:
"""
This API endpoint generates or retrieves the most likely email
address from a domain name, a first name and a last name.
"""
self.validate_domain_or_company(domain, company)
self.validate_split_or_full_name(first_name, last_name, full_name)
result = self.request("email-finder", domain=domain, company=company,
first_name=first_name, last_name=last_name,
full_name=full_name)
return Email(result)
def email_verifier(self):
raise NotImplementedError()
def email_count(self, domain: str=None, company: str=None) -> dict:
"""
Look up number of email addresses for a domain or company.
"""
self.validate_domain_or_company(domain, company)
result = self.request("email-count", domain=domain, company=company)
return result
@property
def calls_available(self):
raise NotImplementedError()
@property
def calls_used(self):
raise NotImplementedError()
@property
def calls_left(self):
raise NotImplementedError()
def request(self, path: str, **params) -> dict:
path = urllib.parse.urljoin(self.BASE, path)
params = self.filter_params(params)
params["api_key"] = self.api_key
response = self.session.get(path, params=params)
response.raise_for_status()
return response.json()["data"]
@staticmethod
def validate_domain_or_company(domain: str, company: str) -> None:
if bool(domain) == bool(company):
raise ValueError("Provide either a domain, or company name.")
@staticmethod
def validate_split_or_full_name(first_name: str, last_name: str,
full_name: str) -> None:
split_name = bool(first_name) or bool(last_name)
if bool(split_name) == bool(full_name):
raise ValueError("Provide either first and last name, or full "
"name.")
@staticmethod
def filter_params(params: dict):
"""
Filter out None values from the given parameter dictionary.
"""
return {k: v for k, v in params.items() if v is not None}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment