setech 1.8.4__tar.gz → 1.8.5__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.
- {setech-1.8.4 → setech-1.8.5}/PKG-INFO +1 -1
- {setech-1.8.4 → setech-1.8.5}/pyproject.toml +1 -1
- {setech-1.8.4 → setech-1.8.5}/src/setech/django/validators.py +14 -4
- {setech-1.8.4 → setech-1.8.5}/src/setech/domain_mx_validator/__init__.py +2 -1
- {setech-1.8.4 → setech-1.8.5}/src/setech/domain_mx_validator/base.py +14 -2
- {setech-1.8.4 → setech-1.8.5}/src/setech/domain_mx_validator/memory.py +5 -5
- {setech-1.8.4 → setech-1.8.5}/LICENCE +0 -0
- {setech-1.8.4 → setech-1.8.5}/README.md +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/__init__.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/api_client/__init__.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/api_client/_base.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/api_client/async_client.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/api_client/sync_client.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/api_client/utils.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/constants/__init__.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/constants/date.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/django/__init__.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/django/constants.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/django/middleware/__init__.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/django/middleware/request_logging.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/django/models.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/django/rest_framework/__init__.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/django/rest_framework/fields.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/django/rest_framework/pagination.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/django/utils.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/domain_mx_validator/redis.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/logging/__init__.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/logging/formatters.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/logging/handlers.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/logging/logging.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/py.typed +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/utils/__init__.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/utils/numeric.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/utils/parse.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/utils/ssn/__init__.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/utils/ssn/base.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/utils/ssn/latvian.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/utils/ssn/lithuanian.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/utils/ssn/utils.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/utils/text.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/utils/time.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/utils/validators.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/utils/various.py +0 -0
- {setech-1.8.4 → setech-1.8.5}/src/setech/utils/warnings.py +0 -0
|
@@ -3,7 +3,7 @@ from django.core.exceptions import ValidationError
|
|
|
3
3
|
from django.core.files import File
|
|
4
4
|
from django.core.validators import FileExtensionValidator, get_available_image_extensions
|
|
5
5
|
|
|
6
|
-
from setech.domain_mx_validator import mx_cache
|
|
6
|
+
from setech.domain_mx_validator import MxLookupError, mx_cache
|
|
7
7
|
from setech.utils import validate_phone_number, validate_ssn
|
|
8
8
|
|
|
9
9
|
__all__ = [
|
|
@@ -30,9 +30,19 @@ def django_phone_number_validator(phone_number: str) -> None:
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def django_validate_email_mx_domain(value: str) -> None:
|
|
33
|
-
domain_part = value.rsplit("@", 1)[-1]
|
|
33
|
+
domain_part = value.rsplit("@", 1)[-1].lower()
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
try:
|
|
36
|
+
has_mx = mx_cache.check_domain(domain_part)
|
|
37
|
+
except MxLookupError:
|
|
36
38
|
raise ValidationError(
|
|
37
|
-
"%(domain)s
|
|
39
|
+
"MX records lookup for %(domain)s failed!",
|
|
40
|
+
params={"domain": domain_part},
|
|
41
|
+
code="mx_lookup_failed",
|
|
42
|
+
) from None
|
|
43
|
+
if not has_mx:
|
|
44
|
+
raise ValidationError(
|
|
45
|
+
"%(domain)s cannot receive emails!",
|
|
46
|
+
params={"domain": domain_part},
|
|
47
|
+
code="invalid_email_domain",
|
|
38
48
|
)
|
|
@@ -5,10 +5,11 @@ try:
|
|
|
5
5
|
except ImportError:
|
|
6
6
|
raise ImportError("setech.domain_mx_validator requires DNS. Install it with: pip install setech[dns]") from None
|
|
7
7
|
|
|
8
|
+
from .base import MxLookupError
|
|
8
9
|
from .memory import MemoryCache
|
|
9
10
|
from .redis import RedisCache
|
|
10
11
|
|
|
11
|
-
__all__ = ["mx_cache"]
|
|
12
|
+
__all__ = ["MxLookupError", "mx_cache"]
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
mx_cache: MemoryCache | RedisCache
|
|
@@ -5,6 +5,15 @@ from abc import ABC, abstractmethod
|
|
|
5
5
|
import dns.exception
|
|
6
6
|
import dns.resolver
|
|
7
7
|
|
|
8
|
+
NO_MX_DNS_ERRORS = (
|
|
9
|
+
dns.resolver.NXDOMAIN,
|
|
10
|
+
dns.resolver.NoAnswer,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MxLookupError(Exception):
|
|
15
|
+
"""The MX lookup could not be completed; the domain may still be valid."""
|
|
16
|
+
|
|
8
17
|
|
|
9
18
|
class BaseDomainCache(ABC):
|
|
10
19
|
logger: logging.Logger
|
|
@@ -21,9 +30,12 @@ class BaseDomainCache(ABC):
|
|
|
21
30
|
try:
|
|
22
31
|
self.logger.info("Looking up records for %s", domain)
|
|
23
32
|
res = dns.resolver.resolve(domain, "MX")
|
|
24
|
-
except
|
|
25
|
-
self.logger.
|
|
33
|
+
except NO_MX_DNS_ERRORS as e:
|
|
34
|
+
self.logger.info("No MX records for '%s': %r", domain, e)
|
|
26
35
|
return [], self.CACHE_TTL_MX_NOT_FOUND
|
|
36
|
+
except dns.exception.DNSException as e:
|
|
37
|
+
self.logger.warning("MX lookup for '%s' failed: %r", domain, e)
|
|
38
|
+
raise MxLookupError(domain) from e
|
|
27
39
|
if not res.rrset:
|
|
28
40
|
return [], self.CACHE_TTL_MX_NOT_FOUND
|
|
29
41
|
self.logger.info("Found MX records for '%s'. %s", domain, res)
|
|
@@ -24,16 +24,16 @@ class MemoryCache(BaseDomainCache):
|
|
|
24
24
|
self._cache_storage = {}
|
|
25
25
|
|
|
26
26
|
def check_domain(self, domain: str) -> bool:
|
|
27
|
-
if self._get_cache_result(domain):
|
|
28
|
-
return
|
|
27
|
+
if cached := self._get_cache_result(domain):
|
|
28
|
+
return bool(cached.records)
|
|
29
29
|
|
|
30
30
|
records, ttl = self.lookup_mx_records(domain)
|
|
31
31
|
self._cache_storage[domain] = MemoryItem(records=records, cached_at=datetime.now(), ttl=ttl)
|
|
32
32
|
return bool(records)
|
|
33
33
|
|
|
34
|
-
def _get_cache_result(self, domain: str) ->
|
|
34
|
+
def _get_cache_result(self, domain: str) -> MemoryItem | None:
|
|
35
35
|
if cached_value := self._cache_storage.get(domain):
|
|
36
36
|
if cached_value.is_valid:
|
|
37
|
-
return
|
|
37
|
+
return cached_value
|
|
38
38
|
self._cache_storage.pop(domain, None)
|
|
39
|
-
return
|
|
39
|
+
return None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|