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
16 changes: 16 additions & 0 deletions mpt_api_client/resources/catalog/products.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
AsyncItemGroupsService,
ItemGroupsService,
)
from mpt_api_client.resources.catalog.products_media import (
AsyncMediaService,
MediaService,
)
from mpt_api_client.resources.catalog.products_parameter_groups import (
AsyncParameterGroupsService,
ParameterGroupsService,
Expand Down Expand Up @@ -59,6 +63,12 @@ def parameter_groups(self, product_id: str) -> ParameterGroupsService:
http_client=self.http_client, endpoint_params={"product_id": product_id}
)

def media(self, product_id: str) -> MediaService:
"""Return media service."""
return MediaService(
http_client=self.http_client, endpoint_params={"product_id": product_id}
)

def product_parameters(self, product_id: str) -> ParametersService:
"""Return product_parameters service."""
return ParametersService(
Expand Down Expand Up @@ -88,6 +98,12 @@ def parameter_groups(self, product_id: str) -> AsyncParameterGroupsService:
http_client=self.http_client, endpoint_params={"product_id": product_id}
)

def media(self, product_id: str) -> AsyncMediaService:
"""Return media service."""
return AsyncMediaService(
http_client=self.http_client, endpoint_params={"product_id": product_id}
)

def product_parameters(self, product_id: str) -> AsyncParametersService:
"""Return product_parameters service."""
return AsyncParametersService(
Expand Down
188 changes: 188 additions & 0 deletions mpt_api_client/resources/catalog/products_media.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import json
from typing import override

from httpx import Response
from httpx._types import FileTypes

from mpt_api_client.http import AsyncService, CreateMixin, DeleteMixin, Service
from mpt_api_client.http.mixins import (
AsyncCreateMixin,
AsyncDeleteMixin,
AsyncUpdateMixin,
UpdateMixin,
)
from mpt_api_client.models import FileModel, Model, ResourceData
from mpt_api_client.resources.catalog.mixins import AsyncPublishableMixin, PublishableMixin


def _json_to_file_payload(resource_data: ResourceData) -> bytes:
return json.dumps(
resource_data, ensure_ascii=False, separators=(",", ":"), allow_nan=False
).encode("utf-8")


class Media(Model):
"""Media resource."""


class MediaServiceConfig:
"""Media service configuration."""

_endpoint = "/public/v1/catalog/products/{product_id}/media"
_model_class = Media
_collection_key = "data"


class MediaService(
CreateMixin[Media],
DeleteMixin,
UpdateMixin[Media],
PublishableMixin[Media],
Service[Media],
MediaServiceConfig,
):
"""Media service."""

@override
def create(
self,
resource_data: ResourceData | None = None,
files: dict[str, FileTypes] | None = None, # noqa: WPS221
) -> Media:
"""Create Media resource.

Currently are two types of media resources available image and video.

Video:
resource_data:
{
"name": "SomeMediaFile",
"description":"Some media description",
"mediaType": "Video",
"url": http://www.somemedia.com/somevideo.avi,
"displayOrder": 1
}
files: Add an image with the video thumbnail

Image:
resource_data:
{
"name": "SomeMediaFile",
"description":"Some media description",
"mediaType": "Video",
"displayOrder": 1
}
files: The image itself

Args:
resource_data: Resource data.
files: Files data.

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

# Note: This is a workaround to fulfill MPT API request format
#
# HTTPx does not support sending json and files in the same call
# currently only supports sending form-data and files in the same call.
# https://www.python-httpx.org/quickstart/#sending-multipart-file-uploads
#
# MPT API expects files and data to be submitted in a multipart form-data upload.
#
# Current workaround is to send the json data as an unnamed file.
# This ends adding the json as payload multipart data.
#
# json.dumps is setup using the same params of httpx json encoder to produce the same
# encodings.

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

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

def download(self, media_id: str) -> FileModel:
"""Download the media file for the given media ID.

Args:
media_id: Media ID.

Returns:
Media file.
"""
response: Response = self._resource_do_request(
media_id, method="GET", headers={"Accept": "*"}
)
return FileModel(response)


class AsyncMediaService(
AsyncCreateMixin[Media],
AsyncDeleteMixin,
AsyncUpdateMixin[Media],
AsyncPublishableMixin[Media],
AsyncService[Media],
MediaServiceConfig,
):
"""Media service."""

@override
async def create(
self,
resource_data: ResourceData | None = None,
files: dict[str, FileTypes] | None = None, # noqa: WPS221
) -> Media:
"""Create Media resource.

Args:
resource_data: Resource data.
files: Files data.

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

# Note: This is a workaround to fulfill MPT API request format
#
# HTTPx does not support sending json and files in the same call
# currently only supports sending form-data and files in the same call.
# https://www.python-httpx.org/quickstart/#sending-multipart-file-uploads
#
# MPT API expects files and data to be submitted in a multipart form-data upload.
#
# Current workaround is to send the json data as an unnamed file.
# This ends adding the json as payload multipart data.
#
# json.dumps is setup using the same params of httpx json encoder to produce the same
# encodings.

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

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

async def download(self, media_id: str) -> FileModel:
"""Download the media file for the given media ID.

Args:
media_id: Media ID.

Returns:
Media file.
"""
response = await self._resource_do_request(media_id, method="GET", headers={"Accept": "*"})
return FileModel(response)
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ extend-ignore =

per-file-ignores =
mpt_api_client/rql/query_builder.py: WPS110 WPS115 WPS210 WPS214
mpt_api_client/resources/catalog/products.py: WPS215
mpt_api_client/resources/catalog/products.py: WPS204 WPS215
mpt_api_client/resources/catalog/items.py: WPS215
mpt_api_client/resources/catalog/products_item_groups.py: WPS215
mpt_api_client/resources/catalog/products_parameter_groups.py: WPS215
mpt_api_client/resources/catalog/products_parameters.py: WPS215
mpt_api_client/resources/catalog/products_media.py: WPS215
tests/http/test_async_service.py: WPS204 WPS202
tests/http/test_service.py: WPS204 WPS202

Expand Down
6 changes: 6 additions & 0 deletions tests/resources/catalog/test_products.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
AsyncItemGroupsService,
ItemGroupsService,
)
from mpt_api_client.resources.catalog.products_media import (
AsyncMediaService,
MediaService,
)
from mpt_api_client.resources.catalog.products_parameter_groups import (
AsyncParameterGroupsService,
ParameterGroupsService,
Expand Down Expand Up @@ -44,6 +48,7 @@ def test_async_mixins_present(async_products_service, method):
[
("item_groups", ItemGroupsService),
("parameter_groups", ParameterGroupsService),
("media", MediaService),
("product_parameters", ParametersService),
],
)
Expand All @@ -59,6 +64,7 @@ def test_property_services(products_service, service_method, expected_service_cl
[
("item_groups", AsyncItemGroupsService),
("parameter_groups", AsyncParameterGroupsService),
("media", AsyncMediaService),
("product_parameters", AsyncParametersService),
],
)
Expand Down
Loading