From 742a14957817a6c472a2aa0f2a16d97d16897fbd Mon Sep 17 00:00:00 2001 From: IzikAJ Date: Mon, 5 Oct 2026 17:22:06 +0300 Subject: [PATCH 1/5] Add templates_api for the paginated /api/templates endpoints - New `client.templates_api.templates` targets /api/accounts/{id}/templates; list returns the `{data, pagination}` object, single-template calls return the unwrapped `Template`. - Internal names use `account_templates` because `TemplatesApi` and `EmailTemplatesApi` already belong to the /api/email_templates resource. - Request bodies are flat (no `email_template` wrap key) and update uses PATCH with the same "at least one field" check as before. - `client.email_templates_api.templates` now emits a DeprecationWarning pointing at `templates_api`; its behavior is unchanged. --- README.md | 3 +- examples/account_templates/__init__.py | 0 examples/account_templates/templates.py | 80 ++++ mailtrap/__init__.py | 5 + mailtrap/api/account_templates.py | 12 + mailtrap/api/resources/account_templates.py | 57 +++ mailtrap/api/templates.py | 11 + mailtrap/client.py | 9 + mailtrap/models/account_templates.py | 88 +++++ tests/unit/api/account_templates/__init__.py | 0 .../test_account_templates.py | 363 ++++++++++++++++++ .../api/email_templates/test_deprecation.py | 12 + tests/unit/models/test_account_templates.py | 52 +++ tests/unit/test_client.py | 12 + 14 files changed, 703 insertions(+), 1 deletion(-) create mode 100644 examples/account_templates/__init__.py create mode 100644 examples/account_templates/templates.py create mode 100644 mailtrap/api/account_templates.py create mode 100644 mailtrap/api/resources/account_templates.py create mode 100644 mailtrap/models/account_templates.py create mode 100644 tests/unit/api/account_templates/__init__.py create mode 100644 tests/unit/api/account_templates/test_account_templates.py create mode 100644 tests/unit/api/email_templates/test_deprecation.py create mode 100644 tests/unit/models/test_account_templates.py diff --git a/README.md b/README.md index 0cd5f38..16a8be9 100644 --- a/README.md +++ b/README.md @@ -239,7 +239,8 @@ The same situation applies to both `client.batch_send()` and `client.sending_api - Contact Imports – [`contacts/contact_imports.py`](examples/contacts/contact_imports.py) ### Email Templates API: -- Templates management – [`email_templates/templates.py`](examples/email_templates/templates.py) +- Templates (paginated, `/api/templates`) – [`account_templates/templates.py`](examples/account_templates/templates.py) +- Templates management (deprecated, use Templates above) – [`email_templates/templates.py`](examples/email_templates/templates.py) ### Sending Domains API: - Sending Domains – [`sending_domains/sending_domains.py`](examples/sending_domains/sending_domains.py) diff --git a/examples/account_templates/__init__.py b/examples/account_templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/account_templates/templates.py b/examples/account_templates/templates.py new file mode 100644 index 0000000..820ed48 --- /dev/null +++ b/examples/account_templates/templates.py @@ -0,0 +1,80 @@ +import os +from typing import Optional + +import mailtrap as mt +from mailtrap.models.account_templates import Template +from mailtrap.models.account_templates import TemplateListResponse +from mailtrap.models.common import DeletedObject + +API_KEY = os.environ["MAILTRAP_API_KEY"] +ACCOUNT_ID = os.environ["MAILTRAP_ACCOUNT_ID"] + +client = mt.MailtrapClient(token=API_KEY, account_id=ACCOUNT_ID) +templates_api = client.templates_api.templates + + +def list_templates() -> TemplateListResponse: + # `token` is the page number (page-token pagination); `per_page` caps at 100. + response = templates_api.get_list(mt.TemplateListParams(per_page=50, token=1)) + print(response.data) + print(response.pagination) + return response + + +def create_template( + name: str, + subject: str, + category: str, + body_html: Optional[str] = None, + body_text: Optional[str] = None, +) -> Template: + params = mt.CreateTemplateParams( + name=name, + subject=subject, + category=category, + body_html=body_html, + body_text=body_text, + ) + return templates_api.create(params) + + +def get_template(template_id: int) -> Template: + return templates_api.get_by_id(template_id) + + +def update_template( + template_id: int, + name: Optional[str] = None, + subject: Optional[str] = None, + category: Optional[str] = None, + body_html: Optional[str] = None, + body_text: Optional[str] = None, +) -> Template: + params = mt.UpdateTemplateParams( + name=name, + subject=subject, + category=category, + body_html=body_html, + body_text=body_text, + ) + return templates_api.update(template_id, params) + + +def delete_template(template_id: int) -> DeletedObject: + return templates_api.delete(template_id) + + +if __name__ == "__main__": + list_templates() + + created = create_template( + name="Welcome", + subject="Welcome aboard", + category="Onboarding", + body_html="

Hello!

", + ) + print(created) + + print(get_template(created.id)) + print(update_template(created.id, subject="Welcome to Mailtrap")) + print(delete_template(created.id)) diff --git a/mailtrap/__init__.py b/mailtrap/__init__.py index eb34f7c..03ac017 100644 --- a/mailtrap/__init__.py +++ b/mailtrap/__init__.py @@ -5,6 +5,11 @@ from .exceptions import AuthorizationError from .exceptions import ClientConfigurationError from .exceptions import MailtrapError +from .models.account_templates import CreateTemplateParams +from .models.account_templates import Template +from .models.account_templates import TemplateListParams +from .models.account_templates import TemplateListResponse +from .models.account_templates import UpdateTemplateParams from .models.accounts import AccountAccessFilterParams from .models.api_tokens import ApiTokenResource from .models.api_tokens import CreateApiTokenParams diff --git a/mailtrap/api/account_templates.py b/mailtrap/api/account_templates.py new file mode 100644 index 0000000..cc266a8 --- /dev/null +++ b/mailtrap/api/account_templates.py @@ -0,0 +1,12 @@ +from mailtrap.api.resources.account_templates import AccountTemplatesApi +from mailtrap.http import HttpClient + + +class TemplatesBaseApi: + def __init__(self, client: HttpClient, account_id: str) -> None: + self._account_id = account_id + self._client = client + + @property + def templates(self) -> AccountTemplatesApi: + return AccountTemplatesApi(account_id=self._account_id, client=self._client) diff --git a/mailtrap/api/resources/account_templates.py b/mailtrap/api/resources/account_templates.py new file mode 100644 index 0000000..af003d3 --- /dev/null +++ b/mailtrap/api/resources/account_templates.py @@ -0,0 +1,57 @@ +from typing import Optional + +from mailtrap.http import HttpClient +from mailtrap.models.account_templates import CreateTemplateParams +from mailtrap.models.account_templates import Template +from mailtrap.models.account_templates import TemplateListParams +from mailtrap.models.account_templates import TemplateListResponse +from mailtrap.models.account_templates import TemplateResponse +from mailtrap.models.account_templates import UpdateTemplateParams +from mailtrap.models.common import DeletedObject + + +class AccountTemplatesApi: + def __init__(self, client: HttpClient, account_id: str) -> None: + self._account_id = account_id + self._client = client + + def get_list( + self, params: Optional[TemplateListParams] = None + ) -> TemplateListResponse: + """ + List email templates in the account. ``params`` paginates the result; + omit it for the first page with API defaults. + """ + query_params = params.api_query_params if params else None + response = self._client.get(self._api_path(), params=query_params or None) + return TemplateListResponse(**response) + + def get_by_id(self, template_id: int) -> Template: + """Get an email template by ID.""" + response = self._client.get(self._api_path(template_id)) + return TemplateResponse(**response).data + + def create(self, template_params: CreateTemplateParams) -> Template: + """Create a new email template.""" + response = self._client.post(self._api_path(), json=template_params.api_data) + return TemplateResponse(**response).data + + def update( + self, template_id: int, template_params: UpdateTemplateParams + ) -> Template: + """Update an email template. Only the supplied fields are changed.""" + response = self._client.patch( + self._api_path(template_id), json=template_params.api_data + ) + return TemplateResponse(**response).data + + def delete(self, template_id: int) -> DeletedObject: + """Delete an email template.""" + self._client.delete(self._api_path(template_id)) + return DeletedObject(template_id) + + def _api_path(self, template_id: Optional[int] = None) -> str: + path = f"/api/accounts/{self._account_id}/templates" + if template_id is not None: + return f"{path}/{template_id}" + return path diff --git a/mailtrap/api/templates.py b/mailtrap/api/templates.py index cc2640d..4a8f888 100644 --- a/mailtrap/api/templates.py +++ b/mailtrap/api/templates.py @@ -1,3 +1,5 @@ +import warnings + from mailtrap.api.resources.templates import TemplatesApi from mailtrap.http import HttpClient @@ -9,4 +11,13 @@ def __init__(self, client: HttpClient, account_id: str) -> None: @property def templates(self) -> TemplatesApi: + """ + Deprecated: use ``MailtrapClient.templates_api``, which serves the + paginated ``/api/templates`` endpoints. + """ + warnings.warn( + "EmailTemplatesApi is deprecated; use MailtrapClient.templates_api", + DeprecationWarning, + stacklevel=2, + ) return TemplatesApi(account_id=self._account_id, client=self._client) diff --git a/mailtrap/client.py b/mailtrap/client.py index 520e7b1..d6c675c 100644 --- a/mailtrap/client.py +++ b/mailtrap/client.py @@ -6,6 +6,7 @@ from pydantic import TypeAdapter +from mailtrap.api.account_templates import TemplatesBaseApi from mailtrap.api.company_info import CompanyInfoBaseApi from mailtrap.api.contacts import ContactsBaseApi from mailtrap.api.email_campaigns import EmailCampaignsBaseApi @@ -103,6 +104,14 @@ def email_templates_api(self) -> EmailTemplatesApi: client=HttpClient(host=GENERAL_HOST, headers=self.headers), ) + @property + def templates_api(self) -> TemplatesBaseApi: + self._validate_account_id("Templates API") + return TemplatesBaseApi( + account_id=cast(str, self.account_id), + client=HttpClient(host=GENERAL_HOST, headers=self.headers), + ) + @property def contacts_api(self) -> ContactsBaseApi: self._validate_account_id("Contacts API") diff --git a/mailtrap/models/account_templates.py b/mailtrap/models/account_templates.py new file mode 100644 index 0000000..3f339f4 --- /dev/null +++ b/mailtrap/models/account_templates.py @@ -0,0 +1,88 @@ +"""Models for the account-scoped, paginated Templates API (``/api/templates``).""" + +from typing import Optional + +from pydantic import Field +from pydantic.dataclasses import dataclass + +from mailtrap.models.common import Pagination +from mailtrap.models.common import RequestParams + + +@dataclass +class Template: + """A single email template.""" + + id: int + uuid: Optional[str] = None + name: Optional[str] = None + category: Optional[str] = None + subject: Optional[str] = None + body_html: Optional[str] = None + body_text: Optional[str] = None + created_at: Optional[str] = None + updated_at: Optional[str] = None + + +@dataclass +class TemplateResponse: + """Envelope of a single-template response.""" + + data: Template + + +@dataclass +class TemplateListResponse: + """Paginated response from listing templates.""" + + data: list[Template] = Field(default_factory=list) + pagination: Optional[Pagination] = None + + +@dataclass +class TemplateListParams(RequestParams): + """ + Query params for listing templates. ``token`` is the page number and + ``per_page`` is capped at 100. + """ + + per_page: Optional[int] = None + token: Optional[int] = None + + +@dataclass +class CreateTemplateParams(RequestParams): + """Attributes for creating a template (sent as a flat JSON body).""" + + name: str + subject: str + category: str + body_html: Optional[str] = None + body_text: Optional[str] = None + + +@dataclass +class UpdateTemplateParams(RequestParams): + """ + Attributes for updating a template (sent as a flat JSON body). All fields + are optional, but at least one must be provided. + """ + + name: Optional[str] = None + subject: Optional[str] = None + category: Optional[str] = None + body_html: Optional[str] = None + body_text: Optional[str] = None + + def __post_init__(self) -> None: + if all( + value is None + for value in [ + self.name, + self.subject, + self.category, + self.body_html, + self.body_text, + ] + ): + raise ValueError("At least one field must be provided for update action") diff --git a/tests/unit/api/account_templates/__init__.py b/tests/unit/api/account_templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/api/account_templates/test_account_templates.py b/tests/unit/api/account_templates/test_account_templates.py new file mode 100644 index 0000000..481dc42 --- /dev/null +++ b/tests/unit/api/account_templates/test_account_templates.py @@ -0,0 +1,363 @@ +import json +from typing import Any +from urllib.parse import parse_qs +from urllib.parse import urlparse + +import pytest +import responses + +from mailtrap.api.resources.account_templates import AccountTemplatesApi +from mailtrap.config import GENERAL_HOST +from mailtrap.exceptions import APIError +from mailtrap.http import HttpClient +from mailtrap.models.account_templates import CreateTemplateParams +from mailtrap.models.account_templates import Template +from mailtrap.models.account_templates import TemplateListParams +from mailtrap.models.account_templates import TemplateListResponse +from mailtrap.models.account_templates import UpdateTemplateParams +from mailtrap.models.common import DeletedObject +from tests import conftest + +ACCOUNT_ID = "321" +TEMPLATE_ID = 26730 +BASE_TEMPLATES_URL = f"https://{GENERAL_HOST}/api/accounts/{ACCOUNT_ID}/templates" + + +@pytest.fixture +def client() -> AccountTemplatesApi: + return AccountTemplatesApi(account_id=ACCOUNT_ID, client=HttpClient(GENERAL_HOST)) + + +@pytest.fixture +def sample_template_dict() -> dict[str, Any]: + return { + "id": TEMPLATE_ID, + "uuid": "b81aabcd-1a1e-41cf-91b6-eca0254b3d96", + "name": "Promotion Template", + "category": "Promotion", + "subject": "Promotion Template subject", + "body_html": "
body
", + "body_text": "Text body", + "created_at": "2026-05-01T10:15:00.000Z", + "updated_at": "2026-05-02T09:00:00.000Z", + } + + +class TestAccountTemplatesApi: + + @pytest.mark.parametrize( + "status_code,response_json,expected_error_message", + [ + ( + conftest.UNAUTHORIZED_STATUS_CODE, + conftest.UNAUTHORIZED_RESPONSE, + conftest.UNAUTHORIZED_ERROR_MESSAGE, + ), + ( + conftest.RATE_LIMIT_ERROR_STATUS_CODE, + conftest.RATE_LIMIT_ERROR_RESPONSE, + conftest.RATE_LIMIT_ERROR_MESSAGE, + ), + ( + conftest.VALIDATION_ERRORS_STATUS_CODE, + {"errors": "token is out of range"}, + "token is out of range", + ), + ], + ) + @responses.activate + def test_get_list_should_raise_api_errors( + self, + client: AccountTemplatesApi, + status_code: int, + response_json: dict, + expected_error_message: str, + ) -> None: + responses.get(BASE_TEMPLATES_URL, status=status_code, json=response_json) + + with pytest.raises(APIError) as exc_info: + client.get_list() + + assert expected_error_message in str(exc_info.value) + + @responses.activate + def test_get_list_should_return_templates_and_pagination( + self, client: AccountTemplatesApi, sample_template_dict: dict + ) -> None: + responses.get( + BASE_TEMPLATES_URL, + json={ + "data": [sample_template_dict, {"id": 26731, "name": "Second"}], + "pagination": { + "token": 1, + "prev_token": None, + "next_token": 2, + "first_url": f"{BASE_TEMPLATES_URL}?per_page=50&token=1", + "prev_url": None, + "current_url": f"{BASE_TEMPLATES_URL}?per_page=50&token=1", + "next_url": f"{BASE_TEMPLATES_URL}?per_page=50&token=2", + }, + }, + status=200, + ) + + result = client.get_list() + + assert isinstance(result, TemplateListResponse) + assert all(isinstance(t, Template) for t in result.data) + assert len(result.data) == 2 + assert result.data[0].id == TEMPLATE_ID + assert result.data[0].uuid == "b81aabcd-1a1e-41cf-91b6-eca0254b3d96" + assert result.data[0].body_html == "
body
" + assert result.data[1].name == "Second" + assert result.data[1].body_html is None + assert result.pagination is not None + assert result.pagination.token == 1 + assert result.pagination.prev_token is None + assert result.pagination.next_token == 2 + assert result.pagination.next_url == f"{BASE_TEMPLATES_URL}?per_page=50&token=2" + + @responses.activate + def test_get_list_should_return_empty_list( + self, client: AccountTemplatesApi + ) -> None: + responses.get( + BASE_TEMPLATES_URL, json={"data": [], "pagination": {"token": 1}}, status=200 + ) + + result = client.get_list() + + assert isinstance(result, TemplateListResponse) + assert result.data == [] + + @responses.activate + def test_get_list_should_send_per_page_and_token_query_params( + self, client: AccountTemplatesApi + ) -> None: + responses.get(BASE_TEMPLATES_URL, json={"data": [], "pagination": {}}, status=200) + + client.get_list(TemplateListParams(per_page=25, token=2)) + + query = parse_qs(urlparse(responses.calls[0].request.url).query) + assert query["per_page"] == ["25"] + assert query["token"] == ["2"] + + @responses.activate + def test_get_list_should_send_no_query_params_by_default( + self, client: AccountTemplatesApi + ) -> None: + responses.get(BASE_TEMPLATES_URL, json={"data": [], "pagination": {}}, status=200) + + client.get_list() + + assert urlparse(responses.calls[0].request.url).query == "" + + @pytest.mark.parametrize( + "status_code,response_json,expected_error_message", + [ + ( + conftest.UNAUTHORIZED_STATUS_CODE, + conftest.UNAUTHORIZED_RESPONSE, + conftest.UNAUTHORIZED_ERROR_MESSAGE, + ), + ( + conftest.NOT_FOUND_STATUS_CODE, + conftest.NOT_FOUND_RESPONSE, + conftest.NOT_FOUND_ERROR_MESSAGE, + ), + ], + ) + @responses.activate + def test_get_by_id_should_raise_api_errors( + self, + client: AccountTemplatesApi, + status_code: int, + response_json: dict, + expected_error_message: str, + ) -> None: + responses.get( + f"{BASE_TEMPLATES_URL}/{TEMPLATE_ID}", + status=status_code, + json=response_json, + ) + + with pytest.raises(APIError) as exc_info: + client.get_by_id(TEMPLATE_ID) + + assert expected_error_message in str(exc_info.value) + + @responses.activate + def test_get_by_id_should_unwrap_data_envelope( + self, client: AccountTemplatesApi, sample_template_dict: dict + ) -> None: + responses.get( + f"{BASE_TEMPLATES_URL}/{TEMPLATE_ID}", + json={"data": sample_template_dict}, + status=200, + ) + + template = client.get_by_id(TEMPLATE_ID) + + assert isinstance(template, Template) + assert template.id == TEMPLATE_ID + assert template.name == "Promotion Template" + assert template.category == "Promotion" + assert template.subject == "Promotion Template subject" + assert template.body_text == "Text body" + assert template.created_at == "2026-05-01T10:15:00.000Z" + + @pytest.mark.parametrize( + "status_code,response_json,expected_error_message", + [ + ( + conftest.UNAUTHORIZED_STATUS_CODE, + conftest.UNAUTHORIZED_RESPONSE, + conftest.UNAUTHORIZED_ERROR_MESSAGE, + ), + ( + conftest.VALIDATION_ERRORS_STATUS_CODE, + {"errors": {"name": ["can't be blank"]}}, + "name: can't be blank", + ), + ], + ) + @responses.activate + def test_create_should_raise_api_errors( + self, + client: AccountTemplatesApi, + status_code: int, + response_json: dict, + expected_error_message: str, + ) -> None: + responses.post(BASE_TEMPLATES_URL, status=status_code, json=response_json) + + with pytest.raises(APIError) as exc_info: + client.create(CreateTemplateParams(name="", subject="s", category="c")) + + assert expected_error_message in str(exc_info.value) + + @responses.activate + def test_create_should_send_flat_body_and_unwrap_response( + self, client: AccountTemplatesApi, sample_template_dict: dict + ) -> None: + responses.post( + BASE_TEMPLATES_URL, json={"data": sample_template_dict}, status=201 + ) + + template = client.create( + CreateTemplateParams( + name="Promotion Template", + subject="Promotion Template subject", + category="Promotion", + body_html="
body
", + ) + ) + + assert isinstance(template, Template) + assert template.id == TEMPLATE_ID + assert json.loads(responses.calls[0].request.body) == { + "name": "Promotion Template", + "subject": "Promotion Template subject", + "category": "Promotion", + "body_html": "
body
", + } + + @pytest.mark.parametrize( + "status_code,response_json,expected_error_message", + [ + ( + conftest.UNAUTHORIZED_STATUS_CODE, + conftest.UNAUTHORIZED_RESPONSE, + conftest.UNAUTHORIZED_ERROR_MESSAGE, + ), + ( + conftest.NOT_FOUND_STATUS_CODE, + conftest.NOT_FOUND_RESPONSE, + conftest.NOT_FOUND_ERROR_MESSAGE, + ), + ( + conftest.VALIDATION_ERRORS_STATUS_CODE, + {"errors": {"subject": ["can't be blank"]}}, + "subject: can't be blank", + ), + ], + ) + @responses.activate + def test_update_should_raise_api_errors( + self, + client: AccountTemplatesApi, + status_code: int, + response_json: dict, + expected_error_message: str, + ) -> None: + responses.patch( + f"{BASE_TEMPLATES_URL}/{TEMPLATE_ID}", + status=status_code, + json=response_json, + ) + + with pytest.raises(APIError) as exc_info: + client.update(TEMPLATE_ID, UpdateTemplateParams(subject="")) + + assert expected_error_message in str(exc_info.value) + + @responses.activate + def test_update_should_patch_flat_body_and_unwrap_response( + self, client: AccountTemplatesApi, sample_template_dict: dict + ) -> None: + responses.patch( + f"{BASE_TEMPLATES_URL}/{TEMPLATE_ID}", + json={"data": {**sample_template_dict, "name": "Renamed"}}, + status=200, + ) + + template = client.update(TEMPLATE_ID, UpdateTemplateParams(name="Renamed")) + + assert isinstance(template, Template) + assert template.name == "Renamed" + assert json.loads(responses.calls[0].request.body) == {"name": "Renamed"} + + @pytest.mark.parametrize( + "status_code,response_json,expected_error_message", + [ + ( + conftest.UNAUTHORIZED_STATUS_CODE, + conftest.UNAUTHORIZED_RESPONSE, + conftest.UNAUTHORIZED_ERROR_MESSAGE, + ), + ( + conftest.NOT_FOUND_STATUS_CODE, + conftest.NOT_FOUND_RESPONSE, + conftest.NOT_FOUND_ERROR_MESSAGE, + ), + ], + ) + @responses.activate + def test_delete_should_raise_api_errors( + self, + client: AccountTemplatesApi, + status_code: int, + response_json: dict, + expected_error_message: str, + ) -> None: + responses.delete( + f"{BASE_TEMPLATES_URL}/{TEMPLATE_ID}", + status=status_code, + json=response_json, + ) + + with pytest.raises(APIError) as exc_info: + client.delete(TEMPLATE_ID) + + assert expected_error_message in str(exc_info.value) + + @responses.activate + def test_delete_should_return_deleted_object( + self, client: AccountTemplatesApi + ) -> None: + responses.delete(f"{BASE_TEMPLATES_URL}/{TEMPLATE_ID}", status=204) + + result = client.delete(TEMPLATE_ID) + + assert isinstance(result, DeletedObject) + assert result.id == TEMPLATE_ID diff --git a/tests/unit/api/email_templates/test_deprecation.py b/tests/unit/api/email_templates/test_deprecation.py new file mode 100644 index 0000000..c69a52f --- /dev/null +++ b/tests/unit/api/email_templates/test_deprecation.py @@ -0,0 +1,12 @@ +import pytest + +from mailtrap.api.templates import EmailTemplatesApi +from mailtrap.config import GENERAL_HOST +from mailtrap.http import HttpClient + + +def test_templates_should_warn_that_the_group_is_deprecated() -> None: + api = EmailTemplatesApi(account_id="321", client=HttpClient(GENERAL_HOST)) + + with pytest.warns(DeprecationWarning, match="MailtrapClient.templates_api"): + _ = api.templates diff --git a/tests/unit/models/test_account_templates.py b/tests/unit/models/test_account_templates.py new file mode 100644 index 0000000..c9a0812 --- /dev/null +++ b/tests/unit/models/test_account_templates.py @@ -0,0 +1,52 @@ +import pytest + +from mailtrap.models.account_templates import CreateTemplateParams +from mailtrap.models.account_templates import TemplateListParams +from mailtrap.models.account_templates import UpdateTemplateParams + + +class TestTemplateListParams: + def test_api_query_params_should_drop_unset_fields(self) -> None: + assert TemplateListParams(per_page=10).api_query_params == {"per_page": 10} + + def test_api_query_params_should_include_all_fields(self) -> None: + params = TemplateListParams(per_page=10, token=2) + assert params.api_query_params == {"per_page": 10, "token": 2} + + +class TestCreateTemplateParams: + def test_api_data_should_return_dict_with_required_props_only(self) -> None: + entity = CreateTemplateParams(name="test", subject="test", category="test") + assert entity.api_data == { + "name": "test", + "subject": "test", + "category": "test", + } + + def test_api_data_should_return_dict_with_all_props(self) -> None: + entity = CreateTemplateParams( + name="test", + subject="test", + category="test", + body_html="

test

", + body_text="test", + ) + assert entity.api_data == { + "name": "test", + "subject": "test", + "category": "test", + "body_html": "

test

", + "body_text": "test", + } + + +class TestUpdateTemplateParams: + def test_raise_error_when_all_fields_are_missing(self) -> None: + with pytest.raises(ValueError) as exc: + _ = UpdateTemplateParams() + + assert "At least one field must be provided for update action" in str(exc) + + def test_api_data_should_return_only_provided_props(self) -> None: + entity = UpdateTemplateParams(name="test", body_text="text") + assert entity.api_data == {"name": "test", "body_text": "text"} diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 02fb138..589910b 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -63,6 +63,18 @@ def test_webhooks_api_requires_account_id(self) -> None: assert "`account_id` is required for Webhooks API" in str(exc_info.value) + def test_templates_api_requires_account_id(self) -> None: + client = self.get_client() + with pytest.raises(mt.ClientConfigurationError) as exc_info: + _ = client.templates_api + + assert "`account_id` is required for Templates API" in str(exc_info.value) + + def test_templates_api_exposes_templates(self) -> None: + client = self.get_client(account_id="321") + + assert client.templates_api.templates is not None + def test_email_campaigns_api_does_not_require_account_id(self) -> None: client = self.get_client() From a7508e0e255c59c76c64d3ce1184ef79a989715b Mon Sep 17 00:00:00 2001 From: IzikAJ Date: Mon, 5 Oct 2026 17:22:41 +0300 Subject: [PATCH 2/5] Drop the example package marker the sibling example dirs do not have --- examples/account_templates/__init__.py | 0 tests/unit/test_client.py | 5 ----- 2 files changed, 5 deletions(-) delete mode 100644 examples/account_templates/__init__.py diff --git a/examples/account_templates/__init__.py b/examples/account_templates/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 589910b..9178089 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -70,11 +70,6 @@ def test_templates_api_requires_account_id(self) -> None: assert "`account_id` is required for Templates API" in str(exc_info.value) - def test_templates_api_exposes_templates(self) -> None: - client = self.get_client(account_id="321") - - assert client.templates_api.templates is not None - def test_email_campaigns_api_does_not_require_account_id(self) -> None: client = self.get_client() From ece062e09ae8c9b2081df70f8011819457c7281a Mon Sep 17 00:00:00 2001 From: IzikAJ Date: Tue, 6 Oct 2026 10:08:35 +0300 Subject: [PATCH 3/5] Note that the /api/templates endpoints are experimental --- README.md | 2 +- mailtrap/api/resources/account_templates.py | 9 ++++++--- .../unit/api/account_templates/test_account_templates.py | 4 +--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 16a8be9..2cc5fa5 100644 --- a/README.md +++ b/README.md @@ -239,7 +239,7 @@ The same situation applies to both `client.batch_send()` and `client.sending_api - Contact Imports – [`contacts/contact_imports.py`](examples/contacts/contact_imports.py) ### Email Templates API: -- Templates (paginated, `/api/templates`) – [`account_templates/templates.py`](examples/account_templates/templates.py) +- Templates (experimental, `/api/templates`) – [`account_templates/templates.py`](examples/account_templates/templates.py) - Templates management (deprecated, use Templates above) – [`email_templates/templates.py`](examples/email_templates/templates.py) ### Sending Domains API: diff --git a/mailtrap/api/resources/account_templates.py b/mailtrap/api/resources/account_templates.py index af003d3..97b3e39 100644 --- a/mailtrap/api/resources/account_templates.py +++ b/mailtrap/api/resources/account_templates.py @@ -11,6 +11,11 @@ class AccountTemplatesApi: + """ + Templates API. The ``/api/templates`` endpoints are experimental: their + request and response shapes may change before general availability. + """ + def __init__(self, client: HttpClient, account_id: str) -> None: self._account_id = account_id self._client = client @@ -36,9 +41,7 @@ def create(self, template_params: CreateTemplateParams) -> Template: response = self._client.post(self._api_path(), json=template_params.api_data) return TemplateResponse(**response).data - def update( - self, template_id: int, template_params: UpdateTemplateParams - ) -> Template: + def update(self, template_id: int, template_params: UpdateTemplateParams) -> Template: """Update an email template. Only the supplied fields are changed.""" response = self._client.patch( self._api_path(template_id), json=template_params.api_data diff --git a/tests/unit/api/account_templates/test_account_templates.py b/tests/unit/api/account_templates/test_account_templates.py index 481dc42..2fd1825 100644 --- a/tests/unit/api/account_templates/test_account_templates.py +++ b/tests/unit/api/account_templates/test_account_templates.py @@ -118,9 +118,7 @@ def test_get_list_should_return_templates_and_pagination( assert result.pagination.next_url == f"{BASE_TEMPLATES_URL}?per_page=50&token=2" @responses.activate - def test_get_list_should_return_empty_list( - self, client: AccountTemplatesApi - ) -> None: + def test_get_list_should_return_empty_list(self, client: AccountTemplatesApi) -> None: responses.get( BASE_TEMPLATES_URL, json={"data": [], "pagination": {"token": 1}}, status=200 ) From 05c1d03af8d343f05b7c8380c34cbb51d9e8c4f9 Mon Sep 17 00:00:00 2001 From: IzikAJ Date: Tue, 6 Oct 2026 11:31:05 +0300 Subject: [PATCH 4/5] fix: name templates_api.templates in the deprecation warning --- mailtrap/api/templates.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mailtrap/api/templates.py b/mailtrap/api/templates.py index 4a8f888..1f6507f 100644 --- a/mailtrap/api/templates.py +++ b/mailtrap/api/templates.py @@ -12,11 +12,11 @@ def __init__(self, client: HttpClient, account_id: str) -> None: @property def templates(self) -> TemplatesApi: """ - Deprecated: use ``MailtrapClient.templates_api``, which serves the - paginated ``/api/templates`` endpoints. + Deprecated: use ``MailtrapClient.templates_api.templates``, which + serves the paginated ``/api/templates`` endpoints. """ warnings.warn( - "EmailTemplatesApi is deprecated; use MailtrapClient.templates_api", + "EmailTemplatesApi is deprecated; use MailtrapClient.templates_api.templates", DeprecationWarning, stacklevel=2, ) From c0395103db082d29467e2c5eb8a6bb3f6a0cb19a Mon Sep 17 00:00:00 2001 From: IzikAJ Date: Tue, 6 Oct 2026 17:16:18 +0300 Subject: [PATCH 5/5] Hold the email templates deprecation and rename to paginated_templates - Remove the DeprecationWarning on email_templates_api.templates and the README "deprecated" label. /api/templates is still experimental, and the public spec still calls /api/email_templates the stable surface. - Rename the account_templates modules and AccountTemplatesApi to paginated_templates and PaginatedTemplatesApi. The old surface is just as account-scoped; the paginated data-envelope contract is what differs. - Document that get_list returns one page and that the next page needs the same per_page. --- README.md | 4 +- .../templates.py | 4 +- mailtrap/__init__.py | 10 ++-- ...nt_templates.py => paginated_templates.py} | 6 +-- ...nt_templates.py => paginated_templates.py} | 20 ++++---- mailtrap/api/templates.py | 11 ----- mailtrap/client.py | 2 +- ...nt_templates.py => paginated_templates.py} | 0 .../api/email_templates/test_deprecation.py | 12 ----- .../__init__.py | 0 .../test_paginated_templates.py} | 46 ++++++++++--------- ...mplates.py => test_paginated_templates.py} | 6 +-- 12 files changed, 51 insertions(+), 70 deletions(-) rename examples/{account_templates => paginated_templates}/templates.py (94%) rename mailtrap/api/{account_templates.py => paginated_templates.py} (50%) rename mailtrap/api/resources/{account_templates.py => paginated_templates.py} (73%) rename mailtrap/models/{account_templates.py => paginated_templates.py} (100%) delete mode 100644 tests/unit/api/email_templates/test_deprecation.py rename tests/unit/api/{account_templates => paginated_templates}/__init__.py (100%) rename tests/unit/api/{account_templates/test_account_templates.py => paginated_templates/test_paginated_templates.py} (89%) rename tests/unit/models/{test_account_templates.py => test_paginated_templates.py} (89%) diff --git a/README.md b/README.md index 2cc5fa5..2d196ca 100644 --- a/README.md +++ b/README.md @@ -239,8 +239,8 @@ The same situation applies to both `client.batch_send()` and `client.sending_api - Contact Imports – [`contacts/contact_imports.py`](examples/contacts/contact_imports.py) ### Email Templates API: -- Templates (experimental, `/api/templates`) – [`account_templates/templates.py`](examples/account_templates/templates.py) -- Templates management (deprecated, use Templates above) – [`email_templates/templates.py`](examples/email_templates/templates.py) +- Templates (experimental, `/api/templates`) – [`paginated_templates/templates.py`](examples/paginated_templates/templates.py) +- Templates management – [`email_templates/templates.py`](examples/email_templates/templates.py) ### Sending Domains API: - Sending Domains – [`sending_domains/sending_domains.py`](examples/sending_domains/sending_domains.py) diff --git a/examples/account_templates/templates.py b/examples/paginated_templates/templates.py similarity index 94% rename from examples/account_templates/templates.py rename to examples/paginated_templates/templates.py index 820ed48..bd99886 100644 --- a/examples/account_templates/templates.py +++ b/examples/paginated_templates/templates.py @@ -2,9 +2,9 @@ from typing import Optional import mailtrap as mt -from mailtrap.models.account_templates import Template -from mailtrap.models.account_templates import TemplateListResponse from mailtrap.models.common import DeletedObject +from mailtrap.models.paginated_templates import Template +from mailtrap.models.paginated_templates import TemplateListResponse API_KEY = os.environ["MAILTRAP_API_KEY"] ACCOUNT_ID = os.environ["MAILTRAP_ACCOUNT_ID"] diff --git a/mailtrap/__init__.py b/mailtrap/__init__.py index 03ac017..96015ce 100644 --- a/mailtrap/__init__.py +++ b/mailtrap/__init__.py @@ -5,11 +5,6 @@ from .exceptions import AuthorizationError from .exceptions import ClientConfigurationError from .exceptions import MailtrapError -from .models.account_templates import CreateTemplateParams -from .models.account_templates import Template -from .models.account_templates import TemplateListParams -from .models.account_templates import TemplateListResponse -from .models.account_templates import UpdateTemplateParams from .models.accounts import AccountAccessFilterParams from .models.api_tokens import ApiTokenResource from .models.api_tokens import CreateApiTokenParams @@ -63,6 +58,11 @@ from .models.mail import MailFromTemplate from .models.messages import UpdateEmailMessageParams from .models.organizations import CreateSubAccountParams +from .models.paginated_templates import CreateTemplateParams +from .models.paginated_templates import Template +from .models.paginated_templates import TemplateListParams +from .models.paginated_templates import TemplateListResponse +from .models.paginated_templates import UpdateTemplateParams from .models.permissions import PermissionResourceParams from .models.projects import ProjectParams from .models.sending_domains import CreateSendingDomainParams diff --git a/mailtrap/api/account_templates.py b/mailtrap/api/paginated_templates.py similarity index 50% rename from mailtrap/api/account_templates.py rename to mailtrap/api/paginated_templates.py index cc266a8..4ad88a3 100644 --- a/mailtrap/api/account_templates.py +++ b/mailtrap/api/paginated_templates.py @@ -1,4 +1,4 @@ -from mailtrap.api.resources.account_templates import AccountTemplatesApi +from mailtrap.api.resources.paginated_templates import PaginatedTemplatesApi from mailtrap.http import HttpClient @@ -8,5 +8,5 @@ def __init__(self, client: HttpClient, account_id: str) -> None: self._client = client @property - def templates(self) -> AccountTemplatesApi: - return AccountTemplatesApi(account_id=self._account_id, client=self._client) + def templates(self) -> PaginatedTemplatesApi: + return PaginatedTemplatesApi(account_id=self._account_id, client=self._client) diff --git a/mailtrap/api/resources/account_templates.py b/mailtrap/api/resources/paginated_templates.py similarity index 73% rename from mailtrap/api/resources/account_templates.py rename to mailtrap/api/resources/paginated_templates.py index 97b3e39..8d2ce75 100644 --- a/mailtrap/api/resources/account_templates.py +++ b/mailtrap/api/resources/paginated_templates.py @@ -1,16 +1,16 @@ from typing import Optional from mailtrap.http import HttpClient -from mailtrap.models.account_templates import CreateTemplateParams -from mailtrap.models.account_templates import Template -from mailtrap.models.account_templates import TemplateListParams -from mailtrap.models.account_templates import TemplateListResponse -from mailtrap.models.account_templates import TemplateResponse -from mailtrap.models.account_templates import UpdateTemplateParams from mailtrap.models.common import DeletedObject +from mailtrap.models.paginated_templates import CreateTemplateParams +from mailtrap.models.paginated_templates import Template +from mailtrap.models.paginated_templates import TemplateListParams +from mailtrap.models.paginated_templates import TemplateListResponse +from mailtrap.models.paginated_templates import TemplateResponse +from mailtrap.models.paginated_templates import UpdateTemplateParams -class AccountTemplatesApi: +class PaginatedTemplatesApi: """ Templates API. The ``/api/templates`` endpoints are experimental: their request and response shapes may change before general availability. @@ -24,8 +24,10 @@ def get_list( self, params: Optional[TemplateListParams] = None ) -> TemplateListResponse: """ - List email templates in the account. ``params`` paginates the result; - omit it for the first page with API defaults. + List email templates in the account, one page at a time. Unlike the + ``email_templates_api`` list, it does not return every template: pass + ``pagination.next_token`` with the same ``per_page`` to get the next + page. Omit ``params`` for the first page with API defaults. """ query_params = params.api_query_params if params else None response = self._client.get(self._api_path(), params=query_params or None) diff --git a/mailtrap/api/templates.py b/mailtrap/api/templates.py index 1f6507f..cc2640d 100644 --- a/mailtrap/api/templates.py +++ b/mailtrap/api/templates.py @@ -1,5 +1,3 @@ -import warnings - from mailtrap.api.resources.templates import TemplatesApi from mailtrap.http import HttpClient @@ -11,13 +9,4 @@ def __init__(self, client: HttpClient, account_id: str) -> None: @property def templates(self) -> TemplatesApi: - """ - Deprecated: use ``MailtrapClient.templates_api.templates``, which - serves the paginated ``/api/templates`` endpoints. - """ - warnings.warn( - "EmailTemplatesApi is deprecated; use MailtrapClient.templates_api.templates", - DeprecationWarning, - stacklevel=2, - ) return TemplatesApi(account_id=self._account_id, client=self._client) diff --git a/mailtrap/client.py b/mailtrap/client.py index d6c675c..85e6236 100644 --- a/mailtrap/client.py +++ b/mailtrap/client.py @@ -6,7 +6,6 @@ from pydantic import TypeAdapter -from mailtrap.api.account_templates import TemplatesBaseApi from mailtrap.api.company_info import CompanyInfoBaseApi from mailtrap.api.contacts import ContactsBaseApi from mailtrap.api.email_campaigns import EmailCampaignsBaseApi @@ -14,6 +13,7 @@ from mailtrap.api.general import GeneralApi from mailtrap.api.inbound import InboundBaseApi from mailtrap.api.organizations import OrganizationsBaseApi +from mailtrap.api.paginated_templates import TemplatesBaseApi from mailtrap.api.resources.stats import StatsApi from mailtrap.api.sending import SendingApi from mailtrap.api.sending_domains import SendingDomainsBaseApi diff --git a/mailtrap/models/account_templates.py b/mailtrap/models/paginated_templates.py similarity index 100% rename from mailtrap/models/account_templates.py rename to mailtrap/models/paginated_templates.py diff --git a/tests/unit/api/email_templates/test_deprecation.py b/tests/unit/api/email_templates/test_deprecation.py deleted file mode 100644 index c69a52f..0000000 --- a/tests/unit/api/email_templates/test_deprecation.py +++ /dev/null @@ -1,12 +0,0 @@ -import pytest - -from mailtrap.api.templates import EmailTemplatesApi -from mailtrap.config import GENERAL_HOST -from mailtrap.http import HttpClient - - -def test_templates_should_warn_that_the_group_is_deprecated() -> None: - api = EmailTemplatesApi(account_id="321", client=HttpClient(GENERAL_HOST)) - - with pytest.warns(DeprecationWarning, match="MailtrapClient.templates_api"): - _ = api.templates diff --git a/tests/unit/api/account_templates/__init__.py b/tests/unit/api/paginated_templates/__init__.py similarity index 100% rename from tests/unit/api/account_templates/__init__.py rename to tests/unit/api/paginated_templates/__init__.py diff --git a/tests/unit/api/account_templates/test_account_templates.py b/tests/unit/api/paginated_templates/test_paginated_templates.py similarity index 89% rename from tests/unit/api/account_templates/test_account_templates.py rename to tests/unit/api/paginated_templates/test_paginated_templates.py index 2fd1825..409360a 100644 --- a/tests/unit/api/account_templates/test_account_templates.py +++ b/tests/unit/api/paginated_templates/test_paginated_templates.py @@ -6,16 +6,16 @@ import pytest import responses -from mailtrap.api.resources.account_templates import AccountTemplatesApi +from mailtrap.api.resources.paginated_templates import PaginatedTemplatesApi from mailtrap.config import GENERAL_HOST from mailtrap.exceptions import APIError from mailtrap.http import HttpClient -from mailtrap.models.account_templates import CreateTemplateParams -from mailtrap.models.account_templates import Template -from mailtrap.models.account_templates import TemplateListParams -from mailtrap.models.account_templates import TemplateListResponse -from mailtrap.models.account_templates import UpdateTemplateParams from mailtrap.models.common import DeletedObject +from mailtrap.models.paginated_templates import CreateTemplateParams +from mailtrap.models.paginated_templates import Template +from mailtrap.models.paginated_templates import TemplateListParams +from mailtrap.models.paginated_templates import TemplateListResponse +from mailtrap.models.paginated_templates import UpdateTemplateParams from tests import conftest ACCOUNT_ID = "321" @@ -24,8 +24,8 @@ @pytest.fixture -def client() -> AccountTemplatesApi: - return AccountTemplatesApi(account_id=ACCOUNT_ID, client=HttpClient(GENERAL_HOST)) +def client() -> PaginatedTemplatesApi: + return PaginatedTemplatesApi(account_id=ACCOUNT_ID, client=HttpClient(GENERAL_HOST)) @pytest.fixture @@ -43,7 +43,7 @@ def sample_template_dict() -> dict[str, Any]: } -class TestAccountTemplatesApi: +class TestPaginatedTemplatesApi: @pytest.mark.parametrize( "status_code,response_json,expected_error_message", @@ -68,7 +68,7 @@ class TestAccountTemplatesApi: @responses.activate def test_get_list_should_raise_api_errors( self, - client: AccountTemplatesApi, + client: PaginatedTemplatesApi, status_code: int, response_json: dict, expected_error_message: str, @@ -82,7 +82,7 @@ def test_get_list_should_raise_api_errors( @responses.activate def test_get_list_should_return_templates_and_pagination( - self, client: AccountTemplatesApi, sample_template_dict: dict + self, client: PaginatedTemplatesApi, sample_template_dict: dict ) -> None: responses.get( BASE_TEMPLATES_URL, @@ -118,7 +118,9 @@ def test_get_list_should_return_templates_and_pagination( assert result.pagination.next_url == f"{BASE_TEMPLATES_URL}?per_page=50&token=2" @responses.activate - def test_get_list_should_return_empty_list(self, client: AccountTemplatesApi) -> None: + def test_get_list_should_return_empty_list( + self, client: PaginatedTemplatesApi + ) -> None: responses.get( BASE_TEMPLATES_URL, json={"data": [], "pagination": {"token": 1}}, status=200 ) @@ -130,7 +132,7 @@ def test_get_list_should_return_empty_list(self, client: AccountTemplatesApi) -> @responses.activate def test_get_list_should_send_per_page_and_token_query_params( - self, client: AccountTemplatesApi + self, client: PaginatedTemplatesApi ) -> None: responses.get(BASE_TEMPLATES_URL, json={"data": [], "pagination": {}}, status=200) @@ -142,7 +144,7 @@ def test_get_list_should_send_per_page_and_token_query_params( @responses.activate def test_get_list_should_send_no_query_params_by_default( - self, client: AccountTemplatesApi + self, client: PaginatedTemplatesApi ) -> None: responses.get(BASE_TEMPLATES_URL, json={"data": [], "pagination": {}}, status=200) @@ -168,7 +170,7 @@ def test_get_list_should_send_no_query_params_by_default( @responses.activate def test_get_by_id_should_raise_api_errors( self, - client: AccountTemplatesApi, + client: PaginatedTemplatesApi, status_code: int, response_json: dict, expected_error_message: str, @@ -186,7 +188,7 @@ def test_get_by_id_should_raise_api_errors( @responses.activate def test_get_by_id_should_unwrap_data_envelope( - self, client: AccountTemplatesApi, sample_template_dict: dict + self, client: PaginatedTemplatesApi, sample_template_dict: dict ) -> None: responses.get( f"{BASE_TEMPLATES_URL}/{TEMPLATE_ID}", @@ -222,7 +224,7 @@ def test_get_by_id_should_unwrap_data_envelope( @responses.activate def test_create_should_raise_api_errors( self, - client: AccountTemplatesApi, + client: PaginatedTemplatesApi, status_code: int, response_json: dict, expected_error_message: str, @@ -236,7 +238,7 @@ def test_create_should_raise_api_errors( @responses.activate def test_create_should_send_flat_body_and_unwrap_response( - self, client: AccountTemplatesApi, sample_template_dict: dict + self, client: PaginatedTemplatesApi, sample_template_dict: dict ) -> None: responses.post( BASE_TEMPLATES_URL, json={"data": sample_template_dict}, status=201 @@ -283,7 +285,7 @@ def test_create_should_send_flat_body_and_unwrap_response( @responses.activate def test_update_should_raise_api_errors( self, - client: AccountTemplatesApi, + client: PaginatedTemplatesApi, status_code: int, response_json: dict, expected_error_message: str, @@ -301,7 +303,7 @@ def test_update_should_raise_api_errors( @responses.activate def test_update_should_patch_flat_body_and_unwrap_response( - self, client: AccountTemplatesApi, sample_template_dict: dict + self, client: PaginatedTemplatesApi, sample_template_dict: dict ) -> None: responses.patch( f"{BASE_TEMPLATES_URL}/{TEMPLATE_ID}", @@ -333,7 +335,7 @@ def test_update_should_patch_flat_body_and_unwrap_response( @responses.activate def test_delete_should_raise_api_errors( self, - client: AccountTemplatesApi, + client: PaginatedTemplatesApi, status_code: int, response_json: dict, expected_error_message: str, @@ -351,7 +353,7 @@ def test_delete_should_raise_api_errors( @responses.activate def test_delete_should_return_deleted_object( - self, client: AccountTemplatesApi + self, client: PaginatedTemplatesApi ) -> None: responses.delete(f"{BASE_TEMPLATES_URL}/{TEMPLATE_ID}", status=204) diff --git a/tests/unit/models/test_account_templates.py b/tests/unit/models/test_paginated_templates.py similarity index 89% rename from tests/unit/models/test_account_templates.py rename to tests/unit/models/test_paginated_templates.py index c9a0812..866d197 100644 --- a/tests/unit/models/test_account_templates.py +++ b/tests/unit/models/test_paginated_templates.py @@ -1,8 +1,8 @@ import pytest -from mailtrap.models.account_templates import CreateTemplateParams -from mailtrap.models.account_templates import TemplateListParams -from mailtrap.models.account_templates import UpdateTemplateParams +from mailtrap.models.paginated_templates import CreateTemplateParams +from mailtrap.models.paginated_templates import TemplateListParams +from mailtrap.models.paginated_templates import UpdateTemplateParams class TestTemplateListParams: