From 6296b58c3665bf04cba99a73eb58679dfda266c8 Mon Sep 17 00:00:00 2001 From: rocribera Date: Mon, 8 Jun 2026 09:48:42 +0200 Subject: [PATCH 1/2] feat: add endpoint to import bulk subs to group --- mailerlite/sdk/groups.py | 31 +++++++++++++++++++++++++++++++ tests/groups_test.py | 24 ++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/mailerlite/sdk/groups.py b/mailerlite/sdk/groups.py index 0972cec..ede8289 100644 --- a/mailerlite/sdk/groups.py +++ b/mailerlite/sdk/groups.py @@ -111,6 +111,37 @@ def delete(self, group_id): return True if response.status_code == 204 else False + def import_subscribers(self, group_id, subscribers): + """ + Import bulk subscribers to a group + + Provides an ability to import bulk subscribers to a group. + Ref: https://developers.mailerlite.com/api/groups#import-bulk-subscribers-to-group + + :param group_id: int Group ID + :param subscribers: list List of subscriber objects to import + :raises: :class: `TypeError` : `group_id` type is not valid + :raises: :class: `TypeError` : `subscribers` type is not valid + :return: JSON array + :rtype: dict + """ + + if not isinstance(group_id, int): + raise TypeError( + f"`group_id` type is not valid. Expected `int`, got {type(group_id)}." + ) + + if not isinstance(subscribers, list): + raise TypeError( + f"`subscribers` type is not valid. Expected `list`, got {type(subscribers)}." + ) + + return self.api_client.request( + "POST", + f"{self.base_api_url}/{group_id}/subscribers/import", + body={"subscribers": subscribers}, + ).json() + def get_group_subscribers(self, group_id, **kwargs): """ Get subscribers belonging to a group diff --git a/tests/groups_test.py b/tests/groups_test.py index 6eb26a8..2fb30d2 100644 --- a/tests/groups_test.py +++ b/tests/groups_test.py @@ -109,6 +109,30 @@ def test_given_correct_group_id_and_name_when_calling_update_then_group_is_updat assert set(group_keys).issubset(response["data"].keys()) assert response["data"]["name"] == name + def test_given_incorrect_group_id_when_calling_import_subscribers_then_type_error_is_returned( + self, + ): + with pytest.raises(TypeError): + self.client.groups.import_subscribers("1234", []) + + def test_given_incorrect_subscribers_when_calling_import_subscribers_then_type_error_is_returned( + self, + ): + with pytest.raises(TypeError): + self.client.groups.import_subscribers(1234, "not-a-list") + + @vcr.use_cassette( + "tests/vcr_cassettes/groups-import-subscribers.yml", + filter_headers=["Authorization"], + ) + def test_given_correct_parameters_when_calling_import_subscribers_then_import_is_queued( + self, + ): + subscribers = [{"email": "test@example.com", "name": "Test User"}] + response = self.client.groups.import_subscribers(pytest.entity_id, subscribers) + + assert isinstance(response, dict) + def test_given_incorrect_group_id_when_calling_get_group_subscribers_then_type_error_is_returned( self, ): From f165a83dcd9e62819bc4b99331dcd1a7fda864ce Mon Sep 17 00:00:00 2001 From: rocribera Date: Mon, 8 Jun 2026 09:56:28 +0200 Subject: [PATCH 2/2] feat: fix naming --- mailerlite/sdk/groups.py | 2 +- tests/groups_test.py | 12 ++--- .../groups-import-subscribers.yml | 48 +++++++++++++++++++ 3 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 tests/vcr_cassettes/groups-import-subscribers.yml diff --git a/mailerlite/sdk/groups.py b/mailerlite/sdk/groups.py index ede8289..06aeddc 100644 --- a/mailerlite/sdk/groups.py +++ b/mailerlite/sdk/groups.py @@ -111,7 +111,7 @@ def delete(self, group_id): return True if response.status_code == 204 else False - def import_subscribers(self, group_id, subscribers): + def import_subscribers_to_group(self, group_id, subscribers): """ Import bulk subscribers to a group diff --git a/tests/groups_test.py b/tests/groups_test.py index 2fb30d2..eae2aa8 100644 --- a/tests/groups_test.py +++ b/tests/groups_test.py @@ -109,27 +109,27 @@ def test_given_correct_group_id_and_name_when_calling_update_then_group_is_updat assert set(group_keys).issubset(response["data"].keys()) assert response["data"]["name"] == name - def test_given_incorrect_group_id_when_calling_import_subscribers_then_type_error_is_returned( + def test_given_incorrect_group_id_when_calling_import_subscribers_to_group_then_type_error_is_returned( self, ): with pytest.raises(TypeError): - self.client.groups.import_subscribers("1234", []) + self.client.groups.import_subscribers_to_group("1234", []) - def test_given_incorrect_subscribers_when_calling_import_subscribers_then_type_error_is_returned( + def test_given_incorrect_subscribers_when_calling_import_subscribers_to_group_then_type_error_is_returned( self, ): with pytest.raises(TypeError): - self.client.groups.import_subscribers(1234, "not-a-list") + self.client.groups.import_subscribers_to_group(1234, "not-a-list") @vcr.use_cassette( "tests/vcr_cassettes/groups-import-subscribers.yml", filter_headers=["Authorization"], ) - def test_given_correct_parameters_when_calling_import_subscribers_then_import_is_queued( + def test_given_correct_parameters_when_calling_import_subscribers_to_group_then_import_is_queued( self, ): subscribers = [{"email": "test@example.com", "name": "Test User"}] - response = self.client.groups.import_subscribers(pytest.entity_id, subscribers) + response = self.client.groups.import_subscribers_to_group(pytest.entity_id, subscribers) assert isinstance(response, dict) diff --git a/tests/vcr_cassettes/groups-import-subscribers.yml b/tests/vcr_cassettes/groups-import-subscribers.yml new file mode 100644 index 0000000..44f74cb --- /dev/null +++ b/tests/vcr_cassettes/groups-import-subscribers.yml @@ -0,0 +1,48 @@ +interactions: +- request: + body: '{"subscribers": [{"email": "test@example.com", "name": "Test User"}]}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Length: + - '69' + Content-Type: + - application/json + User-Agent: + - MailerLite-Python-SDK-Client + method: POST + uri: https://connect.mailerlite.com/api/groups/165971156405323676/subscribers/import + response: + body: + string: '{"message":"Unauthenticated."}' + headers: + CF-RAY: + - a08656bc39a6278a-PRG + Cache-Control: + - no-cache, private + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Mon, 08 Jun 2026 07:56:14 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + access-control-allow-origin: + - '*' + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + x-envoy-upstream-service-time: + - '22' + status: + code: 401 + message: Unauthorized +version: 1