plain.email 0.11.1__tar.gz → 0.12.0__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.
@@ -1,5 +1,5 @@
1
1
  .venv
2
- .env
2
+ /.env
3
3
  *.egg-info
4
4
  *.py[co]
5
5
  __pycache__
@@ -8,8 +8,8 @@ __pycache__
8
8
  # Test apps
9
9
  plain*/tests/.plain
10
10
 
11
- # Ottobot
12
- .aider*
11
+ # Agent scratch files
12
+ /scratch
13
13
 
14
14
  # Plain temp dirs
15
15
  .plain
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.email
3
- Version: 0.11.1
3
+ Version: 0.12.0
4
4
  Summary: Everything you need to send email in Plain.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-Expression: BSD-3-Clause
@@ -1,5 +1,16 @@
1
1
  # plain-email changelog
2
2
 
3
+ ## [0.12.0](https://github.com/dropseed/plain/releases/plain-email@0.12.0) (2025-11-12)
4
+
5
+ ### What's changed
6
+
7
+ - The filebased email backend now requires `EMAIL_FILE_PATH` to be set and raises `ImproperlyConfigured` if not provided ([f4dbcef](https://github.com/dropseed/plain/commit/f4dbcefa929058be517cb1d4ab35bd73a89f26b8))
8
+ - `BaseEmailBackend` now uses Python's abstract base class with `@abstractmethod` for better type checking ([245b5f4](https://github.com/dropseed/plain/commit/245b5f472c89178b8b764869f1624f8fc885b0f7))
9
+
10
+ ### Upgrade instructions
11
+
12
+ - If using the filebased email backend, ensure `EMAIL_FILE_PATH` is configured in your settings or passed when initializing the backend
13
+
3
14
  ## [0.11.1](https://github.com/dropseed/plain/releases/plain-email@0.11.1) (2025-10-06)
4
15
 
5
16
  ### What's changed
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from abc import ABC, abstractmethod
5
6
  from typing import TYPE_CHECKING, Any
6
7
 
7
8
  if TYPE_CHECKING:
@@ -10,7 +11,7 @@ if TYPE_CHECKING:
10
11
  from ..message import EmailMessage
11
12
 
12
13
 
13
- class BaseEmailBackend:
14
+ class BaseEmailBackend(ABC):
14
15
  """
15
16
  Base class for email backend implementations.
16
17
 
@@ -66,11 +67,10 @@ class BaseEmailBackend:
66
67
  ) -> None:
67
68
  self.close()
68
69
 
70
+ @abstractmethod
69
71
  def send_messages(self, email_messages: list[EmailMessage]) -> int:
70
72
  """
71
73
  Send one or more EmailMessage objects and return the number of email
72
74
  messages sent.
73
75
  """
74
- raise NotImplementedError(
75
- "subclasses of BaseEmailBackend must override send_messages() method"
76
- )
76
+ ...
@@ -22,6 +22,10 @@ class EmailBackend(ConsoleEmailBackend):
22
22
  self.file_path = file_path
23
23
  else:
24
24
  self.file_path = getattr(settings, "EMAIL_FILE_PATH", None)
25
+ if not self.file_path:
26
+ raise ImproperlyConfigured(
27
+ "EMAIL_FILE_PATH must be set for the filebased email backend"
28
+ )
25
29
  self.file_path = os.path.abspath(self.file_path)
26
30
  try:
27
31
  os.makedirs(self.file_path, exist_ok=True)
@@ -45,6 +49,7 @@ class EmailBackend(ConsoleEmailBackend):
45
49
  super().__init__(*args, **kwargs)
46
50
 
47
51
  def write_message(self, message: EmailMessage) -> None:
52
+ assert self.stream is not None, "stream should be opened before writing"
48
53
  self.stream.write(message.message().as_bytes() + b"\n")
49
54
  self.stream.write(b"-" * 79)
50
55
  self.stream.write(b"\n")
@@ -84,7 +84,7 @@ class EmailBackend(BaseEmailBackend):
84
84
 
85
85
  # If local_hostname is not specified, socket.getfqdn() gets used.
86
86
  # For performance, we use the cached FQDN for local_hostname.
87
- connection_params = {"local_hostname": DNS_NAME.get_fqdn()}
87
+ connection_params: dict[str, Any] = {"local_hostname": DNS_NAME.get_fqdn()}
88
88
  if self.timeout is not None:
89
89
  connection_params["timeout"] = self.timeout
90
90
  if self.use_ssl:
@@ -158,6 +158,7 @@ class EmailBackend(BaseEmailBackend):
158
158
  sanitize_address(addr, encoding) for addr in email_message.recipients()
159
159
  ]
160
160
  message = email_message.message()
161
+ assert self.connection is not None, "connection should be open before sending"
161
162
  try:
162
163
  self.connection.sendmail(
163
164
  from_email, recipients, message.as_bytes(linesep="\r\n")
@@ -439,7 +439,7 @@ class EmailMessage:
439
439
  return attachment
440
440
 
441
441
  def _create_attachment(
442
- self, filename: str | None, content: str | bytes, mimetype: str | None = None
442
+ self, filename: str | None, content: str | bytes, mimetype: str
443
443
  ) -> SafeMIMEText | SafeMIMEMessage | MIMEBase:
444
444
  """
445
445
  Convert the filename, content, mimetype triple into a MIME attachment
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "plain.email"
3
- version = "0.11.1"
3
+ version = "0.12.0"
4
4
  description = "Everything you need to send email in Plain."
5
5
  authors = [{name = "Dave Gaeddert", email = "dave.gaeddert@dropseed.dev"}]
6
6
  license = "BSD-3-Clause"
File without changes
File without changes