summaryrefslogtreecommitdiff
path: root/django/core/mail/backends/base.py
blob: 9061e9f65463c40cb135c9433e2e1bf3cbf07e7b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"""Base email backend class."""

from django.core.mail import InvalidMailer
from django.utils.deprecation import RemovedInDjango70Warning, warn_about_external_use

# RemovedInDjango70Warning.
_NOT_PROVIDED = object()


class BaseEmailBackend:
    """
    Base class for email backend implementations.

    Subclasses must implement at least send_messages().

    Subclass __init__() should pass alias and unknown **kwargs to
    super().__init__() for proper error reporting.

    open() and close() can be called indirectly by using a backend object as a
    context manager:

       with backend as connection:
           # do something with connection
           pass
    """

    # RemovedInDjango70Warning: fail_silently, _ignore_unknown_kwargs.
    def __init__(
        self,
        fail_silently=_NOT_PROVIDED,
        *,
        alias=None,
        _ignore_unknown_kwargs=None,
        **kwargs,
    ):
        self.alias = alias

        # RemovedInDjango70Warning.
        if fail_silently is _NOT_PROVIDED:
            self._fail_silently = False
        else:
            self._fail_silently = fail_silently
            # Force deprecation warning unless in _ignore_unknown_kwargs.
            kwargs["fail_silently"] = fail_silently
        if _ignore_unknown_kwargs:
            for ignored in _ignore_unknown_kwargs:
                kwargs.pop(ignored, None)

        if kwargs:
            kwarg_names = ", ".join(repr(key) for key in kwargs.keys())

            # RemovedInDjango70Warning.
            if alias is None:
                # Not being initialized from mail.mailers. Unknown kwargs are
                # ignored -- with a deprecation warning if they originated from
                # outside Django (either direct construction of a built-in
                # backend or a super.__init__() call from a custom subclass).
                # Use something more precise than self.__class__.__name__,
                # which is often just "EmailBackend".
                class_name = f"{type(self).__module__}.{type(self).__qualname__}"
                warn_about_external_use(
                    f"{class_name}.__init__() does not support {kwarg_names}. "
                    "In Django 7.0, BaseEmailBackend will raise a TypeError "
                    "for unknown keyword arguments.",
                    RemovedInDjango70Warning,
                    skip_name_prefixes="django.core.mail.backends",
                )
                return

            raise InvalidMailer(f"Unknown options {kwarg_names}.", alias=alias)

    # RemovedInDjango70Warning.
    def __getattr__(self, name):
        if name == "fail_silently":
            msg = (
                "BaseEmailBackend.fail_silently is deprecated. A subclass "
                "that supports fail_silently must set its own attribute."
            )
            warn_about_external_use(msg, RemovedInDjango70Warning)
            return self._fail_silently
        raise AttributeError(
            f"{type(self).__name__!r} object has no attribute {name!r}"
        )

    def open(self):
        """
        Open a network connection.

        This method can be overwritten by backend implementations to
        open a network connection.

        It's up to the backend implementation to track the status of
        a network connection if it's needed by the backend.

        This method can be called by applications to force a single
        network connection to be used when sending mails. See the
        send_messages() method of the SMTP backend for a reference
        implementation.

        The default implementation does nothing.
        """
        pass

    def close(self):
        """Close a network connection."""
        pass

    def __enter__(self):
        try:
            self.open()
        except Exception:
            self.close()
            raise
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    def send_messages(self, email_messages):
        """
        Send one or more EmailMessage objects and return the number of email
        messages sent.
        """
        raise NotImplementedError(
            "subclasses of BaseEmailBackend must override send_messages() method"
        )