Skip to content

Commit 8d91a45

Browse files
committed
Ratchet up mypy settings
1 parent 4691a62 commit 8d91a45

File tree

7 files changed

+44
-14
lines changed

7 files changed

+44
-14
lines changed

email_validator/__main__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import json
1818
import os
1919
import sys
20+
from typing import Any, Dict
2021

2122
from .validate_email import validate_email
2223
from .deliverability import caching_resolver
@@ -27,7 +28,7 @@ def main(dns_resolver=None):
2728
# The dns_resolver argument is for tests.
2829

2930
# Set options from environment variables.
30-
options = {}
31+
options: Dict[str, Any] = {}
3132
for varname in ('ALLOW_SMTPUTF8', 'ALLOW_QUOTED_LOCAL', 'ALLOW_DOMAIN_LITERAL',
3233
'GLOBALLY_DELIVERABLE', 'CHECK_DELIVERABILITY', 'TEST_ENVIRONMENT'):
3334
if varname in os.environ:

email_validator/deliverability.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Optional, Any, Dict
1+
from typing import Any, Dict, Optional
22

33
import ipaddress
44

@@ -8,17 +8,17 @@
88
import dns.exception
99

1010

11-
def caching_resolver(*, timeout: Optional[int] = None, cache=None, dns_resolver=None):
11+
def caching_resolver(*, timeout: Optional[int] = None, cache: Any = None, dns_resolver: Optional[dns.resolver.Resolver] = None) -> dns.resolver.Resolver:
1212
if timeout is None:
1313
from . import DEFAULT_TIMEOUT
1414
timeout = DEFAULT_TIMEOUT
1515
resolver = dns_resolver or dns.resolver.Resolver()
16-
resolver.cache = cache or dns.resolver.LRUCache() # type: ignore
17-
resolver.lifetime = timeout # type: ignore # timeout, in seconds
16+
resolver.cache = cache or dns.resolver.LRUCache()
17+
resolver.lifetime = timeout # timeout, in seconds
1818
return resolver
1919

2020

21-
def validate_email_deliverability(domain: str, domain_i18n: str, timeout: Optional[int] = None, dns_resolver=None):
21+
def validate_email_deliverability(domain: str, domain_i18n: str, timeout: Optional[int] = None, dns_resolver: Optional[dns.resolver.Resolver] = None) -> Dict[str, str]:
2222
# Check that the domain resolves to an MX record. If there is no MX record,
2323
# try an A or AAAA record which is a deprecated fallback for deliverability.
2424
# Raises an EmailUndeliverableError on failure. On success, returns a dict

email_validator/exceptions_types.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import warnings
2-
from typing import Optional
2+
from typing import List, Optional, Tuple
33

44

55
class EmailNotValidError(ValueError):
@@ -56,7 +56,7 @@ class ValidatedEmail:
5656

5757
"""If a deliverability check is performed and if it succeeds, a list of (priority, domain)
5858
tuples of MX records specified in the DNS for the domain."""
59-
mx: list
59+
mx: List[Tuple[int, str]]
6060

6161
"""If no MX records are actually specified in DNS and instead are inferred, through an obsolete
6262
mechanism, from A or AAAA records, the value is the type of DNS record used instead (`A` or `AAAA`)."""

email_validator/syntax.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import unicodedata
88
import idna # implements IDNA 2008; Python's codec is only IDNA 2003
99
import ipaddress
10-
from typing import Optional
10+
from typing import Optional, TypedDict, Union
1111

1212

1313
def split_email(email):
@@ -180,8 +180,14 @@ def safe_character_display(c):
180180
return unicodedata.name(c, h)
181181

182182

183+
class LocalPartValidationResult(TypedDict):
184+
local_part: str
185+
ascii_local_part: Optional[str]
186+
smtputf8: bool
187+
188+
183189
def validate_email_local_part(local: str, allow_smtputf8: bool = True, allow_empty_local: bool = False,
184-
quoted_local_part: bool = False):
190+
quoted_local_part: bool = False) -> LocalPartValidationResult:
185191
"""Validates the syntax of the local part of an email address."""
186192

187193
if len(local) == 0:
@@ -626,6 +632,8 @@ def validate_email_domain_literal(domain_literal):
626632
# a compressed/normalized address.
627633
# RFC 5321 4.1.3 and RFC 5322 3.4.1.
628634

635+
addr: Union[ipaddress.IPv4Address, ipaddress.IPv6Address]
636+
629637
# Try to parse the domain literal as an IPv4 address.
630638
# There is no tag for IPv4 addresses, so we can never
631639
# be sure if the user intends an IPv4 address.

email_validator/validate_email.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1-
from typing import Optional, Union
1+
from typing import Optional, Union, TYPE_CHECKING
22

33
from .exceptions_types import EmailSyntaxError, ValidatedEmail
44
from .syntax import split_email, validate_email_local_part, validate_email_domain_name, validate_email_domain_literal, validate_email_length
55
from .rfc_constants import CASE_INSENSITIVE_MAILBOX_NAMES
66

7+
if TYPE_CHECKING:
8+
import dns.resolver
9+
_Resolver = dns.resolver.Resolver
10+
else:
11+
_Resolver = object
12+
713

814
def validate_email(
915
email: Union[str, bytes],
@@ -18,7 +24,7 @@ def validate_email(
1824
test_environment: Optional[bool] = None,
1925
globally_deliverable: Optional[bool] = None,
2026
timeout: Optional[int] = None,
21-
dns_resolver: Optional[object] = None
27+
dns_resolver: Optional[_Resolver] = None
2228
) -> ValidatedEmail:
2329
"""
2430
Given an email address, and some options, returns a ValidatedEmail instance

pyproject.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
[tool.mypy]
2+
disallow_any_generics = true
3+
disallow_subclassing_any = true
4+
5+
check_untyped_defs = true
6+
disallow_incomplete_defs = true
7+
# disallow_untyped_calls = true
8+
disallow_untyped_decorators = true
9+
# disallow_untyped_defs = true
10+
11+
warn_redundant_casts = true
12+
warn_unused_ignores = true
13+
114
[tool.pytest.ini_options]
215
markers = [
316
"network: marks tests as requiring Internet access",

tests/mocked_dns_response.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
class MockedDnsResponseData:
2121
DATA_PATH = os.path.dirname(__file__) + "/mocked-dns-answers.json"
2222

23+
INSTANCE = None
24+
2325
@staticmethod
2426
def create_resolver():
25-
if not hasattr(MockedDnsResponseData, 'INSTANCE'):
27+
if MockedDnsResponseData.INSTANCE is None:
2628
# Create a singleton instance of this class and load the saved DNS responses.
2729
# Except when BUILD_MOCKED_DNS_RESPONSE_DATA is true, don't load the data.
2830
singleton = MockedDnsResponseData()
@@ -116,6 +118,6 @@ def put(self, key, value):
116118
@pytest.fixture(scope="session", autouse=True)
117119
def MockedDnsResponseDataCleanup(request):
118120
def cleanup_func():
119-
if BUILD_MOCKED_DNS_RESPONSE_DATA:
121+
if BUILD_MOCKED_DNS_RESPONSE_DATA and MockedDnsResponseData.INSTANCE is not None:
120122
MockedDnsResponseData.INSTANCE.save()
121123
request.addfinalizer(cleanup_func)

0 commit comments

Comments
 (0)