From 6e002ac98eb8fd6b683691aa55ef0c09387863be Mon Sep 17 00:00:00 2001 From: Ihor Bilous Date: Fri, 22 Aug 2025 16:36:56 +0300 Subject: [PATCH 1/2] Fix issue #19: Add ContactFieldsApi, related models, tests, examples --- examples/contacts/contact_fields.py | 49 ++++ mailtrap/__init__.py | 3 + mailtrap/api/contacts.py | 12 + mailtrap/api/resources/contact_fields.py | 43 +++ mailtrap/client.py | 9 + mailtrap/models/contacts.py | 30 ++ tests/unit/api/test_contacts.py | 337 +++++++++++++++++++++++ tests/unit/models/test_contacts.py | 52 ++++ 8 files changed, 535 insertions(+) create mode 100644 examples/contacts/contact_fields.py create mode 100644 mailtrap/api/contacts.py create mode 100644 mailtrap/api/resources/contact_fields.py create mode 100644 mailtrap/models/contacts.py create mode 100644 tests/unit/api/test_contacts.py create mode 100644 tests/unit/models/test_contacts.py diff --git a/examples/contacts/contact_fields.py b/examples/contacts/contact_fields.py new file mode 100644 index 0000000..d704d5b --- /dev/null +++ b/examples/contacts/contact_fields.py @@ -0,0 +1,49 @@ +from typing import Optional + +import mailtrap as mt +from mailtrap.models.common import DeletedObject +from mailtrap.models.contacts import ContactField + +API_TOKEN = "YOU_API_TOKEN" +ACCOUNT_ID = "YOU_ACCOUNT_ID" + +client = mt.MailtrapClient(token=API_TOKEN, account_id=ACCOUNT_ID) +contact_fields_api = client.contacts_api.contact_fields + + +def create_contact_field( + name: str, + data_type: str, + merge_tag: str, +) -> ContactField: + params = mt.CreateContactFieldParams( + name=name, + data_type=data_type, + merge_tag=merge_tag, + ) + return contact_fields_api.create(params) + + +def update_contact_field( + contact_field_id: str, + name: Optional[str] = None, + merge_tag: Optional[str] = None, +) -> ContactField: + params = mt.UpdateContactFieldParams(name=name, merge_tag=merge_tag) + return contact_fields_api.update(contact_field_id, params) + + +def list_contact_fields() -> list[ContactField]: + return contact_fields_api.get_list() + + +def get_contact_field(contact_field_id: str) -> ContactField: + return contact_fields_api.get_by_id(contact_field_id) + + +def delete_contact_field(contact_field_id: str) -> DeletedObject: + return contact_fields_api.delete(contact_field_id) + + +if __name__ == "__main__": + print(list_contact_fields()) diff --git a/mailtrap/__init__.py b/mailtrap/__init__.py index 009f79a..14c8d21 100644 --- a/mailtrap/__init__.py +++ b/mailtrap/__init__.py @@ -4,6 +4,9 @@ from .exceptions import AuthorizationError from .exceptions import ClientConfigurationError from .exceptions import MailtrapError +from .models.contacts import ContactField +from .models.contacts import CreateContactFieldParams +from .models.contacts import UpdateContactFieldParams from .models.mail import Address from .models.mail import Attachment from .models.mail import BaseMail diff --git a/mailtrap/api/contacts.py b/mailtrap/api/contacts.py new file mode 100644 index 0000000..cb67eac --- /dev/null +++ b/mailtrap/api/contacts.py @@ -0,0 +1,12 @@ +from mailtrap.api.resources.contact_fields import ContactFieldsApi +from mailtrap.http import HttpClient + + +class ContactsBaseApi: + def __init__(self, client: HttpClient, account_id: str) -> None: + self._account_id = account_id + self._client = client + + @property + def contact_fields(self) -> ContactFieldsApi: + return ContactFieldsApi(account_id=self._account_id, client=self._client) diff --git a/mailtrap/api/resources/contact_fields.py b/mailtrap/api/resources/contact_fields.py new file mode 100644 index 0000000..ccb27cd --- /dev/null +++ b/mailtrap/api/resources/contact_fields.py @@ -0,0 +1,43 @@ +from mailtrap.http import HttpClient +from mailtrap.models.common import DeletedObject +from mailtrap.models.contacts import ContactField +from mailtrap.models.contacts import CreateContactFieldParams +from mailtrap.models.contacts import UpdateContactFieldParams + + +class ContactFieldsApi: + def __init__(self, client: HttpClient, account_id: str) -> None: + self._account_id = account_id + self._client = client + + def get_list(self) -> list[ContactField]: + response = self._client.get(f"/api/accounts/{self._account_id}/contacts/fields") + return [ContactField(**field) for field in response] + + def get_by_id(self, field_id: int) -> ContactField: + response = self._client.get( + f"/api/accounts/{self._account_id}/contacts/fields/{field_id}" + ) + return ContactField(**response) + + def create(self, field_params: CreateContactFieldParams) -> ContactField: + response = self._client.post( + f"/api/accounts/{self._account_id}/contacts/fields", + json=field_params.api_data, + ) + return ContactField(**response) + + def update( + self, field_id: int, field_params: UpdateContactFieldParams + ) -> ContactField: + response = self._client.patch( + f"/api/accounts/{self._account_id}/contacts/fields/{field_id}", + json=field_params.api_data, + ) + return ContactField(**response) + + def delete(self, field_id: int) -> DeletedObject: + self._client.delete( + f"/api/accounts/{self._account_id}/contacts/fields/{field_id}" + ) + return DeletedObject(field_id) diff --git a/mailtrap/client.py b/mailtrap/client.py index f1d9f54..a7160c8 100644 --- a/mailtrap/client.py +++ b/mailtrap/client.py @@ -5,6 +5,7 @@ from pydantic import TypeAdapter +from mailtrap.api.contacts import ContactsBaseApi from mailtrap.api.sending import SendingApi from mailtrap.api.templates import EmailTemplatesApi from mailtrap.api.testing import TestingApi @@ -63,6 +64,14 @@ def email_templates_api(self) -> EmailTemplatesApi: client=HttpClient(host=GENERAL_HOST, headers=self.headers), ) + @property + def contacts_api(self) -> ContactsBaseApi: + self._validate_account_id() + return ContactsBaseApi( + account_id=cast(str, self.account_id), + client=HttpClient(host=GENERAL_HOST, headers=self.headers), + ) + @property def sending_api(self) -> SendingApi: http_client = HttpClient(host=self._sending_api_host, headers=self.headers) diff --git a/mailtrap/models/contacts.py b/mailtrap/models/contacts.py new file mode 100644 index 0000000..d1e5598 --- /dev/null +++ b/mailtrap/models/contacts.py @@ -0,0 +1,30 @@ +from typing import Optional + +from pydantic.dataclasses import dataclass + +from mailtrap.models.common import RequestParams + + +@dataclass +class CreateContactFieldParams(RequestParams): + name: str + data_type: str + merge_tag: str + + +@dataclass +class UpdateContactFieldParams(RequestParams): + name: Optional[str] = None + merge_tag: Optional[str] = None + + def __post_init__(self) -> None: + if all(value is None for value in [self.name, self.merge_tag]): + raise ValueError("At least one field must be provided for update action") + + +@dataclass +class ContactField: + id: int + name: str + data_type: str + merge_tag: str diff --git a/tests/unit/api/test_contacts.py b/tests/unit/api/test_contacts.py new file mode 100644 index 0000000..c9b0e7c --- /dev/null +++ b/tests/unit/api/test_contacts.py @@ -0,0 +1,337 @@ +from typing import Any + +import pytest +import responses + +from mailtrap.api.resources.contact_fields import ContactFieldsApi +from mailtrap.config import GENERAL_HOST +from mailtrap.exceptions import APIError +from mailtrap.http import HttpClient +from mailtrap.models.common import DeletedObject +from mailtrap.models.contacts import ContactField +from mailtrap.models.contacts import CreateContactFieldParams +from mailtrap.models.contacts import UpdateContactFieldParams +from tests import conftest + +ACCOUNT_ID = "321" +FIELD_ID = 6730 +BASE_CONTACT_FIELDS_URL = ( + f"https://{GENERAL_HOST}/api/accounts/{ACCOUNT_ID}/contacts/fields" +) + + +@pytest.fixture +def contact_fields_api() -> ContactFieldsApi: + return ContactFieldsApi(account_id=ACCOUNT_ID, client=HttpClient(GENERAL_HOST)) + + +@pytest.fixture +def sample_contact_field_dict() -> dict[str, Any]: + return { + "id": FIELD_ID, + "name": "First name", + "data_type": "text", + "merge_tag": "first_name", + } + + +@pytest.fixture +def create_contact_field_params() -> CreateContactFieldParams: + return CreateContactFieldParams( + name="My Contact Field", data_type="text", merge_tag="my_contact_field" + ) + + +@pytest.fixture +def update_contact_field_params() -> UpdateContactFieldParams: + return UpdateContactFieldParams(name="Updated name", merge_tag="updated_name") + + +class TestContactsApi: + + @pytest.mark.parametrize( + "status_code,response_json,expected_error_message", + [ + ( + conftest.UNAUTHORIZED_STATUS_CODE, + conftest.UNAUTHORIZED_RESPONSE, + conftest.UNAUTHORIZED_ERROR_MESSAGE, + ), + ( + conftest.FORBIDDEN_STATUS_CODE, + conftest.FORBIDDEN_RESPONSE, + conftest.FORBIDDEN_ERROR_MESSAGE, + ), + ], + ) + @responses.activate + def test_get_contact_fields_should_raise_api_errors( + self, + contact_fields_api: ContactFieldsApi, + status_code: int, + response_json: dict, + expected_error_message: str, + ) -> None: + responses.get( + BASE_CONTACT_FIELDS_URL, + status=status_code, + json=response_json, + ) + + with pytest.raises(APIError) as exc_info: + contact_fields_api.get_list() + + assert expected_error_message in str(exc_info.value) + + @responses.activate + def test_get_contact_fields_should_return_contact_field_list( + self, contact_fields_api: ContactFieldsApi, sample_contact_field_dict: dict + ) -> None: + responses.get( + BASE_CONTACT_FIELDS_URL, + json=[sample_contact_field_dict], + status=200, + ) + + contact_fields = contact_fields_api.get_list() + + assert isinstance(contact_fields, list) + assert all(isinstance(f, ContactField) for f in contact_fields) + assert contact_fields[0].id == FIELD_ID + + @pytest.mark.parametrize( + "status_code,response_json,expected_error_message", + [ + ( + conftest.UNAUTHORIZED_STATUS_CODE, + conftest.UNAUTHORIZED_RESPONSE, + conftest.UNAUTHORIZED_ERROR_MESSAGE, + ), + ( + conftest.FORBIDDEN_STATUS_CODE, + conftest.FORBIDDEN_RESPONSE, + conftest.FORBIDDEN_ERROR_MESSAGE, + ), + ( + conftest.NOT_FOUND_STATUS_CODE, + conftest.NOT_FOUND_RESPONSE, + conftest.NOT_FOUND_ERROR_MESSAGE, + ), + ], + ) + @responses.activate + def test_get_contact_field_should_raise_api_errors( + self, + contact_fields_api: ContactFieldsApi, + status_code: int, + response_json: dict, + expected_error_message: str, + ) -> None: + responses.get( + f"{BASE_CONTACT_FIELDS_URL}/{FIELD_ID}", + status=status_code, + json=response_json, + ) + + with pytest.raises(APIError) as exc_info: + contact_fields_api.get_by_id(FIELD_ID) + + assert expected_error_message in str(exc_info.value) + + @responses.activate + def test_get_contact_field_should_return_contact_field( + self, contact_fields_api: ContactFieldsApi, sample_contact_field_dict: dict + ) -> None: + responses.get( + f"{BASE_CONTACT_FIELDS_URL}/{FIELD_ID}", + json=sample_contact_field_dict, + status=200, + ) + + contact_field = contact_fields_api.get_by_id(FIELD_ID) + + assert isinstance(contact_field, ContactField) + assert contact_field.id == FIELD_ID + assert contact_field.name == "First name" + assert contact_field.data_type == "text" + assert contact_field.merge_tag == "first_name" + + @pytest.mark.parametrize( + "status_code,response_json,expected_error_message", + [ + ( + conftest.UNAUTHORIZED_STATUS_CODE, + conftest.UNAUTHORIZED_RESPONSE, + conftest.UNAUTHORIZED_ERROR_MESSAGE, + ), + ( + conftest.FORBIDDEN_STATUS_CODE, + conftest.FORBIDDEN_RESPONSE, + conftest.FORBIDDEN_ERROR_MESSAGE, + ), + ], + ) + @responses.activate + def test_create_contact_field_should_raise_api_errors( + self, + contact_fields_api: ContactFieldsApi, + create_contact_field_params: CreateContactFieldParams, + status_code: int, + response_json: dict, + expected_error_message: str, + ) -> None: + responses.post( + BASE_CONTACT_FIELDS_URL, + status=status_code, + json=response_json, + ) + + with pytest.raises(APIError) as exc_info: + contact_fields_api.create(create_contact_field_params) + + assert expected_error_message in str(exc_info.value) + + @responses.activate + def test_create_contact_field_should_return_created_contact_field( + self, + contact_fields_api: ContactFieldsApi, + create_contact_field_params: CreateContactFieldParams, + ) -> None: + expected_response = { + "id": FIELD_ID, + "name": "My Contact Field", + "data_type": "text", + "merge_tag": "my_contact_field", + } + responses.post( + BASE_CONTACT_FIELDS_URL, + json=expected_response, + status=201, + ) + + contact_field = contact_fields_api.create(create_contact_field_params) + + assert isinstance(contact_field, ContactField) + assert contact_field.id == FIELD_ID + assert contact_field.name == "My Contact Field" + assert contact_field.data_type == "text" + assert contact_field.merge_tag == "my_contact_field" + + @pytest.mark.parametrize( + "status_code,response_json,expected_error_message", + [ + ( + conftest.UNAUTHORIZED_STATUS_CODE, + conftest.UNAUTHORIZED_RESPONSE, + conftest.UNAUTHORIZED_ERROR_MESSAGE, + ), + ( + conftest.FORBIDDEN_STATUS_CODE, + conftest.FORBIDDEN_RESPONSE, + conftest.FORBIDDEN_ERROR_MESSAGE, + ), + ( + conftest.NOT_FOUND_STATUS_CODE, + conftest.NOT_FOUND_RESPONSE, + conftest.NOT_FOUND_ERROR_MESSAGE, + ), + ], + ) + @responses.activate + def test_update_contact_field_should_raise_api_errors( + self, + contact_fields_api: ContactFieldsApi, + update_contact_field_params: UpdateContactFieldParams, + status_code: int, + response_json: dict, + expected_error_message: str, + ) -> None: + responses.patch( + f"{BASE_CONTACT_FIELDS_URL}/{FIELD_ID}", + status=status_code, + json=response_json, + ) + + with pytest.raises(APIError) as exc_info: + contact_fields_api.update(FIELD_ID, update_contact_field_params) + + assert expected_error_message in str(exc_info.value) + + @responses.activate + def test_update_contact_field_should_return_updated_contact_field( + self, + contact_fields_api: ContactFieldsApi, + update_contact_field_params: UpdateContactFieldParams, + ) -> None: + expected_response = { + "id": FIELD_ID, + "name": "Updated name", + "data_type": "text", + "merge_tag": "updated_name", + } + responses.patch( + f"{BASE_CONTACT_FIELDS_URL}/{FIELD_ID}", + json=expected_response, + status=200, + ) + + contact_field = contact_fields_api.update(FIELD_ID, update_contact_field_params) + + assert isinstance(contact_field, ContactField) + assert contact_field.id == FIELD_ID + assert contact_field.name == "Updated name" + assert contact_field.data_type == "text" + assert contact_field.merge_tag == "updated_name" + + @pytest.mark.parametrize( + "status_code,response_json,expected_error_message", + [ + ( + conftest.UNAUTHORIZED_STATUS_CODE, + conftest.UNAUTHORIZED_RESPONSE, + conftest.UNAUTHORIZED_ERROR_MESSAGE, + ), + ( + conftest.FORBIDDEN_STATUS_CODE, + conftest.FORBIDDEN_RESPONSE, + conftest.FORBIDDEN_ERROR_MESSAGE, + ), + ( + conftest.NOT_FOUND_STATUS_CODE, + conftest.NOT_FOUND_RESPONSE, + conftest.NOT_FOUND_ERROR_MESSAGE, + ), + ], + ) + @responses.activate + def test_delete_contact_field_should_raise_api_errors( + self, + contact_fields_api: ContactFieldsApi, + status_code: int, + response_json: dict, + expected_error_message: str, + ) -> None: + responses.delete( + f"{BASE_CONTACT_FIELDS_URL}/{FIELD_ID}", + status=status_code, + json=response_json, + ) + + with pytest.raises(APIError) as exc_info: + contact_fields_api.delete(FIELD_ID) + + assert expected_error_message in str(exc_info.value) + + @responses.activate + def test_delete_contact_field_should_return_deleted_object( + self, contact_fields_api: ContactFieldsApi + ) -> None: + responses.delete( + f"{BASE_CONTACT_FIELDS_URL}/{FIELD_ID}", + status=204, + ) + + deleted_object = contact_fields_api.delete(FIELD_ID) + + assert isinstance(deleted_object, DeletedObject) + assert deleted_object.id == FIELD_ID diff --git a/tests/unit/models/test_contacts.py b/tests/unit/models/test_contacts.py new file mode 100644 index 0000000..a0b6755 --- /dev/null +++ b/tests/unit/models/test_contacts.py @@ -0,0 +1,52 @@ +import pytest + +from mailtrap.models.contacts import CreateContactFieldParams +from mailtrap.models.contacts import UpdateContactFieldParams + + +class TestCreateContactFieldParams: + def test_create_contact_field_params_api_data_should_return_correct_dict( + self, + ) -> None: + params = CreateContactFieldParams( + name="Test Field", data_type="integer", merge_tag="test_field" + ) + + api_data = params.api_data + + assert api_data == { + "name": "Test Field", + "data_type": "integer", + "merge_tag": "test_field", + } + + +class TestUpdateContactFieldParams: + def test_update_contact_field_params_with_none_values_should_raise_error( + self, + ) -> None: + with pytest.raises( + ValueError, match="At least one field must be provided for update action" + ): + _ = UpdateContactFieldParams() + + def test_update_contact_field_params_with_partial_values_should_not_raise_error( + self, + ) -> None: + params = UpdateContactFieldParams(name="Updated Field") + assert params.name == "Updated Field" + assert params.merge_tag is None + + def test_update_contact_field_params_api_data_should_exclude_none_values( + self, + ) -> None: + params = UpdateContactFieldParams(name="Updated Field") + api_data = params.api_data + + assert api_data == {"name": "Updated Field"} + + def test_update_contact_field_params_api_data_with_all_values(self) -> None: + params = UpdateContactFieldParams(name="Updated Field", merge_tag="updated_field") + api_data = params.api_data + + assert api_data == {"name": "Updated Field", "merge_tag": "updated_field"} From bdfd9b39d3f7797a6d45730dfa92346b0e4d5f70 Mon Sep 17 00:00:00 2001 From: Ihor Bilous Date: Fri, 22 Aug 2025 23:35:34 +0300 Subject: [PATCH 2/2] Fix issue #19: Add _api_path method in Api classes --- examples/contacts/contact_fields.py | 6 +++--- mailtrap/api/resources/contact_fields.py | 20 +++++++++++++------- mailtrap/api/resources/projects.py | 22 +++++++++++++--------- mailtrap/api/resources/templates.py | 22 +++++++++++++--------- 4 files changed, 42 insertions(+), 28 deletions(-) diff --git a/examples/contacts/contact_fields.py b/examples/contacts/contact_fields.py index d704d5b..ed6decc 100644 --- a/examples/contacts/contact_fields.py +++ b/examples/contacts/contact_fields.py @@ -25,7 +25,7 @@ def create_contact_field( def update_contact_field( - contact_field_id: str, + contact_field_id: int, name: Optional[str] = None, merge_tag: Optional[str] = None, ) -> ContactField: @@ -37,11 +37,11 @@ def list_contact_fields() -> list[ContactField]: return contact_fields_api.get_list() -def get_contact_field(contact_field_id: str) -> ContactField: +def get_contact_field(contact_field_id: int) -> ContactField: return contact_fields_api.get_by_id(contact_field_id) -def delete_contact_field(contact_field_id: str) -> DeletedObject: +def delete_contact_field(contact_field_id: int) -> DeletedObject: return contact_fields_api.delete(contact_field_id) diff --git a/mailtrap/api/resources/contact_fields.py b/mailtrap/api/resources/contact_fields.py index ccb27cd..6958d6f 100644 --- a/mailtrap/api/resources/contact_fields.py +++ b/mailtrap/api/resources/contact_fields.py @@ -1,3 +1,5 @@ +from typing import Optional + from mailtrap.http import HttpClient from mailtrap.models.common import DeletedObject from mailtrap.models.contacts import ContactField @@ -11,18 +13,18 @@ def __init__(self, client: HttpClient, account_id: str) -> None: self._client = client def get_list(self) -> list[ContactField]: - response = self._client.get(f"/api/accounts/{self._account_id}/contacts/fields") + response = self._client.get(self._api_path()) return [ContactField(**field) for field in response] def get_by_id(self, field_id: int) -> ContactField: response = self._client.get( - f"/api/accounts/{self._account_id}/contacts/fields/{field_id}" + self._api_path(field_id), ) return ContactField(**response) def create(self, field_params: CreateContactFieldParams) -> ContactField: response = self._client.post( - f"/api/accounts/{self._account_id}/contacts/fields", + self._api_path(), json=field_params.api_data, ) return ContactField(**response) @@ -31,13 +33,17 @@ def update( self, field_id: int, field_params: UpdateContactFieldParams ) -> ContactField: response = self._client.patch( - f"/api/accounts/{self._account_id}/contacts/fields/{field_id}", + self._api_path(field_id), json=field_params.api_data, ) return ContactField(**response) def delete(self, field_id: int) -> DeletedObject: - self._client.delete( - f"/api/accounts/{self._account_id}/contacts/fields/{field_id}" - ) + self._client.delete(self._api_path(field_id)) return DeletedObject(field_id) + + def _api_path(self, field_id: Optional[int] = None) -> str: + path = f"/api/accounts/{self._account_id}/contacts/fields" + if field_id: + return f"{path}/{field_id}" + return path diff --git a/mailtrap/api/resources/projects.py b/mailtrap/api/resources/projects.py index 7df38d4..95aca4d 100644 --- a/mailtrap/api/resources/projects.py +++ b/mailtrap/api/resources/projects.py @@ -1,3 +1,5 @@ +from typing import Optional + from mailtrap.http import HttpClient from mailtrap.models.common import DeletedObject from mailtrap.models.projects import Project @@ -9,31 +11,33 @@ def __init__(self, client: HttpClient, account_id: str) -> None: self._client = client def get_list(self) -> list[Project]: - response = self._client.get(f"/api/accounts/{self._account_id}/projects") + response = self._client.get(self._api_path()) return [Project(**project) for project in response] def get_by_id(self, project_id: int) -> Project: - response = self._client.get( - f"/api/accounts/{self._account_id}/projects/{project_id}" - ) + response = self._client.get(self._api_path(project_id)) return Project(**response) def create(self, project_name: str) -> Project: response = self._client.post( - f"/api/accounts/{self._account_id}/projects", + self._api_path(), json={"project": {"name": project_name}}, ) return Project(**response) def update(self, project_id: int, project_name: str) -> Project: response = self._client.patch( - f"/api/accounts/{self._account_id}/projects/{project_id}", + self._api_path(project_id), json={"project": {"name": project_name}}, ) return Project(**response) def delete(self, project_id: int) -> DeletedObject: - response = self._client.delete( - f"/api/accounts/{self._account_id}/projects/{project_id}", - ) + response = self._client.delete(self._api_path(project_id)) return DeletedObject(**response) + + def _api_path(self, project_id: Optional[int] = None) -> str: + path = f"/api/accounts/{self._account_id}/projects" + if project_id: + return f"{path}/{project_id}" + return path diff --git a/mailtrap/api/resources/templates.py b/mailtrap/api/resources/templates.py index beedbfc..1fd012b 100644 --- a/mailtrap/api/resources/templates.py +++ b/mailtrap/api/resources/templates.py @@ -1,3 +1,5 @@ +from typing import Optional + from mailtrap.http import HttpClient from mailtrap.models.common import DeletedObject from mailtrap.models.templates import CreateEmailTemplateParams @@ -11,18 +13,16 @@ def __init__(self, client: HttpClient, account_id: str) -> None: self._client = client def get_list(self) -> list[EmailTemplate]: - response = self._client.get(f"/api/accounts/{self._account_id}/email_templates") + response = self._client.get(self._api_path()) return [EmailTemplate(**template) for template in response] def get_by_id(self, template_id: int) -> EmailTemplate: - response = self._client.get( - f"/api/accounts/{self._account_id}/email_templates/{template_id}" - ) + response = self._client.get(self._api_path(template_id)) return EmailTemplate(**response) def create(self, template_params: CreateEmailTemplateParams) -> EmailTemplate: response = self._client.post( - f"/api/accounts/{self._account_id}/email_templates", + self._api_path(), json={"email_template": template_params.api_data}, ) return EmailTemplate(**response) @@ -31,13 +31,17 @@ def update( self, template_id: int, template_params: UpdateEmailTemplateParams ) -> EmailTemplate: response = self._client.patch( - f"/api/accounts/{self._account_id}/email_templates/{template_id}", + self._api_path(template_id), json={"email_template": template_params.api_data}, ) return EmailTemplate(**response) def delete(self, template_id: int) -> DeletedObject: - self._client.delete( - f"/api/accounts/{self._account_id}/email_templates/{template_id}" - ) + 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}/email_templates" + if template_id: + return f"{path}/{template_id}" + return path