email-validator 2.2.0__tar.gz → 2.3.0__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.
- {email_validator-2.2.0/email_validator.egg-info → email_validator-2.3.0}/PKG-INFO +8 -7
- {email_validator-2.2.0 → email_validator-2.3.0}/README.md +4 -4
- {email_validator-2.2.0 → email_validator-2.3.0}/email_validator/__init__.py +4 -2
- {email_validator-2.2.0 → email_validator-2.3.0}/email_validator/__main__.py +5 -4
- {email_validator-2.2.0 → email_validator-2.3.0}/email_validator/deliverability.py +1 -1
- email_validator-2.3.0/email_validator/exceptions.py +13 -0
- {email_validator-2.2.0 → email_validator-2.3.0}/email_validator/rfc_constants.py +14 -2
- {email_validator-2.2.0 → email_validator-2.3.0}/email_validator/syntax.py +70 -9
- email_validator-2.2.0/email_validator/exceptions_types.py → email_validator-2.3.0/email_validator/types.py +0 -15
- {email_validator-2.2.0 → email_validator-2.3.0}/email_validator/validate_email.py +39 -10
- email_validator-2.3.0/email_validator/version.py +1 -0
- {email_validator-2.2.0 → email_validator-2.3.0/email_validator.egg-info}/PKG-INFO +8 -7
- {email_validator-2.2.0 → email_validator-2.3.0}/email_validator.egg-info/SOURCES.txt +2 -1
- {email_validator-2.2.0 → email_validator-2.3.0}/setup.cfg +1 -1
- {email_validator-2.2.0 → email_validator-2.3.0}/tests/test_syntax.py +38 -9
- email_validator-2.2.0/email_validator/version.py +0 -1
- {email_validator-2.2.0 → email_validator-2.3.0}/LICENSE +0 -0
- {email_validator-2.2.0 → email_validator-2.3.0}/MANIFEST.in +0 -0
- {email_validator-2.2.0 → email_validator-2.3.0}/email_validator/py.typed +0 -0
- {email_validator-2.2.0 → email_validator-2.3.0}/email_validator.egg-info/dependency_links.txt +0 -0
- {email_validator-2.2.0 → email_validator-2.3.0}/email_validator.egg-info/entry_points.txt +0 -0
- {email_validator-2.2.0 → email_validator-2.3.0}/email_validator.egg-info/requires.txt +0 -0
- {email_validator-2.2.0 → email_validator-2.3.0}/email_validator.egg-info/top_level.txt +0 -0
- {email_validator-2.2.0 → email_validator-2.3.0}/pyproject.toml +0 -0
- {email_validator-2.2.0 → email_validator-2.3.0}/setup.py +0 -0
- {email_validator-2.2.0 → email_validator-2.3.0}/tests/mocked-dns-answers.json +0 -0
- {email_validator-2.2.0 → email_validator-2.3.0}/tests/mocked_dns_response.py +0 -0
- {email_validator-2.2.0 → email_validator-2.3.0}/tests/test_deliverability.py +0 -0
- {email_validator-2.2.0 → email_validator-2.3.0}/tests/test_main.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
2
|
-
Name:
|
|
3
|
-
Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: email-validator
|
|
3
|
+
Version: 2.3.0
|
|
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
|
|
@@ -22,6 +22,7 @@ Description-Content-Type: text/markdown
|
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Requires-Dist: dnspython>=2.0.0
|
|
24
24
|
Requires-Dist: idna>=2.0.0
|
|
25
|
+
Dynamic: license-file
|
|
25
26
|
|
|
26
27
|
email-validator: Validate Email Addresses
|
|
27
28
|
=========================================
|
|
@@ -37,7 +38,7 @@ users by their email address like on a registration form.
|
|
|
37
38
|
Key features:
|
|
38
39
|
|
|
39
40
|
* Checks that an email address has the correct syntax --- great for
|
|
40
|
-
email-based registration/login forms or
|
|
41
|
+
email-based registration/login forms or validating data.
|
|
41
42
|
* Gives friendly English error messages when validation fails that you
|
|
42
43
|
can display to end-users.
|
|
43
44
|
* Checks deliverability (optional): Does the domain name resolve?
|
|
@@ -168,13 +169,14 @@ The `validate_email` function also accepts the following keyword arguments
|
|
|
168
169
|
|
|
169
170
|
`allow_quoted_local=False`: Set to `True` to allow obscure and potentially problematic email addresses in which the part of the address before the @-sign contains spaces, @-signs, or other surprising characters when the local part is surrounded in quotes (so-called quoted-string local parts). In the object returned by `validate_email`, the normalized local part removes any unnecessary backslash-escaping and even removes the surrounding quotes if the address would be valid without them. You can also set `email_validator.ALLOW_QUOTED_LOCAL` to `True` to turn this on for all calls by default.
|
|
170
171
|
|
|
171
|
-
`allow_domain_literal=False`: Set to `True` to allow bracketed IPv4 and "IPv6:"-
|
|
172
|
+
`allow_domain_literal=False`: Set to `True` to allow bracketed IPv4 and "IPv6:"-prefixed IPv6 addresses in the domain part of the email address. No deliverability checks are performed for these addresses. In the object returned by `validate_email`, the normalized domain will use the condensed IPv6 format, if applicable. The object's `domain_address` attribute will hold the parsed `ipaddress.IPv4Address` or `ipaddress.IPv6Address` object if applicable. You can also set `email_validator.ALLOW_DOMAIN_LITERAL` to `True` to turn this on for all calls by default.
|
|
172
173
|
|
|
173
174
|
`allow_display_name=False`: Set to `True` to allow a display name and bracketed address in the input string, like `My Name <me@example.org>`. It's implemented in the spirit but not the letter of RFC 5322 3.4, so it may be stricter or more relaxed than what you want. The display name, if present, is provided in the returned object's `display_name` field after being unquoted and unescaped. You can also set `email_validator.ALLOW_DISPLAY_NAME` to `True` to turn this on for all calls by default.
|
|
174
175
|
|
|
175
176
|
`allow_empty_local=False`: Set to `True` to allow an empty local part (i.e.
|
|
176
177
|
`@example.com`), e.g. for validating Postfix aliases.
|
|
177
178
|
|
|
179
|
+
`strict=False`: Set to `True` to perform additional syntax checks (currently only a local part length check). This should be used by mail service providers at address creation to ensure email addresses meet broad compatibility requirements.
|
|
178
180
|
|
|
179
181
|
### DNS timeout and cache
|
|
180
182
|
|
|
@@ -449,8 +451,7 @@ To release:
|
|
|
449
451
|
* Update CHANGELOG.md.
|
|
450
452
|
* Update the version number in `email_validator/version.py`.
|
|
451
453
|
* Make & push a commit with the new version number and make sure tests pass.
|
|
452
|
-
* Make
|
|
453
|
-
* Make a release at https://github.com/JoshData/python-email-validator/releases/new.
|
|
454
|
+
* Make a release at https://github.com/JoshData/python-email-validator/releases/new creating a new tag (or use command below).
|
|
454
455
|
* Publish a source and wheel distribution to pypi (see command below).
|
|
455
456
|
|
|
456
457
|
```sh
|
|
@@ -12,7 +12,7 @@ users by their email address like on a registration form.
|
|
|
12
12
|
Key features:
|
|
13
13
|
|
|
14
14
|
* Checks that an email address has the correct syntax --- great for
|
|
15
|
-
email-based registration/login forms or
|
|
15
|
+
email-based registration/login forms or validating data.
|
|
16
16
|
* Gives friendly English error messages when validation fails that you
|
|
17
17
|
can display to end-users.
|
|
18
18
|
* Checks deliverability (optional): Does the domain name resolve?
|
|
@@ -143,13 +143,14 @@ The `validate_email` function also accepts the following keyword arguments
|
|
|
143
143
|
|
|
144
144
|
`allow_quoted_local=False`: Set to `True` to allow obscure and potentially problematic email addresses in which the part of the address before the @-sign contains spaces, @-signs, or other surprising characters when the local part is surrounded in quotes (so-called quoted-string local parts). In the object returned by `validate_email`, the normalized local part removes any unnecessary backslash-escaping and even removes the surrounding quotes if the address would be valid without them. You can also set `email_validator.ALLOW_QUOTED_LOCAL` to `True` to turn this on for all calls by default.
|
|
145
145
|
|
|
146
|
-
`allow_domain_literal=False`: Set to `True` to allow bracketed IPv4 and "IPv6:"-
|
|
146
|
+
`allow_domain_literal=False`: Set to `True` to allow bracketed IPv4 and "IPv6:"-prefixed IPv6 addresses in the domain part of the email address. No deliverability checks are performed for these addresses. In the object returned by `validate_email`, the normalized domain will use the condensed IPv6 format, if applicable. The object's `domain_address` attribute will hold the parsed `ipaddress.IPv4Address` or `ipaddress.IPv6Address` object if applicable. You can also set `email_validator.ALLOW_DOMAIN_LITERAL` to `True` to turn this on for all calls by default.
|
|
147
147
|
|
|
148
148
|
`allow_display_name=False`: Set to `True` to allow a display name and bracketed address in the input string, like `My Name <me@example.org>`. It's implemented in the spirit but not the letter of RFC 5322 3.4, so it may be stricter or more relaxed than what you want. The display name, if present, is provided in the returned object's `display_name` field after being unquoted and unescaped. You can also set `email_validator.ALLOW_DISPLAY_NAME` to `True` to turn this on for all calls by default.
|
|
149
149
|
|
|
150
150
|
`allow_empty_local=False`: Set to `True` to allow an empty local part (i.e.
|
|
151
151
|
`@example.com`), e.g. for validating Postfix aliases.
|
|
152
152
|
|
|
153
|
+
`strict=False`: Set to `True` to perform additional syntax checks (currently only a local part length check). This should be used by mail service providers at address creation to ensure email addresses meet broad compatibility requirements.
|
|
153
154
|
|
|
154
155
|
### DNS timeout and cache
|
|
155
156
|
|
|
@@ -424,8 +425,7 @@ To release:
|
|
|
424
425
|
* Update CHANGELOG.md.
|
|
425
426
|
* Update the version number in `email_validator/version.py`.
|
|
426
427
|
* Make & push a commit with the new version number and make sure tests pass.
|
|
427
|
-
* Make
|
|
428
|
-
* Make a release at https://github.com/JoshData/python-email-validator/releases/new.
|
|
428
|
+
* Make a release at https://github.com/JoshData/python-email-validator/releases/new creating a new tag (or use command below).
|
|
429
429
|
* Publish a source and wheel distribution to pypi (see command below).
|
|
430
430
|
|
|
431
431
|
```sh
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING
|
|
2
2
|
|
|
3
3
|
# Export the main method, helper methods, and the public data types.
|
|
4
|
-
from .
|
|
5
|
-
|
|
4
|
+
from .exceptions import EmailNotValidError, EmailSyntaxError, EmailUndeliverableError
|
|
5
|
+
from .types import ValidatedEmail
|
|
6
6
|
from .validate_email import validate_email
|
|
7
7
|
from .version import __version__
|
|
8
8
|
|
|
@@ -27,9 +27,11 @@ else:
|
|
|
27
27
|
# Default values for keyword arguments.
|
|
28
28
|
|
|
29
29
|
ALLOW_SMTPUTF8 = True
|
|
30
|
+
ALLOW_EMPTY_LOCAL = False
|
|
30
31
|
ALLOW_QUOTED_LOCAL = False
|
|
31
32
|
ALLOW_DOMAIN_LITERAL = False
|
|
32
33
|
ALLOW_DISPLAY_NAME = False
|
|
34
|
+
STRICT = False
|
|
33
35
|
GLOBALLY_DELIVERABLE = True
|
|
34
36
|
CHECK_DELIVERABILITY = True
|
|
35
37
|
TEST_ENVIRONMENT = False
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# When using STDIN, no output will be given for valid email addresses.
|
|
13
13
|
#
|
|
14
14
|
# Keyword arguments to validate_email can be set in environment variables
|
|
15
|
-
# of the same name but
|
|
15
|
+
# of the same name but uppercase (see below).
|
|
16
16
|
|
|
17
17
|
import json
|
|
18
18
|
import os
|
|
@@ -21,7 +21,7 @@ from typing import Any, Dict, Optional
|
|
|
21
21
|
|
|
22
22
|
from .validate_email import validate_email, _Resolver
|
|
23
23
|
from .deliverability import caching_resolver
|
|
24
|
-
from .
|
|
24
|
+
from .exceptions import EmailNotValidError
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
def main(dns_resolver: Optional[_Resolver] = None) -> None:
|
|
@@ -29,7 +29,8 @@ def main(dns_resolver: Optional[_Resolver] = None) -> None:
|
|
|
29
29
|
|
|
30
30
|
# Set options from environment variables.
|
|
31
31
|
options: Dict[str, Any] = {}
|
|
32
|
-
for varname in ('ALLOW_SMTPUTF8', 'ALLOW_QUOTED_LOCAL', 'ALLOW_DOMAIN_LITERAL',
|
|
32
|
+
for varname in ('ALLOW_SMTPUTF8', 'ALLOW_EMPTY_LOCAL', 'ALLOW_QUOTED_LOCAL', 'ALLOW_DOMAIN_LITERAL',
|
|
33
|
+
'ALLOW_DISPLAY_NAME',
|
|
33
34
|
'GLOBALLY_DELIVERABLE', 'CHECK_DELIVERABILITY', 'TEST_ENVIRONMENT'):
|
|
34
35
|
if varname in os.environ:
|
|
35
36
|
options[varname.lower()] = bool(os.environ[varname])
|
|
@@ -38,7 +39,7 @@ def main(dns_resolver: Optional[_Resolver] = None) -> None:
|
|
|
38
39
|
options[varname.lower()] = float(os.environ[varname])
|
|
39
40
|
|
|
40
41
|
if len(sys.argv) == 1:
|
|
41
|
-
# Validate the email addresses
|
|
42
|
+
# Validate the email addresses passed line-by-line on STDIN.
|
|
42
43
|
dns_resolver = dns_resolver or caching_resolver()
|
|
43
44
|
for line in sys.stdin:
|
|
44
45
|
email = line.strip()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class EmailNotValidError(ValueError):
|
|
2
|
+
"""Parent class of all exceptions raised by this module."""
|
|
3
|
+
pass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EmailSyntaxError(EmailNotValidError):
|
|
7
|
+
"""Exception raised when an email address fails validation because of its form."""
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EmailUndeliverableError(EmailNotValidError):
|
|
12
|
+
"""Exception raised when an email address fails validation because its domain name does not appear deliverable."""
|
|
13
|
+
pass
|
|
@@ -36,12 +36,24 @@ DOMAIN_LITERAL_CHARS = re.compile(r"[\u0021-\u00FA\u005E-\u007E]")
|
|
|
36
36
|
QTEXT_INTL = re.compile(r"[\u0020-\u007E\u0080-\U0010FFFF]")
|
|
37
37
|
|
|
38
38
|
# Length constants
|
|
39
|
+
|
|
39
40
|
# RFC 3696 + errata 1003 + errata 1690 (https://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690)
|
|
40
|
-
# explains the maximum length of an email address is 254 octets.
|
|
41
|
+
# explains the maximum length of an email address is 254 octets based on RFC 5321 4.5.3.1.3. A
|
|
42
|
+
# maximum local part length is also given at RFC 5321 4.5.3.1.1.
|
|
43
|
+
#
|
|
44
|
+
# But RFC 5321 4.5.3.1 says that these (and other) limits are in a sense suggestions, and longer
|
|
45
|
+
# local parts have been seen in the wild. Consequntely, the local part length is only checked
|
|
46
|
+
# in "strict" mode. Although the email address maximum length is also somewhat of a suggestion,
|
|
47
|
+
# I don't like the idea of having no length checks performed, so I'm leaving that to always be
|
|
48
|
+
# checked.
|
|
41
49
|
EMAIL_MAX_LENGTH = 254
|
|
42
50
|
LOCAL_PART_MAX_LENGTH = 64
|
|
51
|
+
|
|
52
|
+
# Although RFC 5321 4.5.3.1.2 gives a (suggested, see above) limit of 255 octets, RFC 1035 2.3.4 also
|
|
53
|
+
# imposes a length limit (255 octets). But per https://stackoverflow.com/questions/32290167/what-is-the-maximum-length-of-a-dns-name,
|
|
54
|
+
# two of those octets are taken up by the optional final dot and null root label.
|
|
43
55
|
DNS_LABEL_LENGTH_LIMIT = 63 # in "octets", RFC 1035 2.3.1
|
|
44
|
-
DOMAIN_MAX_LENGTH = 253 # in "octets" as transmitted
|
|
56
|
+
DOMAIN_MAX_LENGTH = 253 # in "octets" as transmitted
|
|
45
57
|
|
|
46
58
|
# RFC 2142
|
|
47
59
|
CASE_INSENSITIVE_MAILBOX_NAMES = [
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from .
|
|
1
|
+
from .exceptions import EmailSyntaxError
|
|
2
|
+
from .types import ValidatedEmail
|
|
2
3
|
from .rfc_constants import EMAIL_MAX_LENGTH, LOCAL_PART_MAX_LENGTH, DOMAIN_MAX_LENGTH, \
|
|
3
4
|
DOT_ATOM_TEXT, DOT_ATOM_TEXT_INTL, ATEXT_RE, ATEXT_INTL_DOT_RE, ATEXT_HOSTNAME_INTL, QTEXT_INTL, \
|
|
4
5
|
DNS_LABEL_LENGTH_LIMIT, DOT_ATOM_TEXT_HOSTNAME, DOMAIN_NAME_REGEX, DOMAIN_LITERAL_CHARS
|
|
@@ -57,7 +58,7 @@ def split_email(email: str) -> Tuple[Optional[str], str, str, bool]:
|
|
|
57
58
|
for i, c in enumerate(text):
|
|
58
59
|
# < plus U+0338 (Combining Long Solidus Overlay) normalizes to
|
|
59
60
|
# ≮ U+226E (Not Less-Than), and it would be confusing to treat
|
|
60
|
-
# the < as the start of "<email>" syntax in that case.
|
|
61
|
+
# the < as the start of "<email>" syntax in that case. Likewise,
|
|
61
62
|
# if anything combines with an @ or ", we should probably not
|
|
62
63
|
# treat it as a special character.
|
|
63
64
|
if unicodedata.normalize("NFC", text[i:])[0] != c:
|
|
@@ -82,7 +83,28 @@ def split_email(email: str) -> Tuple[Optional[str], str, str, bool]:
|
|
|
82
83
|
else:
|
|
83
84
|
left_part += c
|
|
84
85
|
|
|
86
|
+
# No special symbol found. The special symbols always
|
|
87
|
+
# include an at-sign, so this always indicates a missing
|
|
88
|
+
# at-sign. The other symbol is optional.
|
|
85
89
|
if len(left_part) == len(text):
|
|
90
|
+
# The full-width at-sign might occur in CJK contexts.
|
|
91
|
+
# We can't accept it because we only accept addresess
|
|
92
|
+
# that are actually valid. But if this is common we
|
|
93
|
+
# may want to consider accepting and normalizing full-
|
|
94
|
+
# width characters for the other special symbols (and
|
|
95
|
+
# full-width dot is already accepted in internationalized
|
|
96
|
+
# domains) with a new option.
|
|
97
|
+
# See https://news.ycombinator.com/item?id=42235268.
|
|
98
|
+
if "@" in text:
|
|
99
|
+
raise EmailSyntaxError("The email address has the \"full-width\" at-sign (@) character instead of a regular at-sign.")
|
|
100
|
+
|
|
101
|
+
# Check another near-homoglyph for good measure because
|
|
102
|
+
# homoglyphs in place of required characters could be
|
|
103
|
+
# very confusing. We may want to consider checking for
|
|
104
|
+
# homoglyphs anywhere we look for a special symbol.
|
|
105
|
+
if "﹫" in text:
|
|
106
|
+
raise EmailSyntaxError('The email address has the "small commercial at" character instead of a regular at-sign.')
|
|
107
|
+
|
|
86
108
|
raise EmailSyntaxError("An email address must have an @-sign.")
|
|
87
109
|
|
|
88
110
|
# The right part is whatever is left.
|
|
@@ -207,7 +229,7 @@ class LocalPartValidationResult(TypedDict):
|
|
|
207
229
|
|
|
208
230
|
|
|
209
231
|
def validate_email_local_part(local: str, allow_smtputf8: bool = True, allow_empty_local: bool = False,
|
|
210
|
-
quoted_local_part: bool = False) -> LocalPartValidationResult:
|
|
232
|
+
quoted_local_part: bool = False, strict: bool = False) -> LocalPartValidationResult:
|
|
211
233
|
"""Validates the syntax of the local part of an email address."""
|
|
212
234
|
|
|
213
235
|
if len(local) == 0:
|
|
@@ -229,7 +251,7 @@ def validate_email_local_part(local: str, allow_smtputf8: bool = True, allow_emp
|
|
|
229
251
|
# internationalized, then the UTF-8 encoding may be longer, but
|
|
230
252
|
# that may not be relevant. We will check the total address length
|
|
231
253
|
# instead.
|
|
232
|
-
if len(local) > LOCAL_PART_MAX_LENGTH:
|
|
254
|
+
if strict and len(local) > LOCAL_PART_MAX_LENGTH:
|
|
233
255
|
reason = get_length_reason(local, limit=LOCAL_PART_MAX_LENGTH)
|
|
234
256
|
raise EmailSyntaxError(f"The email address is too long before the @-sign {reason}.")
|
|
235
257
|
|
|
@@ -280,8 +302,8 @@ def validate_email_local_part(local: str, allow_smtputf8: bool = True, allow_emp
|
|
|
280
302
|
valid = "dot-atom"
|
|
281
303
|
requires_smtputf8 = True
|
|
282
304
|
|
|
283
|
-
# There are no
|
|
284
|
-
# it was originally quoted, it is probably valid. More characters
|
|
305
|
+
# There are no dot-atom syntax restrictions on quoted local parts, so
|
|
306
|
+
# if it was originally quoted, it is probably valid. More characters
|
|
285
307
|
# are allowed, like @-signs, spaces, and quotes, and there are no
|
|
286
308
|
# restrictions on the placement of dots, as in dot-atom local parts.
|
|
287
309
|
elif quoted_local_part:
|
|
@@ -438,6 +460,36 @@ def check_dot_atom(label: str, start_descr: str, end_descr: str, is_hostname: bo
|
|
|
438
460
|
raise EmailSyntaxError("An email address cannot have a period and a hyphen next to each other.")
|
|
439
461
|
|
|
440
462
|
|
|
463
|
+
def uts46_valid_char(char: str) -> bool:
|
|
464
|
+
# By exhaustively searching for characters rejected by
|
|
465
|
+
# for c in (chr(i) for i in range(0x110000)):
|
|
466
|
+
# idna.uts46_remap(c, std3_rules=False, transitional=False)
|
|
467
|
+
# I found the following rules are pretty close.
|
|
468
|
+
c = ord(char)
|
|
469
|
+
if 0x80 <= c <= 0x9f:
|
|
470
|
+
# 8-bit ASCII range.
|
|
471
|
+
return False
|
|
472
|
+
elif ((0x2010 <= c <= 0x2060 and not (0x2024 <= c <= 0x2026) and not (0x2028 <= c <= 0x202E))
|
|
473
|
+
or c in (0x00AD, 0x2064, 0xFF0E)
|
|
474
|
+
or 0x200B <= c <= 0x200D
|
|
475
|
+
or 0x1BCA0 <= c <= 0x1BCA3):
|
|
476
|
+
# Characters that are permitted but fall into one of the
|
|
477
|
+
# tests below.
|
|
478
|
+
return True
|
|
479
|
+
elif unicodedata.category(chr(c)) in ("Cf", "Cn", "Co", "Cs", "Zs", "Zl", "Zp"):
|
|
480
|
+
# There are a bunch of Zs characters including regular space
|
|
481
|
+
# that are allowed by UTS46 but are not allowed in domain
|
|
482
|
+
# names anyway.
|
|
483
|
+
#
|
|
484
|
+
# There are some Cn (unassigned) characters that the idna
|
|
485
|
+
# package doesn't reject but we can, I think.
|
|
486
|
+
return False
|
|
487
|
+
elif "002E" in unicodedata.decomposition(chr(c)).split(" "):
|
|
488
|
+
# Characters that decompose into a sequence with a dot.
|
|
489
|
+
return False
|
|
490
|
+
return True
|
|
491
|
+
|
|
492
|
+
|
|
441
493
|
class DomainNameValidationResult(TypedDict):
|
|
442
494
|
ascii_domain: str
|
|
443
495
|
domain: str
|
|
@@ -462,6 +514,15 @@ def validate_email_domain_name(domain: str, test_environment: bool = False, glob
|
|
|
462
514
|
# they may not be valid, safe, or sensible Unicode strings.
|
|
463
515
|
check_unsafe_chars(domain)
|
|
464
516
|
|
|
517
|
+
# Reject characters that would be rejected by UTS-46 normalization next but
|
|
518
|
+
# with an error message under our control.
|
|
519
|
+
bad_chars = {
|
|
520
|
+
safe_character_display(c) for c in domain
|
|
521
|
+
if not uts46_valid_char(c)
|
|
522
|
+
}
|
|
523
|
+
if bad_chars:
|
|
524
|
+
raise EmailSyntaxError("The part after the @-sign contains invalid characters: " + ", ".join(sorted(bad_chars)) + ".")
|
|
525
|
+
|
|
465
526
|
# Perform UTS-46 normalization, which includes casefolding, NFC normalization,
|
|
466
527
|
# and converting all label separators (the period/full stop, fullwidth full stop,
|
|
467
528
|
# ideographic full stop, and halfwidth ideographic full stop) to regular dots.
|
|
@@ -607,12 +668,12 @@ def validate_email_domain_name(domain: str, test_environment: bool = False, glob
|
|
|
607
668
|
# case for this.
|
|
608
669
|
bad_chars = {
|
|
609
670
|
safe_character_display(c)
|
|
610
|
-
for c in
|
|
671
|
+
for c in domain_i18n
|
|
611
672
|
if not ATEXT_HOSTNAME_INTL.match(c)
|
|
612
673
|
}
|
|
613
674
|
if bad_chars:
|
|
614
675
|
raise EmailSyntaxError("The part after the @-sign contains invalid characters: " + ", ".join(sorted(bad_chars)) + ".")
|
|
615
|
-
check_unsafe_chars(
|
|
676
|
+
check_unsafe_chars(domain_i18n)
|
|
616
677
|
|
|
617
678
|
# Check that it can be encoded back to IDNA ASCII. We have no test
|
|
618
679
|
# case for this.
|
|
@@ -642,7 +703,7 @@ def validate_email_length(addrinfo: ValidatedEmail) -> None:
|
|
|
642
703
|
# form is checked first because it is the original input.
|
|
643
704
|
# 2) The normalized email address. We perform Unicode NFC normalization of
|
|
644
705
|
# the local part, we normalize the domain to internationalized characters
|
|
645
|
-
# (if
|
|
706
|
+
# (if originally IDNA ASCII) which also includes Unicode normalization,
|
|
646
707
|
# and we may remove quotes in quoted local parts. We recommend that
|
|
647
708
|
# callers use this string, so it must be valid.
|
|
648
709
|
# 3) The email address with the IDNA ASCII representation of the domain
|
|
@@ -2,21 +2,6 @@ import warnings
|
|
|
2
2
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
class EmailNotValidError(ValueError):
|
|
6
|
-
"""Parent class of all exceptions raised by this module."""
|
|
7
|
-
pass
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class EmailSyntaxError(EmailNotValidError):
|
|
11
|
-
"""Exception raised when an email address fails validation because of its form."""
|
|
12
|
-
pass
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class EmailUndeliverableError(EmailNotValidError):
|
|
16
|
-
"""Exception raised when an email address fails validation because its domain name does not appear deliverable."""
|
|
17
|
-
pass
|
|
18
|
-
|
|
19
|
-
|
|
20
5
|
class ValidatedEmail:
|
|
21
6
|
"""The validate_email function returns objects of this type holding the normalized form of the email address
|
|
22
7
|
and other information."""
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from typing import Optional, Union, TYPE_CHECKING
|
|
2
2
|
import unicodedata
|
|
3
3
|
|
|
4
|
-
from .
|
|
4
|
+
from .exceptions import EmailSyntaxError
|
|
5
|
+
from .types import ValidatedEmail
|
|
5
6
|
from .syntax import split_email, validate_email_local_part, validate_email_domain_name, validate_email_domain_literal, validate_email_length
|
|
6
7
|
from .rfc_constants import CASE_INSENSITIVE_MAILBOX_NAMES
|
|
7
8
|
|
|
@@ -17,10 +18,11 @@ def validate_email(
|
|
|
17
18
|
/, # prior arguments are positional-only
|
|
18
19
|
*, # subsequent arguments are keyword-only
|
|
19
20
|
allow_smtputf8: Optional[bool] = None,
|
|
20
|
-
allow_empty_local: bool =
|
|
21
|
+
allow_empty_local: Optional[bool] = None,
|
|
21
22
|
allow_quoted_local: Optional[bool] = None,
|
|
22
23
|
allow_domain_literal: Optional[bool] = None,
|
|
23
24
|
allow_display_name: Optional[bool] = None,
|
|
25
|
+
strict: Optional[bool] = None,
|
|
24
26
|
check_deliverability: Optional[bool] = None,
|
|
25
27
|
test_environment: Optional[bool] = None,
|
|
26
28
|
globally_deliverable: Optional[bool] = None,
|
|
@@ -34,16 +36,20 @@ def validate_email(
|
|
|
34
36
|
"""
|
|
35
37
|
|
|
36
38
|
# Fill in default values of arguments.
|
|
37
|
-
from . import ALLOW_SMTPUTF8, ALLOW_QUOTED_LOCAL, ALLOW_DOMAIN_LITERAL, ALLOW_DISPLAY_NAME, \
|
|
38
|
-
GLOBALLY_DELIVERABLE, CHECK_DELIVERABILITY, TEST_ENVIRONMENT, DEFAULT_TIMEOUT
|
|
39
|
+
from . import ALLOW_SMTPUTF8, ALLOW_EMPTY_LOCAL, ALLOW_QUOTED_LOCAL, ALLOW_DOMAIN_LITERAL, ALLOW_DISPLAY_NAME, \
|
|
40
|
+
STRICT, GLOBALLY_DELIVERABLE, CHECK_DELIVERABILITY, TEST_ENVIRONMENT, DEFAULT_TIMEOUT
|
|
39
41
|
if allow_smtputf8 is None:
|
|
40
42
|
allow_smtputf8 = ALLOW_SMTPUTF8
|
|
43
|
+
if allow_empty_local is None:
|
|
44
|
+
allow_empty_local = ALLOW_EMPTY_LOCAL
|
|
41
45
|
if allow_quoted_local is None:
|
|
42
46
|
allow_quoted_local = ALLOW_QUOTED_LOCAL
|
|
43
47
|
if allow_domain_literal is None:
|
|
44
48
|
allow_domain_literal = ALLOW_DOMAIN_LITERAL
|
|
45
49
|
if allow_display_name is None:
|
|
46
50
|
allow_display_name = ALLOW_DISPLAY_NAME
|
|
51
|
+
if strict is None:
|
|
52
|
+
strict = STRICT
|
|
47
53
|
if check_deliverability is None:
|
|
48
54
|
check_deliverability = CHECK_DELIVERABILITY
|
|
49
55
|
if test_environment is None:
|
|
@@ -53,14 +59,18 @@ def validate_email(
|
|
|
53
59
|
if timeout is None and dns_resolver is None:
|
|
54
60
|
timeout = DEFAULT_TIMEOUT
|
|
55
61
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
if isinstance(email, str):
|
|
63
|
+
pass
|
|
64
|
+
elif isinstance(email, bytes):
|
|
65
|
+
# Allow email to be a bytes instance as if it is what
|
|
66
|
+
# will be transmitted on the wire. But assume SMTPUTF8
|
|
67
|
+
# is unavailable, so it must be ASCII.
|
|
60
68
|
try:
|
|
61
69
|
email = email.decode("ascii")
|
|
62
70
|
except ValueError as e:
|
|
63
71
|
raise EmailSyntaxError("The email address is not valid ASCII.") from e
|
|
72
|
+
else:
|
|
73
|
+
raise TypeError("email must be str or bytes")
|
|
64
74
|
|
|
65
75
|
# Split the address into the display name (or None), the local part
|
|
66
76
|
# (before the @-sign), and the domain part (after the @-sign).
|
|
@@ -70,6 +80,14 @@ def validate_email(
|
|
|
70
80
|
display_name, local_part, domain_part, is_quoted_local_part \
|
|
71
81
|
= split_email(email)
|
|
72
82
|
|
|
83
|
+
if display_name:
|
|
84
|
+
# UTS #39 3.3 Email Security Profiles for Identifiers requires
|
|
85
|
+
# display names (incorrectly called "quoted-string-part" there)
|
|
86
|
+
# to be NFC normalized. Since these are not a part of what we
|
|
87
|
+
# are really validating, we won't check that the input was NFC
|
|
88
|
+
# normalized, but we'll normalize in output.
|
|
89
|
+
display_name = unicodedata.normalize("NFC", display_name)
|
|
90
|
+
|
|
73
91
|
# Collect return values in this instance.
|
|
74
92
|
ret = ValidatedEmail()
|
|
75
93
|
ret.original = ((local_part if not is_quoted_local_part
|
|
@@ -84,7 +102,8 @@ def validate_email(
|
|
|
84
102
|
local_part_info = validate_email_local_part(local_part,
|
|
85
103
|
allow_smtputf8=allow_smtputf8,
|
|
86
104
|
allow_empty_local=allow_empty_local,
|
|
87
|
-
quoted_local_part=is_quoted_local_part
|
|
105
|
+
quoted_local_part=is_quoted_local_part,
|
|
106
|
+
strict=strict)
|
|
88
107
|
ret.local_part = local_part_info["local_part"]
|
|
89
108
|
ret.ascii_local_part = local_part_info["ascii_local_part"]
|
|
90
109
|
ret.smtputf8 = local_part_info["smtputf8"]
|
|
@@ -92,13 +111,23 @@ def validate_email(
|
|
|
92
111
|
# RFC 6532 section 3.1 says that Unicode NFC normalization should be applied,
|
|
93
112
|
# so we'll return the NFC-normalized local part. Since the caller may use that
|
|
94
113
|
# string in place of the original string, ensure it is also valid.
|
|
114
|
+
#
|
|
115
|
+
# UTS #39 3.3 Email Security Profiles for Identifiers requires local parts
|
|
116
|
+
# to be NFKC normalized, which loses some information in characters that can
|
|
117
|
+
# be decomposed. We might want to consider applying NFKC normalization, but
|
|
118
|
+
# we can't make the change easily because it would break database lookups
|
|
119
|
+
# for any caller that put a normalized address from a previous version of
|
|
120
|
+
# this library. (UTS #39 seems to require that the *input* be NKFC normalized
|
|
121
|
+
# and has other requirements that are hard to check without additional Unicode
|
|
122
|
+
# data, and I don't know whether the rules really apply in the wild.)
|
|
95
123
|
normalized_local_part = unicodedata.normalize("NFC", ret.local_part)
|
|
96
124
|
if normalized_local_part != ret.local_part:
|
|
97
125
|
try:
|
|
98
126
|
validate_email_local_part(normalized_local_part,
|
|
99
127
|
allow_smtputf8=allow_smtputf8,
|
|
100
128
|
allow_empty_local=allow_empty_local,
|
|
101
|
-
quoted_local_part=is_quoted_local_part
|
|
129
|
+
quoted_local_part=is_quoted_local_part,
|
|
130
|
+
strict=strict)
|
|
102
131
|
except EmailSyntaxError as e:
|
|
103
132
|
raise EmailSyntaxError("After Unicode normalization: " + str(e)) from e
|
|
104
133
|
ret.local_part = normalized_local_part
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.3.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
2
|
-
Name:
|
|
3
|
-
Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: email-validator
|
|
3
|
+
Version: 2.3.0
|
|
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
|
|
@@ -22,6 +22,7 @@ Description-Content-Type: text/markdown
|
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Requires-Dist: dnspython>=2.0.0
|
|
24
24
|
Requires-Dist: idna>=2.0.0
|
|
25
|
+
Dynamic: license-file
|
|
25
26
|
|
|
26
27
|
email-validator: Validate Email Addresses
|
|
27
28
|
=========================================
|
|
@@ -37,7 +38,7 @@ users by their email address like on a registration form.
|
|
|
37
38
|
Key features:
|
|
38
39
|
|
|
39
40
|
* Checks that an email address has the correct syntax --- great for
|
|
40
|
-
email-based registration/login forms or
|
|
41
|
+
email-based registration/login forms or validating data.
|
|
41
42
|
* Gives friendly English error messages when validation fails that you
|
|
42
43
|
can display to end-users.
|
|
43
44
|
* Checks deliverability (optional): Does the domain name resolve?
|
|
@@ -168,13 +169,14 @@ The `validate_email` function also accepts the following keyword arguments
|
|
|
168
169
|
|
|
169
170
|
`allow_quoted_local=False`: Set to `True` to allow obscure and potentially problematic email addresses in which the part of the address before the @-sign contains spaces, @-signs, or other surprising characters when the local part is surrounded in quotes (so-called quoted-string local parts). In the object returned by `validate_email`, the normalized local part removes any unnecessary backslash-escaping and even removes the surrounding quotes if the address would be valid without them. You can also set `email_validator.ALLOW_QUOTED_LOCAL` to `True` to turn this on for all calls by default.
|
|
170
171
|
|
|
171
|
-
`allow_domain_literal=False`: Set to `True` to allow bracketed IPv4 and "IPv6:"-
|
|
172
|
+
`allow_domain_literal=False`: Set to `True` to allow bracketed IPv4 and "IPv6:"-prefixed IPv6 addresses in the domain part of the email address. No deliverability checks are performed for these addresses. In the object returned by `validate_email`, the normalized domain will use the condensed IPv6 format, if applicable. The object's `domain_address` attribute will hold the parsed `ipaddress.IPv4Address` or `ipaddress.IPv6Address` object if applicable. You can also set `email_validator.ALLOW_DOMAIN_LITERAL` to `True` to turn this on for all calls by default.
|
|
172
173
|
|
|
173
174
|
`allow_display_name=False`: Set to `True` to allow a display name and bracketed address in the input string, like `My Name <me@example.org>`. It's implemented in the spirit but not the letter of RFC 5322 3.4, so it may be stricter or more relaxed than what you want. The display name, if present, is provided in the returned object's `display_name` field after being unquoted and unescaped. You can also set `email_validator.ALLOW_DISPLAY_NAME` to `True` to turn this on for all calls by default.
|
|
174
175
|
|
|
175
176
|
`allow_empty_local=False`: Set to `True` to allow an empty local part (i.e.
|
|
176
177
|
`@example.com`), e.g. for validating Postfix aliases.
|
|
177
178
|
|
|
179
|
+
`strict=False`: Set to `True` to perform additional syntax checks (currently only a local part length check). This should be used by mail service providers at address creation to ensure email addresses meet broad compatibility requirements.
|
|
178
180
|
|
|
179
181
|
### DNS timeout and cache
|
|
180
182
|
|
|
@@ -449,8 +451,7 @@ To release:
|
|
|
449
451
|
* Update CHANGELOG.md.
|
|
450
452
|
* Update the version number in `email_validator/version.py`.
|
|
451
453
|
* Make & push a commit with the new version number and make sure tests pass.
|
|
452
|
-
* Make
|
|
453
|
-
* Make a release at https://github.com/JoshData/python-email-validator/releases/new.
|
|
454
|
+
* Make a release at https://github.com/JoshData/python-email-validator/releases/new creating a new tag (or use command below).
|
|
454
455
|
* Publish a source and wheel distribution to pypi (see command below).
|
|
455
456
|
|
|
456
457
|
```sh
|
|
@@ -7,10 +7,11 @@ setup.py
|
|
|
7
7
|
email_validator/__init__.py
|
|
8
8
|
email_validator/__main__.py
|
|
9
9
|
email_validator/deliverability.py
|
|
10
|
-
email_validator/
|
|
10
|
+
email_validator/exceptions.py
|
|
11
11
|
email_validator/py.typed
|
|
12
12
|
email_validator/rfc_constants.py
|
|
13
13
|
email_validator/syntax.py
|
|
14
|
+
email_validator/types.py
|
|
14
15
|
email_validator/validate_email.py
|
|
15
16
|
email_validator/version.py
|
|
16
17
|
email_validator.egg-info/PKG-INFO
|
|
@@ -295,24 +295,35 @@ def test_email_valid(email_input: str, output: ValidatedEmail) -> None:
|
|
|
295
295
|
),
|
|
296
296
|
),
|
|
297
297
|
(
|
|
298
|
-
's\u0323\u0307@nfc.tld',
|
|
298
|
+
'\"s\u0323\u0307\" <s\u0323\u0307@nfc.tld>',
|
|
299
299
|
MakeValidatedEmail(
|
|
300
300
|
local_part='\u1E69',
|
|
301
301
|
smtputf8=True,
|
|
302
302
|
ascii_domain='nfc.tld',
|
|
303
303
|
domain='nfc.tld',
|
|
304
304
|
normalized='\u1E69@nfc.tld',
|
|
305
|
+
display_name='\u1E69'
|
|
306
|
+
),
|
|
307
|
+
),
|
|
308
|
+
(
|
|
309
|
+
'@@fullwidth.at',
|
|
310
|
+
MakeValidatedEmail(
|
|
311
|
+
local_part='@',
|
|
312
|
+
smtputf8=True,
|
|
313
|
+
ascii_domain='fullwidth.at',
|
|
314
|
+
domain='fullwidth.at',
|
|
315
|
+
normalized='@@fullwidth.at',
|
|
305
316
|
),
|
|
306
317
|
),
|
|
307
318
|
],
|
|
308
319
|
)
|
|
309
320
|
def test_email_valid_intl_local_part(email_input: str, output: ValidatedEmail) -> None:
|
|
310
321
|
# Check that it passes when allow_smtputf8 is True.
|
|
311
|
-
assert validate_email(email_input, check_deliverability=False) == output
|
|
322
|
+
assert validate_email(email_input, check_deliverability=False, allow_display_name=True) == output
|
|
312
323
|
|
|
313
324
|
# Check that it fails when allow_smtputf8 is False.
|
|
314
325
|
with pytest.raises(EmailSyntaxError) as exc_info:
|
|
315
|
-
validate_email(email_input, allow_smtputf8=False, check_deliverability=False)
|
|
326
|
+
validate_email(email_input, allow_smtputf8=False, check_deliverability=False, allow_display_name=True)
|
|
316
327
|
assert "Internationalized characters before the @-sign are not supported: " in str(exc_info.value)
|
|
317
328
|
|
|
318
329
|
|
|
@@ -363,6 +374,8 @@ def test_domain_literal() -> None:
|
|
|
363
374
|
'email_input,error_msg',
|
|
364
375
|
[
|
|
365
376
|
('hello.world', 'An email address must have an @-sign.'),
|
|
377
|
+
('hello@world', 'The email address has the "full-width" at-sign (@) character instead of a regular at-sign.'),
|
|
378
|
+
('hello﹫world', 'The email address has the "small commercial at" character instead of a regular at-sign.'),
|
|
366
379
|
('my@localhost', 'The part after the @-sign is not valid. It should have a period.'),
|
|
367
380
|
('my@.leadingdot.com', 'An email address cannot have a period immediately after the @-sign.'),
|
|
368
381
|
('my@.leadingfwdot.com', 'An email address cannot have a period immediately after the @-sign.'),
|
|
@@ -389,9 +402,7 @@ def test_domain_literal() -> None:
|
|
|
389
402
|
('.leadingdot@domain.com', 'An email address cannot start with a period.'),
|
|
390
403
|
('twodots..here@domain.com', 'An email address cannot have two periods in a row.'),
|
|
391
404
|
('trailingdot.@domain.email', 'An email address cannot have a period immediately before the @-sign.'),
|
|
392
|
-
('me@⒈wouldbeinvalid.com',
|
|
393
|
-
"The part after the @-sign contains invalid characters (Codepoint U+2488 not allowed "
|
|
394
|
-
"at position 1 in '⒈wouldbeinvalid.com')."),
|
|
405
|
+
('me@⒈wouldbeinvalid.com', "The part after the @-sign contains invalid characters: '⒈'."),
|
|
395
406
|
('me@\u037e.com', "The part after the @-sign contains invalid characters after Unicode normalization: ';'."),
|
|
396
407
|
('me@\u1fef.com', "The part after the @-sign contains invalid characters after Unicode normalization: '`'."),
|
|
397
408
|
('@example.com', 'There must be something before the @-sign.'),
|
|
@@ -404,9 +415,6 @@ def test_domain_literal() -> None:
|
|
|
404
415
|
('test@\n', 'The part after the @-sign contains invalid characters: U+000A.'),
|
|
405
416
|
('bad"quotes"@example.com', 'The email address contains invalid characters before the @-sign: \'"\'.'),
|
|
406
417
|
('obsolete."quoted".atom@example.com', 'The email address contains invalid characters before the @-sign: \'"\'.'),
|
|
407
|
-
('11111111112222222222333333333344444444445555555555666666666677777@example.com', 'The email address is too long before the @-sign (1 character too many).'),
|
|
408
|
-
('111111111122222222223333333333444444444455555555556666666666777777@example.com', 'The email address is too long before the @-sign (2 characters too many).'),
|
|
409
|
-
('\uFB2C111111122222222223333333333444444444455555555556666666666777777@example.com', 'After Unicode normalization: The email address is too long before the @-sign (2 characters too many).'),
|
|
410
418
|
('me@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.11111111112222222222333333333344444444445555555555.com', 'The email address is too long after the @-sign (1 character too many).'),
|
|
411
419
|
('me@中1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444.com', 'The email address is too long after the @-sign (1 byte too many after IDNA encoding).'),
|
|
412
420
|
('me@\uFB2C1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444.com', 'The email address is too long after the @-sign (5 bytes too many after IDNA encoding).'),
|
|
@@ -456,6 +464,22 @@ def test_email_invalid_syntax(email_input: str, error_msg: str) -> None:
|
|
|
456
464
|
assert str(exc_info.value) == error_msg
|
|
457
465
|
|
|
458
466
|
|
|
467
|
+
@pytest.mark.parametrize(
|
|
468
|
+
'email_input,error_msg',
|
|
469
|
+
[
|
|
470
|
+
('11111111112222222222333333333344444444445555555555666666666677777@example.com', 'The email address is too long before the @-sign (1 character too many).'),
|
|
471
|
+
('111111111122222222223333333333444444444455555555556666666666777777@example.com', 'The email address is too long before the @-sign (2 characters too many).'),
|
|
472
|
+
('\uFB2C111111122222222223333333333444444444455555555556666666666777777@example.com', 'After Unicode normalization: The email address is too long before the @-sign (2 characters too many).'),
|
|
473
|
+
])
|
|
474
|
+
def test_email_invalid_syntax_strict(email_input: str, error_msg: str) -> None:
|
|
475
|
+
# Since these all have syntax errors, deliverability
|
|
476
|
+
# checks do not arise.
|
|
477
|
+
validate_email(email_input, check_deliverability=False) # pass without strict
|
|
478
|
+
with pytest.raises(EmailSyntaxError) as exc_info:
|
|
479
|
+
validate_email(email_input, strict=True, check_deliverability=False)
|
|
480
|
+
assert str(exc_info.value) == error_msg
|
|
481
|
+
|
|
482
|
+
|
|
459
483
|
@pytest.mark.parametrize(
|
|
460
484
|
'email_input',
|
|
461
485
|
[
|
|
@@ -717,6 +741,11 @@ def test_pyisemail_tests(email_input: str, status: str) -> None:
|
|
|
717
741
|
validate_email(email_input, test_environment=True)
|
|
718
742
|
validate_email(email_input, allow_quoted_local=True, allow_domain_literal=True, test_environment=True)
|
|
719
743
|
|
|
744
|
+
elif status == "ISEMAIL_RFC5322_LOCAL_TOOLONG":
|
|
745
|
+
# Requires strict.
|
|
746
|
+
with pytest.raises(EmailSyntaxError):
|
|
747
|
+
validate_email(email_input, strict=True, test_environment=True)
|
|
748
|
+
|
|
720
749
|
elif status == "ISEMAIL_RFC5321_QUOTEDSTRING":
|
|
721
750
|
# Quoted-literal local parts are only valid with an option.
|
|
722
751
|
with pytest.raises(EmailSyntaxError):
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.2.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{email_validator-2.2.0 → email_validator-2.3.0}/email_validator.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|