Skip to content

Support sandbox and bulk modes #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: deprecate-dead-python-versions
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mailtrap/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .client import MailtrapClient
from .exceptions import APIError
from .exceptions import AuthorizationError
from .exceptions import ClientConfigurationError
from .exceptions import MailtrapError
from .mail import Address
from .mail import Attachment
Expand Down
51 changes: 47 additions & 4 deletions mailtrap/client.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,43 @@
from typing import NoReturn
from typing import Optional
from typing import Union

import requests

from mailtrap.exceptions import APIError
from mailtrap.exceptions import AuthorizationError
from mailtrap.exceptions import ClientConfigurationError
from mailtrap.mail.base import BaseMail


class MailtrapClient:
DEFAULT_HOST = "send.api.mailtrap.io"
DEFAULT_PORT = 443
BULK_HOST = "bulk.api.mailtrap.io"
SANDBOX_HOST = "sandbox.api.mailtrap.io"

def __init__(
self,
token: str,
api_host: str = DEFAULT_HOST,
api_host: Optional[str] = None,
api_port: int = DEFAULT_PORT,
bulk: bool = False,
sandbox: bool = False,
inbox_id: Optional[str] = None,
) -> None:
self.token = token
self.api_host = api_host
self.api_port = api_port
self.bulk = bulk
self.sandbox = sandbox
self.inbox_id = inbox_id

self._validate_itself()

def send(self, mail: BaseMail) -> dict[str, Union[bool, list[str]]]:
url = f"{self.base_url}/api/send"
response = requests.post(url, headers=self.headers, json=mail.api_data)
response = requests.post(
self.api_send_url, headers=self.headers, json=mail.api_data
)

if response.ok:
data: dict[str, Union[bool, list[str]]] = response.json()
Expand All @@ -34,7 +47,15 @@ def send(self, mail: BaseMail) -> dict[str, Union[bool, list[str]]]:

@property
def base_url(self) -> str:
return f"https://{self.api_host.rstrip('/')}:{self.api_port}"
return f"https://{self._host.rstrip('/')}:{self.api_port}"

@property
def api_send_url(self) -> str:
url = f"{self.base_url}/api/send"
if self.sandbox and self.inbox_id:
return f"{url}/{self.inbox_id}"

return url

@property
def headers(self) -> dict[str, str]:
Expand All @@ -46,6 +67,16 @@ def headers(self) -> dict[str, str]:
),
}

@property
def _host(self) -> str:
if self.api_host:
return self.api_host
if self.sandbox:
return self.SANDBOX_HOST
if self.bulk:
return self.BULK_HOST
return self.DEFAULT_HOST

Comment on lines +70 to +79

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should find a way to make it more flexible for future changes—hopefully unnecessary—but what if the sandbox host changes? In that case, we wouldn’t be able to handle it.

Suggested change
@property
def _host(self) -> str:
if self.api_host:
return self.api_host
if self.sandbox:
return self.SANDBOX_HOST
if self.bulk:
return self.BULK_HOST
return self.DEFAULT_HOST
@property
def _host(self) -> str:
if self.api_host and self.sandbox:
return self.api_host
if self.api_host and self.bulk:
return self.api_host
if self.sandbox:
return self.SANDBOX_HOST
if self.bulk:
return self.BULK_HOST
if self.api_host:
return self.api_host
return self.DEFAULT_HOST

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's doubtful that the host will be changed, but the client supports api_host, which has the highest priority and always takes part of the API URL if it's present, regardless of other parameters.

@staticmethod
def _handle_failed_response(response: requests.Response) -> NoReturn:
status_code = response.status_code
Expand All @@ -55,3 +86,15 @@ def _handle_failed_response(response: requests.Response) -> NoReturn:
raise AuthorizationError(data["errors"])

raise APIError(status_code, data["errors"])

def _validate_itself(self) -> None:
if self.sandbox and not self.inbox_id:
raise ClientConfigurationError("`inbox_id` is required for sandbox mode")

if not self.sandbox and self.inbox_id:
raise ClientConfigurationError(
"`inbox_id` is not allowed in non-sandbox mode"
)

if self.bulk and self.sandbox:
raise ClientConfigurationError("bulk mode is not allowed in sandbox mode")
5 changes: 5 additions & 0 deletions mailtrap/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ class MailtrapError(Exception):
pass


class ClientConfigurationError(MailtrapError):
def __init__(self, message: str) -> None:
super().__init__(message)


class APIError(MailtrapError):
def __init__(self, status: int, errors: list[str]) -> None:
self.status = status
Expand Down
45 changes: 45 additions & 0 deletions tests/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,56 @@ def get_client(**kwargs: Any) -> mt.MailtrapClient:
props = {"token": "fake_token", **kwargs}
return mt.MailtrapClient(**props)

@pytest.mark.parametrize(
"arguments",
[
{"sandbox": True},
{"inbox_id": "12345"},
{"bulk": True, "sandbox": True, "inbox_id": "12345"},
],
)
def test_client_validation(self, arguments: dict[str, Any]) -> None:
with pytest.raises(mt.ClientConfigurationError):
self.get_client(**arguments)

def test_base_url_should_truncate_slash_from_host(self) -> None:
client = self.get_client(api_host="example.send.com/", api_port=543)

assert client.base_url == "https://example.send.com:543"

@pytest.mark.parametrize(
"arguments, expected_url",
[
({}, "https://send.api.mailtrap.io:443/api/send"),
(
{"api_host": "example.send.com", "api_port": 543},
"https://example.send.com:543/api/send",
),
(
{"api_host": "example.send.com", "sandbox": True, "inbox_id": "12345"},
"https://example.send.com:443/api/send/12345",
),
(
{"api_host": "example.send.com", "bulk": True},
"https://example.send.com:443/api/send",
),
(
{"sandbox": True, "inbox_id": "12345"},
"https://sandbox.api.mailtrap.io:443/api/send/12345",
),
(
{"bulk": True},
"https://bulk.api.mailtrap.io:443/api/send",
),
],
)
def test_api_send_url_should_return_default_sending_url(
self, arguments: dict[str, Any], expected_url: str
) -> None:
client = self.get_client(**arguments)

assert client.api_send_url == expected_url

def test_headers_should_return_appropriate_dict(self) -> None:
client = self.get_client()

Expand Down