Skip to content
Merged
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
119 changes: 119 additions & 0 deletions mpt_api_client/resources/notifications/batches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from httpx._types import FileTypes

from mpt_api_client.http import AsyncService, Service
from mpt_api_client.http.mixins import _json_to_file_payload
from mpt_api_client.models import FileModel, Model, ResourceData


class Batch(Model):
"""Notifications Batch resource."""


class BatchesServiceConfig:
"""Notifications Batches service configuration."""

_endpoint = "/public/v1/notifications/batches"
_model_class = Batch
_collection_key = "data"


class BatchesService(
Service[Batch],
BatchesServiceConfig,
):
"""Notifications Batches service."""

def create(
self,
resource_data: ResourceData | None = None,
files: dict[str, FileTypes] | None = None, # noqa: WPS221
data_key: str = "_attachment_data",
) -> Model:
"""Create batch with attachments.

Args:
resource_data: batch data.
files: Files data.
data_key: Key to use for the JSON data in the multipart form.

Returns:
Created resource.
"""
files = files or {}

if resource_data:
files[data_key] = (
None,
_json_to_file_payload(resource_data),
"application/json",
)

response = self.http_client.post(self.endpoint, files=files)
response.raise_for_status()
return self._model_class.from_response(response)

def get_batch_attachment(self, batch_id: str, attachment_id: str) -> FileModel:
"""Get batch attachment.

Args:
batch_id: Batch ID.
attachment_id: Attachment ID.

Returns:
FileModel containing the attachment.
"""
response = self.http_client.get(f"{self.endpoint}/{batch_id}/attachments/{attachment_id}")
response.raise_for_status()
return FileModel(response)


class AsyncBatchesService(
AsyncService[Batch],
BatchesServiceConfig,
):
"""Async Notifications Batches service."""

async def create(
self,
resource_data: ResourceData | None = None,
files: dict[str, FileTypes] | None = None, # noqa: WPS221
data_key: str = "_attachment_data",
) -> Model:
"""Create batch with attachments.

Args:
resource_data: batch data.
files: Files data.
data_key: Key to use for the JSON data in the multipart form.

Returns:
Created resource.
"""
files = files or {}

if resource_data:
files[data_key] = (
None,
_json_to_file_payload(resource_data),
"application/json",
)

response = await self.http_client.post(self.endpoint, files=files)
response.raise_for_status()
return self._model_class.from_response(response)

async def get_batch_attachment(self, batch_id: str, attachment_id: str) -> FileModel:
"""Get batch attachment.

Args:
batch_id: Batch ID.
attachment_id: Attachment ID.

Returns:
FileModel containing the attachment.
"""
response = await self.http_client.get(
f"{self.endpoint}/{batch_id}/attachments/{attachment_id}"
)
response.raise_for_status()
return FileModel(response)
4 changes: 2 additions & 2 deletions mpt_api_client/resources/notifications/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ class MessagesService(
Service[Message],
MessagesServiceConfig,
):
"""Notifications Messages service (no CRUD, no block/unblock)."""
"""Notifications Messages service."""


class AsyncMessagesService(
AsyncService[Message],
MessagesServiceConfig,
):
"""Async Notifications Messages service (no CRUD, no block/unblock)."""
"""Async Notifications Messages service."""
11 changes: 11 additions & 0 deletions mpt_api_client/resources/notifications/notifications.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from mpt_api_client.http import AsyncHTTPClient, HTTPClient
from mpt_api_client.resources.notifications.batches import AsyncBatchesService, BatchesService
from mpt_api_client.resources.notifications.categories import (
AsyncCategoriesService,
CategoriesService,
Expand Down Expand Up @@ -28,6 +29,11 @@ def messages(self) -> MessagesService:
"""Messages service."""
return MessagesService(http_client=self.http_client)

@property
def batches(self) -> BatchesService:
"""Batches service."""
return BatchesService(http_client=self.http_client)


class AsyncNotifications:
"""Notifications MPT API Module."""
Expand All @@ -49,3 +55,8 @@ def contacts(self) -> AsyncContactsService:
def messages(self) -> AsyncMessagesService:
"""Async Messages service."""
return AsyncMessagesService(http_client=self.http_client)

@property
def batches(self) -> AsyncBatchesService:
"""Async Batches service."""
return AsyncBatchesService(http_client=self.http_client)
118 changes: 118 additions & 0 deletions tests/resources/notifications/test_batches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import io

import httpx
import pytest
import respx

from mpt_api_client.resources.notifications.batches import (
AsyncBatchesService,
BatchesService,
)


@pytest.fixture
def batches_service(http_client):
return BatchesService(http_client=http_client)


@pytest.fixture
def async_batches_service(async_http_client):
return AsyncBatchesService(http_client=async_http_client)


@pytest.mark.parametrize("method", ["get", "create", "iterate", "get_batch_attachment"])
def test_sync_batches_service_methods(batches_service, method):
assert hasattr(batches_service, method)


@pytest.mark.parametrize("method", ["get", "create", "iterate", "get_batch_attachment"])
def test_async_batches_service_methods(async_batches_service, method):
assert hasattr(async_batches_service, method)


def test_sync_get_batch_attachment(batches_service):
attachment_content = b"Attachment file content or binary data"
with respx.mock:
mock_route = respx.get(
"https://api.example.com/public/v1/notifications/batches/BAT-123/attachments/ATT-456"
).mock(
return_value=httpx.Response(
status_code=httpx.codes.OK,
headers={
"content-type": "application/octet-stream",
"content-disposition": (
'form-data; name="file"; filename="batch_attachment.pdf"'
),
},
content=attachment_content,
)
)
downloaded_file = batches_service.get_batch_attachment("BAT-123", "ATT-456")

assert mock_route.call_count == 1
assert downloaded_file.file_contents == attachment_content
assert downloaded_file.content_type == "application/octet-stream"
assert downloaded_file.filename == "batch_attachment.pdf"


@pytest.mark.asyncio
async def test_async_get_batch_attachment(async_batches_service):
attachment_content = b"Attachment file content or binary data"
with respx.mock:
mock_route = respx.get(
"https://api.example.com/public/v1/notifications/batches/BAT-123/attachments/ATT-456"
).mock(
return_value=httpx.Response(
status_code=httpx.codes.OK,
headers={
"content-type": "application/octet-stream",
"content-disposition": (
'form-data; name="file"; filename="batch_attachment.pdf"'
),
},
content=attachment_content,
)
)
downloaded_file = await async_batches_service.get_batch_attachment("BAT-123", "ATT-456")

assert mock_route.call_count == 1
assert downloaded_file.file_contents == attachment_content
assert downloaded_file.content_type == "application/octet-stream"
assert downloaded_file.filename == "batch_attachment.pdf"


def test_sync_batches_create_with_data(batches_service):
batch_data = {"name": "Test Batch"}
with respx.mock:
mock_route = respx.post("https://api.example.com/public/v1/notifications/batches").mock(
return_value=httpx.Response(
status_code=httpx.codes.OK,
json={"id": "BAT-133", "name": "Test Batch"},
)
)
files = {"attachment": ("test.pdf", io.BytesIO(b"PDF content"), "application/pdf")}
new_batch = batches_service.create(batch_data, files=files)
request = mock_route.calls[0].request
assert b'Content-Disposition: form-data; name="_attachment_data"' in request.content
assert mock_route.call_count == 1
assert new_batch.id == "BAT-133"
assert new_batch.name == "Test Batch"


@pytest.mark.asyncio
async def test_async_batches_create_with_data(async_batches_service):
batch_data = {"name": "Test Batch"}
with respx.mock:
mock_route = respx.post("https://api.example.com/public/v1/notifications/batches").mock(
return_value=httpx.Response(
status_code=httpx.codes.OK,
json={"id": "BAT-133", "name": "Test Batch"},
)
)
files = {"attachment": ("test.pdf", io.BytesIO(b"PDF content"), "application/pdf")}
new_batch = await async_batches_service.create(batch_data, files=files)
request = mock_route.calls[0].request
assert b'Content-Disposition: form-data; name="_attachment_data"' in request.content
assert mock_route.call_count == 1
assert new_batch.id == "BAT-133"
assert new_batch.name == "Test Batch"
11 changes: 7 additions & 4 deletions tests/resources/notifications/test_notifications.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest

from mpt_api_client.resources import AsyncNotifications, Notifications
from mpt_api_client.resources.notifications.batches import AsyncBatchesService, BatchesService
from mpt_api_client.resources.notifications.categories import (
AsyncCategoriesService,
CategoriesService,
Expand Down Expand Up @@ -29,12 +30,13 @@ def test_async_notifications_init(async_http_client):
("categories", CategoriesService),
("contacts", ContactsService),
("messages", MessagesService),
("batches", BatchesService),
],
)
def test_notifications_properties(http_client, attr_name, expected):
commerce = Notifications(http_client=http_client)
notifications = Notifications(http_client=http_client)

service = getattr(commerce, attr_name)
service = getattr(notifications, attr_name)

assert isinstance(service, expected)

Expand All @@ -45,11 +47,12 @@ def test_notifications_properties(http_client, attr_name, expected):
("categories", AsyncCategoriesService),
("contacts", AsyncContactsService),
("messages", AsyncMessagesService),
("batches", AsyncBatchesService),
],
)
def test_async_notifications_properties(http_client, attr_name, expected):
commerce = AsyncNotifications(http_client=http_client)
notifications = AsyncNotifications(http_client=http_client)

service = getattr(commerce, attr_name)
service = getattr(notifications, attr_name)

assert isinstance(service, expected)