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.
Files changed (44) hide show
  1. {setech-1.8.4 → setech-1.8.5}/PKG-INFO +1 -1
  2. {setech-1.8.4 → setech-1.8.5}/pyproject.toml +1 -1
  3. {setech-1.8.4 → setech-1.8.5}/src/setech/django/validators.py +14 -4
  4. {setech-1.8.4 → setech-1.8.5}/src/setech/domain_mx_validator/__init__.py +2 -1
  5. {setech-1.8.4 → setech-1.8.5}/src/setech/domain_mx_validator/base.py +14 -2
  6. {setech-1.8.4 → setech-1.8.5}/src/setech/domain_mx_validator/memory.py +5 -5
  7. {setech-1.8.4 → setech-1.8.5}/LICENCE +0 -0
  8. {setech-1.8.4 → setech-1.8.5}/README.md +0 -0
  9. {setech-1.8.4 → setech-1.8.5}/src/setech/__init__.py +0 -0
  10. {setech-1.8.4 → setech-1.8.5}/src/setech/api_client/__init__.py +0 -0
  11. {setech-1.8.4 → setech-1.8.5}/src/setech/api_client/_base.py +0 -0
  12. {setech-1.8.4 → setech-1.8.5}/src/setech/api_client/async_client.py +0 -0
  13. {setech-1.8.4 → setech-1.8.5}/src/setech/api_client/sync_client.py +0 -0
  14. {setech-1.8.4 → setech-1.8.5}/src/setech/api_client/utils.py +0 -0
  15. {setech-1.8.4 → setech-1.8.5}/src/setech/constants/__init__.py +0 -0
  16. {setech-1.8.4 → setech-1.8.5}/src/setech/constants/date.py +0 -0
  17. {setech-1.8.4 → setech-1.8.5}/src/setech/django/__init__.py +0 -0
  18. {setech-1.8.4 → setech-1.8.5}/src/setech/django/constants.py +0 -0
  19. {setech-1.8.4 → setech-1.8.5}/src/setech/django/middleware/__init__.py +0 -0
  20. {setech-1.8.4 → setech-1.8.5}/src/setech/django/middleware/request_logging.py +0 -0
  21. {setech-1.8.4 → setech-1.8.5}/src/setech/django/models.py +0 -0
  22. {setech-1.8.4 → setech-1.8.5}/src/setech/django/rest_framework/__init__.py +0 -0
  23. {setech-1.8.4 → setech-1.8.5}/src/setech/django/rest_framework/fields.py +0 -0
  24. {setech-1.8.4 → setech-1.8.5}/src/setech/django/rest_framework/pagination.py +0 -0
  25. {setech-1.8.4 → setech-1.8.5}/src/setech/django/utils.py +0 -0
  26. {setech-1.8.4 → setech-1.8.5}/src/setech/domain_mx_validator/redis.py +0 -0
  27. {setech-1.8.4 → setech-1.8.5}/src/setech/logging/__init__.py +0 -0
  28. {setech-1.8.4 → setech-1.8.5}/src/setech/logging/formatters.py +0 -0
  29. {setech-1.8.4 → setech-1.8.5}/src/setech/logging/handlers.py +0 -0
  30. {setech-1.8.4 → setech-1.8.5}/src/setech/logging/logging.py +0 -0
  31. {setech-1.8.4 → setech-1.8.5}/src/setech/py.typed +0 -0
  32. {setech-1.8.4 → setech-1.8.5}/src/setech/utils/__init__.py +0 -0
  33. {setech-1.8.4 → setech-1.8.5}/src/setech/utils/numeric.py +0 -0
  34. {setech-1.8.4 → setech-1.8.5}/src/setech/utils/parse.py +0 -0
  35. {setech-1.8.4 → setech-1.8.5}/src/setech/utils/ssn/__init__.py +0 -0
  36. {setech-1.8.4 → setech-1.8.5}/src/setech/utils/ssn/base.py +0 -0
  37. {setech-1.8.4 → setech-1.8.5}/src/setech/utils/ssn/latvian.py +0 -0
  38. {setech-1.8.4 → setech-1.8.5}/src/setech/utils/ssn/lithuanian.py +0 -0
  39. {setech-1.8.4 → setech-1.8.5}/src/setech/utils/ssn/utils.py +0 -0
  40. {setech-1.8.4 → setech-1.8.5}/src/setech/utils/text.py +0 -0
  41. {setech-1.8.4 → setech-1.8.5}/src/setech/utils/time.py +0 -0
  42. {setech-1.8.4 → setech-1.8.5}/src/setech/utils/validators.py +0 -0
  43. {setech-1.8.4 → setech-1.8.5}/src/setech/utils/various.py +0 -0
  44. {setech-1.8.4 → setech-1.8.5}/src/setech/utils/warnings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: setech
3
- Version: 1.8.4
3
+ Version: 1.8.5
4
4
  Summary: Setech utilities
5
5
  Keywords: api-client,logging,setech,utility,utils
6
6
  Author: Eriks Karls
@@ -4,7 +4,7 @@ requires = [ "uv-build>=0.11.18,<0.12", "wheel" ]
4
4
 
5
5
  [project]
6
6
  name = "setech"
7
- version = "1.8.4"
7
+ version = "1.8.5"
8
8
  description = "Setech utilities"
9
9
  readme = "README.md"
10
10
  keywords = [ "api-client", "logging", "setech", "utility", "utils" ]
@@ -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
- if not mx_cache.check_domain(domain_part):
35
+ try:
36
+ has_mx = mx_cache.check_domain(domain_part)
37
+ except MxLookupError:
36
38
  raise ValidationError(
37
- "%(domain)s cannot receive emails!", params={"domain": domain_part}, code="invalid_email_domain"
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 dns.exception.DNSException as e:
25
- self.logger.warning("No records found! %s", e)
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 True
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) -> bool:
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 True
37
+ return cached_value
38
38
  self._cache_storage.pop(domain, None)
39
- return False
39
+ return None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes