Created
January 19, 2018 00:06
-
-
Save skion/0d4ca4c4c2e97a6a549207c55fb925d0 to your computer and use it in GitHub Desktop.
A Hunter.io client optimised for interactive usage, written in Python 3.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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