diff --git a/README.md b/README.md index 6c6f37a..3639353 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# email_hunter_python -### An Email Hunter API client written in Python +# hunter_python +### A Hunter API client written in Python ## Installation Requirements: @@ -9,30 +9,30 @@ Requirements: To install: ``` -pip install email-hunter-python +pip install hunter-python ``` To update: ``` -pip install --upgrade email-hunter-python +pip install --upgrade hunter-python ``` ## Usage -email_hunter_python supports the three main methods of the [Email Hunter](https://emailhunter.co/api/docs) API: -`search`, `generate`, and `exist`. There are two ways to use email_hunter_python: +hunter_python supports the three main methods of the [Hunter](https://hunter.io/api/docs) API: +`search`, `find` and `verify`. There are two ways to use hunter_python: * As a Python library * As a command line (CLI) tool. -#### To use the email_hunter_python Python library: +#### To use the hunter_python Python library: Import the client and instantiate it: ```python -from email_hunter import EmailHunterClient +from hunter import HunterClient ``` ``` -client = EmailHunterClient('my_api_key') +client = HunterClient('my_api_key') ``` You can search: @@ -40,9 +40,14 @@ You can search: client.search('google.com') ``` -A max of 100 results are returned, so use offset to paginate: +By default 10 results are returned, so use offset to paginate: ```python -client.search('google.com', offset=1) +client.search('google.com', offset=10) +``` + +You can also limit the number of results: +```python +client.search('google.com', limit=5) ``` You can also change type (personal or generic): @@ -50,33 +55,34 @@ You can also change type (personal or generic): client.search('google.com', type_='personal') ``` -You can generate: +You can find an email: ```python -client.generate('google.com', 'Sergey', 'Brin') +client.find('google.com', 'Sergey', 'Brin') ``` -And you can check if an email exists: +And you can verify the deliverability of an email address: ```python -client.exist('sergey@google.com') +client.verify('sergey@google.com') ``` -#### To use email_hunter_python as a CLI tool: +#### To use hunter_python as a CLI tool: ``` -email_hunter [command name] [api_key] [other args] +hunter [command name] [api_key] [other args] ``` -The command name is `search`, `generate` or `exist`, the api_key is the API key associated with your Email Hunter +The command name is `search`, `find` or `verify`, the api_key is the API key associated with your Hunter account The other arguments depend on the command you are using: ``` ---domain Required for search and generate commands ---offset Optional, used with search command. +--domain Required for search and find commands +--limit Optional, used with search command +--offset Optional, used with search command --type Optional, used with search command ---first_name Required for generate command ---last_name Required for generate command ---email Required for exist command +--first_name Required for find command +--last_name Required for find command +--email Required for verify command --file Path to a CSV to be used with the specified command. CSV must have a column for each argument used. ``` @@ -84,10 +90,10 @@ The other arguments depend on the command you are using: The file argument is useful when you want to make several requests of the same type. For example if you wanted to find the email addresses for several people at an organization you would do the following: ``` -email_hunter generate [api_key] --file people.csv > emails.csv +hunter find [api_key] --file people.csv > emails.csv ``` -Where `people.csv` looks like: +Where `people.csv` looks like: ``` domain,first_name,last_name google.com,larry,page diff --git a/email_hunter/__init__.py b/email_hunter/__init__.py deleted file mode 100644 index 6a7ee54..0000000 --- a/email_hunter/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright 2015 Alan Vezina. All rights reserved. -from .email_hunter_client import EmailHunterClient -import email_hunter.cli as cli - -__title = 'EmailHunter' -__author__ = 'Alan Vezina' -__version__ = '1.1.0' -__license__ = 'MIT' -__all__ = ['EmailHunterClient', 'cli'] diff --git a/email_hunter/email_hunter_client.py b/email_hunter/email_hunter_client.py deleted file mode 100644 index a295a5e..0000000 --- a/email_hunter/email_hunter_client.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright 2015 Alan Vezina. All rights reserved. -import requests - - -class EmailHunterClient: - def __init__(self, api_key, api_version='v1'): - self.api_key = api_key - self.api_version = api_version - self.base_url = 'https://api.emailhunter.co/{}/'.format(api_version) - - def _make_request(self, url, payload): - r = requests.get(url, params=payload) - data = r.json() - # Raise error if not 200 OK - r.raise_for_status() - - return data - - def search(self, domain, offset=0, type_=None): - """ - Returns all the email addresses found using one given domain name, with Email Hunter's sources. - :param domain: The domain name to check for email addresses - :return: A list of email addresses, with type and sources - """ - url = self.base_url + 'search' - payload = {'api_key': self.api_key, 'domain': domain, 'offset': offset} - - if type_: - payload['type'] = type_ - - data = self._make_request(url, payload) - - return data['emails'] - - def exist(self, email): - """ - Checks if a given email address has been found in the EmailHunter base and returns the sources. - :param email: the email address you want to check - :return: Tuple, 'exist' (boolean) and sources (list of dicts) - """ - url = self.base_url + 'exist' - payload = {'api_key': self.api_key, 'email': email} - data = self._make_request(url, payload) - - return data['exist'], data['sources'] - - def generate(self, domain, first_name, last_name): - """ - Guesses the most likely email of a person from their first name, last name, and a domain name - :param domain: The domain name to search - :param first_name: The first name of the person to search for. - :param last_name: The last name of the person to search for. - :return: Tuple, email, score (int) - """ - url = self.base_url + 'generate' - payload = {'api_key': self.api_key, 'domain': domain, 'first_name': first_name, 'last_name': last_name} - data = self._make_request(url, payload) - - return data['email'], data['score'] - - def verify(self, email): - """ - Checks if a given email address is deliverable and has been found on the internet. - :param email: the email address you want to check - :return: Dict - """ - url = self.base_url + 'verify' - payload = {'api_key': self.api_key, 'email': email} - data = self._make_request(url, payload) - - return data diff --git a/hunter/__init__.py b/hunter/__init__.py new file mode 100644 index 0000000..c89a21b --- /dev/null +++ b/hunter/__init__.py @@ -0,0 +1,9 @@ +# Copyright 2015 Alan Vezina. All rights reserved. +from .hunter_client import HunterClient +import hunter.cli as cli + +__title = 'Hunter' +__author__ = 'Alan Vezina' +__version__ = '2.0.0' +__license__ = 'MIT' +__all__ = ['HunterClient', 'cli'] diff --git a/email_hunter/cli.py b/hunter/cli.py similarity index 56% rename from email_hunter/cli.py rename to hunter/cli.py index 25bc658..b9d537b 100644 --- a/email_hunter/cli.py +++ b/hunter/cli.py @@ -4,7 +4,7 @@ import json import time from functools import reduce -from email_hunter import EmailHunterClient +from hunter import HunterClient THROTTLE = 0.2 @@ -28,7 +28,7 @@ def validate_search_file(reader: DictReader): return True -def validate_generate_file(reader: DictReader): +def validate_find_file(reader: DictReader): valid = True field_names = reader.fieldnames @@ -44,7 +44,7 @@ def validate_generate_file(reader: DictReader): return valid -def validate_exist_file(reader: DictReader): +def validate_verify_file(reader: DictReader): field_names = reader.fieldnames if 'email' not in field_names: @@ -54,7 +54,7 @@ def validate_exist_file(reader: DictReader): return True -def search(client: EmailHunterClient, domain, offset, type_, print_header=True, is_file_output=False): +def search(client: HunterClient, domain, limit, offset, type_, print_header=True, is_file_output=False): if is_file_output: header = 'domain,email,type,sources' line_format = '{},{},{},{}' @@ -63,11 +63,11 @@ def search(client: EmailHunterClient, domain, offset, type_, print_header=True, line_format = '{}\t{}\t{}\t{}' try: - emails = client.search(domain, offset, type_) + data = client.search(domain, limit, offset, type_) except Exception as e: print('Error during search request: {}'.format(e)) else: - for data in emails: + for data in data['emails']: email = data['value'] type_ = data['type'] sources = reduce_sources(data['sources']) @@ -79,44 +79,47 @@ def search(client: EmailHunterClient, domain, offset, type_, print_header=True, print(line_format.format(domain, email, type_, sources)) -def generate(client: EmailHunterClient, domain, first_name, last_name, print_header=True, is_file_output=False): +def find(client: HunterClient, domain, first_name, last_name, print_header=True, is_file_output=False): try: - email, score = client.generate(domain, first_name, last_name) + data = client.find(domain, first_name, last_name) except Exception as e: - print('Error during request: {}'.format(e)) + print('Error during find request: {}'.format(e)) else: + sources = reduce_sources(data['sources']) if is_file_output: if print_header: - print('domain,first_name,last_name,email,score') + print('domain,first_name,last_name,email,score,sources') - print('{},{},{},{},{}'.format(domain, first_name, last_name, email, score)) + print('{},{},{},{},{},{}'.format(domain, first_name, last_name, data['email'], data['score'], sources)) else: print('Domain:\t{}'.format(domain)) print('First Name:\t{}'.format(first_name)) print('Last Name:\t{}'.format(last_name)) - print('Email:\t{}'.format(email)) - print('Score:\t{}'.format(score)) + print('Email:\t{}'.format(data['email'])) + print('Score:\t{}'.format(data['score'])) + print('Sources:\t{}'.format(json.dumps(sources, indent=2))) -def exist(client: EmailHunterClient, email, print_header=True, is_file_output=False): +def verify(client: HunterClient, email, print_header=True, is_file_output=False): try: - exist_, sources = client.exist(email) + data = client.verify(email) except Exception as e: - print('Error during exist request: {}'.format(e)) + print('Error during verify request: {}'.format(e)) else: + sources = reduce_sources(data['sources']) if is_file_output: if print_header: - print('email,exist,sources') + print('email,result,score,sources') - sources = reduce_sources(sources) - print('{},{},{}'.format(email, exist_, sources)) + print('{},{},{},{}'.format(email, data['result'], data['score'], sources)) else: print('Email:\t{}'.format(email)) - print('Exist:\t{}'.format(exist_)) + print('Result:\t{}'.format(data['result'])) + print('Score:\t{}'.format(data['score'])) print('Sources:\t{}'.format(json.dumps(sources, indent=2))) -def handle_search_file(client: EmailHunterClient, reader: DictReader): +def handle_search_file(client: HunterClient, reader: DictReader): if not validate_search_file(reader): return @@ -124,15 +127,16 @@ def handle_search_file(client: EmailHunterClient, reader: DictReader): for line in reader: domain = line['domain'].strip() + limit = line.get('limit', 10) offset = line.get('offset', 0) type_ = line.get('type') - search(client, domain, offset, type_, print_header=print_header, is_file_output=True) + search(client, domain, limit, offset, type_, print_header=print_header, is_file_output=True) print_header = False time.sleep(THROTTLE) -def handle_generate_file(client: EmailHunterClient, reader: DictReader): - if not validate_generate_file(reader): +def handle_find_file(client: HunterClient, reader: DictReader): + if not validate_find_file(reader): return print_header = True @@ -141,27 +145,27 @@ def handle_generate_file(client: EmailHunterClient, reader: DictReader): domain = line['domain'].strip() first_name = line['first_name'].strip() last_name = line['last_name'].strip() - generate(client, domain, first_name, last_name, print_header=print_header, is_file_output=True) + find(client, domain, first_name, last_name, print_header=print_header, is_file_output=True) print_header = False time.sleep(THROTTLE) -def handle_exist_file(client: EmailHunterClient, reader: DictReader): - if not validate_exist_file(reader): +def handle_verify_file(client: HunterClient, reader: DictReader): + if not validate_verify_file(reader): return print_header = True for line in reader: email = line['email'] - exist(client, email, print_header=print_header, is_file_output=True) + verify(client, email, print_header=print_header, is_file_output=True) print_header = False time.sleep(THROTTLE) -def handle_cli(command, api_key, domain=None, offset=0, type=None, first_name=None, last_name=None, email=None, +def handle_cli(command, api_key, domain=None, limit=10, offset=0, type=None, first_name=None, last_name=None, email=None, file=None): - client = EmailHunterClient(api_key) + client = HunterClient(api_key) reader = None if file is not None: @@ -174,41 +178,44 @@ def handle_cli(command, api_key, domain=None, offset=0, type=None, first_name=No elif domain: print('Searching {} for emails'.format(domain)) + if limit: + print('Limit: {}'.format(limit)) + if offset: print('Offset: {}'.format(offset)) if type: print('Type: {}'.format(type)) - search(client, domain, offset, type) + search(client, domain, limit, offset, type) else: - print('domain is required when using the generate command') - elif command == 'generate': + print('domain is required when using the find command') + elif command == 'find': if file: - handle_generate_file(client, reader) + handle_find_file(client, reader) else: valid = True if not domain: - print('domain is required when using the generate command') + print('domain is required when using the find command') if not first_name: - print('first_name is required when using the generate command') + print('first_name is required when using the find command') if not last_name: - print('last_name is required when using the generate command') + print('last_name is required when using the find command') if valid: print('Finding email for {}, {}, {}'.format(domain, first_name, last_name)) - generate(client, domain, first_name, last_name) - elif command == 'exist': + find(client, domain, first_name, last_name) + elif command == 'verify': if file: - handle_exist_file(client, reader) + handle_verify_file(client, reader) elif email: - print('Checking if {} exists'.format(email)) - exist(client, email) + print('Verifying deliverability of {}'.format(email)) + verify(client, email) else: - print('email is required when using the exist command') + print('email is required when using the verify command') else: print('Invalid command {}'.format(command)) @@ -221,15 +228,16 @@ def main(): TODO: parse args here :return: """ - parser = argparse.ArgumentParser(description='Email Hunter CLI') - parser.add_argument('command', help='The API command to run. Choices: search, exist, or generate') + parser = argparse.ArgumentParser(description='Hunter CLI') + parser.add_argument('command', help='The API command to run. Choices: search, verify or find') parser.add_argument('api_key', help='The API key for your account') - parser.add_argument('--domain', help='Required for search and generate commands') + parser.add_argument('--domain', help='Required for search and find commands') + parser.add_argument('--limit', help='Optional, used with search command.') parser.add_argument('--offset', help='Optional, used with search command.') parser.add_argument('--type', help='Optional, used with search command') - parser.add_argument('--first_name', help='Required for generate command') - parser.add_argument('--last_name', help='Required for generate command') - parser.add_argument('--email', help='Required for exist command') + parser.add_argument('--first_name', help='Required for find command') + parser.add_argument('--last_name', help='Required for find command') + parser.add_argument('--email', help='Required for verify command') file_help = 'Path to a CSV to be used with the specified command. CSV must have a column for each argument used' parser.add_argument('--file', help=file_help) args = parser.parse_args() diff --git a/hunter/hunter_client.py b/hunter/hunter_client.py new file mode 100644 index 0000000..d381237 --- /dev/null +++ b/hunter/hunter_client.py @@ -0,0 +1,59 @@ +# Copyright 2015 Alan Vezina. All rights reserved. +import requests + + +class HunterClient: + def __init__(self, api_key, api_version='v2'): + self.api_key = api_key + self.api_version = api_version + self.base_url = 'https://api.hunter.io/{}/'.format(api_version) + + def _make_request(self, url, payload): + r = requests.get(url, params=payload) + data = r.json() + # Raise error if not 200 OK + r.raise_for_status() + + return data + + def search(self, domain, limit=10, offset=0, type_=None): + """ + Returns all the email addresses found using one given domain name, with Email Hunter's sources. + :param domain: The domain name to check for email addresses + :return: Dict + """ + url = self.base_url + 'domain-search' + payload = {'api_key': self.api_key, 'domain': domain, 'limit': limit, 'offset': offset} + + if type_: + payload['type'] = type_ + + data = self._make_request(url, payload) + + return data['data'] + + def find(self, domain, first_name, last_name): + """ + Generates the most likely email of a person from their first name, last name, and a domain name + :param domain: The domain name to search + :param first_name: The first name of the person to search for. + :param last_name: The last name of the person to search for. + :return: Dict + """ + url = self.base_url + 'email-finder' + payload = {'api_key': self.api_key, 'domain': domain, 'first_name': first_name, 'last_name': last_name} + data = self._make_request(url, payload) + + return data['data'] + + def verify(self, email): + """ + Verifies if a given email address is deliverable and has been found on the Internet. + :param email: the email address you want to check + :return: Dict + """ + url = self.base_url + 'email-verifier' + payload = {'api_key': self.api_key, 'email': email} + data = self._make_request(url, payload) + + return data['data'] diff --git a/setup.py b/setup.py index 2adb8c7..0a40d44 100644 --- a/setup.py +++ b/setup.py @@ -1,27 +1,27 @@ import os from setuptools import setup, find_packages -short_description = 'Client for Email Hunter REST API' +short_description = 'Client for Hunter REST API' long_description = short_description if os.path.exists('README.txt'): long_description = open('README.txt').read() setup( - name='email_hunter_python', - version='1.1.0', + name='hunter_python', + version='2.0.0', description=short_description, long_description=long_description, license='MIT', - keywords='email hunter client rest api cli', + keywords='hunter client rest api cli', author='Alan Vezina', author_email='alan.vezina@gmail.com', - url='https://github.com/tipsqueal/email-hunter-python', + url='https://github.com/tipsqueal/hunter-python', packages=find_packages(), install_requires=['requests'], entry_points=''' [console_scripts] - email_hunter=email_hunter.cli:main + hunter=hunter.cli:main ''', classifiers=[ 'Development Status :: 5 - Production/Stable',