email-validator 2.1.0.post1__tar.gz → 2.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. email_validator-2.1.2/LICENSE +27 -0
  2. {email_validator-2.1.0.post1 → email_validator-2.1.2}/PKG-INFO +11 -7
  3. {email_validator-2.1.0.post1 → email_validator-2.1.2}/README.md +8 -4
  4. {email_validator-2.1.0.post1 → email_validator-2.1.2}/email_validator/__init__.py +0 -2
  5. {email_validator-2.1.0.post1 → email_validator-2.1.2}/email_validator/deliverability.py +36 -10
  6. {email_validator-2.1.0.post1 → email_validator-2.1.2}/email_validator/exceptions_types.py +2 -5
  7. {email_validator-2.1.0.post1 → email_validator-2.1.2}/email_validator/rfc_constants.py +3 -3
  8. {email_validator-2.1.0.post1 → email_validator-2.1.2}/email_validator/syntax.py +41 -35
  9. {email_validator-2.1.0.post1 → email_validator-2.1.2}/email_validator/validate_email.py +2 -2
  10. email_validator-2.1.2/email_validator/version.py +1 -0
  11. {email_validator-2.1.0.post1 → email_validator-2.1.2}/email_validator.egg-info/PKG-INFO +12 -8
  12. {email_validator-2.1.0.post1 → email_validator-2.1.2}/pyproject.toml +1 -1
  13. {email_validator-2.1.0.post1 → email_validator-2.1.2}/setup.cfg +2 -2
  14. {email_validator-2.1.0.post1 → email_validator-2.1.2}/tests/mocked-dns-answers.json +66 -11
  15. {email_validator-2.1.0.post1 → email_validator-2.1.2}/tests/mocked_dns_response.py +4 -3
  16. {email_validator-2.1.0.post1 → email_validator-2.1.2}/tests/test_deliverability.py +25 -22
  17. {email_validator-2.1.0.post1 → email_validator-2.1.2}/tests/test_syntax.py +31 -5
  18. email_validator-2.1.0.post1/LICENSE +0 -121
  19. email_validator-2.1.0.post1/email_validator/version.py +0 -1
  20. {email_validator-2.1.0.post1 → email_validator-2.1.2}/MANIFEST.in +0 -0
  21. {email_validator-2.1.0.post1 → email_validator-2.1.2}/email_validator/__main__.py +0 -0
  22. {email_validator-2.1.0.post1 → email_validator-2.1.2}/email_validator/py.typed +0 -0
  23. {email_validator-2.1.0.post1 → email_validator-2.1.2}/email_validator.egg-info/SOURCES.txt +0 -0
  24. {email_validator-2.1.0.post1 → email_validator-2.1.2}/email_validator.egg-info/dependency_links.txt +0 -0
  25. {email_validator-2.1.0.post1 → email_validator-2.1.2}/email_validator.egg-info/entry_points.txt +0 -0
  26. {email_validator-2.1.0.post1 → email_validator-2.1.2}/email_validator.egg-info/requires.txt +0 -0
  27. {email_validator-2.1.0.post1 → email_validator-2.1.2}/email_validator.egg-info/top_level.txt +0 -0
  28. {email_validator-2.1.0.post1 → email_validator-2.1.2}/setup.py +0 -0
  29. {email_validator-2.1.0.post1 → email_validator-2.1.2}/tests/test_main.py +0 -0
@@ -0,0 +1,27 @@
1
+ This is free and unencumbered software released into the public
2
+ domain.
3
+
4
+ Anyone is free to copy, modify, publish, use, compile, sell, or
5
+ distribute this software, either in source code form or as a
6
+ compiled binary, for any purpose, commercial or non-commercial,
7
+ and by any means.
8
+
9
+ In jurisdictions that recognize copyright laws, the author or
10
+ authors of this software dedicate any and all copyright
11
+ interest in the software to the public domain. We make this
12
+ dedication for the benefit of the public at large and to the
13
+ detriment of our heirs and successors. We intend this
14
+ dedication to be an overt act of relinquishment in perpetuity
15
+ of all present and future rights to this software under
16
+ copyright law.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
22
+ ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
23
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
+ THE SOFTWARE.
26
+
27
+ For more information, please refer to <https://unlicense.org/>
@@ -1,15 +1,15 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: email_validator
3
- Version: 2.1.0.post1
3
+ Version: 2.1.2
4
4
  Summary: A robust email address syntax and deliverability validation library.
5
5
  Home-page: https://github.com/JoshData/python-email-validator
6
6
  Author: Joshua Tauberer
7
7
  Author-email: jt@occams.info
8
- License: CC0 (copyright waived)
8
+ License: Unlicense
9
9
  Keywords: email address validator
10
10
  Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Intended Audience :: Developers
12
- Classifier: License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
12
+ Classifier: License :: OSI Approved :: The Unlicense (Unlicense)
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Programming Language :: Python :: 3.8
15
15
  Classifier: Programming Language :: Python :: 3.9
@@ -37,8 +37,8 @@ necessarily for composing an email message, see below).
37
37
 
38
38
  Key features:
39
39
 
40
- * Checks that an email address has the correct syntax --- good for
41
- registration/login forms or other uses related to identifying users.
40
+ * Checks that an email address has the correct syntax --- great for
41
+ email-based registration/login forms or validing data.
42
42
  * Gives friendly English error messages when validation fails that you
43
43
  can display to end-users.
44
44
  * Checks deliverability (optional): Does the domain name resolve?
@@ -233,7 +233,7 @@ local parts, a wider range of Unicode characters are allowed.
233
233
 
234
234
  A surprisingly large number of Unicode characters are not safe to display,
235
235
  especially when the email address is concatenated with other text, so this
236
- library tries to protect you by not permitting resvered, non-, private use,
236
+ library tries to protect you by not permitting reserved, non-, private use,
237
237
  formatting (which can be used to alter the display order of characters),
238
238
  whitespace, and control characters, and combining characters
239
239
  as the first character of the local part and the domain name (so that they
@@ -448,7 +448,6 @@ or likely to cause trouble:
448
448
  No one uses these forms anymore, and I can't think of any reason why anyone
449
449
  using this library would need to accept them.
450
450
 
451
-
452
451
  Testing
453
452
  -------
454
453
 
@@ -480,3 +479,8 @@ git tag v$(cat email_validator/version.py | sed "s/.* = //" | sed 's/"//g')
480
479
  git push --tags
481
480
  ./release_to_pypi.sh
482
481
  ```
482
+
483
+ License
484
+ -------
485
+
486
+ This project is free of any copyright restrictions per the [Unlicense](https://unlicense.org/). (Prior to Feb. 4, 2024, the project was made available under the terms of the [CC0 1.0 Universal public domain dedication](http://creativecommons.org/publicdomain/zero/1.0/).) See [LICENSE](LICENSE) and [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -12,8 +12,8 @@ necessarily for composing an email message, see below).
12
12
 
13
13
  Key features:
14
14
 
15
- * Checks that an email address has the correct syntax --- good for
16
- registration/login forms or other uses related to identifying users.
15
+ * Checks that an email address has the correct syntax --- great for
16
+ email-based registration/login forms or validing data.
17
17
  * Gives friendly English error messages when validation fails that you
18
18
  can display to end-users.
19
19
  * Checks deliverability (optional): Does the domain name resolve?
@@ -208,7 +208,7 @@ local parts, a wider range of Unicode characters are allowed.
208
208
 
209
209
  A surprisingly large number of Unicode characters are not safe to display,
210
210
  especially when the email address is concatenated with other text, so this
211
- library tries to protect you by not permitting resvered, non-, private use,
211
+ library tries to protect you by not permitting reserved, non-, private use,
212
212
  formatting (which can be used to alter the display order of characters),
213
213
  whitespace, and control characters, and combining characters
214
214
  as the first character of the local part and the domain name (so that they
@@ -423,7 +423,6 @@ or likely to cause trouble:
423
423
  No one uses these forms anymore, and I can't think of any reason why anyone
424
424
  using this library would need to accept them.
425
425
 
426
-
427
426
  Testing
428
427
  -------
429
428
 
@@ -455,3 +454,8 @@ git tag v$(cat email_validator/version.py | sed "s/.* = //" | sed 's/"//g')
455
454
  git push --tags
456
455
  ./release_to_pypi.sh
457
456
  ```
457
+
458
+ License
459
+ -------
460
+
461
+ This project is free of any copyright restrictions per the [Unlicense](https://unlicense.org/). (Prior to Feb. 4, 2024, the project was made available under the terms of the [CC0 1.0 Universal public domain dedication](http://creativecommons.org/publicdomain/zero/1.0/).) See [LICENSE](LICENSE) and [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -1,5 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
-
3
1
  # Export the main method, helper methods, and the public data types.
4
2
  from .exceptions_types import ValidatedEmail, EmailNotValidError, \
5
3
  EmailSyntaxError, EmailUndeliverableError
@@ -1,16 +1,18 @@
1
1
  from typing import Optional, Any, Dict
2
2
 
3
+ import ipaddress
4
+
3
5
  from .exceptions_types import EmailUndeliverableError
4
6
 
5
7
  import dns.resolver
6
8
  import dns.exception
7
9
 
8
10
 
9
- def caching_resolver(*, timeout: Optional[int] = None, cache=None):
11
+ def caching_resolver(*, timeout: Optional[int] = None, cache=None, dns_resolver=None):
10
12
  if timeout is None:
11
13
  from . import DEFAULT_TIMEOUT
12
14
  timeout = DEFAULT_TIMEOUT
13
- resolver = dns.resolver.Resolver()
15
+ resolver = dns_resolver or dns.resolver.Resolver()
14
16
  resolver.cache = cache or dns.resolver.LRUCache() # type: ignore
15
17
  resolver.lifetime = timeout # type: ignore # timeout, in seconds
16
18
  return resolver
@@ -57,10 +59,30 @@ def validate_email_deliverability(domain: str, domain_i18n: str, timeout: Option
57
59
  deliverability_info["mx_fallback_type"] = None
58
60
 
59
61
  except dns.resolver.NoAnswer:
60
- # If there was no MX record, fall back to an A record. (RFC 5321 Section 5)
62
+ # If there was no MX record, fall back to an A or AAA record
63
+ # (RFC 5321 Section 5). Check A first since it's more common.
64
+
65
+ # If the A/AAAA response has no Globally Reachable IP address,
66
+ # treat the response as if it were NoAnswer, i.e., the following
67
+ # address types are not allowed fallbacks: Private-Use, Loopback,
68
+ # Link-Local, and some other obscure ranges. See
69
+ # https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
70
+ # https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
71
+ # (Issue #134.)
72
+ def is_global_addr(ipaddr):
73
+ try:
74
+ ipaddr = ipaddress.ip_address(ipaddr)
75
+ except ValueError:
76
+ return False
77
+ return ipaddr.is_global
78
+
61
79
  try:
62
80
  response = dns_resolver.resolve(domain, "A")
63
- deliverability_info["mx"] = [(0, str(r)) for r in response]
81
+
82
+ if not any(is_global_addr(r.address) for r in response):
83
+ raise dns.resolver.NoAnswer # fall back to AAAA
84
+
85
+ deliverability_info["mx"] = [(0, domain)]
64
86
  deliverability_info["mx_fallback_type"] = "A"
65
87
 
66
88
  except dns.resolver.NoAnswer:
@@ -69,15 +91,19 @@ def validate_email_deliverability(domain: str, domain_i18n: str, timeout: Option
69
91
  # (It's unclear if SMTP servers actually do this.)
70
92
  try:
71
93
  response = dns_resolver.resolve(domain, "AAAA")
72
- deliverability_info["mx"] = [(0, str(r)) for r in response]
94
+
95
+ if not any(is_global_addr(r.address) for r in response):
96
+ raise dns.resolver.NoAnswer
97
+
98
+ deliverability_info["mx"] = [(0, domain)]
73
99
  deliverability_info["mx_fallback_type"] = "AAAA"
74
100
 
75
- except dns.resolver.NoAnswer:
101
+ except dns.resolver.NoAnswer as e:
76
102
  # If there was no MX, A, or AAAA record, then mail to
77
103
  # this domain is not deliverable, although the domain
78
104
  # name has other records (otherwise NXDOMAIN would
79
105
  # have been raised).
80
- raise EmailUndeliverableError(f"The domain name {domain_i18n} does not accept email.")
106
+ raise EmailUndeliverableError(f"The domain name {domain_i18n} does not accept email.") from e
81
107
 
82
108
  # Check for a SPF (RFC 7208) reject-all record ("v=spf1 -all") which indicates
83
109
  # no emails are sent from this domain (similar to a Null MX record
@@ -96,10 +122,10 @@ def validate_email_deliverability(domain: str, domain_i18n: str, timeout: Option
96
122
  # No TXT records means there is no SPF policy, so we cannot take any action.
97
123
  pass
98
124
 
99
- except dns.resolver.NXDOMAIN:
125
+ except dns.resolver.NXDOMAIN as e:
100
126
  # The domain name does not exist --- there are no records of any sort
101
127
  # for the domain name.
102
- raise EmailUndeliverableError(f"The domain name {domain_i18n} does not exist.")
128
+ raise EmailUndeliverableError(f"The domain name {domain_i18n} does not exist.") from e
103
129
 
104
130
  except dns.resolver.NoNameservers:
105
131
  # All nameservers failed to answer the query. This might be a problem
@@ -122,6 +148,6 @@ def validate_email_deliverability(domain: str, domain_i18n: str, timeout: Option
122
148
  # Unhandled conditions should not propagate.
123
149
  raise EmailUndeliverableError(
124
150
  "There was an error while checking if the domain name in the email address is deliverable: " + str(e)
125
- )
151
+ ) from e
126
152
 
127
153
  return deliverability_info
@@ -17,7 +17,7 @@ class EmailUndeliverableError(EmailNotValidError):
17
17
  pass
18
18
 
19
19
 
20
- class ValidatedEmail(object):
20
+ class ValidatedEmail:
21
21
  """The validate_email function returns objects of this type holding the normalized form of the email address
22
22
  and other information."""
23
23
 
@@ -80,7 +80,6 @@ class ValidatedEmail(object):
80
80
 
81
81
  @property
82
82
  def email(self):
83
- import warnings
84
83
  warnings.warn("ValidatedEmail.email is deprecated and will be removed, use ValidatedEmail.normalized instead", DeprecationWarning)
85
84
  return self.normalized
86
85
 
@@ -126,9 +125,7 @@ class ValidatedEmail(object):
126
125
  """This helps producing the README."""
127
126
  def as_constructor(self):
128
127
  return "ValidatedEmail(" \
129
- + ",".join("\n {}={}".format(
130
- key,
131
- repr(getattr(self, key)))
128
+ + ",".join(f"\n {key}={repr(getattr(self, key))}"
132
129
  for key in ('normalized', 'local_part', 'domain',
133
130
  'ascii_email', 'ascii_local_part', 'ascii_domain',
134
131
  'smtputf8', 'mx', 'mx_fallback_type')
@@ -12,7 +12,7 @@ DOT_ATOM_TEXT = re.compile('[' + ATEXT + ']+(?:\\.[' + ATEXT + r']+)*\Z')
12
12
  # addresses to also include three specific ranges of UTF8 defined in
13
13
  # RFC 3629 section 4, which appear to be the Unicode code points from
14
14
  # U+0080 to U+10FFFF.
15
- ATEXT_INTL = ATEXT + u"\u0080-\U0010FFFF"
15
+ ATEXT_INTL = ATEXT + "\u0080-\U0010FFFF"
16
16
  ATEXT_INTL_RE = re.compile('[.' + ATEXT_INTL + ']') # ATEXT_INTL plus dots
17
17
  DOT_ATOM_TEXT_INTL = re.compile('[' + ATEXT_INTL + ']+(?:\\.[' + ATEXT_INTL + r']+)*\Z')
18
18
 
@@ -42,11 +42,11 @@ QTEXT_INTL = re.compile(r"[\u0020-\u007E\u0080-\U0010FFFF]")
42
42
  EMAIL_MAX_LENGTH = 254
43
43
  LOCAL_PART_MAX_LENGTH = 64
44
44
  DNS_LABEL_LENGTH_LIMIT = 63 # in "octets", RFC 1035 2.3.1
45
- DOMAIN_MAX_LENGTH = 255 # in "octets", RFC 1035 2.3.4 and RFC 5321 4.5.3.1.2
45
+ DOMAIN_MAX_LENGTH = 253 # in "octets" as transmitted, RFC 1035 2.3.4 and RFC 5321 4.5.3.1.2, and see https://stackoverflow.com/questions/32290167/what-is-the-maximum-length-of-a-dns-name
46
46
 
47
47
  # RFC 2142
48
48
  CASE_INSENSITIVE_MAILBOX_NAMES = [
49
- 'info', 'marking', 'sales', 'support', # section 3
49
+ 'info', 'marketing', 'sales', 'support', # section 3
50
50
  'abuse', 'noc', 'security', # section 4
51
51
  'postmaster', 'hostmaster', 'usenet', 'news', 'webmaster', 'www', 'uucp', 'ftp', # section 5
52
52
  ]
@@ -26,7 +26,6 @@ def split_email(email):
26
26
  # Since backslash-escaping is no longer needed because
27
27
  # the quotes are removed, remove backslash-escaping
28
28
  # to return in the normalized form.
29
- import re
30
29
  local_part = re.sub(r"\\(.)", "\\1", local_part)
31
30
 
32
31
  return local_part, domain_part, True
@@ -72,14 +71,14 @@ def validate_email_local_part(local: str, allow_smtputf8: bool = True, allow_emp
72
71
  if len(local) == 0:
73
72
  if not allow_empty_local:
74
73
  raise EmailSyntaxError("There must be something before the @-sign.")
75
- else:
76
- # The caller allows an empty local part. Useful for validating certain
77
- # Postfix aliases.
78
- return {
79
- "local_part": local,
80
- "ascii_local_part": local,
81
- "smtputf8": False,
82
- }
74
+
75
+ # The caller allows an empty local part. Useful for validating certain
76
+ # Postfix aliases.
77
+ return {
78
+ "local_part": local,
79
+ "ascii_local_part": local,
80
+ "smtputf8": False,
81
+ }
83
82
 
84
83
  # Check the length of the local part by counting characters.
85
84
  # (RFC 5321 4.5.3.1.1)
@@ -124,11 +123,11 @@ def validate_email_local_part(local: str, allow_smtputf8: bool = True, allow_emp
124
123
  # Check for invalid characters against the non-internationalized
125
124
  # permitted character set.
126
125
  # (RFC 5322 3.2.3)
127
- bad_chars = set(
126
+ bad_chars = {
128
127
  safe_character_display(c)
129
128
  for c in local
130
129
  if not ATEXT_RE.match(c)
131
- )
130
+ }
132
131
  if bad_chars:
133
132
  raise EmailSyntaxError("Internationalized characters before the @-sign are not supported: " + ", ".join(sorted(bad_chars)) + ".")
134
133
 
@@ -148,20 +147,20 @@ def validate_email_local_part(local: str, allow_smtputf8: bool = True, allow_emp
148
147
  # (RFC 5321 4.1.2. RFC 5322 lists additional permitted *obsolete*
149
148
  # characters which are *not* allowed here. RFC 6531 section 3.3
150
149
  # extends the range to UTF8 strings.)
151
- bad_chars = set(
150
+ bad_chars = {
152
151
  safe_character_display(c)
153
152
  for c in local
154
153
  if not QTEXT_INTL.match(c)
155
- )
154
+ }
156
155
  if bad_chars:
157
156
  raise EmailSyntaxError("The email address contains invalid characters in quotes before the @-sign: " + ", ".join(sorted(bad_chars)) + ".")
158
157
 
159
158
  # See if any characters are outside of the ASCII range.
160
- bad_chars = set(
159
+ bad_chars = {
161
160
  safe_character_display(c)
162
161
  for c in local
163
162
  if not (32 <= ord(c) <= 126)
164
- )
163
+ }
165
164
  if bad_chars:
166
165
  requires_smtputf8 = True
167
166
 
@@ -191,8 +190,8 @@ def validate_email_local_part(local: str, allow_smtputf8: bool = True, allow_emp
191
190
  # want to have an unhandled exception later.
192
191
  try:
193
192
  local.encode("utf8")
194
- except ValueError:
195
- raise EmailSyntaxError("The email address contains an invalid character.")
193
+ except ValueError as e:
194
+ raise EmailSyntaxError("The email address contains an invalid character.") from e
196
195
 
197
196
  # If this address passes only by the quoted string form, re-quote it
198
197
  # and backslash-escape quotes and backslashes (removing any unnecessary
@@ -213,11 +212,11 @@ def validate_email_local_part(local: str, allow_smtputf8: bool = True, allow_emp
213
212
 
214
213
  # Check for invalid characters.
215
214
  # (RFC 5322 3.2.3, plus RFC 6531 3.3)
216
- bad_chars = set(
215
+ bad_chars = {
217
216
  safe_character_display(c)
218
217
  for c in local
219
218
  if not ATEXT_INTL_RE.match(c)
220
- )
219
+ }
221
220
  if bad_chars:
222
221
  raise EmailSyntaxError("The email address contains invalid characters before the @-sign: " + ", ".join(sorted(bad_chars)) + ".")
223
222
 
@@ -264,7 +263,7 @@ def check_unsafe_chars(s, allow_space=False):
264
263
  # as far as I can tell, but they violate the spirit of the non-internationalized
265
264
  # specification that email addresses do not contain line breaks when not quoted.
266
265
  bad_chars.add(c)
267
- elif category[0] in ("C", "Z"):
266
+ elif category[0] == "C":
268
267
  # Control, format, surrogate, private use, and unassigned code points (C)
269
268
  # are all unsafe in various ways. Control and format characters can affect
270
269
  # text rendering if the email address is concatenated with other text.
@@ -306,11 +305,11 @@ def validate_email_domain_name(domain, test_environment=False, globally_delivera
306
305
 
307
306
  # Check for invalid characters before normalization.
308
307
  # (RFC 952 plus RFC 6531 section 3.3 for internationalized addresses)
309
- bad_chars = set(
308
+ bad_chars = {
310
309
  safe_character_display(c)
311
310
  for c in domain
312
311
  if not ATEXT_HOSTNAME_INTL.match(c)
313
- )
312
+ }
314
313
  if bad_chars:
315
314
  raise EmailSyntaxError("The part after the @-sign contains invalid characters: " + ", ".join(sorted(bad_chars)) + ".")
316
315
 
@@ -322,15 +321,17 @@ def validate_email_domain_name(domain, test_environment=False, globally_delivera
322
321
 
323
322
  # Perform UTS-46 normalization, which includes casefolding, NFC normalization,
324
323
  # and converting all label separators (the period/full stop, fullwidth full stop,
325
- # ideographic full stop, and halfwidth ideographic full stop) to basic periods.
324
+ # ideographic full stop, and halfwidth ideographic full stop) to regular dots.
326
325
  # It will also raise an exception if there is an invalid character in the input,
327
- # such as "⒈" which is invalid because it would expand to include a period.
326
+ # such as "⒈" which is invalid because it would expand to include a dot.
327
+ # Since several characters are normalized to a dot, this has to come before
328
+ # checks related to dots, like check_dot_atom which comes next.
328
329
  try:
329
330
  domain = idna.uts46_remap(domain, std3_rules=False, transitional=False)
330
331
  except idna.IDNAError as e:
331
- raise EmailSyntaxError(f"The part after the @-sign contains invalid characters ({e}).")
332
+ raise EmailSyntaxError(f"The part after the @-sign contains invalid characters ({e}).") from e
332
333
 
333
- # The domain part is made up period-separated "labels." Each label must
334
+ # The domain part is made up dot-separated "labels." Each label must
334
335
  # have at least one character and cannot start or end with dashes, which
335
336
  # means there are some surprising restrictions on periods and dashes.
336
337
  # Check that before we do IDNA encoding because the IDNA library gives
@@ -362,6 +363,8 @@ def validate_email_domain_name(domain, test_environment=False, globally_delivera
362
363
  # For ASCII-only domains, the transformation does nothing and is safe to
363
364
  # apply. However, to ensure we don't rely on the idna library for basic
364
365
  # syntax checks, we don't use it if it's not needed.
366
+ #
367
+ # uts46 is off here because it is handled above.
365
368
  try:
366
369
  ascii_domain = idna.encode(domain, uts46=False).decode("ascii")
367
370
  except idna.IDNAError as e:
@@ -370,8 +373,11 @@ def validate_email_domain_name(domain, test_environment=False, globally_delivera
370
373
  # the length check is applied to a string that is different from the
371
374
  # one the user supplied. Also I'm not sure if the length check applies
372
375
  # to the internationalized form, the IDNA ASCII form, or even both!
373
- raise EmailSyntaxError("The email address is too long after the @-sign.")
374
- raise EmailSyntaxError(f"The part after the @-sign contains invalid characters ({e}).")
376
+ raise EmailSyntaxError("The email address is too long after the @-sign.") from e
377
+
378
+ # Other errors seem to not be possible because the call to idna.uts46_remap
379
+ # would have already raised them.
380
+ raise EmailSyntaxError(f"The part after the @-sign contains invalid characters ({e}).") from e
375
381
 
376
382
  # Check the syntax of the string returned by idna.encode.
377
383
  # It should never fail.
@@ -433,15 +439,15 @@ def validate_email_domain_name(domain, test_environment=False, globally_delivera
433
439
  try:
434
440
  domain_i18n = idna.decode(ascii_domain.encode('ascii'))
435
441
  except idna.IDNAError as e:
436
- raise EmailSyntaxError(f"The part after the @-sign is not valid IDNA ({e}).")
442
+ raise EmailSyntaxError(f"The part after the @-sign is not valid IDNA ({e}).") from e
437
443
 
438
444
  # Check for invalid characters after normalization. These
439
445
  # should never arise. See the similar checks above.
440
- bad_chars = set(
446
+ bad_chars = {
441
447
  safe_character_display(c)
442
448
  for c in domain
443
449
  if not ATEXT_HOSTNAME_INTL.match(c)
444
- )
450
+ }
445
451
  if bad_chars:
446
452
  raise EmailSyntaxError("The part after the @-sign contains invalid characters: " + ", ".join(sorted(bad_chars)) + ".")
447
453
  check_unsafe_chars(domain)
@@ -511,7 +517,7 @@ def validate_email_domain_literal(domain_literal):
511
517
  try:
512
518
  addr = ipaddress.IPv4Address(domain_literal)
513
519
  except ValueError as e:
514
- raise EmailSyntaxError(f"The address in brackets after the @-sign is not valid: It is not an IPv4 address ({e}) or is missing an address literal tag.")
520
+ raise EmailSyntaxError(f"The address in brackets after the @-sign is not valid: It is not an IPv4 address ({e}) or is missing an address literal tag.") from e
515
521
 
516
522
  # Return the IPv4Address object and the domain back unchanged.
517
523
  return {
@@ -524,7 +530,7 @@ def validate_email_domain_literal(domain_literal):
524
530
  try:
525
531
  addr = ipaddress.IPv6Address(domain_literal[5:])
526
532
  except ValueError as e:
527
- raise EmailSyntaxError(f"The IPv6 address in brackets after the @-sign is not valid ({e}).")
533
+ raise EmailSyntaxError(f"The IPv6 address in brackets after the @-sign is not valid ({e}).") from e
528
534
 
529
535
  # Return the IPv6Address object and construct a normalized
530
536
  # domain literal.
@@ -544,11 +550,11 @@ def validate_email_domain_literal(domain_literal):
544
550
 
545
551
  # Check for permitted ASCII characters. This actually doesn't matter
546
552
  # since there will be an exception after anyway.
547
- bad_chars = set(
553
+ bad_chars = {
548
554
  safe_character_display(c)
549
555
  for c in domain_literal
550
556
  if not DOMAIN_LITERAL_CHARS.match(c)
551
- )
557
+ }
552
558
  if bad_chars:
553
559
  raise EmailSyntaxError("The part after the @-sign contains invalid characters in brackets: " + ", ".join(sorted(bad_chars)) + ".")
554
560
 
@@ -49,8 +49,8 @@ def validate_email(
49
49
  if not isinstance(email, str):
50
50
  try:
51
51
  email = email.decode("ascii")
52
- except ValueError:
53
- raise EmailSyntaxError("The email address is not valid ASCII.")
52
+ except ValueError as e:
53
+ raise EmailSyntaxError("The email address is not valid ASCII.") from e
54
54
 
55
55
  # Split the address into the local part (before the @-sign)
56
56
  # and the domain part (after the @-sign). Normally, there
@@ -0,0 +1 @@
1
+ __version__ = "2.1.2"
@@ -1,15 +1,15 @@
1
1
  Metadata-Version: 2.1
2
- Name: email-validator
3
- Version: 2.1.0.post1
2
+ Name: email_validator
3
+ Version: 2.1.2
4
4
  Summary: A robust email address syntax and deliverability validation library.
5
5
  Home-page: https://github.com/JoshData/python-email-validator
6
6
  Author: Joshua Tauberer
7
7
  Author-email: jt@occams.info
8
- License: CC0 (copyright waived)
8
+ License: Unlicense
9
9
  Keywords: email address validator
10
10
  Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Intended Audience :: Developers
12
- Classifier: License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
12
+ Classifier: License :: OSI Approved :: The Unlicense (Unlicense)
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Programming Language :: Python :: 3.8
15
15
  Classifier: Programming Language :: Python :: 3.9
@@ -37,8 +37,8 @@ necessarily for composing an email message, see below).
37
37
 
38
38
  Key features:
39
39
 
40
- * Checks that an email address has the correct syntax --- good for
41
- registration/login forms or other uses related to identifying users.
40
+ * Checks that an email address has the correct syntax --- great for
41
+ email-based registration/login forms or validing data.
42
42
  * Gives friendly English error messages when validation fails that you
43
43
  can display to end-users.
44
44
  * Checks deliverability (optional): Does the domain name resolve?
@@ -233,7 +233,7 @@ local parts, a wider range of Unicode characters are allowed.
233
233
 
234
234
  A surprisingly large number of Unicode characters are not safe to display,
235
235
  especially when the email address is concatenated with other text, so this
236
- library tries to protect you by not permitting resvered, non-, private use,
236
+ library tries to protect you by not permitting reserved, non-, private use,
237
237
  formatting (which can be used to alter the display order of characters),
238
238
  whitespace, and control characters, and combining characters
239
239
  as the first character of the local part and the domain name (so that they
@@ -448,7 +448,6 @@ or likely to cause trouble:
448
448
  No one uses these forms anymore, and I can't think of any reason why anyone
449
449
  using this library would need to accept them.
450
450
 
451
-
452
451
  Testing
453
452
  -------
454
453
 
@@ -480,3 +479,8 @@ git tag v$(cat email_validator/version.py | sed "s/.* = //" | sed 's/"//g')
480
479
  git push --tags
481
480
  ./release_to_pypi.sh
482
481
  ```
482
+
483
+ License
484
+ -------
485
+
486
+ This project is free of any copyright restrictions per the [Unlicense](https://unlicense.org/). (Prior to Feb. 4, 2024, the project was made available under the terms of the [CC0 1.0 Universal public domain dedication](http://creativecommons.org/publicdomain/zero/1.0/).) See [LICENSE](LICENSE) and [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -1,4 +1,4 @@
1
1
  [tool.pytest.ini_options]
2
2
  markers = [
3
3
  "network: marks tests as requiring Internet access",
4
- ]
4
+ ]
@@ -7,12 +7,12 @@ long_description_content_type = text/markdown
7
7
  url = https://github.com/JoshData/python-email-validator
8
8
  author = Joshua Tauberer
9
9
  author_email = jt@occams.info
10
- license = CC0 (copyright waived)
10
+ license = Unlicense
11
11
  license_files = LICENSE
12
12
  classifiers =
13
13
  Development Status :: 5 - Production/Stable
14
14
  Intended Audience :: Developers
15
- License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
15
+ License :: OSI Approved :: The Unlicense (Unlicense)
16
16
  Programming Language :: Python :: 3
17
17
  Programming Language :: Python :: 3.8
18
18
  Programming Language :: Python :: 3.9
@@ -7,20 +7,41 @@
7
7
  },
8
8
  "answer": [
9
9
  "10 alt1.gmail-smtp-in.l.google.com.",
10
- "30 alt3.gmail-smtp-in.l.google.com.",
11
- "5 gmail-smtp-in.l.google.com.",
12
10
  "20 alt2.gmail-smtp-in.l.google.com.",
13
- "40 alt4.gmail-smtp-in.l.google.com."
11
+ "30 alt3.gmail-smtp-in.l.google.com.",
12
+ "40 alt4.gmail-smtp-in.l.google.com.",
13
+ "5 gmail-smtp-in.l.google.com."
14
14
  ]
15
15
  },
16
16
  {
17
17
  "query": {
18
- "name": "xkxufoekjvjfjeodlfmdfjcu.com",
18
+ "name": "pages.github.com",
19
19
  "type": "MX",
20
20
  "class": "IN"
21
21
  },
22
22
  "answer": []
23
23
  },
24
+ {
25
+ "query": {
26
+ "name": "pages.github.com",
27
+ "type": "A",
28
+ "class": "IN"
29
+ },
30
+ "answer": [
31
+ "185.199.108.153",
32
+ "185.199.109.153",
33
+ "185.199.110.153",
34
+ "185.199.111.153"
35
+ ]
36
+ },
37
+ {
38
+ "query": {
39
+ "name": "pages.github.com",
40
+ "type": "TXT",
41
+ "class": "IN"
42
+ },
43
+ "answer": []
44
+ },
24
45
  {
25
46
  "query": {
26
47
  "name": "xkxufoekjvjfjeodlfmdfjcu.com",
@@ -39,6 +60,32 @@
39
60
  "0 ."
40
61
  ]
41
62
  },
63
+ {
64
+ "query": {
65
+ "name": "g.mail.com",
66
+ "type": "MX",
67
+ "class": "IN"
68
+ },
69
+ "answer": []
70
+ },
71
+ {
72
+ "query": {
73
+ "name": "g.mail.com",
74
+ "type": "A",
75
+ "class": "IN"
76
+ },
77
+ "answer": []
78
+ },
79
+ {
80
+ "query": {
81
+ "name": "g.mail.com",
82
+ "type": "AAAA",
83
+ "class": "IN"
84
+ },
85
+ "answer": [
86
+ "::1"
87
+ ]
88
+ },
42
89
  {
43
90
  "query": {
44
91
  "name": "nellis.af.mil",
@@ -64,13 +111,13 @@
64
111
  "class": "IN"
65
112
  },
66
113
  "answer": [
67
- "\"v=spf1 -all\"",
68
- "\"MS=ms47108184\""
114
+ "\"MS=ms47108184\"",
115
+ "\"v=spf1 -all\""
69
116
  ]
70
117
  },
71
118
  {
72
119
  "query": {
73
- "name": "mail.example",
120
+ "name": "justtxt.joshdata.me",
74
121
  "type": "MX",
75
122
  "class": "IN"
76
123
  },
@@ -78,16 +125,24 @@
78
125
  },
79
126
  {
80
127
  "query": {
81
- "name": "mail.example",
82
- "type": "ANY",
128
+ "name": "justtxt.joshdata.me",
129
+ "type": "A",
83
130
  "class": "IN"
84
131
  },
85
132
  "answer": []
86
133
  },
87
134
  {
88
135
  "query": {
89
- "name": "mail.example.com",
90
- "type": "MX",
136
+ "name": "justtxt.joshdata.me",
137
+ "type": "AAAA",
138
+ "class": "IN"
139
+ },
140
+ "answer": []
141
+ },
142
+ {
143
+ "query": {
144
+ "name": "mail.example",
145
+ "type": "ANY",
91
146
  "class": "IN"
92
147
  },
93
148
  "answer": []
@@ -32,7 +32,8 @@ class MockedDnsResponseData:
32
32
 
33
33
  # Return a new dns.resolver.Resolver configured for caching
34
34
  # using the singleton instance.
35
- return caching_resolver(cache=MockedDnsResponseData.INSTANCE)
35
+ dns_resolver = dns.resolver.Resolver(configure=BUILD_MOCKED_DNS_RESPONSE_DATA)
36
+ return caching_resolver(cache=MockedDnsResponseData.INSTANCE, dns_resolver=dns_resolver)
36
37
 
37
38
  def __init__(self):
38
39
  self.data = {}
@@ -72,10 +73,10 @@ class MockedDnsResponseData:
72
73
  "type": dns.rdatatype.to_text(key[1]),
73
74
  "class": dns.rdataclass.to_text(key[2]),
74
75
  },
75
- "answer": [
76
+ "answer": sorted([
76
77
  rr.to_text()
77
78
  for rr in value
78
- ]
79
+ ])
79
80
  }
80
81
  for key, value in self.data.items()
81
82
  ]
@@ -10,30 +10,33 @@ from mocked_dns_response import MockedDnsResponseData, MockedDnsResponseDataClea
10
10
  RESOLVER = MockedDnsResponseData.create_resolver()
11
11
 
12
12
 
13
- def test_deliverability_found():
14
- response = validate_email_deliverability('gmail.com', 'gmail.com', dns_resolver=RESOLVER)
15
- assert response.keys() == {'mx', 'mx_fallback_type'}
16
- assert response['mx_fallback_type'] is None
17
- assert len(response['mx']) > 1
18
- assert len(response['mx'][0]) == 2
19
- assert isinstance(response['mx'][0][0], int)
20
- assert response['mx'][0][1].endswith('.com')
21
-
22
-
23
- def test_deliverability_fails():
24
- # No MX record.
25
- domain = 'xkxufoekjvjfjeodlfmdfjcu.com'
26
- with pytest.raises(EmailUndeliverableError, match=f'The domain name {domain} does not exist'):
27
- validate_email_deliverability(domain, domain, dns_resolver=RESOLVER)
13
+ @pytest.mark.parametrize(
14
+ 'domain,expected_response',
15
+ [
16
+ ('gmail.com', {'mx': [(5, 'gmail-smtp-in.l.google.com'), (10, 'alt1.gmail-smtp-in.l.google.com'), (20, 'alt2.gmail-smtp-in.l.google.com'), (30, 'alt3.gmail-smtp-in.l.google.com'), (40, 'alt4.gmail-smtp-in.l.google.com')], 'mx_fallback_type': None}),
17
+ ('pages.github.com', {'mx': [(0, 'pages.github.com')], 'mx_fallback_type': 'A'}),
18
+ ],
19
+ )
20
+ def test_deliverability_found(domain, expected_response):
21
+ response = validate_email_deliverability(domain, domain, dns_resolver=RESOLVER)
22
+ assert response == expected_response
28
23
 
29
- # Null MX record.
30
- domain = 'example.com'
31
- with pytest.raises(EmailUndeliverableError, match=f'The domain name {domain} does not accept email'):
32
- validate_email_deliverability(domain, domain, dns_resolver=RESOLVER)
33
24
 
34
- # No MX record, A record fallback, reject-all SPF record.
35
- domain = 'nellis.af.mil'
36
- with pytest.raises(EmailUndeliverableError, match=f'The domain name {domain} does not send email'):
25
+ @pytest.mark.parametrize(
26
+ 'domain,error',
27
+ [
28
+ ('xkxufoekjvjfjeodlfmdfjcu.com', 'The domain name {domain} does not exist'),
29
+ ('example.com', 'The domain name {domain} does not accept email'), # Null MX record
30
+ ('g.mail.com', 'The domain name {domain} does not accept email'), # No MX record but invalid AAAA record fallback (issue #134)
31
+ ('nellis.af.mil', 'The domain name {domain} does not send email'), # No MX record, A record fallback, reject-all SPF record.
32
+
33
+ # No MX or A/AAAA records, but some other DNS records must
34
+ # exist such that the response is NOANSWER instead of NXDOMAIN.
35
+ ('justtxt.joshdata.me', 'The domain name {domain} does not accept email'),
36
+ ],
37
+ )
38
+ def test_deliverability_fails(domain, error):
39
+ with pytest.raises(EmailUndeliverableError, match=error.format(domain=domain)):
37
40
  validate_email_deliverability(domain, domain, dns_resolver=RESOLVER)
38
41
 
39
42
 
@@ -68,15 +68,41 @@ from email_validator import EmailSyntaxError, \
68
68
  ascii_email='jeff@xn--fiqq24b10vi0d.tw',
69
69
  ),
70
70
  ),
71
+ (
72
+ '"quoted local part"@example.org',
73
+ ValidatedEmail(
74
+ local_part='"quoted local part"',
75
+ ascii_local_part='"quoted local part"',
76
+ smtputf8=False,
77
+ ascii_domain='example.org',
78
+ domain='example.org',
79
+ normalized='"quoted local part"@example.org',
80
+ ascii_email='"quoted local part"@example.org'
81
+ ),
82
+ ),
83
+ (
84
+ '"de-quoted.local.part"@example.org',
85
+ ValidatedEmail(
86
+ local_part='de-quoted.local.part',
87
+ ascii_local_part='de-quoted.local.part',
88
+ smtputf8=False,
89
+ ascii_domain='example.org',
90
+ domain='example.org',
91
+ normalized='de-quoted.local.part@example.org',
92
+ ascii_email='de-quoted.local.part@example.org'
93
+ ),
94
+ ),
71
95
  ],
72
96
  )
73
97
  def test_email_valid(email_input, output):
74
98
  # These addresses do not require SMTPUTF8. See test_email_valid_intl_local_part
75
99
  # for addresses that are valid but require SMTPUTF8. Check that it passes with
76
100
  # allow_smtput8 both on and off.
77
- emailinfo = validate_email(email_input, check_deliverability=False, allow_smtputf8=False)
101
+ emailinfo = validate_email(email_input, check_deliverability=False, allow_smtputf8=False,
102
+ allow_quoted_local=True)
78
103
  assert emailinfo == output
79
- assert validate_email(email_input, check_deliverability=False, allow_smtputf8=True) == output
104
+ assert validate_email(email_input, check_deliverability=False, allow_smtputf8=True,
105
+ allow_quoted_local=True) == output
80
106
 
81
107
  # Check that the old `email` attribute to access the normalized form still works
82
108
  # if the DeprecationWarning is suppressed.
@@ -317,9 +343,9 @@ def test_domain_literal():
317
343
  ('obsolete."quoted".atom@example.com', 'The email address contains invalid characters before the @-sign: \'"\'.'),
318
344
  ('11111111112222222222333333333344444444445555555555666666666677777@example.com', 'The email address is too long before the @-sign (1 character too many).'),
319
345
  ('111111111122222222223333333333444444444455555555556666666666777777@example.com', 'The email address is too long before the @-sign (2 characters too many).'),
320
- ('me@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.111111111122222222223333333333444444444455555555556.com', 'The email address is too long (4 characters too many).'),
321
- ('me@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555566.com', 'The email address is too long after the @-sign (1 character too many).'),
322
- ('me@中1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555566.com', 'The email address is too long after the @-sign.'),
346
+ ('meme@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.com', 'The email address is too long (4 characters too many).'),
347
+ ('me@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.11111111112222222222333333333344444444445555555555.com', 'The email address is too long after the @-sign (1 character too many).'),
348
+ ('me@中1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444.com', 'The email address is too long after the @-sign.'),
323
349
  ('my.long.address@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.11111111112222222222333333333344444.info', 'The email address is too long (2 characters too many).'),
324
350
  ('my.long.address@λ111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.11111111112222222222333333.info', 'The email address is too long (when converted to IDNA ASCII).'),
325
351
  ('my.long.address@λ111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444.info', 'The email address is too long (at least 1 character too many).'),
@@ -1,121 +0,0 @@
1
- Creative Commons Legal Code
2
-
3
- CC0 1.0 Universal
4
-
5
- CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6
- LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7
- ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8
- INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9
- REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10
- PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11
- THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12
- HEREUNDER.
13
-
14
- Statement of Purpose
15
-
16
- The laws of most jurisdictions throughout the world automatically confer
17
- exclusive Copyright and Related Rights (defined below) upon the creator
18
- and subsequent owner(s) (each and all, an "owner") of an original work of
19
- authorship and/or a database (each, a "Work").
20
-
21
- Certain owners wish to permanently relinquish those rights to a Work for
22
- the purpose of contributing to a commons of creative, cultural and
23
- scientific works ("Commons") that the public can reliably and without fear
24
- of later claims of infringement build upon, modify, incorporate in other
25
- works, reuse and redistribute as freely as possible in any form whatsoever
26
- and for any purposes, including without limitation commercial purposes.
27
- These owners may contribute to the Commons to promote the ideal of a free
28
- culture and the further production of creative, cultural and scientific
29
- works, or to gain reputation or greater distribution for their Work in
30
- part through the use and efforts of others.
31
-
32
- For these and/or other purposes and motivations, and without any
33
- expectation of additional consideration or compensation, the person
34
- associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35
- is an owner of Copyright and Related Rights in the Work, voluntarily
36
- elects to apply CC0 to the Work and publicly distribute the Work under its
37
- terms, with knowledge of his or her Copyright and Related Rights in the
38
- Work and the meaning and intended legal effect of CC0 on those rights.
39
-
40
- 1. Copyright and Related Rights. A Work made available under CC0 may be
41
- protected by copyright and related or neighboring rights ("Copyright and
42
- Related Rights"). Copyright and Related Rights include, but are not
43
- limited to, the following:
44
-
45
- i. the right to reproduce, adapt, distribute, perform, display,
46
- communicate, and translate a Work;
47
- ii. moral rights retained by the original author(s) and/or performer(s);
48
- iii. publicity and privacy rights pertaining to a person's image or
49
- likeness depicted in a Work;
50
- iv. rights protecting against unfair competition in regards to a Work,
51
- subject to the limitations in paragraph 4(a), below;
52
- v. rights protecting the extraction, dissemination, use and reuse of data
53
- in a Work;
54
- vi. database rights (such as those arising under Directive 96/9/EC of the
55
- European Parliament and of the Council of 11 March 1996 on the legal
56
- protection of databases, and under any national implementation
57
- thereof, including any amended or successor version of such
58
- directive); and
59
- vii. other similar, equivalent or corresponding rights throughout the
60
- world based on applicable law or treaty, and any national
61
- implementations thereof.
62
-
63
- 2. Waiver. To the greatest extent permitted by, but not in contravention
64
- of, applicable law, Affirmer hereby overtly, fully, permanently,
65
- irrevocably and unconditionally waives, abandons, and surrenders all of
66
- Affirmer's Copyright and Related Rights and associated claims and causes
67
- of action, whether now known or unknown (including existing as well as
68
- future claims and causes of action), in the Work (i) in all territories
69
- worldwide, (ii) for the maximum duration provided by applicable law or
70
- treaty (including future time extensions), (iii) in any current or future
71
- medium and for any number of copies, and (iv) for any purpose whatsoever,
72
- including without limitation commercial, advertising or promotional
73
- purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74
- member of the public at large and to the detriment of Affirmer's heirs and
75
- successors, fully intending that such Waiver shall not be subject to
76
- revocation, rescission, cancellation, termination, or any other legal or
77
- equitable action to disrupt the quiet enjoyment of the Work by the public
78
- as contemplated by Affirmer's express Statement of Purpose.
79
-
80
- 3. Public License Fallback. Should any part of the Waiver for any reason
81
- be judged legally invalid or ineffective under applicable law, then the
82
- Waiver shall be preserved to the maximum extent permitted taking into
83
- account Affirmer's express Statement of Purpose. In addition, to the
84
- extent the Waiver is so judged Affirmer hereby grants to each affected
85
- person a royalty-free, non transferable, non sublicensable, non exclusive,
86
- irrevocable and unconditional license to exercise Affirmer's Copyright and
87
- Related Rights in the Work (i) in all territories worldwide, (ii) for the
88
- maximum duration provided by applicable law or treaty (including future
89
- time extensions), (iii) in any current or future medium and for any number
90
- of copies, and (iv) for any purpose whatsoever, including without
91
- limitation commercial, advertising or promotional purposes (the
92
- "License"). The License shall be deemed effective as of the date CC0 was
93
- applied by Affirmer to the Work. Should any part of the License for any
94
- reason be judged legally invalid or ineffective under applicable law, such
95
- partial invalidity or ineffectiveness shall not invalidate the remainder
96
- of the License, and in such case Affirmer hereby affirms that he or she
97
- will not (i) exercise any of his or her remaining Copyright and Related
98
- Rights in the Work or (ii) assert any associated claims and causes of
99
- action with respect to the Work, in either case contrary to Affirmer's
100
- express Statement of Purpose.
101
-
102
- 4. Limitations and Disclaimers.
103
-
104
- a. No trademark or patent rights held by Affirmer are waived, abandoned,
105
- surrendered, licensed or otherwise affected by this document.
106
- b. Affirmer offers the Work as-is and makes no representations or
107
- warranties of any kind concerning the Work, express, implied,
108
- statutory or otherwise, including without limitation warranties of
109
- title, merchantability, fitness for a particular purpose, non
110
- infringement, or the absence of latent or other defects, accuracy, or
111
- the present or absence of errors, whether or not discoverable, all to
112
- the greatest extent permissible under applicable law.
113
- c. Affirmer disclaims responsibility for clearing rights of other persons
114
- that may apply to the Work or any use thereof, including without
115
- limitation any person's Copyright and Related Rights in the Work.
116
- Further, Affirmer disclaims responsibility for obtaining any necessary
117
- consents, permissions or other rights required for any use of the
118
- Work.
119
- d. Affirmer understands and acknowledges that Creative Commons is not a
120
- party to this document and has no duty or obligation with respect to
121
- this CC0 or use of the Work.
@@ -1 +0,0 @@
1
- __version__ = "2.1.0.post1"