1- from .exceptions_types import EmailSyntaxError
1+ from .exceptions_types import EmailSyntaxError , ValidatedEmail
22from .rfc_constants import EMAIL_MAX_LENGTH , LOCAL_PART_MAX_LENGTH , DOMAIN_MAX_LENGTH , \
33 DOT_ATOM_TEXT , DOT_ATOM_TEXT_INTL , ATEXT_RE , ATEXT_INTL_DOT_RE , ATEXT_HOSTNAME_INTL , QTEXT_INTL , \
44 DNS_LABEL_LENGTH_LIMIT , DOT_ATOM_TEXT_HOSTNAME , DOMAIN_NAME_REGEX , DOMAIN_LITERAL_CHARS
77import unicodedata
88import idna # implements IDNA 2008; Python's codec is only IDNA 2003
99import ipaddress
10- from typing import Optional , TypedDict , Union
10+ from typing import Optional , Tuple , TypedDict , Union
1111
1212
13- def split_email (email ) :
13+ def split_email (email : str ) -> Tuple [ Optional [ str ], str , str , bool ] :
1414 # Return the display name, unescaped local part, and domain part
1515 # of the address, and whether the local part was quoted. If no
1616 # display name was present and angle brackets do not surround
@@ -46,7 +46,7 @@ def split_email(email):
4646 # We assume the input string is already stripped of leading and
4747 # trailing CFWS.
4848
49- def split_string_at_unquoted_special (text , specials ) :
49+ def split_string_at_unquoted_special (text : str , specials : Tuple [ str , ...]) -> Tuple [ str , str ] :
5050 # Split the string at the first character in specials (an @-sign
5151 # or left angle bracket) that does not occur within quotes.
5252 inside_quote = False
@@ -77,7 +77,7 @@ def split_string_at_unquoted_special(text, specials):
7777
7878 return left_part , right_part
7979
80- def unquote_quoted_string (text ) :
80+ def unquote_quoted_string (text : str ) -> Tuple [ str , bool ] :
8181 # Remove surrounding quotes and unescape escaped backslashes
8282 # and quotes. Escapes are parsed liberally. I think only
8383 # backslashes and quotes can be escaped but we'll allow anything
@@ -155,15 +155,15 @@ def unquote_quoted_string(text):
155155 return display_name , local_part , domain_part , is_quoted_local_part
156156
157157
158- def get_length_reason (addr , utf8 = False , limit = EMAIL_MAX_LENGTH ):
158+ def get_length_reason (addr : str , utf8 : bool = False , limit : int = EMAIL_MAX_LENGTH ) -> str :
159159 """Helper function to return an error message related to invalid length."""
160160 diff = len (addr ) - limit
161161 prefix = "at least " if utf8 else ""
162162 suffix = "s" if diff > 1 else ""
163163 return f"({ prefix } { diff } character{ suffix } too many)"
164164
165165
166- def safe_character_display (c ) :
166+ def safe_character_display (c : str ) -> str :
167167 # Return safely displayable characters in quotes.
168168 if c == '\\ ' :
169169 return f"\" { c } \" " # can't use repr because it escapes it
@@ -351,7 +351,7 @@ def validate_email_local_part(local: str, allow_smtputf8: bool = True, allow_emp
351351 raise EmailSyntaxError ("The email address contains invalid characters before the @-sign." )
352352
353353
354- def check_unsafe_chars (s , allow_space = False ):
354+ def check_unsafe_chars (s : str , allow_space : bool = False ) -> None :
355355 # Check for unsafe characters or characters that would make the string
356356 # invalid or non-sensible Unicode.
357357 bad_chars = set ()
@@ -403,7 +403,7 @@ def check_unsafe_chars(s, allow_space=False):
403403 + ", " .join (safe_character_display (c ) for c in sorted (bad_chars )) + "." )
404404
405405
406- def check_dot_atom (label , start_descr , end_descr , is_hostname ) :
406+ def check_dot_atom (label : str , start_descr : str , end_descr : str , is_hostname : bool ) -> None :
407407 # RFC 5322 3.2.3
408408 if label .endswith ("." ):
409409 raise EmailSyntaxError (end_descr .format ("period" ))
@@ -422,7 +422,12 @@ def check_dot_atom(label, start_descr, end_descr, is_hostname):
422422 raise EmailSyntaxError ("An email address cannot have a period and a hyphen next to each other." )
423423
424424
425- def validate_email_domain_name (domain , test_environment = False , globally_deliverable = True ):
425+ class DomainNameValidationResult (TypedDict ):
426+ ascii_domain : str
427+ domain : str
428+
429+
430+ def validate_email_domain_name (domain : str , test_environment : bool = False , globally_deliverable : bool = True ) -> DomainNameValidationResult :
426431 """Validates the syntax of the domain part of an email address."""
427432
428433 # Check for invalid characters before normalization.
@@ -586,7 +591,7 @@ def validate_email_domain_name(domain, test_environment=False, globally_delivera
586591 }
587592
588593
589- def validate_email_length (addrinfo ) :
594+ def validate_email_length (addrinfo : ValidatedEmail ) -> None :
590595 # If the email address has an ASCII representation, then we assume it may be
591596 # transmitted in ASCII (we can't assume SMTPUTF8 will be used on all hops to
592597 # the destination) and the length limit applies to ASCII characters (which is
@@ -627,7 +632,12 @@ def validate_email_length(addrinfo):
627632 raise EmailSyntaxError (f"The email address is too long { reason } ." )
628633
629634
630- def validate_email_domain_literal (domain_literal ):
635+ class DomainLiteralValidationResult (TypedDict ):
636+ domain_address : Union [ipaddress .IPv4Address , ipaddress .IPv6Address ]
637+ domain : str
638+
639+
640+ def validate_email_domain_literal (domain_literal : str ) -> DomainLiteralValidationResult :
631641 # This is obscure domain-literal syntax. Parse it and return
632642 # a compressed/normalized address.
633643 # RFC 5321 4.1.3 and RFC 5322 3.4.1.
0 commit comments