Skip to content

Commit

Permalink
Export Sender
Browse files Browse the repository at this point in the history
  • Loading branch information
jwodder committed Mar 14, 2021
1 parent a859b3e commit df6757b
Show file tree
Hide file tree
Showing 11 changed files with 37 additions and 19 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
v0.2.0 (in development)
-----------------------
- Require the `port` field of `SMTPSender` to be non-negative
- Mark `Sender` as `runtime_checkable` and export it

v0.1.0 (2021-03-06)
-------------------
Expand Down
15 changes: 5 additions & 10 deletions docs/pyapi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,12 @@ examples.
Sender Objects
--------------

Sender objects support, at minimum, the following protocol:
.. autoclass:: Sender()
:special-members: __enter__, __exit__

- They can be used as context managers, and their ``__enter__`` methods return
``self``.

- Within its own context, calling a sender's ``send(msg:
email.message.EmailMessage)`` method sends the given e-mail.

In addition, ``outgoing``'s built-in senders are reentrant__ and reusable__ as
context managers, and their ``send()`` methods can be called outside of a
context.
In addition to the base protocol, ``outgoing``'s built-in senders are
reentrant__ and reusable__ as context managers, and their ``send()`` methods
can be called outside of a context.

__ https://docs.python.org/3/library/contextlib.html#reentrant-context-managers
__ https://docs.python.org/3/library/contextlib.html#reusable-context-managers
Expand Down
2 changes: 2 additions & 0 deletions src/outgoing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
)
from .core import (
DEFAULT_CONFIG_SECTION,
Sender,
from_config_file,
from_dict,
get_default_configpath,
Expand Down Expand Up @@ -57,6 +58,7 @@
"OpenClosable",
"Password",
"Path",
"Sender",
"StandardPassword",
"from_config_file",
"from_dict",
Expand Down
17 changes: 15 additions & 2 deletions src/outgoing/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
from .util import AnyPath

if sys.version_info[:2] >= (3, 8):
from typing import Protocol
from typing import Protocol, runtime_checkable
else:
from typing_extensions import Protocol
from typing_extensions import Protocol, runtime_checkable

DEFAULT_CONFIG_SECTION = "outgoing"

Expand All @@ -28,7 +28,19 @@
S = TypeVar("S", bound="Sender")


@runtime_checkable
class Sender(Protocol):
"""
`Sender` is a `~typing.Protocol` implemented by sender objects. The
protocol requires the following behavior:
- Sender objects can be used as context managers, and their ``__enter__``
methods return ``self``.
- Within its own context, calling a sender's ``send(msg:
email.message.EmailMessage)`` method sends the given e-mail.
"""

def __enter__(self: S) -> S:
...

Expand All @@ -41,6 +53,7 @@ def __exit__(
...

def send(self, msg: EmailMessage) -> Any:
""" Send ``msg`` or raise an exception if that's not possible """
...


Expand Down
3 changes: 2 additions & 1 deletion test/test_senders/test_babyl.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pathlib import Path
from mailbits import email2dict
import pytest
from outgoing import from_dict
from outgoing import Sender, from_dict
from outgoing.senders.mailboxes import BabylSender


Expand All @@ -16,6 +16,7 @@ def test_babyl_construct(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Non
},
configpath=str(tmp_path / "foo.txt"),
)
assert isinstance(sender, Sender)
assert isinstance(sender, BabylSender)
assert sender.dict() == {
"configpath": tmp_path / "foo.txt",
Expand Down
3 changes: 2 additions & 1 deletion test/test_senders/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
from typing import List, Union
import pytest
from pytest_mock import MockerFixture
from outgoing import from_dict
from outgoing import Sender, from_dict
from outgoing.senders.command import CommandSender


def test_command_construct_default(tmp_path: Path) -> None:
sender = from_dict({"method": "command"}, configpath=tmp_path / "foo.toml")
assert isinstance(sender, Sender)
assert isinstance(sender, CommandSender)
assert sender.dict() == {
"configpath": tmp_path / "foo.toml",
Expand Down
3 changes: 2 additions & 1 deletion test/test_senders/test_maildir.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from typing import Optional
from mailbits import email2dict
import pytest
from outgoing import from_dict
from outgoing import Sender, from_dict
from outgoing.senders.mailboxes import MaildirSender


Expand All @@ -22,6 +22,7 @@ def test_maildir_construct(
},
configpath=str(tmp_path / "foo.txt"),
)
assert isinstance(sender, Sender)
assert isinstance(sender, MaildirSender)
assert sender.dict() == {
"configpath": tmp_path / "foo.txt",
Expand Down
3 changes: 2 additions & 1 deletion test/test_senders/test_mbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pathlib import Path
from mailbits import email2dict
import pytest
from outgoing import from_dict
from outgoing import Sender, from_dict
from outgoing.senders.mailboxes import MboxSender


Expand All @@ -16,6 +16,7 @@ def test_mbox_construct(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None
},
configpath=str(tmp_path / "foo.txt"),
)
assert isinstance(sender, Sender)
assert isinstance(sender, MboxSender)
assert sender.dict() == {
"configpath": tmp_path / "foo.txt",
Expand Down
3 changes: 2 additions & 1 deletion test/test_senders/test_mh.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from typing import List, Union
from mailbits import email2dict
import pytest
from outgoing import from_dict
from outgoing import Sender, from_dict
from outgoing.senders.mailboxes import MHSender


Expand All @@ -22,6 +22,7 @@ def test_mh_construct(
},
configpath=str(tmp_path / "foo.txt"),
)
assert isinstance(sender, Sender)
assert isinstance(sender, MHSender)
assert sender.dict() == {
"configpath": tmp_path / "foo.txt",
Expand Down
3 changes: 2 additions & 1 deletion test/test_senders/test_mmdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pathlib import Path
from mailbits import email2dict
import pytest
from outgoing import from_dict
from outgoing import Sender, from_dict
from outgoing.senders.mailboxes import MMDFSender


Expand All @@ -16,6 +16,7 @@ def test_mmdf_construct(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None
},
configpath=str(tmp_path / "foo.txt"),
)
assert isinstance(sender, Sender)
assert isinstance(sender, MMDFSender)
assert sender.dict() == {
"configpath": tmp_path / "foo.txt",
Expand Down
3 changes: 2 additions & 1 deletion test/test_senders/test_smtp.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pytest
from pytest_mock import MockerFixture
from smtpdfix import SMTPDFix
from outgoing import from_dict
from outgoing import Sender, from_dict
from outgoing.errors import InvalidConfigError
from outgoing.senders.smtp import SMTPSender

Expand All @@ -33,6 +33,7 @@ def test_smtp_construct_default_ssl(
},
configpath=str(tmp_path / "foo.txt"),
)
assert isinstance(sender, Sender)
assert isinstance(sender, SMTPSender)
assert sender.dict() == {
"configpath": tmp_path / "foo.txt",
Expand Down

0 comments on commit df6757b

Please sign in to comment.