diff --git a/mailtrap/__init__.py b/mailtrap/__init__.py index 276cc45..f03b693 100644 --- a/mailtrap/__init__.py +++ b/mailtrap/__init__.py @@ -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 diff --git a/mailtrap/client.py b/mailtrap/client.py index 9d33682..60a1d6a 100644 --- a/mailtrap/client.py +++ b/mailtrap/client.py @@ -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() @@ -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]: @@ -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 + @staticmethod def _handle_failed_response(response: requests.Response) -> NoReturn: status_code = response.status_code @@ -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") diff --git a/mailtrap/exceptions.py b/mailtrap/exceptions.py index 978f1b8..f8c7c67 100644 --- a/mailtrap/exceptions.py +++ b/mailtrap/exceptions.py @@ -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 diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index b34ea2c..29d3679 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -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()