Skip to content

Commit fff2059

Browse files
committed
MPT-13322 Add catalog products media
1 parent e47e712 commit fff2059

5 files changed

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