Skip to content

Commit

Permalink
Release 2.4.2
Browse files Browse the repository at this point in the history
  • Loading branch information
kakshay21 committed Feb 7, 2021
1 parent b06e815 commit d4e149d
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 43 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.pyc
.vscode
build
7 changes: 7 additions & 0 deletions build/lib/verify_email/tests/test_verify_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,12 @@ def test_valid_email(self):
def test_invalid_email(self):
self.assertEqual(False, verify_email(self.emails[1]))

def test_email_with_debugger(self):
self.assertEqual(False, verify_email(self.emails[2], debug=True))

def test_multiple_emails(self):
self.assertEqual([True, False, False, False], verify_email(self.emails))


if __name__ == '__main__':
unittest.main()
112 changes: 71 additions & 41 deletions build/lib/verify_email/verify_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,31 @@
import threading
import collections.abc as abc


EMAIL_REGEX = r'(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)'
MX_DNS_CACHE = {}
MX_CHECK_CACHE = {}

# Set up logging on module load and avoid adding 'ch' or 'logger' to module
# namespace. We could assign the logger to a module level name, but it is only
# used by two functions, and this approach demonstrates using the 'logging'
# namespace to retrieve arbitrary loggers.

def setup_module_logger(name):
"""Set up module level logging with formatting"""
logger = logging.getLogger(name)
ch = logging.StreamHandler()
# Really should not be configuring formats in a library, see
# https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
ch.setFormatter(formatter)
logger.addHandler(ch)


setup_module_logger('verify_email')


def is_list(obj):
return isinstance(obj, abc.Sequence) and not isinstance(obj, str)

Expand All @@ -37,33 +58,22 @@ async def get_mx_hosts(email):
return mx_hosts


async def enable_logger(name):
logger = logging.getLogger(name)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)
return logger

async def handler_verify(mx_hosts, email, debug, timeout=None):
logger = None
if debug:
logger = await enable_logger('verify_email')
async def handler_verify(mx_hosts, email, timeout=None):
for mx in mx_hosts:
res = await network_calls(mx, email, debug, logger, timeout)
res = await network_calls(mx, email, timeout)
if res:
return res
return False


async def syntax_check(email):
if re.match(r'(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)', email):
if re.match(EMAIL_REGEX, email):
return True
return False


async def _verify_email(email, timeout=None, verify=True, debug=False):
async def _verify_email(email, timeout=None, verify=True):
'''Validate email by syntax check, domain check and handler check.
'''
is_valid_syntax = await syntax_check(email)
Expand All @@ -73,54 +83,74 @@ async def _verify_email(email, timeout=None, verify=True, debug=False):
if mx_hosts is None:
return False
else:
return await handler_verify(mx_hosts, email, debug, timeout)
return await handler_verify(mx_hosts, email, timeout)
else:
return False

def verify_email(emails, timeout=None, verify=True, debug=False):
if debug:
logger = logging.getLogger('verify_email')
logger.setLevel(logging.DEBUG)
result = []
if not is_list(emails):
emails = [emails]

# asyncio events doesn't fully support windows platform
# See: https://github.com/kakshay21/verify_email/issues/34#issuecomment-616971628
if sys.platform == "win32":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
loop = asyncio.get_event_loop()

for email in emails:
resp = asyncio.run(_verify_email(email, timeout, verify, debug))
resp = loop.run_until_complete(_verify_email(email, timeout, verify))
result.append(resp)

return result if len(result) > 1 else result[0]

async def network_calls(mx, email, debug, logger, timeout):
if not timeout:
timeout = 20
async def verify_email_async(emails, timeout=None, verify=True, debug=False):
if debug:
logger = logging.getLogger('verify_email')
logger.setLevel(logging.DEBUG)
result = []
if not is_list(emails):
emails = [emails]


for email in emails:
result.append(await _verify_email(email, timeout, verify))

return result if len(result) > 1 else result[0]

async def network_calls(mx, email, timeout=20):
logger = logging.getLogger('verify_email')
result = False
try:
smtp = smtplib.SMTP(mx.host)
smtp = smtplib.SMTP(mx.host, timeout=timeout)
status, _ = smtp.ehlo()
if status != 250:
if status >= 400:
smtp.quit()
if debug:
logger.debug(f'{mx} answer: {status} - {_}')
logger.debug(f'{mx} answer: {status} - {_}\n')
return False
smtp.mail('')
status, _ = smtp.rcpt(email)
if status >= 500:
smtp.quit()
if debug:
logger.debug(f'{mx} answer: {status} - {_}')
return False
if status == 250:
smtp.quit()
return True
if status >= 400:
logger.debug(f'{mx} answer: {status} - {_}\n')
result = False
if status >= 200 and status <= 250:
result = True

if debug:
logger.debug(f'{mx} answer: {status} - {_}', mx, status, _)
logger.debug(f'{mx} answer: {status} - {_}\n')
smtp.quit()

except smtplib.SMTPServerDisconnected:
if debug:
logger.debug(f'Server not permits verify user, {mx} disconected.')
logger.debug(f'Server does not permit verify user, {mx} disconnected.\n')
except smtplib.SMTPConnectError:
if debug:
logger.debug(f'Unable to connect to {mx}.')
logger.debug(f'Unable to connect to {mx}.\n')
except socket.timeout as e:
logger.debug(f'Timeout connecting to server {mx}: {e}.\n')
return None
except socket.error as e:
if debug:
logger.debug(f'ServerError or socket.error exception raised {e}.')
logger.debug(f'ServerError or socket.error exception raised {e}.\n')
return None

return result
Binary file modified dist/verify_email-2.4.1-py3-none-any.whl
Binary file not shown.
Binary file added dist/verify_email-2.4.1-py3.9.egg
Binary file not shown.
Binary file modified dist/verify_email-2.4.1.tar.gz
Binary file not shown.
Binary file added dist/verify_email-2.4.2-py3-none-any.whl
Binary file not shown.
Binary file added dist/verify_email-2.4.2.tar.gz
Binary file not shown.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name='verify_email',
version='2.4.1',
version='2.4.2',
author='Kumar Akshay',
author_email='k.akshay9721@gmail.com',
description='A small package for email verification',
Expand Down
9 changes: 8 additions & 1 deletion verify_email.egg-info/PKG-INFO
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: verify-email
Version: 2.4.1
Version: 2.4.2
Summary: A small package for email verification
Home-page: https://github.com/kakshay21/verify_email
Author: Kumar Akshay
Expand Down Expand Up @@ -46,6 +46,13 @@ Description: # verify-email
>>> verify_email(['foo@bar.com', 'example@foo.com'])
[False, False]
```
Also, note that some emails will likely fail in validation, if so you can check the reason of failure
using debug flag.
```
>>> from verify_email import verify_email
>>> verify_email('foo@bar.com', debug=True)
```

see for more examples [examples.py](https://github.com/kakshay21/verify_email/blob/master/examples.py)

## Contribute
Expand Down

0 comments on commit d4e149d

Please sign in to comment.