Skip to content

Commit 90de554

Browse files
committed
Add data removal api endpoints
1 parent bec4047 commit 90de554

8 files changed

Lines changed: 179 additions & 0 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import asyncio
2+
import os
3+
4+
from dotenv import load_dotenv
5+
6+
import postmark
7+
8+
load_dotenv()
9+
account = postmark.AccountClient(os.environ["POSTMARK_ACCOUNT_TOKEN"])
10+
11+
12+
async def main():
13+
result = await account.data_removals.create(
14+
requested_by="admin@example.com",
15+
requested_for="user@example.com",
16+
notify_when_completed=True,
17+
)
18+
19+
print(f"ID: {result.id}")
20+
print(f"Status: {result.status}")
21+
22+
23+
asyncio.run(main())
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import asyncio
2+
import os
3+
4+
from dotenv import load_dotenv
5+
6+
import postmark
7+
8+
load_dotenv()
9+
account = postmark.AccountClient(os.environ["POSTMARK_ACCOUNT_TOKEN"])
10+
11+
12+
async def main():
13+
result = await account.data_removals.get(42)
14+
15+
print(f"ID: {result.id}")
16+
print(f"Status: {result.status}")
17+
18+
19+
asyncio.run(main())

postmark/clients/account_client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import httpx
66

7+
from postmark.models.data_removals import DataRemovalManager
78
from postmark.models.domains import DomainManager
89
from postmark.models.servers import AccountServerManager
910
from postmark.models.signatures import SenderSignatureManager
@@ -42,6 +43,7 @@ def __init__(self, account_token: str):
4243
self.server = AccountServerManager(self)
4344
self.domain = DomainManager(self)
4445
self.signature = SenderSignatureManager(self)
46+
self.data_removals = DataRemovalManager(self)
4547

4648
async def request(self, method: str, endpoint: str, **kwargs) -> httpx.Response:
4749
"""
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .manager import DataRemovalManager
2+
from .schemas import DataRemoval
3+
4+
__all__ = ["DataRemoval", "DataRemovalManager"]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from postmark.utils.types import HTTPClient
2+
3+
from .schemas import DataRemoval
4+
5+
6+
class DataRemovalManager:
7+
def __init__(self, client: HTTPClient):
8+
self.client = client
9+
10+
async def create(
11+
self,
12+
requested_by: str,
13+
requested_for: str,
14+
notify_when_completed: bool = False,
15+
) -> DataRemoval:
16+
response = await self.client.post(
17+
"/data-removals",
18+
json={
19+
"RequestedBy": requested_by,
20+
"RequestedFor": requested_for,
21+
"NotifyWhenCompleted": notify_when_completed,
22+
},
23+
)
24+
return DataRemoval(**response.json())
25+
26+
async def get(self, removal_id: int) -> DataRemoval:
27+
response = await self.client.get(f"/data-removals/{removal_id}")
28+
return DataRemoval(**response.json())
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from pydantic import BaseModel, ConfigDict, Field
2+
3+
4+
class DataRemoval(BaseModel):
5+
model_config = ConfigDict(populate_by_name=True)
6+
7+
id: int = Field(alias="ID")
8+
status: str = Field(alias="Status")

tests/conftest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from httpx import Response
77

88
from postmark.models.bounces import BounceManager
9+
from postmark.models.data_removals import DataRemovalManager
910
from postmark.models.domains import DomainManager
1011
from postmark.models.inbound import InboundManager
1112
from postmark.models.inbound_rules import InboundRuleManager
@@ -128,3 +129,8 @@ def webhooks(fake_client):
128129
@pytest.fixture
129130
def suppressions(fake_client):
130131
return SuppressionManager(fake_client), fake_client
132+
133+
134+
@pytest.fixture
135+
def data_removals(fake_client):
136+
return DataRemovalManager(fake_client), fake_client

tests/test_data_removals.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"""Tests for DataRemovalManager."""
2+
3+
import pytest
4+
5+
# ---------------------------------------------------------------------------
6+
# Shared fixture data
7+
# ---------------------------------------------------------------------------
8+
9+
REMOVAL = {"ID": 42, "Status": "Pending"}
10+
REMOVAL_DONE = {"ID": 42, "Status": "Done"}
11+
12+
13+
# ---------------------------------------------------------------------------
14+
# Create data removal
15+
# ---------------------------------------------------------------------------
16+
17+
18+
class TestCreateDataRemoval:
19+
@pytest.mark.asyncio
20+
async def test_create_success(self, data_removals):
21+
manager, fake = data_removals
22+
fake.mock_post_response(REMOVAL)
23+
24+
result = await manager.create(
25+
requested_by="admin@example.com",
26+
requested_for="user@example.com",
27+
)
28+
29+
assert result.id == 42
30+
assert result.status == "Pending"
31+
32+
@pytest.mark.asyncio
33+
async def test_create_calls_correct_endpoint(self, data_removals):
34+
manager, fake = data_removals
35+
fake.mock_post_response(REMOVAL)
36+
37+
await manager.create(
38+
requested_by="admin@example.com",
39+
requested_for="user@example.com",
40+
)
41+
42+
fake.post.assert_called_once_with(
43+
"/data-removals",
44+
json={
45+
"RequestedBy": "admin@example.com",
46+
"RequestedFor": "user@example.com",
47+
"NotifyWhenCompleted": False,
48+
},
49+
)
50+
51+
@pytest.mark.asyncio
52+
async def test_create_with_notify(self, data_removals):
53+
manager, fake = data_removals
54+
fake.mock_post_response(REMOVAL)
55+
56+
await manager.create(
57+
requested_by="admin@example.com",
58+
requested_for="user@example.com",
59+
notify_when_completed=True,
60+
)
61+
62+
body = fake.post.call_args[1]["json"]
63+
assert body["NotifyWhenCompleted"] is True
64+
65+
66+
# ---------------------------------------------------------------------------
67+
# Get data removal
68+
# ---------------------------------------------------------------------------
69+
70+
71+
class TestGetDataRemoval:
72+
@pytest.mark.asyncio
73+
async def test_get_success(self, data_removals):
74+
manager, fake = data_removals
75+
fake.mock_get_response(REMOVAL_DONE)
76+
77+
result = await manager.get(42)
78+
79+
assert result.id == 42
80+
assert result.status == "Done"
81+
82+
@pytest.mark.asyncio
83+
async def test_get_calls_correct_endpoint(self, data_removals):
84+
manager, fake = data_removals
85+
fake.mock_get_response(REMOVAL)
86+
87+
await manager.get(42)
88+
89+
fake.get.assert_called_once_with("/data-removals/42")

0 commit comments

Comments
 (0)