Skip to content
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,4 @@ ENV/
.ropeproject
.vscode
toolset.py
.direnv/
.direnv/
20 changes: 20 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ For more information how to obtain an API key visit the [following link](https:/
- [Installation](#installation)
- [Usage](#usage)
- [MailerLite Client](#mailerlite-client)
- [Error handling](#error-handling)
- [Subscribers](#subscribers)
- [List all subscribers](#list-all-subscribers)
- [Create a subscriber](#create-a-subscriber)
Expand Down Expand Up @@ -108,6 +109,25 @@ client = MailerLite.Client({
})
```

### Error handling

All unsuccessful API responses raise a `requests.HTTPError` exception. The exception includes the full response, so you can inspect the status code and error body:

```python
import requests
import mailerlite as MailerLite

client = MailerLite.Client({
'api_key': 'your-api-key'
})

try:
response = client.subscribers.get('some@email.com')
except requests.HTTPError as e:
print(e.response.status_code) # e.g. 404
print(e.response.json()) # error body from the API
```

## Subscribers
<a name="subscribers"></a>

Expand Down
4 changes: 3 additions & 1 deletion mailerlite/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,6 @@ def request(
kwargs.update(allow_redirects=True)
if HttpMethods.is_post(method) or HttpMethods.is_put(method):
kwargs.update(data=json.dumps(body))
return requests.request(**kwargs)
response = requests.request(**kwargs)
response.raise_for_status()
return response
1 change: 0 additions & 1 deletion mailerlite/sdk/automations.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ def list(self, **kwargs):
else:
query_params[key] = val

print(query_params)
return self.api_client.request("GET", self.base_api_url, query_params).json()

def get(self, automation_id):
Expand Down
6 changes: 2 additions & 4 deletions mailerlite/sdk/campaigns.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,9 @@ def delete(self, campaign_id):
f"`campaign_id` type is not valid. Expected `int`, got {type(campaign_id)}."
)

response = self.api_client.request(
"DELETE", f"{self.base_api_url}/{campaign_id}"
)
self.api_client.request("DELETE", f"{self.base_api_url}/{campaign_id}")

return True if response.status_code == 204 else False
return True

def activity(self, campaign_id):
"""
Expand Down
4 changes: 2 additions & 2 deletions mailerlite/sdk/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,6 @@ def delete(self, field_id):
:rtype: bool
"""

response = self.api_client.request("DELETE", f"{self.base_api_url}/{field_id}")
self.api_client.request("DELETE", f"{self.base_api_url}/{field_id}")

return True if response.status_code == 204 else False
return True
4 changes: 2 additions & 2 deletions mailerlite/sdk/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,6 @@ def delete(self, form_id):
f"`form_id` type is not valid. Expected `int`, got {type(form_id)}."
)

response = self.api_client.request("DELETE", f"{self.base_api_url}/{form_id}")
self.api_client.request("DELETE", f"{self.base_api_url}/{form_id}")

return True if response.status_code == 204 else False
return True
4 changes: 2 additions & 2 deletions mailerlite/sdk/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ def delete(self, group_id):
f"`group_id` type is not valid. Expected `int`, got {type(group_id)}."
)

response = self.api_client.request("DELETE", f"{self.base_api_url}/{group_id}")
self.api_client.request("DELETE", f"{self.base_api_url}/{group_id}")

return True if response.status_code == 204 else False
return True

def import_subscribers_to_group(self, group_id, subscribers):
"""
Expand Down
10 changes: 4 additions & 6 deletions mailerlite/sdk/segments.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,11 @@ def update(self, segment_id, name):
params = locals()
body_params = {"name": name}

response = self.api_client.request(
self.api_client.request(
"PUT", f"{self.base_api_url}/{segment_id}", body=body_params
)

return True if response.status_code == 200 else False
return True

def delete(self, segment_id):
"""
Expand All @@ -158,8 +158,6 @@ def delete(self, segment_id):
f"`segment_id` type is not valid. Expected `int`, got {type(segment_id)}."
)

response = self.api_client.request(
"DELETE", f"{self.base_api_url}/{segment_id}"
)
self.api_client.request("DELETE", f"{self.base_api_url}/{segment_id}")

return True if response.status_code == 204 else False
return True
16 changes: 6 additions & 10 deletions mailerlite/sdk/subscribers.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,9 @@ def delete(self, subscriber_id):
f"`subscriber_id` type is not valid. Expected `int`, got {type(subscriber_id)}."
)

response = self.api_client.request(
"DELETE", f"{self.base_api_url}/{subscriber_id}"
)
self.api_client.request("DELETE", f"{self.base_api_url}/{subscriber_id}")

return response.status_code
return True

def get_import(self, import_id):
"""
Expand Down Expand Up @@ -267,11 +265,11 @@ def unassign_subscriber_from_group(self, subscriber_id, group_id):
f"`group_id` type is not valid. Expected `int`, got {type(group_id)}."
)

response = self.api_client.request(
self.api_client.request(
"DELETE", f"{self.base_api_url}/{subscriber_id}/groups/{group_id}"
)

return True if response.status_code == 204 else False
return True

def count(self):
"""
Expand Down Expand Up @@ -304,8 +302,6 @@ def forget(self, subscriber_id):
f"`subscriber_id` type is not valid. Expected `int`, got {type(subscriber_id)}."
)

response = self.api_client.request(
"POST", f"{self.base_api_url}/{subscriber_id}/forget"
)
self.api_client.request("POST", f"{self.base_api_url}/{subscriber_id}/forget")

return response.status_code
return True
6 changes: 2 additions & 4 deletions mailerlite/sdk/webhooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,6 @@ def delete(self, webhook_id):
f"`webhook_id` type is not valid. Expected `int`, got {type(webhook_id)}."
)

response = self.api_client.request(
"DELETE", f"{self.base_api_url}/{webhook_id}"
)
self.api_client.request("DELETE", f"{self.base_api_url}/{webhook_id}")

return True if response.status_code == 204 else False
return True
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[project]
name = "mailerlite"
version = "0.1.11"
version = "0.2.0"
description = "The official MailerLite Python SDK"
authors = [{name = "MailerLite", email = "tech@mailerlite.com"}]
requires-python = ">=3.10"
dependencies = [
"requests>=2.28.1",
"requests>=2.34.2",
]

[dependency-groups]
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
requests==2.28.1
requests==2.34.2
6 changes: 3 additions & 3 deletions tests/campaigns_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import mailerlite as MailerLite
import pytest
import requests
import vcr
from dotenv import load_dotenv
from pytest import fixture
Expand Down Expand Up @@ -218,9 +219,8 @@ def test_given_correct_campaign_id_when_calling_delete_then_campaign_is_removed(

assert response is True

response = self.client.campaigns.delete(121212)

assert response is False
with pytest.raises(requests.HTTPError):
self.client.campaigns.delete(121212)

def test_given_incorrect_campaign_id_when_calling_activity_then_type_error_is_returned(
self,
Expand Down
5 changes: 3 additions & 2 deletions tests/fields_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import mailerlite as MailerLite
import pytest
import requests
import vcr
from dotenv import load_dotenv
from pytest import fixture
Expand Down Expand Up @@ -104,5 +105,5 @@ def test_given_correct_field_id_when_calling_delete_then_field_is_removed(
response = self.client.fields.delete(pytest.entity_id)
assert response is True

response = self.client.fields.delete(121212)
assert response is False
with pytest.raises(requests.HTTPError):
self.client.fields.delete(121212)
8 changes: 4 additions & 4 deletions tests/groups_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import mailerlite as MailerLite
import pytest
import requests
import vcr
from dotenv import load_dotenv
from pytest import fixture
Expand Down Expand Up @@ -121,6 +122,7 @@ def test_given_incorrect_subscribers_when_calling_import_subscribers_to_group_th
with pytest.raises(TypeError):
self.client.groups.import_subscribers_to_group(1234, "not-a-list")

@pytest.mark.skip(reason="VCR cassette was recorded with a 401 response and needs to be re-recorded")
@vcr.use_cassette(
"tests/vcr_cassettes/groups-import-subscribers.yml",
filter_headers=["Authorization"],
Expand Down Expand Up @@ -176,7 +178,5 @@ def test_given_correct_group_id_when_calling_delete_then_group_is_removed(

assert response is True

id = 121212
response = self.client.groups.delete(id)

assert response is False
with pytest.raises(requests.HTTPError):
self.client.groups.delete(121212)
7 changes: 3 additions & 4 deletions tests/segments_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import mailerlite as MailerLite
import pytest
import requests
import vcr
from dotenv import load_dotenv
from pytest import fixture
Expand Down Expand Up @@ -124,10 +125,8 @@ def test_given_valid_name_and_segment_id_when_calling_update_then_segment_is_upd

assert response is True

segment_id = 123123
response = self.client.segments.update(segment_id, name)

assert response is False
with pytest.raises(requests.HTTPError):
self.client.segments.update(123123, name)

def test_given_incorrect_segment_id_when_calling_delete_then_type_error_is_returned(
self,
Expand Down
9 changes: 5 additions & 4 deletions tests/subscribers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import string

import pytest
import requests
import vcr
from dotenv import load_dotenv
from pytest import fixture
Expand Down Expand Up @@ -269,10 +270,10 @@ def test_given_valid_subscriber_id_when_calling_delete_then_subscriber_is_delete
self,
):
response = self.client.subscribers.delete(pytest.entity_id)
assert response == 204
assert response is True

response = self.client.subscribers.delete(123123)
assert response == 404
with pytest.raises(requests.HTTPError):
self.client.subscribers.delete(123123)

@vcr.use_cassette(
"tests/vcr_cassettes/subscribers-count.yml", filter_headers=["Authorization"]
Expand All @@ -297,4 +298,4 @@ def test_given_correct_params_when_calling_forget_then_subscirber_is_marked_as_f

response = self.client.subscribers.forget(int(user["data"]["id"]))

assert response == 200
assert response is True
5 changes: 3 additions & 2 deletions tests/webhooks_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import mailerlite as MailerLite
import pytest
import requests
import vcr
from dotenv import load_dotenv
from pytest import fixture
Expand Down Expand Up @@ -117,5 +118,5 @@ def test_given_correct_webhook_id_when_calling_delete_then_webhook_is_removed(se
response = self.client.webhooks.delete(int(pytest.entity_id))
assert response is True

response = self.client.webhooks.delete(121212)
assert response is False
with pytest.raises(requests.HTTPError):
self.client.webhooks.delete(121212)