Skip to content

Commit bf13a6c

Browse files
committed
Add toggle for disabling gTLD domains
In some cases, it doesn't make sense within the domain of the problem you're solving to accept a domain at a gTLD, in particular when that gTLD is not registered. To ease the use of the library in cases such as this, `is_email` now takes a flag called `allow_gtld` that disallows any address where the domain part of the email address is a gTLD.
1 parent 58b8200 commit bf13a6c

7 files changed

Lines changed: 98 additions & 16 deletions

File tree

README.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,23 @@ These are primary indicators of whether an email address can even be
7777
issued at that domain. However, a valid response here *is not a guarantee
7878
that the email exists*, merely that is *can* exist.
7979

80+
If you want to limit using a `gTLD`_ as the domain part of the email
81+
address, you can do so with a flag:
82+
83+
.. code-block:: python
84+
85+
from pyisemail import is_email
86+
87+
address = "thiswont@workatall"
88+
bool_result_with_check = is_email(address, allow_gtld=False)
89+
detailed_result_with_check = is_email(address, allow_gtld=False, diagnose=True)
90+
8091
In addition to the base ``is_email`` functionality, you can also use the
8192
validators by themselves. Check the validator source doe to see how this
8293
works.
8394

95+
.. _gTLD: https://en.wikipedia.org/wiki/Generic_top-level_domain
96+
8497
Uninstall
8598
---------
8699

pyisemail/__init__.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,36 @@
22
from pyisemail.email_validator import EmailValidator
33
from pyisemail.reference import Reference
44
from pyisemail.validators import DNSValidator
5+
from pyisemail.validators import GTLDValidator
56
from pyisemail.validators import ParserValidator
67
from pyisemail.version import VERSION
78

89
__version__ = VERSION
910
__all__ = ['is_email']
1011

1112

12-
def is_email(address, check_dns=False, diagnose=False):
13+
def is_email(address, check_dns=False, diagnose=False, allow_gtld=True):
1314
"""Validate an email address.
1415
1516
Keyword arguments:
1617
address --- the email address as a string
1718
check_dns --- flag for whether to check the DNS status of the domain
1819
diagnose --- flag for whether to return True/False or a Diagnosis
20+
allow_gtld --- flag for whether to prevent gTLDs as the domain
1921
2022
"""
2123

2224
threshold = BaseDiagnosis.CATEGORIES["THRESHOLD"]
2325
d = ParserValidator().is_email(address, True)
24-
if check_dns is True and d < BaseDiagnosis.CATEGORIES["DNSWARN"]:
25-
threshold = BaseDiagnosis.CATEGORIES["VALID"]
26-
d = max(d, DNSValidator().is_valid(address.split("@")[1], True))
26+
27+
if d < BaseDiagnosis.CATEGORIES["DNSWARN"]:
28+
domain = address.split("@")[1]
29+
30+
if check_dns is True or allow_gtld is False:
31+
threshold = BaseDiagnosis.CATEGORIES["VALID"]
32+
if check_dns is True:
33+
d = max(d, DNSValidator().is_valid(domain, True))
34+
if allow_gtld is False:
35+
d = max(d, GTLDValidator().is_valid(domain, True))
2736

2837
return d if diagnose else d < threshold

pyisemail/diagnosis/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from pyisemail.diagnosis.cfws_diagnosis import CFWSDiagnosis
33
from pyisemail.diagnosis.deprecated_diagnosis import DeprecatedDiagnosis
44
from pyisemail.diagnosis.dns_diagnosis import DNSDiagnosis
5+
from pyisemail.diagnosis.gtld_diagnosis import GTLDDiagnosis
56
from pyisemail.diagnosis.invalid_diagnosis import InvalidDiagnosis
67
from pyisemail.diagnosis.rfc5321_diagnosis import RFC5321Diagnosis
78
from pyisemail.diagnosis.rfc5322_diagnosis import RFC5322Diagnosis
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from pyisemail.diagnosis import BaseDiagnosis
2+
3+
4+
class GTLDDiagnosis(BaseDiagnosis):
5+
6+
"""A diagnosis indicating that a domain is a disallowed gTLD."""
7+
8+
DESCRIPTION = "Address uses a gTLD as its domain."
9+
10+
ERROR_CODES = {"GTLD": 2}
11+
12+
MESSAGES = {
13+
"GTLD": (
14+
"Address has a gTLD as its domain and you "
15+
"have disallowed those in your check."
16+
)
17+
}

pyisemail/validators/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from pyisemail.validators.dns_validator import DNSValidator
2+
from pyisemail.validators.gtld_validator import GTLDValidator
23
from pyisemail.validators.parser_validator import ParserValidator
34

4-
__all__ = ['DNSValidator', 'ParserValidator']
5+
__all__ = ['DNSValidator', 'GTLDValidator', 'ParserValidator']
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from pyisemail.diagnosis import GTLDDiagnosis, ValidDiagnosis
2+
3+
4+
class GTLDValidator(object):
5+
def is_valid(self, domain, diagnose=False):
6+
7+
"""Check whether a domain is a gTLD.
8+
9+
Keyword arguments:
10+
domain --- the domain to check
11+
diagnose --- flag to report a diagnosis or a boolean (default False)
12+
13+
"""
14+
15+
if "." in domain:
16+
d = ValidDiagnosis()
17+
else:
18+
d = GTLDDiagnosis("GTLD")
19+
20+
return d

tests/test_is_email.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
from testscenarios import TestWithScenarios
22
from unittest import TestCase
3+
34
try:
45
from unittest.mock import patch
56
except ImportError:
67
from mock import patch
78
import dns.resolver
89
from pyisemail import is_email
9-
from pyisemail.diagnosis import BaseDiagnosis, DNSDiagnosis
10+
from pyisemail.diagnosis import (
11+
BaseDiagnosis,
12+
DNSDiagnosis,
13+
GTLDDiagnosis,
14+
ValidDiagnosis,
15+
)
1016
from tests.validators import create_diagnosis, get_scenarios
1117

1218

1319
class IsEmailTest(TestWithScenarios):
1420

1521
scenarios = get_scenarios("tests.xml")
16-
threshold = BaseDiagnosis.CATEGORIES['THRESHOLD']
22+
threshold = BaseDiagnosis.CATEGORIES["THRESHOLD"]
1723

1824
def test_without_diagnosis(self):
1925
result = is_email(self.address)
@@ -22,8 +28,10 @@ def test_without_diagnosis(self):
2228
self.assertEqual(
2329
result,
2430
expected,
25-
("%s (%s): Got %s, but expected %s."
26-
% (self.id, self.address, result, expected))
31+
(
32+
"%s (%s): Got %s, but expected %s."
33+
% (self.id, self.address, result, expected)
34+
),
2735
)
2836

2937
def test_with_diagnosis(self):
@@ -33,20 +41,33 @@ def test_with_diagnosis(self):
3341
self.assertEqual(
3442
result,
3543
expected,
36-
("%s (%s): Got %s, but expected %s."
37-
% (self.id, self.address, result, expected))
44+
(
45+
"%s (%s): Got %s, but expected %s."
46+
% (self.id, self.address, result, expected)
47+
),
3848
)
3949

4050

41-
@patch('dns.resolver.query', side_effect=dns.resolver.NoAnswer)
51+
@patch("dns.resolver.query", side_effect=dns.resolver.NoAnswer)
4252
class CheckDNSFailureTestCase(TestCase):
43-
4453
def test_without_diagnosis(self, mocked_method):
45-
result = is_email('test@example.com', check_dns=True)
54+
result = is_email("test@example.com", check_dns=True)
4655
expected = False
4756
self.assertEqual(result, expected)
4857

4958
def test_with_diagnosis(self, mocked_method):
50-
result = is_email('test@example.com', check_dns=True, diagnose=True)
51-
expected = DNSDiagnosis('NO_RECORD')
59+
result = is_email("test@example.com", check_dns=True, diagnose=True)
60+
expected = DNSDiagnosis("NO_RECORD")
5261
self.assertEqual(result, expected)
62+
63+
64+
class GTLDTest(TestCase):
65+
def test_with_diagnosis(self):
66+
self.assertTrue(is_email("a@b"))
67+
self.assertFalse(is_email("a@b", allow_gtld=False))
68+
69+
def test_without_diagnosis(self):
70+
self.assertEqual(is_email("a@b", diagnose=True), ValidDiagnosis())
71+
self.assertEqual(
72+
is_email("a@b", allow_gtld=False, diagnose=True), GTLDDiagnosis("GTLD")
73+
)

0 commit comments

Comments
 (0)