From b99329b41afe16bb861f5a3d3a063516124f36be Mon Sep 17 00:00:00 2001 From: ainuo-AI <2470447645@qq.com> Date: Fri, 13 Nov 2026 22:56:49 +0800 Subject: [PATCH] Return None for MX when deliverability is unknown --- CHANGELOG.md | 1 + email_validator/types.py | 11 +++++++---- email_validator/validate_email.py | 4 +--- tests/test_deliverability.py | 11 +++++++++++ 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b3afa7..54c011c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ In Development -------------- +* The `mx` field is now `None` when a deliverability check cannot be completed, as documented, instead of being absent from the returned object. * Python 3.8 and 3.9 are no longer supported. * The `original` field of the returned validated email object incorrectly un-escaped the local part of the email address. Now it returns the original address, without any display name, as passed to validate_email. * add support python3.15 diff --git a/email_validator/types.py b/email_validator/types.py index c285d60..8e1116e 100644 --- a/email_validator/types.py +++ b/email_validator/types.py @@ -40,8 +40,9 @@ class ValidatedEmail: smtputf8: bool """If a deliverability check is performed and if it succeeds, a list of (priority, domain) - tuples of MX records specified in the DNS for the domain.""" - mx: list[tuple[int, str]] + tuples of MX records specified in the DNS for the domain. None if the check + could not be completed because of a temporary issue.""" + mx: Optional[list[tuple[int, str]]] """If no MX records are actually specified in DNS and instead are inferred, through an obsolete mechanism, from A or AAAA records, the value is the type of DNS record used instead (`A` or `AAAA`).""" @@ -92,6 +93,8 @@ def __getitem__(self, key: str) -> Optional[str] | bool | list[tuple[int, str]]: def __eq__(self, other: object) -> bool: if not isinstance(other, ValidatedEmail): return False + self_mx = getattr(self, 'mx', None) + other_mx = getattr(other, 'mx', None) return ( getattr(self, 'original', None) == getattr(other, 'original', None) and self.normalized == other.normalized @@ -101,8 +104,8 @@ def __eq__(self, other: object) -> bool: and getattr(self, 'ascii_local_part', None) == getattr(other, 'ascii_local_part', None) and getattr(self, 'ascii_domain', None) == getattr(other, 'ascii_domain', None) and self.smtputf8 == other.smtputf8 - and repr(sorted(self.mx) if getattr(self, 'mx', None) else None) - == repr(sorted(other.mx) if getattr(other, 'mx', None) else None) + and repr(sorted(self_mx) if self_mx else None) + == repr(sorted(other_mx) if other_mx else None) and getattr(self, 'mx_fallback_type', None) == getattr(other, 'mx_fallback_type', None) and getattr(self, 'display_name', None) == getattr(other, 'display_name', None) ) diff --git a/email_validator/validate_email.py b/email_validator/validate_email.py index 07b398a..ccc5bba 100644 --- a/email_validator/validate_email.py +++ b/email_validator/validate_email.py @@ -199,9 +199,7 @@ def validate_email( deliverability_info = validate_email_deliverability( ret.ascii_domain, ret.domain, timeout, dns_resolver ) - mx = deliverability_info.get("mx") - if mx is not None: - ret.mx = mx + ret.mx = deliverability_info.get("mx") ret.mx_fallback_type = deliverability_info.get("mx_fallback_type") return ret diff --git a/tests/test_deliverability.py b/tests/test_deliverability.py index e1307c2..21e3de3 100644 --- a/tests/test_deliverability.py +++ b/tests/test_deliverability.py @@ -1,5 +1,6 @@ from typing import Any +import dns.resolver import pytest import re @@ -66,6 +67,16 @@ def test_deliverability_dns_timeout() -> None: assert response.get("unknown-deliverability") == "timeout" +@pytest.mark.parametrize('dns_resolver', [RESOLVER, dns.resolver.Resolver(configure=False)]) +def test_validated_email_mx_unknown(dns_resolver: dns.resolver.Resolver) -> None: + # The saved resolver simulates a timeout; the empty resolver has no nameservers. + result = validate_email('me@timeout.com', dns_resolver=dns_resolver) + assert result.normalized == 'me@timeout.com' + assert result.mx is None + assert result.mx_fallback_type is None + assert result.as_dict()['mx'] is None + + def test_timeout_and_resolver() -> None: with pytest.raises(ValueError, match="It's not valid to pass both timeout and dns_resolver."): validate_email_deliverability('timeout.com', 'timeout.com', timeout=1, dns_resolver=RESOLVER)