Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-email_validator for openSUSE:Factory checked in at 2021-08-23 10:07:36 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-email_validator (Old) and /work/SRC/openSUSE:Factory/.python-email_validator.new.1899 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-email_validator" Mon Aug 23 10:07:36 2021 rev:8 rq:912682 version:1.1.3 Changes: -------- --- /work/SRC/openSUSE:Factory/python-email_validator/python-email_validator.changes 2020-10-25 18:08:42.243463625 +0100 +++ /work/SRC/openSUSE:Factory/.python-email_validator.new.1899/python-email_validator.changes 2021-08-23 10:07:38.492283364 +0200 @@ -1,0 +2,18 @@ +Mon Aug 16 09:14:16 UTC 2021 - Fusion Future <qydwhotm...@gmail.com> + +- Update to 1.1.3: + * Add possibility to cache dns lookups (#58) + * Add py39 and setup_py to setup_cfg (#57) +- Changes from 1.1.2: + * Refactor: Main refactored, tests added for main (#52) + * Simplify email equality check into return statement (#51) + * Dedupe length reason logic and declare magic numbers as + constants (#50) + * Fix: ValidatedEmail is not JSON serializable (#49) + * Use dnspython's resolve method when available (#46) + * Package name should have a dash not an underscore + * Mention Punycode normalization, re-do fields as a table +- Drop fix-tests-strings.patch which is not needed. +- Move skipped tests to spec file, drop skip-tests-using-network.patch. + +------------------------------------------------------------------- Old: ---- fix-tests-strings.patch skip-tests-using-network.patch v1.1.1.tar.gz New: ---- v1.1.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-email_validator.spec ++++++ --- /var/tmp/diff_new_pack.1bVCH4/_old 2021-08-23 10:07:39.932281684 +0200 +++ /var/tmp/diff_new_pack.1bVCH4/_new 2021-08-23 10:07:39.936281680 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-email_validator # -# Copyright (c) 2020 SUSE LLC +# Copyright (c) 2021 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,15 +19,13 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define skip_python2 1 Name: python-email_validator -Version: 1.1.1 +Version: 1.1.3 Release: 0 Summary: A robust email syntax and deliverability validation library for Python License: CC0-1.0 Group: Development/Languages/Python URL: https://github.com/JoshData/python-email-validator Source: https://github.com/JoshData/python-email-validator/archive/v%{version}.tar.gz -Patch0: skip-tests-using-network.patch -Patch1: fix-tests-strings.patch BuildRequires: %{python_module dnspython >= 1.15.0} BuildRequires: %{python_module idna >= 2.0.0} BuildRequires: %{python_module pytest >= 5.0} @@ -38,7 +36,7 @@ Requires: python-idna >= 2.0.0 Requires: python-setuptools Requires(post): update-alternatives -Requires(postun): update-alternatives +Requires(postun):update-alternatives BuildArch: noarch %python_subpackages @@ -59,10 +57,6 @@ %prep %setup -q -n python-email-validator-%{version} -%patch0 -p1 -%if %{?suse_version} <= 1500 -%patch1 -p1 -%endif %build %python_build @@ -73,7 +67,7 @@ %python_expand %fdupes %{buildroot}%{$python_sitelib} %check -%pytest +%pytest tests -k 'not (test_deliverability_no_records or test_deliverability_found or test_deliverability_fails or test_deliverability_dns_timeout or test_main_single_good_input or test_main_multi_input or test_main_input_shim or test_validate_email__with_caching_resolver or test_validate_email__with_configured_resolver)' %post %python_install_alternative email_validator ++++++ v1.1.1.tar.gz -> v1.1.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-email-validator-1.1.1/.travis.yml new/python-email-validator-1.1.3/.travis.yml --- old/python-email-validator-1.1.1/.travis.yml 2020-05-19 13:15:08.000000000 +0200 +++ new/python-email-validator-1.1.3/.travis.yml 2021-06-12 13:01:42.000000000 +0200 @@ -10,6 +10,7 @@ - '3.6' - '3.7' - '3.8' +- '3.9' install: - make install diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-email-validator-1.1.1/Makefile new/python-email-validator-1.1.3/Makefile --- old/python-email-validator-1.1.1/Makefile 2020-05-19 13:15:08.000000000 +0200 +++ new/python-email-validator-1.1.3/Makefile 2021-06-12 13:01:42.000000000 +0200 @@ -9,7 +9,7 @@ .PHONY: lint lint: #python setup.py check -rms - flake8 --ignore=E501,E126 email_validator tests + flake8 --ignore=E501,E126,W503 email_validator tests .PHONY: test test: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-email-validator-1.1.1/README.md new/python-email-validator-1.1.3/README.md --- old/python-email-validator-1.1.1/README.md 2020-05-19 13:15:08.000000000 +0200 +++ new/python-email-validator-1.1.3/README.md 2021-06-12 13:01:42.000000000 +0200 @@ -1,19 +1,20 @@ -email\_validator -================ +email-validator: Validate Email Addresses +========================================= A robust email address syntax and deliverability validation library for -Python 2.7/3.4+ by [Joshua Tauberer](https://razor.occams.info). +Python 2.7/3.4+ by [Joshua Tauberer](https://joshdata.me). -This library validates that a string is of the form `x...@y.com`. This is +This library validates that a string is of the form `n...@example.com`. This is the sort of validation you would want for an email-based login form on a website. Key features: -* Good for validating email addresses used for logins/identity. -* Friendly error messages when validation fails (appropriate to show +* Checks that an email address has the correct syntax --- good for + login forms or other uses related to identifying users. +* Gives friendly error messages when validation fails (appropriate to show to end users). -* (optionally) Checks deliverability: Does the domain name resolve? +* (optionally) Checks deliverability: Does the domain name resolve? And you can override the default DNS resolver. * Supports internationalized domain names and (optionally) internationalized local parts. * Normalizes email addresses (super important for internationalized @@ -37,7 +38,7 @@ This package [is on PyPI](https://pypi.org/project/email-validator/), so: ```sh -pip install email_validator +pip install email-validator ``` `pip3` also works. @@ -68,23 +69,27 @@ put the normalized form in your database and always normalize before checking if an address is in your database. -The validator will accept internationalized email addresses, but email -addresses with non-ASCII characters in the *local* part of the address -(before the @-sign) require the -[SMTPUTF8](https://tools.ietf.org/html/rfc6531) extension which may not -be supported by your mail submission library or your outbound mail -server. If you know ahead of time that SMTPUTF8 is not supported then -**add the keyword argument allow\_smtputf8=False to fail validation for -addresses that would require SMTPUTF8**: +When validating many email addresses or to control the timeout (the default is 15 seconds), create a caching [dns.resolver.Resolver](https://dnspython.readthedocs.io/en/latest/resolver-class.html) to reuse in each call: ```python -valid = validate_email(email, allow_smtputf8=False) +from email_validator import validate_email, caching_resolver + +resolver = caching_resolver(timeout=10) + +while True: + valid = validate_email(email, dns_resolver=resolver) ``` +The validator will accept internationalized email addresses, but not all +mail systems can send email to an addresses with non-ASCII characters in +the *local* part of the address (before the @-sign). See the `allow_smtputf8` +option below. + + Overview -------- -The module provides a single function `validate_email(email_address)` which +The module provides a function `validate_email(email_address)` which takes an email address (either a `str` or ASCII `bytes`) and: - Raises a `EmailNotValidError` with a helpful, human-readable error @@ -127,6 +132,9 @@ `allow_empty_local=False`: Set to `True` to allow an empty local part (i.e. `@example.com`), e.g. for validating Postfix aliases. + +`dns_resolver=None`: Pass an instance of [dns.resolver.Resolver](https://dnspython.readthedocs.io/en/latest/resolver-class.html) to control the DNS resolver including setting a timeout and [a cache](https://dnspython.readthedocs.io/en/latest/resolver-caching.html). The `caching_resolver` function shown above is a helper function to construct a dns.resolver.Resolver with a [LRUCache](https://dnspython.readthedocs.io/en/latest/resolver-caching.html#dns.resolver.LRUCache). Reuse the same resolver instance across calls to `validate_email` to make use of the cache. + Internationalized email addresses --------------------------------- @@ -230,13 +238,12 @@ normalization](https://en.wikipedia.org/wiki/Unicode_equivalence) of the whole address (which turns characters plus [combining characters](https://en.wikipedia.org/wiki/Combining_character) into -precomposed characters where possible and replaces certain Unicode -characters (such as angstrom and ohm) with other equivalent code points -(a-with-ring and omega, respectively)), replacement of [fullwidth and +precomposed characters where possible, replacement of [fullwidth and halfwidth characters](https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms) -in the domain part, and possibly other -[UTS46](http://unicode.org/reports/tr46) mappings on the domain part. +in the domain part, possibly other +[UTS46](http://unicode.org/reports/tr46) mappings on the domain part, +and conversion from Punycode to Unicode characters. (See [RFC 6532 (internationalized email) section 3.1](https://tools.ietf.org/html/rfc6532#section-3.1) and [RFC 5895 @@ -283,6 +290,10 @@ fields provide a normalized form of the email address and domain name (casefolding and Unicode normalization as required by IDNA 2008). +Calling `validate_email` with the ASCII form of the above email address, +`exam...@xn--bdk.life`, returns the exact same information (i.e., the +`email` field always will contain Unicode characters, not Punycode). + For the fictitious address `???-t...@joshdata.me`, which has an internationalized local part, the returned object is: @@ -309,55 +320,17 @@ When an email address passes validation, the fields in the returned object are: -`email`: The canonical form of the email address, mostly useful for - display purposes. This merely combines the `local_part` and `domain` - fields (see below). - -`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`. - -`local_part`: The local part of the given email address (before the @-sign) with - Unicode NFC normalization applied. - -`ascii_local_part`: If set, the local part, which is composed of ASCII characters only. - -`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. - -`ascii_domain`: The [IDNA](https://tools.ietf.org/html/rfc5891) - [Punycode](https://www.rfc-editor.org/rfc/rfc3492.txt)-encoded - form of the domain part of the given email address, as - it would be transmitted on the wire. - -`smtputf8`: A boolean indicating that the - [SMTPUTF8](https://tools.ietf.org/html/rfc6531) feature of your - mail relay will be required to transmit messages to this address - because the local part of the address has non-ASCII characters (the - local part cannot be IDNA-encoded). If `allow_smtputf8=False` is - passed as an argument, this flag will always be false because an - exception is raised if it would have been true. - -`mx`: A list of (priority, domain) tuples of MX records specified in the - DNS for the domain (see [RFC 5321 section - 5](https://tools.ietf.org/html/rfc5321#section-5)). May be `None` if - the deliverability check could not be completed because of a temporary - issue like a timeout. - -`mx_fallback_type`: `None` if an `MX` record is found. 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`). May be `None` if the deliverability check - could not be completed because of a temporary issue like a timeout. +| Field | Value | +| -----:|-------| +| `email` | The normalized form of the email address that you should put in your database. This merely combines the `local_part` and `domain` fields (see below). | +| `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`. | +| `local_part` | The local part of the given email address (before the @-sign) with Unicode NFC normalization applied. | +| `ascii_local_part` | If set, the local part, which is composed of ASCII characters only. | +| `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. | +| `ascii_domain` | The [IDNA](https://tools.ietf.org/html/rfc5891) [Punycode](https://www.rfc-editor.org/rfc/rfc3492.txt)-encoded form of the domain part of the given email address, as it would be transmitted on the wire. | +| `smtputf8` | A boolean indicating that the [SMTPUTF8](https://tools.ietf.org/html/rfc6531) feature of your mail relay will be required to transmit messages to this address because the local part of the address has non-ASCII characters (the local part cannot be IDNA-encoded). If `allow_smtputf8=False` is passed as an argument, this flag will always be false because an exception is raised if it would have been true. | +| `mx` | A list of (priority, domain) tuples of MX records specified in the DNS for the domain (see [RFC 5321 section 5](https://tools.ietf.org/html/rfc5321#section-5)). May be `None` if the deliverability check could not be completed because of a temporary issue like a timeout. | +| `mx_fallback_type` | `None` if an `MX` record is found. 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`). May be `None` if the deliverability check could not be completed because of a temporary issue like a timeout. | Assumptions ----------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-email-validator-1.1.1/email_validator/__init__.py new/python-email-validator-1.1.3/email_validator/__init__.py --- old/python-email-validator-1.1.1/email_validator/__init__.py 2020-05-19 13:15:08.000000000 +0200 +++ new/python-email-validator-1.1.3/email_validator/__init__.py 2021-06-12 13:01:42.000000000 +0200 @@ -29,6 +29,13 @@ # the beginning or end of a *dot-atom component* of a hostname either. ATEXT_HOSTNAME = r'(?:(?:[a-zA-Z0-9][a-zA-Z0-9\-]*)?[a-zA-Z0-9])' +# Length constants +# RFC 3696 + errata 1003 + errata 1690 (https://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690) +# explains the maximum length of an email address is 254 octets. +EMAIL_MAX_LENGTH = 254 +LOCAL_PART_MAX_LENGTH = 64 +DOMAIN_MAX_LENGTH = 255 + # ease compatibility in type checking if sys.version_info >= (3,): unicode_class = str @@ -135,14 +142,18 @@ """Tests use this.""" def __eq__(self, other): - if self.email == other.email and self.local_part == other.local_part and self.domain == other.domain \ - and self.ascii_email == other.ascii_email and self.ascii_local_part == other.ascii_local_part \ - and self.ascii_domain == other.ascii_domain \ - and self.smtputf8 == other.smtputf8 \ - and repr(sorted(self.mx) if self.mx else self.mx) == repr(sorted(other.mx) if other.mx else other.mx) \ - and self.mx_fallback_type == other.mx_fallback_type: - return True - return False + return ( + self.email == other.email + and self.local_part == other.local_part + and self.domain == other.domain + and self.ascii_email == other.ascii_email + and self.ascii_local_part == other.ascii_local_part + and self.ascii_domain == other.ascii_domain + and self.smtputf8 == other.smtputf8 + and repr(sorted(self.mx) if self.mx else self.mx) + == repr(sorted(other.mx) if other.mx else other.mx) + and self.mx_fallback_type == other.mx_fallback_type + ) """This helps producing the README.""" def as_constructor(self): @@ -156,6 +167,25 @@ ) \ + ")" + """Convenience method for accessing ValidatedEmail as a dict""" + def as_dict(self): + return self.__dict__ + + +def __get_length_reason(addr, utf8=False, limit=EMAIL_MAX_LENGTH): + diff = len(addr) - limit + reason = "({}{} character{} too many)" + prefix = "at least " if utf8 else "" + suffix = "s" if diff > 1 else "" + return reason.format(prefix, diff, suffix) + + +def caching_resolver(timeout=DEFAULT_TIMEOUT, cache=None): + resolver = dns.resolver.Resolver() + resolver.cache = cache or dns.resolver.LRUCache() + resolver.lifetime = timeout # timeout, in seconds + return resolver + def validate_email( email, @@ -163,6 +193,7 @@ allow_empty_local=False, check_deliverability=True, timeout=DEFAULT_TIMEOUT, + dns_resolver=None ): """ Validates an email address, raising an EmailNotValidError if the address is not valid or returning a dict of @@ -208,9 +239,6 @@ if not ret.smtputf8: ret.ascii_email = ret.ascii_local_part + "@" + ret.ascii_domain - # RFC 3696 + errata 1003 + errata 1690 (https://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690) - # explains the maximum length of an email address is 254 octets. - # # If the email address has an ASCII representation, then we assume it may be # transmitted in ASCII (we can't assume SMTPUTF8 will be used on all hops to # the destination) and the length limit applies to ASCII characters (which is @@ -231,38 +259,31 @@ # longer than the number of characters. # # See the length checks on the local part and the domain. - if ret.ascii_email and len(ret.ascii_email) > 254: + if ret.ascii_email and len(ret.ascii_email) > EMAIL_MAX_LENGTH: if ret.ascii_email == ret.email: - reason = " ({} character{} too many)".format( - len(ret.ascii_email) - 254, - "s" if (len(ret.ascii_email) - 254 != 1) else "" - ) - elif len(ret.email) > 254: + reason = __get_length_reason(ret.ascii_email) + elif len(ret.email) > EMAIL_MAX_LENGTH: # If there are more than 254 characters, then the ASCII # form is definitely going to be too long. - reason = " (at least {} character{} too many)".format( - len(ret.email) - 254, - "s" if (len(ret.email) - 254 != 1) else "" - ) + reason = __get_length_reason(ret.email, utf8=True) else: - reason = " (when converted to IDNA ASCII)" - raise EmailSyntaxError("The email address is too long{}.".format(reason)) - if len(ret.email.encode("utf8")) > 254: - if len(ret.email) > 254: + reason = "(when converted to IDNA ASCII)" + raise EmailSyntaxError("The email address is too long {}.".format(reason)) + if len(ret.email.encode("utf8")) > EMAIL_MAX_LENGTH: + if len(ret.email) > EMAIL_MAX_LENGTH: # If there are more than 254 characters, then the UTF-8 # encoding is definitely going to be too long. - reason = " (at least {} character{} too many)".format( - len(ret.email) - 254, - "s" if (len(ret.email) - 254 != 1) else "" - ) + reason = __get_length_reason(ret.email, utf8=True) else: - reason = " (when encoded in bytes)" - raise EmailSyntaxError("The email address is too long{}.".format(reason)) + reason = "(when encoded in bytes)" + raise EmailSyntaxError("The email address is too long {}.".format(reason)) if check_deliverability: # Validate the email address's deliverability and update the # return dict with metadata. - deliverability_info = validate_email_deliverability(ret["domain"], ret["domain_i18n"], timeout) + deliverability_info = validate_email_deliverability( + ret["domain"], ret["domain_i18n"], timeout, dns_resolver + ) if "mx" in deliverability_info: ret.mx = deliverability_info["mx"] ret.mx_fallback_type = deliverability_info["mx-fallback"] @@ -291,11 +312,9 @@ # internationalized, then the UTF-8 encoding may be longer, but # that may not be relevant. We will check the total address length # instead. - if len(local) > 64: - raise EmailSyntaxError("The email address is too long before the @-sign ({} character{} too many).".format( - len(local) - 64, - "s" if (len(local) - 64 != 1) else "" - )) + if len(local) > LOCAL_PART_MAX_LENGTH: + reason = __get_length_reason(local, limit=LOCAL_PART_MAX_LENGTH) + raise EmailSyntaxError("The email address is too long before the @-sign {}.".format(reason)) # Check the local part against the regular expression for the older ASCII requirements. m = re.match(DOT_ATOM_TEXT + "\\Z", local) @@ -400,7 +419,7 @@ # on the assumption that the domain may be transmitted without SMTPUTF8 # as IDNA ASCII. This is also checked by idna.encode, so this exception # is never reached. - if len(ascii_domain) > 255: + if len(ascii_domain) > DOMAIN_MAX_LENGTH: raise EmailSyntaxError("The email address is too long after the @-sign.") # A "dot atom text", per RFC 2822 3.2.4, but using the restricted @@ -434,12 +453,31 @@ } -def validate_email_deliverability(domain, domain_i18n, timeout=DEFAULT_TIMEOUT): +def validate_email_deliverability(domain, domain_i18n, timeout=DEFAULT_TIMEOUT, dns_resolver=None): # Check that the domain resolves to an MX record. If there is no MX record, # try an A or AAAA record which is a deprecated fallback for deliverability. - # Add a trailing period to ensure the domain name is treated as fully qualified. - domain += '.' + # If no dns.resolver.Resolver was given, get dnspython's default resolver. + # Override the default resolver's timeout. This may affect other uses of + # dnspython in this process. + if dns_resolver is None: + dns_resolver = dns.resolver.get_default_resolver() + dns_resolver.lifetime = timeout + + def dns_resolver_resolve_shim(domain, record): + try: + # dns.resolver.Resolver.resolve is new to dnspython 2.x. + # https://dnspython.readthedocs.io/en/latest/resolver-class.html#dns.resolver.Resolver.resolve + return dns_resolver.resolve(domain, record) + except AttributeError: + # dnspython 2.x is only available in Python 3.6 and later. For earlier versions + # of Python, we maintain compatibility with dnspython 1.x which has a + # dnspython.resolver.Resolver.query method instead. The only difference is that + # query may treat the domain as relative and use the system's search domains, + # which we prevent by adding a "." to the domain name to make it absolute. + # dns.resolver.Resolver.query is deprecated in dnspython version 2.x. + # https://dnspython.readthedocs.io/en/latest/resolver-class.html#dns.resolver.Resolver.query + return dns_resolver.query(domain + ".", record) try: # We need a way to check how timeouts are handled in the tests. So we @@ -448,28 +486,23 @@ if getattr(validate_email_deliverability, 'TEST_CHECK_TIMEOUT', False): raise dns.exception.Timeout() - resolver = dns.resolver.get_default_resolver() - - if timeout: - resolver.lifetime = timeout - try: # Try resolving for MX records and get them in sorted priority order. - response = dns.resolver.query(domain, "MX") + response = dns_resolver_resolve_shim(domain, "MX") mtas = sorted([(r.preference, str(r.exchange).rstrip('.')) for r in response]) mx_fallback = None except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): # If there was no MX record, fall back to an A record. try: - response = dns.resolver.query(domain, "A") + response = dns_resolver_resolve_shim(domain, "A") mtas = [(0, str(r)) for r in response] mx_fallback = "A" except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): # If there was no A record, fall back to an AAAA record. try: - response = dns.resolver.query(domain, "AAAA") + response = dns_resolver_resolve_shim(domain, "AAAA") mtas = [(0, str(r)) for r in response] mx_fallback = "AAAA" except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): @@ -504,32 +537,31 @@ import sys import json + def __utf8_input_shim(input_str): + if sys.version_info < (3,): + return input_str.decode("utf-8") + return input_str + + def __utf8_output_shim(output_str): + if sys.version_info < (3,): + return unicode_class(output_str).encode("utf-8") + return output_str + if len(sys.argv) == 1: - # Read lines for STDIN and validate the email address on each line. - allow_smtputf8 = True for line in sys.stdin: + email = __utf8_input_shim(line.strip()) try: - email = line.strip() - if sys.version_info < (3,): - email = email.decode("utf8") # assume utf8 in input - validate_email(email, allow_smtputf8=allow_smtputf8) + validate_email(email) except EmailNotValidError as e: - print(email, e) + print(__utf8_output_shim("{} {}".format(email, e))) else: # Validate the email address passed on the command line. - email = sys.argv[1] - allow_smtputf8 = True - check_deliverability = True - if sys.version_info < (3,): - email = email.decode("utf8") # assume utf8 in input + email = __utf8_input_shim(sys.argv[1]) try: - result = validate_email(email, allow_smtputf8=allow_smtputf8, check_deliverability=check_deliverability) - print(json.dumps(result, indent=2, sort_keys=True, ensure_ascii=False)) + result = validate_email(email) + print(json.dumps(result.as_dict(), indent=2, sort_keys=True, ensure_ascii=False)) except EmailNotValidError as e: - if sys.version_info < (3,): - print(unicode_class(e).encode("utf8")) - else: - print(e) + print(__utf8_output_shim(e)) if __name__ == "__main__": diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-email-validator-1.1.1/setup.cfg new/python-email-validator-1.1.3/setup.cfg --- old/python-email-validator-1.1.1/setup.cfg 2020-05-19 13:15:08.000000000 +0200 +++ new/python-email-validator-1.1.3/setup.cfg 2021-06-12 13:01:42.000000000 +0200 @@ -1,8 +1,42 @@ -[bdist_wheel] -universal = 1 - [metadata] +name = email_validator +version = 1.1.3 +description = A robust email syntax and deliverability validation library for Python 2.x/3.x. +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/JoshData/python-email-validator +author = Joshua Tauberer +author_email = j...@occams.info +license = CC0 (copyright waived) license_file = LICENSE +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Topic :: Software Development :: Libraries :: Python Modules +keywords = email address validator + +[options] +packages = find: +install_requires = + dnspython>=1.15.0 + idna>=2.0.0 +python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* + +[options.entry_points] +console_scripts = + email_validator=email_validator:main + +[bdist_wheel] +universal = 1 [flake8] max-line-length = 120 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-email-validator-1.1.1/setup.py new/python-email-validator-1.1.3/setup.py --- old/python-email-validator-1.1.1/setup.py 2020-05-19 13:15:08.000000000 +0200 +++ new/python-email-validator-1.1.3/setup.py 2021-06-12 13:01:42.000000000 +0200 @@ -1,49 +1,2 @@ -# -*- coding: utf-8 -*- - -from setuptools import setup, find_packages -from codecs import open - -setup( - name='email_validator', - version='1.1.1', - - description='A robust email syntax and deliverability validation library for Python 2.x/3.x.', - long_description=open("README.md", encoding='utf-8').read(), - long_description_content_type="text/markdown", - url='https://github.com/JoshData/python-email-validator', - - author=u'Joshua Tauberer', - author_email=u'j...@occams.info', - license='CC0 (copyright waived)', - - # See https://pypi.org/pypi?%3Aaction=list_classifiers - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication', - - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries :: Python Modules', - - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - ], - - keywords="email address validator", - - packages=find_packages(), - install_requires=[ - "idna>=2.0.0", - "dnspython>=1.15.0"], - - entry_points={ - 'console_scripts': [ - 'email_validator=email_validator:main', - ], - }, -) +from setuptools import setup +setup() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-email-validator-1.1.1/tests/test_main.py new/python-email-validator-1.1.3/tests/test_main.py --- old/python-email-validator-1.1.1/tests/test_main.py 2020-05-19 13:15:08.000000000 +0200 +++ new/python-email-validator-1.1.3/tests/test_main.py 2021-06-12 13:01:42.000000000 +0200 @@ -1,7 +1,11 @@ +from unittest import mock +import dns.resolver import pytest from email_validator import EmailSyntaxError, EmailUndeliverableError, \ validate_email, validate_email_deliverability, \ - ValidatedEmail + caching_resolver, ValidatedEmail +# Let's test main but rename it to be clear +from email_validator import main as validator_main @pytest.mark.parametrize( @@ -250,6 +254,13 @@ assert str(exc_info.value) == error_msg +def test_dict_accessor(): + input_email = "testa...@example.com" + valid_email = validate_email(input_email, check_deliverability=False) + assert isinstance(valid_email.as_dict(), dict) + assert valid_email.as_dict()["original_email"] == input_email + + def test_deliverability_no_records(): assert validate_email_deliverability('example.com', 'example.com') == {'mx': [(0, '')], 'mx-fallback': None} @@ -277,3 +288,85 @@ assert response.get("unknown-deliverability") == "timeout" validate_email('t...@gmail.com') del validate_email_deliverability.TEST_CHECK_TIMEOUT + + +def test_main_single_good_input(monkeypatch, capsys): + import json + test_email = "t...@example.com" + monkeypatch.setattr('sys.argv', ['email_validator', test_email]) + validator_main() + stdout, _ = capsys.readouterr() + output = json.loads(str(stdout)) + assert isinstance(output, dict) + assert validate_email(test_email).original_email == output["original_email"] + + +def test_main_single_bad_input(monkeypatch, capsys): + bad_email = 't...@..com' + monkeypatch.setattr('sys.argv', ['email_validator', bad_email]) + validator_main() + stdout, _ = capsys.readouterr() + assert stdout == 'An email address cannot have a period immediately after the @-sign.\n' + + +def test_main_multi_input(monkeypatch, capsys): + import io + test_cases = ["t...@example.com", "te...@example.com", "test@.com", "test3@.com"] + test_input = io.StringIO("\n".join(test_cases)) + monkeypatch.setattr('sys.stdin', test_input) + monkeypatch.setattr('sys.argv', ['email_validator']) + validator_main() + stdout, _ = capsys.readouterr() + assert test_cases[0] not in stdout + assert test_cases[1] not in stdout + assert test_cases[2] in stdout + assert test_cases[3] in stdout + + +def test_main_input_shim(monkeypatch, capsys): + import json + monkeypatch.setattr('sys.version_info', (2, 7)) + test_email = b"t...@example.com" + monkeypatch.setattr('sys.argv', ['email_validator', test_email]) + validator_main() + stdout, _ = capsys.readouterr() + output = json.loads(str(stdout)) + assert isinstance(output, dict) + assert validate_email(test_email).original_email == output["original_email"] + + +def test_main_output_shim(monkeypatch, capsys): + monkeypatch.setattr('sys.version_info', (2, 7)) + test_email = b"test@.com" + monkeypatch.setattr('sys.argv', ['email_validator', test_email]) + validator_main() + stdout, _ = capsys.readouterr() + + # This looks bad but it has to do with the way python 2.7 prints vs py3 + # The \n is part of the print statement, not part of the string, which is what the b'...' is + # Since we're mocking py 2.7 here instead of actually using 2.7, this was the closest I could get + assert stdout == "b'An email address cannot have a period immediately after the @-sign.'\n" + + +@mock.patch("dns.resolver.LRUCache.put") +def test_validate_email__with_caching_resolver(mocked_put): + dns_resolver = caching_resolver() + validate_email("t...@gmail.com", dns_resolver=dns_resolver) + assert mocked_put.called + + with mock.patch("dns.resolver.LRUCache.get") as mocked_get: + validate_email("t...@gmail.com", dns_resolver=dns_resolver) + assert mocked_get.called + + +@mock.patch("dns.resolver.LRUCache.put") +def test_validate_email__with_configured_resolver(mocked_put): + dns_resolver = dns.resolver.Resolver() + dns_resolver.lifetime = 10 + dns_resolver.cache = dns.resolver.LRUCache(max_size=1000) + validate_email("t...@gmail.com", dns_resolver=dns_resolver) + assert mocked_put.called + + with mock.patch("dns.resolver.LRUCache.get") as mocked_get: + validate_email("t...@gmail.com", dns_resolver=dns_resolver) + assert mocked_get.called