email-validator 2.1.1__tar.gz → 2.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {email_validator-2.1.1/email_validator.egg-info → email_validator-2.1.2}/PKG-INFO +1 -1
  2. {email_validator-2.1.1 → email_validator-2.1.2}/email_validator/deliverability.py +29 -3
  3. {email_validator-2.1.1 → email_validator-2.1.2}/email_validator/rfc_constants.py +1 -1
  4. email_validator-2.1.2/email_validator/version.py +1 -0
  5. {email_validator-2.1.1 → email_validator-2.1.2/email_validator.egg-info}/PKG-INFO +1 -1
  6. {email_validator-2.1.1 → email_validator-2.1.2}/pyproject.toml +1 -1
  7. {email_validator-2.1.1 → email_validator-2.1.2}/tests/mocked-dns-answers.json +55 -0
  8. {email_validator-2.1.1 → email_validator-2.1.2}/tests/test_deliverability.py +25 -28
  9. {email_validator-2.1.1 → email_validator-2.1.2}/tests/test_syntax.py +3 -3
  10. email_validator-2.1.1/email_validator/version.py +0 -1
  11. {email_validator-2.1.1 → email_validator-2.1.2}/LICENSE +0 -0
  12. {email_validator-2.1.1 → email_validator-2.1.2}/MANIFEST.in +0 -0
  13. {email_validator-2.1.1 → email_validator-2.1.2}/README.md +0 -0
  14. {email_validator-2.1.1 → email_validator-2.1.2}/email_validator/__init__.py +0 -0
  15. {email_validator-2.1.1 → email_validator-2.1.2}/email_validator/__main__.py +0 -0
  16. {email_validator-2.1.1 → email_validator-2.1.2}/email_validator/exceptions_types.py +0 -0
  17. {email_validator-2.1.1 → email_validator-2.1.2}/email_validator/py.typed +0 -0
  18. {email_validator-2.1.1 → email_validator-2.1.2}/email_validator/syntax.py +0 -0
  19. {email_validator-2.1.1 → email_validator-2.1.2}/email_validator/validate_email.py +0 -0
  20. {email_validator-2.1.1 → email_validator-2.1.2}/email_validator.egg-info/SOURCES.txt +0 -0
  21. {email_validator-2.1.1 → email_validator-2.1.2}/email_validator.egg-info/dependency_links.txt +0 -0
  22. {email_validator-2.1.1 → email_validator-2.1.2}/email_validator.egg-info/entry_points.txt +0 -0
  23. {email_validator-2.1.1 → email_validator-2.1.2}/email_validator.egg-info/requires.txt +0 -0
  24. {email_validator-2.1.1 → email_validator-2.1.2}/email_validator.egg-info/top_level.txt +0 -0
  25. {email_validator-2.1.1 → email_validator-2.1.2}/setup.cfg +0 -0
  26. {email_validator-2.1.1 → email_validator-2.1.2}/setup.py +0 -0
  27. {email_validator-2.1.1 → email_validator-2.1.2}/tests/mocked_dns_response.py +0 -0
  28. {email_validator-2.1.1 → email_validator-2.1.2}/tests/test_main.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: email_validator
3
- Version: 2.1.1
3
+ Version: 2.1.2
4
4
  Summary: A robust email address syntax and deliverability validation library.
5
5
  Home-page: https://github.com/JoshData/python-email-validator
6
6
  Author: Joshua Tauberer
@@ -1,5 +1,7 @@
1
1
  from typing import Optional, Any, Dict
2
2
 
3
+ import ipaddress
4
+
3
5
  from .exceptions_types import EmailUndeliverableError
4
6
 
5
7
  import dns.resolver
@@ -57,10 +59,30 @@ def validate_email_deliverability(domain: str, domain_i18n: str, timeout: Option
57
59
  deliverability_info["mx_fallback_type"] = None
58
60
 
59
61
  except dns.resolver.NoAnswer:
60
- # If there was no MX record, fall back to an A record. (RFC 5321 Section 5)
62
+ # If there was no MX record, fall back to an A or AAA record
63
+ # (RFC 5321 Section 5). Check A first since it's more common.
64
+
65
+ # If the A/AAAA response has no Globally Reachable IP address,
66
+ # treat the response as if it were NoAnswer, i.e., the following
67
+ # address types are not allowed fallbacks: Private-Use, Loopback,
68
+ # Link-Local, and some other obscure ranges. See
69
+ # https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
70
+ # https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
71
+ # (Issue #134.)
72
+ def is_global_addr(ipaddr):
73
+ try:
74
+ ipaddr = ipaddress.ip_address(ipaddr)
75
+ except ValueError:
76
+ return False
77
+ return ipaddr.is_global
78
+
61
79
  try:
62
80
  response = dns_resolver.resolve(domain, "A")
63
- deliverability_info["mx"] = [(0, str(r)) for r in response]
81
+
82
+ if not any(is_global_addr(r.address) for r in response):
83
+ raise dns.resolver.NoAnswer # fall back to AAAA
84
+
85
+ deliverability_info["mx"] = [(0, domain)]
64
86
  deliverability_info["mx_fallback_type"] = "A"
65
87
 
66
88
  except dns.resolver.NoAnswer:
@@ -69,7 +91,11 @@ def validate_email_deliverability(domain: str, domain_i18n: str, timeout: Option
69
91
  # (It's unclear if SMTP servers actually do this.)
70
92
  try:
71
93
  response = dns_resolver.resolve(domain, "AAAA")
72
- deliverability_info["mx"] = [(0, str(r)) for r in response]
94
+
95
+ if not any(is_global_addr(r.address) for r in response):
96
+ raise dns.resolver.NoAnswer
97
+
98
+ deliverability_info["mx"] = [(0, domain)]
73
99
  deliverability_info["mx_fallback_type"] = "AAAA"
74
100
 
75
101
  except dns.resolver.NoAnswer as e:
@@ -42,7 +42,7 @@ QTEXT_INTL = re.compile(r"[\u0020-\u007E\u0080-\U0010FFFF]")
42
42
  EMAIL_MAX_LENGTH = 254
43
43
  LOCAL_PART_MAX_LENGTH = 64
44
44
  DNS_LABEL_LENGTH_LIMIT = 63 # in "octets", RFC 1035 2.3.1
45
- DOMAIN_MAX_LENGTH = 255 # in "octets", RFC 1035 2.3.4 and RFC 5321 4.5.3.1.2
45
+ DOMAIN_MAX_LENGTH = 253 # in "octets" as transmitted, RFC 1035 2.3.4 and RFC 5321 4.5.3.1.2, and see https://stackoverflow.com/questions/32290167/what-is-the-maximum-length-of-a-dns-name
46
46
 
47
47
  # RFC 2142
48
48
  CASE_INSENSITIVE_MAILBOX_NAMES = [
@@ -0,0 +1 @@
1
+ __version__ = "2.1.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: email_validator
3
- Version: 2.1.1
3
+ Version: 2.1.2
4
4
  Summary: A robust email address syntax and deliverability validation library.
5
5
  Home-page: https://github.com/JoshData/python-email-validator
6
6
  Author: Joshua Tauberer
@@ -1,4 +1,4 @@
1
1
  [tool.pytest.ini_options]
2
2
  markers = [
3
3
  "network: marks tests as requiring Internet access",
4
- ]
4
+ ]
@@ -13,6 +13,35 @@
13
13
  "5 gmail-smtp-in.l.google.com."
14
14
  ]
15
15
  },
16
+ {
17
+ "query": {
18
+ "name": "pages.github.com",
19
+ "type": "MX",
20
+ "class": "IN"
21
+ },
22
+ "answer": []
23
+ },
24
+ {
25
+ "query": {
26
+ "name": "pages.github.com",
27
+ "type": "A",
28
+ "class": "IN"
29
+ },
30
+ "answer": [
31
+ "185.199.108.153",
32
+ "185.199.109.153",
33
+ "185.199.110.153",
34
+ "185.199.111.153"
35
+ ]
36
+ },
37
+ {
38
+ "query": {
39
+ "name": "pages.github.com",
40
+ "type": "TXT",
41
+ "class": "IN"
42
+ },
43
+ "answer": []
44
+ },
16
45
  {
17
46
  "query": {
18
47
  "name": "xkxufoekjvjfjeodlfmdfjcu.com",
@@ -31,6 +60,32 @@
31
60
  "0 ."
32
61
  ]
33
62
  },
63
+ {
64
+ "query": {
65
+ "name": "g.mail.com",
66
+ "type": "MX",
67
+ "class": "IN"
68
+ },
69
+ "answer": []
70
+ },
71
+ {
72
+ "query": {
73
+ "name": "g.mail.com",
74
+ "type": "A",
75
+ "class": "IN"
76
+ },
77
+ "answer": []
78
+ },
79
+ {
80
+ "query": {
81
+ "name": "g.mail.com",
82
+ "type": "AAAA",
83
+ "class": "IN"
84
+ },
85
+ "answer": [
86
+ "::1"
87
+ ]
88
+ },
34
89
  {
35
90
  "query": {
36
91
  "name": "nellis.af.mil",
@@ -10,36 +10,33 @@ from mocked_dns_response import MockedDnsResponseData, MockedDnsResponseDataClea
10
10
  RESOLVER = MockedDnsResponseData.create_resolver()
11
11
 
12
12
 
13
- def test_deliverability_found():
14
- response = validate_email_deliverability('gmail.com', 'gmail.com', dns_resolver=RESOLVER)
15
- assert response.keys() == {'mx', 'mx_fallback_type'}
16
- assert response['mx_fallback_type'] is None
17
- assert len(response['mx']) > 1
18
- assert len(response['mx'][0]) == 2
19
- assert isinstance(response['mx'][0][0], int)
20
- assert response['mx'][0][1].endswith('.com')
21
-
22
-
23
- def test_deliverability_fails():
24
- # Domain does not exist.
25
- domain = 'xkxufoekjvjfjeodlfmdfjcu.com'
26
- with pytest.raises(EmailUndeliverableError, match=f'The domain name {domain} does not exist'):
27
- validate_email_deliverability(domain, domain, dns_resolver=RESOLVER)
28
-
29
- # Null MX record.
30
- domain = 'example.com'
31
- with pytest.raises(EmailUndeliverableError, match=f'The domain name {domain} does not accept email'):
32
- validate_email_deliverability(domain, domain, dns_resolver=RESOLVER)
13
+ @pytest.mark.parametrize(
14
+ 'domain,expected_response',
15
+ [
16
+ ('gmail.com', {'mx': [(5, 'gmail-smtp-in.l.google.com'), (10, 'alt1.gmail-smtp-in.l.google.com'), (20, 'alt2.gmail-smtp-in.l.google.com'), (30, 'alt3.gmail-smtp-in.l.google.com'), (40, 'alt4.gmail-smtp-in.l.google.com')], 'mx_fallback_type': None}),
17
+ ('pages.github.com', {'mx': [(0, 'pages.github.com')], 'mx_fallback_type': 'A'}),
18
+ ],
19
+ )
20
+ def test_deliverability_found(domain, expected_response):
21
+ response = validate_email_deliverability(domain, domain, dns_resolver=RESOLVER)
22
+ assert response == expected_response
33
23
 
34
- # No MX record, A record fallback, reject-all SPF record.
35
- domain = 'nellis.af.mil'
36
- with pytest.raises(EmailUndeliverableError, match=f'The domain name {domain} does not send email'):
37
- validate_email_deliverability(domain, domain, dns_resolver=RESOLVER)
38
24
 
39
- # No MX or A/AAAA records, but some other DNS records must
40
- # exist such that the response is NOANSWER instead of NXDOMAIN.
41
- domain = 'justtxt.joshdata.me'
42
- with pytest.raises(EmailUndeliverableError, match=f'The domain name {domain} does not accept email'):
25
+ @pytest.mark.parametrize(
26
+ 'domain,error',
27
+ [
28
+ ('xkxufoekjvjfjeodlfmdfjcu.com', 'The domain name {domain} does not exist'),
29
+ ('example.com', 'The domain name {domain} does not accept email'), # Null MX record
30
+ ('g.mail.com', 'The domain name {domain} does not accept email'), # No MX record but invalid AAAA record fallback (issue #134)
31
+ ('nellis.af.mil', 'The domain name {domain} does not send email'), # No MX record, A record fallback, reject-all SPF record.
32
+
33
+ # No MX or A/AAAA records, but some other DNS records must
34
+ # exist such that the response is NOANSWER instead of NXDOMAIN.
35
+ ('justtxt.joshdata.me', 'The domain name {domain} does not accept email'),
36
+ ],
37
+ )
38
+ def test_deliverability_fails(domain, error):
39
+ with pytest.raises(EmailUndeliverableError, match=error.format(domain=domain)):
43
40
  validate_email_deliverability(domain, domain, dns_resolver=RESOLVER)
44
41
 
45
42
 
@@ -343,9 +343,9 @@ def test_domain_literal():
343
343
  ('obsolete."quoted".atom@example.com', 'The email address contains invalid characters before the @-sign: \'"\'.'),
344
344
  ('11111111112222222222333333333344444444445555555555666666666677777@example.com', 'The email address is too long before the @-sign (1 character too many).'),
345
345
  ('111111111122222222223333333333444444444455555555556666666666777777@example.com', 'The email address is too long before the @-sign (2 characters too many).'),
346
- ('me@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.111111111122222222223333333333444444444455555555556.com', 'The email address is too long (4 characters too many).'),
347
- ('me@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555566.com', 'The email address is too long after the @-sign (1 character too many).'),
348
- ('me@中1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555566.com', 'The email address is too long after the @-sign.'),
346
+ ('meme@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.com', 'The email address is too long (4 characters too many).'),
347
+ ('me@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.11111111112222222222333333333344444444445555555555.com', 'The email address is too long after the @-sign (1 character too many).'),
348
+ ('me@中1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444.com', 'The email address is too long after the @-sign.'),
349
349
  ('my.long.address@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.11111111112222222222333333333344444.info', 'The email address is too long (2 characters too many).'),
350
350
  ('my.long.address@λ111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.11111111112222222222333333.info', 'The email address is too long (when converted to IDNA ASCII).'),
351
351
  ('my.long.address@λ111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444.info', 'The email address is too long (at least 1 character too many).'),
@@ -1 +0,0 @@
1
- __version__ = "2.1.1"
File without changes