108 lines
3.5 KiB
Python
108 lines
3.5 KiB
Python
from email import errors as email_errors
|
|
from email import headerregistry as email_reg
|
|
from email import utils as email_utils
|
|
|
|
from django.core import exceptions, validators
|
|
|
|
|
|
class CourrielError(Exception):
|
|
""" Something went wrong and you'll have to take a guess """
|
|
|
|
|
|
class InvalidAddressError(exceptions.ValidationError, CourrielError):
|
|
""" Failed to parse an address definition """
|
|
|
|
|
|
class InvalidRecipientsError(CourrielError):
|
|
""" Can't be send to recipients """
|
|
|
|
|
|
class InvalidSenderError(CourrielError):
|
|
""" Missing or invalid sender """
|
|
|
|
|
|
class InvalidGabarit(CourrielError):
|
|
""" Something is wrong with the template """
|
|
|
|
|
|
class InvalidFrontMatter(InvalidGabarit):
|
|
""" Failed to parse template front matter """
|
|
|
|
|
|
class MarkdownEmailError(InvalidGabarit):
|
|
""" Error in markdown email or template loading """
|
|
|
|
|
|
class UnresolvedVariableError(CourrielError):
|
|
""" A template variable could not be resolved """
|
|
|
|
|
|
class ValidAddress(email_reg.Address):
|
|
""" extends email.headerregistry.Address so it can be used where django expect an email address.
|
|
Except it should never be invalid """
|
|
|
|
def __new__(cls, display_name='', username='', domain='', addr_spec=None):
|
|
""" Builds a ValidAddress from either an Address or a string, or raise InvalidAddressError """
|
|
try:
|
|
spec = addr_spec or f"{username}@{domain}"
|
|
cls._validate_spec(spec)
|
|
instance = super().__new__(cls)
|
|
return instance
|
|
except email_errors.InvalidHeaderDefect as e:
|
|
raise InvalidAddressError(f"{spec}") from e
|
|
|
|
@classmethod
|
|
def _validate_spec(cls, spec):
|
|
try:
|
|
validators.validate_email(spec)
|
|
except exceptions.ValidationError as e:
|
|
raise InvalidAddressError(f"{spec}") from e
|
|
|
|
@classmethod
|
|
def factory(cls, input) -> 'ValidAddress':
|
|
""" ensure Address instance from str, None or Address """
|
|
if isinstance(input, ValidAddress):
|
|
return input
|
|
if isinstance(input, email_reg.Address): # converting to subclass
|
|
return ValidAddress(display_name=input.display_name, username=input.username, domain=input.domain)
|
|
if input is None:
|
|
return ValidAddress(display_name='')
|
|
if isinstance(input, str):
|
|
try:
|
|
name, spec = email_utils.parseaddr(input)
|
|
return cls(name, addr_spec=spec)
|
|
except email_errors.InvalidHeaderDefect:
|
|
# parsing failed and may have split username, let's keep full input as display name
|
|
return cls(display_name=input)
|
|
raise TypeError(f"Cannot create ValidAddress from {type(input).__name__}")
|
|
|
|
@property
|
|
def email(self):
|
|
""" shorthand for addr_spec"""
|
|
return self.addr_spec
|
|
|
|
|
|
class ValidAddressList(list):
|
|
""" only accepts valid addresses """
|
|
|
|
def _validate(self, value):
|
|
""" converts to ValidAddress, may raise InvalidAddress """
|
|
return ValidAddress.factory(value)
|
|
|
|
def __init__(self, iterable=()):
|
|
validated = [self._validate(i) for i in iterable]
|
|
super().__init__(validated)
|
|
|
|
def append(self, item):
|
|
super().append(self._validate(item))
|
|
|
|
def insert(self, index, item):
|
|
super().insert(index, self._validate(item))
|
|
|
|
def extend(self, items):
|
|
for item in items:
|
|
super().append(self._validate(item))
|
|
|
|
def __setitem__(self, index, item):
|
|
super().__setitem__(index, self._validate(item))
|