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
1 change: 1 addition & 0 deletions e2e_config.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"notifications.batch.id": "MST-3638-2460-4825",
"notifications.category.id": "NTC-6157-0397",
"notifications.contact.id": "CTT-0001-9158",
"notifications.directory.id": "DIR-9614-2985",
"notifications.message.id": "MSG-0000-6215-1019-0139",
"notifications.subscriber.id": "NTS-0829-7123-7123",
"integration.extension.id": "EXT-6587-4477",
Expand Down
53 changes: 53 additions & 0 deletions mpt_api_client/resources/notifications/directories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from mpt_api_client.http import AsyncService, Service
from mpt_api_client.http.mixins import (
AsyncCollectionMixin,
AsyncGetMixin,
CollectionMixin,
GetMixin,
)
from mpt_api_client.models import Model
from mpt_api_client.models.model import BaseModel


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

Attributes:
account: Reference to the account owning the directory.
origin: Reference to the account the directory originates from.
type: Directory type.
contacts: References to the contacts belonging to the directory.
audit: Audit information (created, updated events).
"""

account: BaseModel | None
origin: BaseModel | None
type: str | None
contacts: list[BaseModel] | None
audit: BaseModel | None


class DirectoriesServiceConfig:
"""Notifications Directories service configuration."""

_endpoint = "/public/v1/notifications/directories"
_model_class = Directory
_collection_key = "data"


class DirectoriesService(
GetMixin[Directory],
CollectionMixin[Directory],
Service[Directory],
DirectoriesServiceConfig,
):
"""Notifications Directories service."""


class AsyncDirectoriesService(
AsyncGetMixin[Directory],
AsyncCollectionMixin[Directory],
AsyncService[Directory],
DirectoriesServiceConfig,
):
"""Async Notifications Directories service."""
53 changes: 53 additions & 0 deletions mpt_api_client/resources/notifications/footers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from mpt_api_client.http import AsyncService, Service
from mpt_api_client.http.mixins import (
AsyncCollectionMixin,
AsyncManagedResourceMixin,
CollectionMixin,
ManagedResourceMixin,
)
from mpt_api_client.models import Model
from mpt_api_client.models.model import BaseModel


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

Attributes:
language_code: Language code of the footer.
content: Content of the footer.
is_default: Whether this footer is the default one.
status: Footer status.
audit: Audit information (created, updated events).
"""

language_code: str | None
content: str | None
is_default: bool | None
status: str | None
audit: BaseModel | None


class FootersServiceConfig:
"""Notifications Footers service configuration."""

_endpoint = "/public/v1/notifications/footers"
_model_class = Footer
_collection_key = "data"


class FootersService(
ManagedResourceMixin[Footer],
CollectionMixin[Footer],
Service[Footer],
FootersServiceConfig,
):
"""Notifications Footers service."""


class AsyncFootersService(
AsyncManagedResourceMixin[Footer],
AsyncCollectionMixin[Footer],
AsyncService[Footer],
FootersServiceConfig,
):
"""Async Notifications Footers service."""
39 changes: 39 additions & 0 deletions mpt_api_client/resources/notifications/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,20 @@
CategoriesService,
)
from mpt_api_client.resources.notifications.contacts import AsyncContactsService, ContactsService
from mpt_api_client.resources.notifications.directories import (
AsyncDirectoriesService,
DirectoriesService,
)
from mpt_api_client.resources.notifications.footers import AsyncFootersService, FootersService
from mpt_api_client.resources.notifications.messages import AsyncMessagesService, MessagesService
from mpt_api_client.resources.notifications.subscribers import (
AsyncSubscribersService,
SubscribersService,
)
from mpt_api_client.resources.notifications.webhooks import (
AsyncWebhooksService,
WebhooksService,
)


class Notifications:
Expand Down Expand Up @@ -51,6 +60,16 @@ def contacts(self) -> ContactsService:
"""Contacts service."""
return ContactsService(http_client=self.http_client)

@property
def directories(self) -> DirectoriesService:
"""Directories service."""
return DirectoriesService(http_client=self.http_client)

@property
def footers(self) -> FootersService:
"""Footers service."""
return FootersService(http_client=self.http_client)

@property
def messages(self) -> MessagesService:
"""Messages service."""
Expand All @@ -61,6 +80,11 @@ def subscribers(self) -> SubscribersService:
"""Subscriptions service."""
return SubscribersService(http_client=self.http_client)

@property
def webhooks(self) -> WebhooksService:
"""Webhooks service."""
return WebhooksService(http_client=self.http_client)


class AsyncNotifications:
"""Notifications MPT API Module."""
Expand Down Expand Up @@ -100,6 +124,16 @@ def contacts(self) -> AsyncContactsService:
"""Async Contacts service."""
return AsyncContactsService(http_client=self.http_client)

@property
def directories(self) -> AsyncDirectoriesService:
"""Async Directories service."""
return AsyncDirectoriesService(http_client=self.http_client)

@property
def footers(self) -> AsyncFootersService:
"""Async Footers service."""
return AsyncFootersService(http_client=self.http_client)

@property
def messages(self) -> AsyncMessagesService:
"""Async Messages service."""
Expand All @@ -109,3 +143,8 @@ def messages(self) -> AsyncMessagesService:
def subscribers(self) -> AsyncSubscribersService:
"""Subscriptions service."""
return AsyncSubscribersService(http_client=self.http_client)

@property
def webhooks(self) -> AsyncWebhooksService:
"""Async Webhooks service."""
return AsyncWebhooksService(http_client=self.http_client)
81 changes: 81 additions & 0 deletions mpt_api_client/resources/notifications/webhooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from mpt_api_client.http import AsyncService, Service
from mpt_api_client.http.mixins import (
AsyncCollectionMixin,
AsyncDisableMixin,
AsyncEnableMixin,
AsyncManagedResourceMixin,
CollectionMixin,
DisableMixin,
EnableMixin,
ManagedResourceMixin,
)
from mpt_api_client.models import Model
from mpt_api_client.models.model import BaseModel


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

Attributes:
name: Webhook name.
url: Endpoint the webhook calls.
description: Webhook description.
status: Webhook status.
type: Webhook type.
secret: Shared secret used to sign webhook calls.
statistics: Call statistics of the webhook.
object_type: Type of the object the webhook is bound to.
account: Reference to the account owning the webhook.
object: Reference to the object the webhook is bound to.
criteria: Key/value criteria narrowing when the webhook fires.
last_success: Last successful call.
last_failure: Last failed call.
last_call: Last call regardless of its outcome.
audit: Audit information (created, updated events).
"""

name: str | None
url: str | None
description: str | None
status: str | None
type: str | None
secret: str | None
statistics: BaseModel | None
object_type: str | None
account: BaseModel | None
object: BaseModel | None
criteria: list[BaseModel] | None
last_success: BaseModel | None
last_failure: BaseModel | None
last_call: BaseModel | None
audit: BaseModel | None


class WebhooksServiceConfig:
"""Notifications Webhooks service configuration."""

_endpoint = "/public/v1/notifications/webhooks"
_model_class = Webhook
_collection_key = "data"


class WebhooksService(
EnableMixin[Webhook],
DisableMixin[Webhook],
ManagedResourceMixin[Webhook],
CollectionMixin[Webhook],
Service[Webhook],
WebhooksServiceConfig,
):
"""Notifications Webhooks service."""


class AsyncWebhooksService(
AsyncEnableMixin[Webhook],
AsyncDisableMixin[Webhook],
AsyncManagedResourceMixin[Webhook],
AsyncCollectionMixin[Webhook],
AsyncService[Webhook],
WebhooksServiceConfig,
):
"""Async Notifications Webhooks service."""
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ per-file-ignores = [
"mpt_api_client/resources/commerce/*.py: WPS235 WPS215",
"mpt_api_client/resources/exchange/*.py: WPS235 WPS215",
"mpt_api_client/resources/integration/*.py: WPS204 WPS214 WPS215 WPS235",
"mpt_api_client/resources/notifications/footers.py: WPS110 WPS215",
"mpt_api_client/resources/notifications/notifications.py: WPS214",
"mpt_api_client/resources/helpdesk/*.py: WPS204 WPS215 WPS214",
"mpt_api_client/resources/program/*.py: WPS204 WPS215 WPS235",
"mpt_api_client/rql/query_builder.py: WPS110 WPS115 WPS210 WPS214",
Expand Down
56 changes: 56 additions & 0 deletions tests/e2e/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,62 @@ def create_fixture_resource_and_delete(service, resource_data, upload_file=None)
_delete_resource(service, resource)


async def _async_create_first_free(service, candidates, is_taken):
remaining = list(candidates)
while remaining:
candidate = remaining.pop(0)
try:
return await service.create(candidate)
except MPTAPIError as error:
if not is_taken(error):
raise
raise RuntimeError(f"Every candidate payload is already taken: {candidates}")


def _create_first_free(service, candidates, is_taken):
for candidate in candidates:
try:
return service.create(candidate)
except MPTAPIError as error:
if not is_taken(error):
raise
raise RuntimeError(f"Every candidate payload is already taken: {candidates}")


@asynccontextmanager
async def async_create_first_free_fixture_and_delete(service, candidates, is_taken):
"""Create the first candidate the platform accepts, then delete it on teardown.

``candidates`` are payloads that differ only in a server-enforced uniqueness key and
``is_taken(error)`` tells whether a rejected create means that key is already in use.
The create call itself is the claim, so two runs that race for the same key cannot both
succeed and the loser moves on to the next candidate instead of failing.
"""
resource = await _async_create_first_free(service, candidates, is_taken)

try:
yield resource
finally:
await _delete_async_resource(service, resource)


@contextmanager
def create_first_free_fixture_and_delete(service, candidates, is_taken):
"""Create the first candidate the platform accepts, then delete it on teardown.

``candidates`` are payloads that differ only in a server-enforced uniqueness key and
``is_taken(error)`` tells whether a rejected create means that key is already in use.
The create call itself is the claim, so two runs that race for the same key cannot both
succeed and the loser moves on to the next candidate instead of failing.
"""
resource = _create_first_free(service, candidates, is_taken)

try:
yield resource
finally:
_delete_resource(service, resource)


@asynccontextmanager
async def async_create_fixture_resource_and_finalize(service, resource_data, finalize, logger):
"""Create a resource, then transition it to a terminal state on teardown.
Expand Down
Empty file.
16 changes: 16 additions & 0 deletions tests/e2e/notifications/directories/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import pytest


@pytest.fixture
def directories_service(mpt_ops):
return mpt_ops.notifications.directories


@pytest.fixture
def async_directories_service(async_mpt_ops):
return async_mpt_ops.notifications.directories


@pytest.fixture(scope="session")
def directory_id(e2e_config):
return e2e_config["notifications.directory.id"]
25 changes: 25 additions & 0 deletions tests/e2e/notifications/directories/test_async_directories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import pytest

from mpt_api_client.exceptions import MPTAPIError
from tests.e2e.helper import assert_async_service_filter_with_iterate

pytestmark = [pytest.mark.flaky]


async def test_get_directory(async_directories_service, directory_id):
result = await async_directories_service.get(directory_id)

assert result.id == directory_id


async def test_get_directory_not_found(async_directories_service):
bogus_id = "DIR-0000-0000"

with pytest.raises(MPTAPIError, match=r"404 Not Found"):
await async_directories_service.get(bogus_id)


async def test_filter_directories(async_directories_service, directory_id):
await assert_async_service_filter_with_iterate(
async_directories_service, directory_id, None
) # act
Loading
Loading