Fixes #85, optional skipping of the autoupdate

This commit is contained in:
László Károlyi 2021-12-06 17:58:28 +01:00
parent 07512c45db
commit 91fb131abf
Signed by: karolyi
GPG Key ID: 2DCAF25E55735BFE
8 changed files with 38 additions and 24 deletions

View File

@ -1,3 +1,6 @@
1.0.4:
- Skipping the startup update with setting an environment variable called `PY3VE_IGNORE_UPDATER`
1.0.3:
- Moving project off github
- Static type check fixes

View File

@ -172,5 +172,7 @@ The update can be triggered manually:
`callback`: An optional Callable (function/method) to be called when the update is done.
You can completely skip the auto-update on startup by setting the environment variable `PY3VE_IGNORE_UPDATER` to any value.
# Read the [FAQ](https://gitea.ksol.io/karolyi/py3-validate-email/src/branch/master/FAQ.md)!

View File

@ -57,7 +57,7 @@ class BuildPyCommand(build_py):
setup(
name='py3-validate-email',
version='1.0.3',
version='1.0.4',
packages=find_packages(exclude=['tests']),
install_requires=['dnspython~=2.1', 'idna~=3.0', 'filelock~=3.0'],
author='László Károlyi',

View File

@ -11,11 +11,13 @@ from validate_email.validate_email import (
class BlacklistCheckTestCase(TestCase):
'Testing if the included blacklist filtering works.'
def setUpClass():
def setUpClass(): # type: ignore
update_builtin_blacklist(force=False, background=False)
def test_blacklist_positive(self):
'Disallows blacklist item: mailinator.com.'
# The setting of the PY3VE_IGNORE_UPDATER variable doesn't
# matter here as the module has already download an initial list
with self.assertRaises(DomainBlacklistedError):
domainlist_check(EmailAddress('pm2@mailinator.com'))
with self.assertRaises(DomainBlacklistedError):

View File

@ -16,11 +16,11 @@ class InstallTest(TestCase):
'Testing package installation.'
def test_datadir_is_in_place(self):
'Data directory should be in the virtualenv.'
'Data directory should be in the virtualenv *after installation*.'
output = check_output([
executable, '-c', (
'import sys;sys.path.remove("");import validate_email;'
'print(validate_email.updater.BLACKLIST_FILEPATH_INSTALLED);'
'import sys;sys.path.remove("");import validate_email;' +
'print(validate_email.updater.BLACKLIST_FILEPATH_INSTALLED);' +
'print(validate_email.updater.ETAG_FILEPATH_INSTALLED, end="")'
)]).decode('ascii')
bl_path, etag_path = output.split('\n')

View File

@ -3,14 +3,14 @@ from re import compile as re_compile
HOST_REGEX = re_compile(
# max length for domain name labels is 63 characters per RFC 1034
r'((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+)'
r'((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+)' +
r'(?:[A-Z0-9-]{2,63}(?<!-))\Z', IGNORECASE)
LITERAL_REGEX = re_compile(
# literal form, ipv4 or ipv6 address (SMTP 4.1.3)
r'\[([A-f0-9:\.]+)\]\Z', IGNORECASE)
USER_REGEX = re_compile(
# dot-atom
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z"
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z" +
# quoted-string
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013'
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013' +
r'\014\016-\177])*"\Z)', IGNORECASE)

View File

@ -1,4 +1,5 @@
from logging import getLogger
from pathlib import Path
from typing import Optional
from filelock import FileLock
@ -6,8 +7,8 @@ from filelock import FileLock
from .email_address import EmailAddress
from .exceptions import DomainBlacklistedError
from .updater import (
BLACKLIST_FILEPATH_INSTALLED, BLACKLIST_FILEPATH_TMP, LOCK_PATH,
update_builtin_blacklist)
BLACKLIST_FILEPATH_INSTALLED, BLACKLIST_FILEPATH_TMP, ENV_IGNORE_UPDATER,
LOCK_PATH, TMP_PATH, update_builtin_blacklist)
SetOrNone = Optional[set]
LOGGER = getLogger(__name__)
@ -17,7 +18,6 @@ class DomainListValidator(object):
'Check the provided email against domain lists.'
domain_whitelist = set()
domain_blacklist = set('localhost')
_is_builtin_bl_used: bool = False
def __init__(
self, whitelist: SetOrNone = None, blacklist: SetOrNone = None):
@ -26,11 +26,10 @@ class DomainListValidator(object):
if blacklist:
self.domain_blacklist = set(x.lower() for x in blacklist)
else:
self._is_builtin_bl_used = True
self.reload_builtin_blacklist()
@property
def _blacklist_path(self) -> str:
def _blacklist_path(self) -> Path:
'Return the path of the `blacklist.txt` that should be loaded.'
try:
# Zero size: file is touched to indicate the preinstalled
@ -43,9 +42,8 @@ class DomainListValidator(object):
def reload_builtin_blacklist(self):
'(Re)load our built-in blacklist.'
if not self._is_builtin_bl_used:
return
with FileLock(lock_file=LOCK_PATH):
TMP_PATH.mkdir(exist_ok=True)
with FileLock(lock_file=str(LOCK_PATH)):
bl_path = self._blacklist_path
LOGGER.debug(msg=f'(Re)loading blacklist from {bl_path}')
try:
@ -57,7 +55,10 @@ class DomainListValidator(object):
x.strip().lower() for x in lines if x.strip())
def __call__(self, email_address: EmailAddress) -> bool:
'Do the checking here.'
"""
Check if the email domain is valid, raise
`DomainBlacklistedError` if not.
"""
if email_address.domain in self.domain_whitelist:
return True
if email_address.domain in self.domain_blacklist:

View File

@ -1,5 +1,6 @@
from http.client import HTTPResponse
from logging import getLogger
from os import environ
from pathlib import Path
from tempfile import gettempdir, gettempprefix
from threading import Thread
@ -19,9 +20,9 @@ except ImportError:
LOGGER = getLogger(__name__)
TMP_PATH = Path(gettempdir()).joinpath(
f'{gettempprefix()}-py3-validate-email-{geteuid()}')
TMP_PATH.mkdir(exist_ok=True)
ENV_IGNORE_UPDATER = environ.get('PY3VE_IGNORE_UPDATER')
BLACKLIST_URL = (
'https://raw.githubusercontent.com/disposable-email-domains/'
'https://raw.githubusercontent.com/disposable-email-domains/' +
'disposable-email-domains/master/disposable_email_blocklist.conf')
LIB_PATH_DEFAULT = Path(__file__).resolve().parent.joinpath('data')
BLACKLIST_FILEPATH_INSTALLED = LIB_PATH_DEFAULT.joinpath('blacklist.txt')
@ -117,9 +118,11 @@ class BlacklistUpdater(object):
self, force: bool = False, callback: Optional[Callable] = None):
'Start optionally updating the blacklist.txt file.'
# Locking to avoid multi-process update on multi-process startup
# Import filelock locally because this module is als used by setup.py
# Import filelock locally because this module is als used by
# setup.py
from filelock import FileLock
with FileLock(lock_file=LOCK_PATH):
TMP_PATH.mkdir(exist_ok=True)
with FileLock(lock_file=str(LOCK_PATH)):
self._process(force=force)
# Always execute callback because multiple processes can have
# different versions of blacklists (one before, one after
@ -135,12 +138,15 @@ def update_builtin_blacklist(
Update and reload the built-in blacklist. Return the `Thread` used
to do the background update, so it can be `join()`-ed.
"""
if ENV_IGNORE_UPDATER:
LOGGER.debug(msg='Skipping update of built-in blacklist.')
return
LOGGER.debug(msg='Starting optional update of built-in blacklist.')
blacklist_updater = BlacklistUpdater()
kwargs = dict(force=force, callback=callback)
if not background:
blacklist_updater.process(**kwargs)
return
bl_thread = Thread(target=blacklist_updater.process, kwargs=kwargs)
bl_thread.start()
return bl_thread
updater_thread = Thread(target=blacklist_updater.process, kwargs=kwargs)
updater_thread.start()
return updater_thread