Skip to content

Commit de6527f

Browse files
committed
Rename the email field of ValidatedEmail to normalized to be clearer about its importance
1 parent 08d9254 commit de6527f

File tree

6 files changed

+56
-47
lines changed

6 files changed

+56
-47
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ There are no significant changes to which email addresses are considered valid/i
1111
* Some syntax error messages have changed because they are now checked explicitly rather than as a part of other checks.
1212
* The quoted-string local part syntax (e.g. multiple @-signs, spaces, etc. if surrounded by quotes) and domain-literal addresses (e.g. @[192.XXX...] or @[IPv6:...]) are now parsed but not considered valid by default. Better error messages are now given for these addresses since it can be confusing for a technically valid address to be rejected, and new allow_quoted_local and allow_domain_literal options are added to allow these addresses if you really need them.
1313
* Some other error messages have changed to not repeat the email address in the error message.
14+
* The `email` field on the returned `ValidatedEmail` object has been renamed to `normalized` to be clearer about its importance, but access via `.email` is also still supported.
1415
* The library has been reorganized internally into smaller modules.
1516
* The tests have been reorganized and expanded. Deliverability tests now mostly use captured DNS responses so they can be run off-line.
1617
* The __main__ tool now reads options to validate_email from environment variables.

README.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ try:
6565
# Check that the email address is valid. Turn on check_deliverability
6666
# for first-time validations like on account creation pages (but not
6767
# login pages).
68-
validation = validate_email(email, check_deliverability=False)
68+
emailinfo = validate_email(email, check_deliverability=False)
6969

7070
# After this point, use only the normalized form of the email address,
7171
# especially before going to a database query.
72-
email = validation.email
72+
email = emailinfo.normalized
7373

7474
except EmailNotValidError as e:
7575

@@ -158,7 +158,7 @@ from email_validator import validate_email, caching_resolver
158158
resolver = caching_resolver(timeout=10)
159159

160160
while True:
161-
email = validate_email(email, dns_resolver=resolver).email
161+
validate_email(email, dns_resolver=resolver)
162162
```
163163

164164
### Test addresses
@@ -248,8 +248,8 @@ This library gives you back the ASCII-ized form in the `ascii_email`
248248
field in the returned object, which you can get like this:
249249

250250
```python
251-
valid = validate_email(email, allow_smtputf8=False)
252-
email = valid.ascii_email
251+
emailinfo = validate_email(email, allow_smtputf8=False)
252+
email = emailinfo.ascii_email
253253
```
254254

255255
The local part is left alone (if it has internationalized characters
@@ -274,9 +274,9 @@ equivalent in domain names to their ASCII counterparts. This library
274274
normalizes them to their ASCII counterparts:
275275

276276
```python
277-
valid = validate_email("me@Domain.com")
278-
print(valid.email)
279-
print(valid.ascii_email)
277+
emailinfo = validate_email("me@Domain.com")
278+
print(emailinfo.normalized)
279+
print(emailinfo.ascii_email)
280280
# prints "me@domain.com" twice
281281
```
282282

@@ -320,7 +320,7 @@ For the email address `test@joshdata.me`, the returned object is:
320320

321321
```python
322322
ValidatedEmail(
323-
email='test@joshdata.me',
323+
normalized='test@joshdata.me',
324324
local_part='test',
325325
domain='joshdata.me',
326326
ascii_email='test@joshdata.me',
@@ -334,7 +334,7 @@ internationalized domain but ASCII local part, the returned object is:
334334

335335
```python
336336
ValidatedEmail(
337-
email='example@ツ.life',
337+
normalized='example@ツ.life',
338338
local_part='example',
339339
domain='ツ.life',
340340
ascii_email='example@xn--bdk.life',
@@ -357,7 +357,7 @@ internationalized local part, the returned object is:
357357

358358
```python
359359
ValidatedEmail(
360-
email='ツ-test@joshdata.me',
360+
normalized='ツ-test@joshdata.me',
361361
local_part='ツ-test',
362362
domain='joshdata.me',
363363
ascii_email=None,
@@ -380,8 +380,8 @@ are:
380380

381381
| Field | Value |
382382
| -----:|-------|
383-
| `email` | The normalized form of the email address that you should put in your database. This combines the `local_part` and `domain` fields (see below). |
384-
| `ascii_email` | If set, an ASCII-only form of the email address by replacing the domain part with [IDNA](https://tools.ietf.org/html/rfc5891) [Punycode](https://www.rfc-editor.org/rfc/rfc3492.txt). This field will be present when an ASCII-only form of the email address exists (including if the email address is already ASCII). If the local part of the email address contains internationalized characters, `ascii_email` will be `None`. If set, it merely combines `ascii_local_part` and `ascii_domain`. |
383+
| `normalized` | The normalized form of the email address that you should put in your database. This combines the `local_part` and `domain` fields (see below). |
384+
| `ascii_email` | If set, an ASCII-only form of the normalized email address by replacing the domain part with [IDNA](https://tools.ietf.org/html/rfc5891) [Punycode](https://www.rfc-editor.org/rfc/rfc3492.txt). This field will be present when an ASCII-only form of the email address exists (including if the email address is already ASCII). If the local part of the email address contains internationalized characters, `ascii_email` will be `None`. If set, it merely combines `ascii_local_part` and `ascii_domain`. |
385385
| `local_part` | The normalized local part of the given email address (before the @-sign). Normalization includes Unicode NFC normalization and removing unnecessary quoted-string quotes and backslashes. If `allow_quoted_local` is True and the surrounding quotes are necessary, the quotes _will_ be present in this field. |
386386
| `ascii_local_part` | If set, the local part, which is composed of ASCII characters only. |
387387
| `domain` | The canonical internationalized Unicode form of the domain part of the email address. If the returned string contains non-ASCII characters, either the [SMTPUTF8](https://tools.ietf.org/html/rfc6531) feature of your mail relay will be required to transmit the message or else the email address's domain part must be converted to IDNA ASCII first: Use `ascii_domain` field instead. |

email_validator/exceptions_types.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ class ValidatedEmail(object):
2222
and other information."""
2323

2424
"""The email address that was passed to validate_email. (If passed as bytes, this will be a string.)"""
25-
original_email: str
25+
original: str
2626

2727
"""The normalized email address, which should always be used in preferance to the original address.
2828
The normalized address converts an IDNA ASCII domain name to Unicode, if possible, and performs
2929
Unicode normalization on the local part and on the domain (if originally Unicode). It is the
3030
concatenation of the local_part and domain attributes, separated by an @-sign."""
31-
email: str
31+
normalized: str
3232

3333
"""The local part of the email address after Unicode normalization."""
3434
local_part: str
@@ -68,14 +68,22 @@ def __init__(self, **kwargs):
6868
setattr(self, k, v)
6969

7070
def __repr__(self):
71-
return f"<ValidatedEmail {self.email}>"
71+
return f"<ValidatedEmail {self.normalized}>"
72+
73+
"""For backwards compatibility, support old field names."""
74+
def __getattr__(self, key):
75+
if key == "original_email":
76+
return self.original
77+
if key == "email":
78+
return self.normalized
79+
raise AttributeError()
7280

7381
"""For backwards compatibility, some fields are also exposed through a dict-like interface. Note
7482
that some of the names changed when they became attributes."""
7583
def __getitem__(self, key):
7684
warnings.warn("dict-like access to the return value of validate_email is deprecated and may not be supported in the future.", DeprecationWarning, stacklevel=2)
7785
if key == "email":
78-
return self.email
86+
return self.normalized
7987
if key == "email_ascii":
8088
return self.ascii_email
8189
if key == "local":
@@ -97,7 +105,7 @@ def __eq__(self, other):
97105
if not isinstance(other, ValidatedEmail):
98106
return False
99107
return (
100-
self.email == other.email
108+
self.normalized == other.normalized
101109
and self.local_part == other.local_part
102110
and self.domain == other.domain
103111
and getattr(self, 'ascii_email', None) == getattr(other, 'ascii_email', None)

email_validator/validate_email.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def validate_email(
7676

7777
# Collect return values in this instance.
7878
ret = ValidatedEmail()
79-
ret.original_email = email
79+
ret.original = email
8080

8181
# Validate the email address's local part syntax and get a normalized form.
8282
# If the original address was quoted and the decoded local part is a valid
@@ -113,7 +113,7 @@ def validate_email(
113113
ret.ascii_domain = domain_part_info["ascii_domain"]
114114

115115
# Construct the complete normalized form.
116-
ret.email = ret.local_part + "@" + ret.domain
116+
ret.normalized = ret.local_part + "@" + ret.domain
117117

118118
# If the email address has an ASCII form, add it.
119119
if not ret.smtputf8:
@@ -144,20 +144,20 @@ def validate_email(
144144
#
145145
# See the length checks on the local part and the domain.
146146
if ret.ascii_email and len(ret.ascii_email) > EMAIL_MAX_LENGTH:
147-
if ret.ascii_email == ret.email:
147+
if ret.ascii_email == ret.normalized:
148148
reason = get_length_reason(ret.ascii_email)
149-
elif len(ret.email) > EMAIL_MAX_LENGTH:
149+
elif len(ret.normalized) > EMAIL_MAX_LENGTH:
150150
# If there are more than 254 characters, then the ASCII
151151
# form is definitely going to be too long.
152-
reason = get_length_reason(ret.email, utf8=True)
152+
reason = get_length_reason(ret.normalized, utf8=True)
153153
else:
154154
reason = "(when converted to IDNA ASCII)"
155155
raise EmailSyntaxError(f"The email address is too long {reason}.")
156-
if len(ret.email.encode("utf8")) > EMAIL_MAX_LENGTH:
157-
if len(ret.email) > EMAIL_MAX_LENGTH:
156+
if len(ret.normalized.encode("utf8")) > EMAIL_MAX_LENGTH:
157+
if len(ret.normalized) > EMAIL_MAX_LENGTH:
158158
# If there are more than 254 characters, then the UTF-8
159159
# encoding is definitely going to be too long.
160-
reason = get_length_reason(ret.email, utf8=True)
160+
reason = get_length_reason(ret.normalized, utf8=True)
161161
else:
162162
reason = "(when encoded in bytes)"
163163
raise EmailSyntaxError(f"The email address is too long {reason}.")

tests/test_main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def test_dict_accessor():
1313
input_email = "testaddr@example.tld"
1414
valid_email = validate_email(input_email, check_deliverability=False)
1515
assert isinstance(valid_email.as_dict(), dict)
16-
assert valid_email.as_dict()["original_email"] == input_email
16+
assert valid_email.as_dict()["original"] == input_email
1717

1818

1919
def test_main_single_good_input(monkeypatch, capsys):
@@ -24,7 +24,7 @@ def test_main_single_good_input(monkeypatch, capsys):
2424
stdout, _ = capsys.readouterr()
2525
output = json.loads(str(stdout))
2626
assert isinstance(output, dict)
27-
assert validate_email(test_email, dns_resolver=RESOLVER).original_email == output["original_email"]
27+
assert validate_email(test_email, dns_resolver=RESOLVER).original == output["original"]
2828

2929

3030
def test_main_single_bad_input(monkeypatch, capsys):
@@ -53,7 +53,7 @@ def test_bytes_input():
5353
input_email = b"testaddr@example.tld"
5454
valid_email = validate_email(input_email, check_deliverability=False)
5555
assert isinstance(valid_email.as_dict(), dict)
56-
assert valid_email.as_dict()["email"] == input_email.decode("utf8")
56+
assert valid_email.as_dict()["normalized"] == input_email.decode("utf8")
5757

5858
input_email = "testaddr中example.tld".encode("utf32")
5959
with pytest.raises(EmailSyntaxError):

tests/test_syntax.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
smtputf8=False,
1717
ascii_domain='example.tld',
1818
domain='example.tld',
19-
email='Abc@example.tld',
19+
normalized='Abc@example.tld',
2020
ascii_email='Abc@example.tld',
2121
),
2222
),
@@ -28,7 +28,7 @@
2828
smtputf8=False,
2929
ascii_domain='test-example.com',
3030
domain='test-example.com',
31-
email='Abc.123@test-example.com',
31+
normalized='Abc.123@test-example.com',
3232
ascii_email='Abc.123@test-example.com',
3333
),
3434
),
@@ -40,7 +40,7 @@
4040
smtputf8=False,
4141
ascii_domain='example.tld',
4242
domain='example.tld',
43-
email='user+mailbox/department=shipping@example.tld',
43+
normalized='user+mailbox/department=shipping@example.tld',
4444
ascii_email='user+mailbox/department=shipping@example.tld',
4545
),
4646
),
@@ -52,7 +52,7 @@
5252
smtputf8=False,
5353
ascii_domain='example.tld',
5454
domain='example.tld',
55-
email="!#$%&'*+-/=?^_`.{|}~@example.tld",
55+
normalized="!#$%&'*+-/=?^_`.{|}~@example.tld",
5656
ascii_email="!#$%&'*+-/=?^_`.{|}~@example.tld",
5757
),
5858
),
@@ -64,7 +64,7 @@
6464
smtputf8=False,
6565
ascii_domain='xn--fiqq24b10vi0d.tw',
6666
domain='臺網中心.tw',
67-
email='jeff@臺網中心.tw',
67+
normalized='jeff@臺網中心.tw',
6868
ascii_email='jeff@xn--fiqq24b10vi0d.tw',
6969
),
7070
),
@@ -88,7 +88,7 @@ def test_email_valid(email_input, output):
8888
smtputf8=True,
8989
ascii_domain='xn--5nqv22n.xn--lhr59c',
9090
domain='郵件.商務',
91-
email='伊昭傑@郵件.商務',
91+
normalized='伊昭傑@郵件.商務',
9292
),
9393
),
9494
(
@@ -98,7 +98,7 @@ def test_email_valid(email_input, output):
9898
smtputf8=True,
9999
ascii_domain='xn--l2bl7a9d.xn--o1b8dj2ki',
100100
domain='मोहन.ईन्फो',
101-
email='राम@मोहन.ईन्फो',
101+
normalized='राम@मोहन.ईन्फो',
102102
),
103103
),
104104
(
@@ -108,7 +108,7 @@ def test_email_valid(email_input, output):
108108
smtputf8=True,
109109
ascii_domain='xn--80ajglhfv.xn--j1aef',
110110
domain='екзампл.ком',
111-
email='юзер@екзампл.ком',
111+
normalized='юзер@екзампл.ком',
112112
),
113113
),
114114
(
@@ -118,7 +118,7 @@ def test_email_valid(email_input, output):
118118
smtputf8=True,
119119
ascii_domain='xn--mxahbxey0c.xn--xxaf0a',
120120
domain='εχαμπλε.ψομ',
121-
email='θσερ@εχαμπλε.ψομ',
121+
normalized='θσερ@εχαμπλε.ψομ',
122122
),
123123
),
124124
(
@@ -128,7 +128,7 @@ def test_email_valid(email_input, output):
128128
smtputf8=True,
129129
ascii_domain='xn--fiqq24b10vi0d.tw',
130130
domain='臺網中心.tw',
131-
email='葉士豪@臺網中心.tw',
131+
normalized='葉士豪@臺網中心.tw',
132132
),
133133
),
134134
(
@@ -138,7 +138,7 @@ def test_email_valid(email_input, output):
138138
smtputf8=True,
139139
ascii_domain='xn--fiqq24b10vi0d.xn--kpry57d',
140140
domain='臺網中心.台灣',
141-
email='葉士豪@臺網中心.台灣',
141+
normalized='葉士豪@臺網中心.台灣',
142142
),
143143
),
144144
(
@@ -148,7 +148,7 @@ def test_email_valid(email_input, output):
148148
smtputf8=True,
149149
ascii_domain='xn--fiqq24b10vi0d.tw',
150150
domain='臺網中心.tw',
151-
email='jeff葉@臺網中心.tw',
151+
normalized='jeff葉@臺網中心.tw',
152152
),
153153
),
154154
(
@@ -158,7 +158,7 @@ def test_email_valid(email_input, output):
158158
smtputf8=True,
159159
ascii_domain='example.tld',
160160
domain='example.tld',
161-
email='ñoñó@example.tld',
161+
normalized='ñoñó@example.tld',
162162
),
163163
),
164164
(
@@ -168,7 +168,7 @@ def test_email_valid(email_input, output):
168168
smtputf8=True,
169169
ascii_domain='example.tld',
170170
domain='example.tld',
171-
email='我買@example.tld',
171+
normalized='我買@example.tld',
172172
),
173173
),
174174
(
@@ -178,7 +178,7 @@ def test_email_valid(email_input, output):
178178
smtputf8=True,
179179
ascii_domain='example.tld',
180180
domain='example.tld',
181-
email='甲斐黒川日本@example.tld',
181+
normalized='甲斐黒川日本@example.tld',
182182
),
183183
),
184184
(
@@ -188,7 +188,7 @@ def test_email_valid(email_input, output):
188188
smtputf8=True,
189189
ascii_domain='example.tld',
190190
domain='example.tld',
191-
email='чебурашкаящик-с-апельсинами.рф@example.tld',
191+
normalized='чебурашкаящик-с-апельсинами.рф@example.tld',
192192
),
193193
),
194194
(
@@ -198,7 +198,7 @@ def test_email_valid(email_input, output):
198198
smtputf8=True,
199199
ascii_domain='domain.with.idn.tld',
200200
domain='domain.with.idn.tld',
201-
email='उदाहरण.परीक्ष@domain.with.idn.tld',
201+
normalized='उदाहरण.परीक्ष@domain.with.idn.tld',
202202
),
203203
),
204204
(
@@ -208,7 +208,7 @@ def test_email_valid(email_input, output):
208208
smtputf8=True,
209209
ascii_domain='xn--qxaa9ba.gr',
210210
domain='εεττ.gr',
211-
email='ιωάννης@εεττ.gr',
211+
normalized='ιωάννης@εεττ.gr',
212212
),
213213
),
214214
],

0 commit comments

Comments
 (0)