Skip to content

Commit f34a2d9

Browse files
authored
MPT-13322 Add catalog products media (#34)
2 parents e47e712 + 2c9513c commit f34a2d9

5 files changed

Lines changed: 416 additions & 1 deletion

File tree

mpt_api_client/resources/catalog/products.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
AsyncItemGroupsService,
1616
ItemGroupsService,
1717
)
18+
from mpt_api_client.resources.catalog.products_media import (
19+
AsyncMediaService,
20+
MediaService,
21+
)
1822
from mpt_api_client.resources.catalog.products_parameter_groups import (
1923
AsyncParameterGroupsService,
2024
ParameterGroupsService,
@@ -59,6 +63,12 @@ def parameter_groups(self, product_id: str) -> ParameterGroupsService:
5963
http_client=self.http_client, endpoint_params={"product_id": product_id}
6064
)
6165

66+
def media(self, product_id: str) -> MediaService:
67+
"""Return media service."""
68+
return MediaService(
69+
http_client=self.http_client, endpoint_params={"product_id": product_id}
70+
)
71+
6272
def product_parameters(self, product_id: str) -> ParametersService:
6373
"""Return product_parameters service."""
6474
return ParametersService(
@@ -88,6 +98,12 @@ def parameter_groups(self, product_id: str) -> AsyncParameterGroupsService:
8898
http_client=self.http_client, endpoint_params={"product_id": product_id}
8999
)
90100

101+
def media(self, product_id: str) -> AsyncMediaService:
102+
"""Return media service."""
103+
return AsyncMediaService(
104+
http_client=self.http_client, endpoint_params={"product_id": product_id}
105+
)
106+
91107
def product_parameters(self, product_id: str) -> AsyncParametersService:
92108
"""Return product_parameters service."""
93109
return AsyncParametersService(
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import json
2+
from typing import override
3+
4+
from httpx import Response
5+
from httpx._types import FileTypes
6+
7+
from mpt_api_client.http import AsyncService, CreateMixin, DeleteMixin, Service
8+
from mpt_api_client.http.mixins import (
9+
AsyncCreateMixin,
10+
AsyncDeleteMixin,
11+
AsyncUpdateMixin,
12+
UpdateMixin,
13+
)
14+
from mpt_api_client.models import FileModel, Model, ResourceData
15+
from mpt_api_client.resources.catalog.mixins import AsyncPublishableMixin, PublishableMixin
16+
17+
18+
def _json_to_file_payload(resource_data: ResourceData) -> bytes:
19+
return json.dumps(
20+
resource_data, ensure_ascii=False, separators=(",", ":"), allow_nan=False
21+
).encode("utf-8")
22+
23+
24+
class Media(Model):
25+
"""Media resource."""
26+
27+
28+
class MediaServiceConfig:
29+
"""Media service configuration."""
30+
31+
_endpoint = "/public/v1/catalog/products/{product_id}/media"
32+
_model_class = Media
33+
_collection_key = "data"
34+
35+
36+
class MediaService(
37+
CreateMixin[Media],
38+
DeleteMixin,
39+
UpdateMixin[Media],
40+
PublishableMixin[Media],
41+
Service[Media],
42+
MediaServiceConfig,
43+
):
44+
"""Media service."""
45+
46+
@override
47+
def create(
48+
self,
49+
resource_data: ResourceData | None = None,
50+
files: dict[str, FileTypes] | None = None, # noqa: WPS221
51+
) -> Media:
52+
"""Create Media resource.
53+
54+
Currently are two types of media resources available image and video.
55+
56+
Video:
57+
resource_data:
58+
{
59+
"name": "SomeMediaFile",
60+
"description":"Some media description",
61+
"mediaType": "Video",
62+
"url": http://www.somemedia.com/somevideo.avi,
63+
"displayOrder": 1
64+
}
65+
files: Add an image with the video thumbnail
66+
67+
Image:
68+
resource_data:
69+
{
70+
"name": "SomeMediaFile",
71+
"description":"Some media description",
72+
"mediaType": "Video",
73+
"displayOrder": 1
74+
}
75+
files: The image itself
76+
77+
Args:
78+
resource_data: Resource data.
79+
files: Files data.
80+
81+
Returns:
82+
Media resource.
83+
"""
84+
files = files or {}
85+
86+
# Note: This is a workaround to fulfill MPT API request format
87+
#
88+
# HTTPx does not support sending json and files in the same call
89+
# currently only supports sending form-data and files in the same call.
90+
# https://www.python-httpx.org/quickstart/#sending-multipart-file-uploads
91+
#
92+
# MPT API expects files and data to be submitted in a multipart form-data upload.
93+
#
94+
# Current workaround is to send the json data as an unnamed file.
95+
# This ends adding the json as payload multipart data.
96+
#
97+
# json.dumps is setup using the same params of httpx json encoder to produce the same
98+
# encodings.
99+
100+
if resource_data:
101+
files["_media_data"] = (
102+
None,
103+
_json_to_file_payload(resource_data),
104+
"application/json",
105+
)
106+
107+
response = self.http_client.post(self.endpoint, files=files)
108+
response.raise_for_status()
109+
return Media.from_response(response)
110+
111+
def download(self, media_id: str) -> FileModel:
112+
"""Download the media file for the given media ID.
113+
114+
Args:
115+
media_id: Media ID.
116+
117+
Returns:
118+
Media file.
119+
"""
120+
response: Response = self._resource_do_request(
121+
media_id, method="GET", headers={"Accept": "*"}
122+
)
123+
return FileModel(response)
124+
125+
126+
class AsyncMediaService(
127+
AsyncCreateMixin[Media],
128+
AsyncDeleteMixin,
129+
AsyncUpdateMixin[Media],
130+
AsyncPublishableMixin[Media],
131+
AsyncService[Media],
132+
MediaServiceConfig,
133+
):
134+
"""Media service."""
135+
136+
@override
137+
async def create(
138+
self,
139+
resource_data: ResourceData | None = None,
140+
files: dict[str, FileTypes] | None = None, # noqa: WPS221
141+
) -> Media:
142+
"""Create Media resource.
143+
144+
Args:
145+
resource_data: Resource data.
146+
files: Files data.
147+
148+
Returns:
149+
Media resource.
150+
"""
151+
files = files or {}
152+
153+
# Note: This is a workaround to fulfill MPT API request format
154+
#
155+
# HTTPx does not support sending json and files in the same call
156+
# currently only supports sending form-data and files in the same call.
157+
# https://www.python-httpx.org/quickstart/#sending-multipart-file-uploads
158+
#
159+
# MPT API expects files and data to be submitted in a multipart form-data upload.
160+
#
161+
# Current workaround is to send the json data as an unnamed file.
162+
# This ends adding the json as payload multipart data.
163+
#
164+
# json.dumps is setup using the same params of httpx json encoder to produce the same
165+
# encodings.
166+
167+
if resource_data:
168+
files["_media_data"] = (
169+
None,
170+
_json_to_file_payload(resource_data),
171+
"application/json",
172+
)
173+
174+
response = await self.http_client.post(self.endpoint, files=files)
175+
response.raise_for_status()
176+
return Media.from_response(response)
177+
178+
async def download(self, media_id: str) -> FileModel:
179+
"""Download the media file for the given media ID.
180+
181+
Args:
182+
media_id: Media ID.
183+
184+
Returns:
185+
Media file.
186+
"""
187+
response = await self._resource_do_request(media_id, method="GET", headers={"Accept": "*"})
188+
return FileModel(response)

setup.cfg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ extend-ignore =
3333

3434
per-file-ignores =
3535
mpt_api_client/rql/query_builder.py: WPS110 WPS115 WPS210 WPS214
36-
mpt_api_client/resources/catalog/products.py: WPS215
36+
mpt_api_client/resources/catalog/products.py: WPS204 WPS215
3737
mpt_api_client/resources/catalog/items.py: WPS215
3838
mpt_api_client/resources/catalog/products_item_groups.py: WPS215
3939
mpt_api_client/resources/catalog/products_parameter_groups.py: WPS215
4040
mpt_api_client/resources/catalog/products_parameters.py: WPS215
41+
mpt_api_client/resources/catalog/products_media.py: WPS215
4142
tests/http/test_async_service.py: WPS204 WPS202
4243
tests/http/test_service.py: WPS204 WPS202
4344

tests/resources/catalog/test_products.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
AsyncItemGroupsService,
66
ItemGroupsService,
77
)
8+
from mpt_api_client.resources.catalog.products_media import (
9+
AsyncMediaService,
10+
MediaService,
11+
)
812
from mpt_api_client.resources.catalog.products_parameter_groups import (
913
AsyncParameterGroupsService,
1014
ParameterGroupsService,
@@ -44,6 +48,7 @@ def test_async_mixins_present(async_products_service, method):
4448
[
4549
("item_groups", ItemGroupsService),
4650
("parameter_groups", ParameterGroupsService),
51+
("media", MediaService),
4752
("product_parameters", ParametersService),
4853
],
4954
)
@@ -59,6 +64,7 @@ def test_property_services(products_service, service_method, expected_service_cl
5964
[
6065
("item_groups", AsyncItemGroupsService),
6166
("parameter_groups", AsyncParameterGroupsService),
67+
("media", AsyncMediaService),
6268
("product_parameters", AsyncParametersService),
6369
],
6470
)

0 commit comments

Comments
 (0)