Files

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))