Skip to content

Commit a8e143b

Browse files
authored
MPT-13324 Add catalog products terms variants (#38)
2 parents f34a2d9 + 17b751a commit a8e143b

20 files changed

+791
-563
lines changed

mpt_api_client/http/mixins.py

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
1+
import json
12
from urllib.parse import urljoin
23

3-
from mpt_api_client.models import ResourceData
4+
from httpx import Response
5+
from httpx._types import FileTypes
6+
7+
from mpt_api_client.models import FileModel, ResourceData
8+
9+
10+
def _json_to_file_payload(resource_data: ResourceData) -> bytes:
11+
return json.dumps(
12+
resource_data, ensure_ascii=False, separators=(",", ":"), allow_nan=False
13+
).encode("utf-8")
414

515

616
class CreateMixin[Model]:
@@ -48,6 +58,53 @@ def update(self, resource_id: str, resource_data: ResourceData) -> Model:
4858
return self._resource_action(resource_id, "PUT", json=resource_data) # type: ignore[attr-defined, no-any-return]
4959

5060

61+
class FileOperationsMixin[Model]:
62+
"""Mixin that provides create and download methods for file-based resources."""
63+
64+
def create(
65+
self,
66+
resource_data: ResourceData | None = None,
67+
files: dict[str, FileTypes] | None = None, # noqa: WPS221
68+
data_key: str = "_attachment_data",
69+
) -> Model:
70+
"""Create resource with file support.
71+
72+
Args:
73+
resource_data: Resource data.
74+
files: Files data.
75+
data_key: Key to use for the JSON data in the multipart form.
76+
77+
Returns:
78+
Created resource.
79+
"""
80+
files = files or {}
81+
82+
if resource_data:
83+
files[data_key] = (
84+
None,
85+
_json_to_file_payload(resource_data),
86+
"application/json",
87+
)
88+
89+
response = self.http_client.post(self.endpoint, files=files) # type: ignore[attr-defined]
90+
response.raise_for_status()
91+
return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]
92+
93+
def download(self, resource_id: str) -> FileModel:
94+
"""Download the file for the given resource ID.
95+
96+
Args:
97+
resource_id: Resource ID.
98+
99+
Returns:
100+
File model containing the downloaded file.
101+
"""
102+
response: Response = self._resource_do_request( # type: ignore[attr-defined]
103+
resource_id, method="GET", headers={"Accept": "*"}
104+
)
105+
return FileModel(response)
106+
107+
51108
class AsyncCreateMixin[Model]:
52109
"""Create resource mixin."""
53110

@@ -92,3 +149,50 @@ async def update(self, resource_id: str, resource_data: ResourceData) -> Model:
92149
93150
"""
94151
return await self._resource_action(resource_id, "PUT", json=resource_data) # type: ignore[attr-defined, no-any-return]
152+
153+
154+
class AsyncFileOperationsMixin[Model]:
155+
"""Async mixin that provides create and download methods for file-based resources."""
156+
157+
async def create(
158+
self,
159+
resource_data: ResourceData | None = None,
160+
files: dict[str, FileTypes] | None = None, # noqa: WPS221
161+
data_key: str = "_attachment_data",
162+
) -> Model:
163+
"""Create resource with file support.
164+
165+
Args:
166+
resource_data: Resource data.
167+
files: Files data.
168+
data_key: Key to use for the JSON data in the multipart form.
169+
170+
Returns:
171+
Created resource.
172+
"""
173+
files = files or {}
174+
175+
if resource_data:
176+
files[data_key] = (
177+
None,
178+
_json_to_file_payload(resource_data),
179+
"application/json",
180+
)
181+
182+
response = await self.http_client.post(self.endpoint, files=files) # type: ignore[attr-defined]
183+
response.raise_for_status()
184+
return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]
185+
186+
async def download(self, resource_id: str) -> FileModel:
187+
"""Download the file for the given resource ID.
188+
189+
Args:
190+
resource_id: Resource ID.
191+
192+
Returns:
193+
File model containing the downloaded file.
194+
"""
195+
response = await self._resource_do_request( # type: ignore[attr-defined]
196+
resource_id, method="GET", headers={"Accept": "*"}
197+
)
198+
return FileModel(response)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from mpt_api_client.http import AsyncService, DeleteMixin, Service
2+
from mpt_api_client.http.mixins import (
3+
AsyncDeleteMixin,
4+
AsyncFileOperationsMixin,
5+
AsyncUpdateMixin,
6+
FileOperationsMixin,
7+
UpdateMixin,
8+
)
9+
from mpt_api_client.models import Model
10+
from mpt_api_client.resources.catalog.mixins import (
11+
AsyncPublishableMixin,
12+
PublishableMixin,
13+
)
14+
15+
16+
class TermVariant(Model):
17+
"""Term variant resource."""
18+
19+
20+
class TermVariantServiceConfig:
21+
"""Term variant service configuration."""
22+
23+
_endpoint = "/public/v1/catalog/products/terms/{term_id}/variants"
24+
_model_class = TermVariant
25+
_collection_key = "data"
26+
27+
28+
class TermVariantService(
29+
FileOperationsMixin[TermVariant],
30+
DeleteMixin,
31+
UpdateMixin[TermVariant],
32+
PublishableMixin[TermVariant],
33+
Service[TermVariant],
34+
TermVariantServiceConfig,
35+
):
36+
"""Term variant service."""
37+
38+
39+
class AsyncTermVariantService(
40+
AsyncFileOperationsMixin[TermVariant],
41+
AsyncDeleteMixin,
42+
AsyncUpdateMixin[TermVariant],
43+
AsyncPublishableMixin[TermVariant],
44+
AsyncService[TermVariant],
45+
TermVariantServiceConfig,
46+
):
47+
"""Async Term variant service."""
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from mpt_api_client.http import AsyncService, CreateMixin, DeleteMixin, Service
2+
from mpt_api_client.http.mixins import (
3+
AsyncCreateMixin,
4+
AsyncDeleteMixin,
5+
AsyncUpdateMixin,
6+
UpdateMixin,
7+
)
8+
from mpt_api_client.models import Model
9+
from mpt_api_client.resources.catalog.mixins import AsyncPublishableMixin, PublishableMixin
10+
from mpt_api_client.resources.catalog.product_term_variants import (
11+
AsyncTermVariantService,
12+
TermVariantService,
13+
)
14+
15+
16+
class Term(Model):
17+
"""Term resource."""
18+
19+
20+
class TermServiceConfig:
21+
"""Term service configuration."""
22+
23+
_endpoint = "/public/v1/catalog/products/{product_id}/terms"
24+
_model_class = Term
25+
_collection_key = "data"
26+
27+
28+
class TermService(
29+
CreateMixin[Term],
30+
DeleteMixin,
31+
UpdateMixin[Term],
32+
PublishableMixin[Term],
33+
Service[Term],
34+
TermServiceConfig,
35+
):
36+
"""Term service."""
37+
38+
def variants(self, term_id: str) -> TermVariantService:
39+
"""Access term variants service."""
40+
return TermVariantService(
41+
http_client=self.http_client,
42+
endpoint_params={"term_id": term_id},
43+
)
44+
45+
46+
class AsyncTermService(
47+
AsyncCreateMixin[Term],
48+
AsyncDeleteMixin,
49+
AsyncUpdateMixin[Term],
50+
AsyncPublishableMixin[Term],
51+
AsyncService[Term],
52+
TermServiceConfig,
53+
):
54+
"""Async Term service."""
55+
56+
def variants(self, term_id: str) -> AsyncTermVariantService:
57+
"""Access async term variants service."""
58+
return AsyncTermVariantService(
59+
http_client=self.http_client,
60+
endpoint_params={"term_id": term_id},
61+
)

mpt_api_client/resources/catalog/products.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@
1111
AsyncPublishableMixin,
1212
PublishableMixin,
1313
)
14+
from mpt_api_client.resources.catalog.product_terms import (
15+
AsyncTermService,
16+
TermService,
17+
)
18+
from mpt_api_client.resources.catalog.products_documents import (
19+
AsyncDocumentService,
20+
DocumentService,
21+
)
1422
from mpt_api_client.resources.catalog.products_item_groups import (
1523
AsyncItemGroupsService,
1624
ItemGroupsService,
@@ -27,6 +35,10 @@
2735
AsyncParametersService,
2836
ParametersService,
2937
)
38+
from mpt_api_client.resources.catalog.products_templates import (
39+
AsyncTemplatesService,
40+
TemplatesService,
41+
)
3042

3143

3244
class Product(Model):
@@ -69,12 +81,28 @@ def media(self, product_id: str) -> MediaService:
6981
http_client=self.http_client, endpoint_params={"product_id": product_id}
7082
)
7183

84+
def documents(self, product_id: str) -> DocumentService:
85+
"""Return documents service."""
86+
return DocumentService(
87+
http_client=self.http_client, endpoint_params={"product_id": product_id}
88+
)
89+
7290
def product_parameters(self, product_id: str) -> ParametersService:
7391
"""Return product_parameters service."""
7492
return ParametersService(
7593
http_client=self.http_client, endpoint_params={"product_id": product_id}
7694
)
7795

96+
def templates(self, product_id: str) -> TemplatesService:
97+
"""Return templates service."""
98+
return TemplatesService(
99+
http_client=self.http_client, endpoint_params={"product_id": product_id}
100+
)
101+
102+
def terms(self, product_id: str) -> TermService:
103+
"""Return terms service."""
104+
return TermService(http_client=self.http_client, endpoint_params={"product_id": product_id})
105+
78106

79107
class AsyncProductsService(
80108
AsyncCreateMixin[Product],
@@ -104,8 +132,26 @@ def media(self, product_id: str) -> AsyncMediaService:
104132
http_client=self.http_client, endpoint_params={"product_id": product_id}
105133
)
106134

135+
def documents(self, product_id: str) -> AsyncDocumentService:
136+
"""Return documents service."""
137+
return AsyncDocumentService(
138+
http_client=self.http_client, endpoint_params={"product_id": product_id}
139+
)
140+
107141
def product_parameters(self, product_id: str) -> AsyncParametersService:
108142
"""Return product_parameters service."""
109143
return AsyncParametersService(
110144
http_client=self.http_client, endpoint_params={"product_id": product_id}
111145
)
146+
147+
def templates(self, product_id: str) -> AsyncTemplatesService:
148+
"""Return templates service."""
149+
return AsyncTemplatesService(
150+
http_client=self.http_client, endpoint_params={"product_id": product_id}
151+
)
152+
153+
def terms(self, product_id: str) -> AsyncTermService:
154+
"""Return terms service."""
155+
return AsyncTermService(
156+
http_client=self.http_client, endpoint_params={"product_id": product_id}
157+
)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from mpt_api_client.http import AsyncService, DeleteMixin, Service
2+
from mpt_api_client.http.mixins import (
3+
AsyncDeleteMixin,
4+
AsyncFileOperationsMixin,
5+
AsyncUpdateMixin,
6+
FileOperationsMixin,
7+
UpdateMixin,
8+
)
9+
from mpt_api_client.models import Model
10+
from mpt_api_client.resources.catalog.mixins import AsyncPublishableMixin, PublishableMixin
11+
12+
13+
class Document(Model):
14+
"""Document resource."""
15+
16+
17+
class DocumentServiceConfig:
18+
"""Document service configuration."""
19+
20+
_endpoint = "/public/v1/catalog/products/{product_id}/documents"
21+
_model_class = Document
22+
_collection_key = "data"
23+
24+
25+
class DocumentService(
26+
FileOperationsMixin[Document],
27+
DeleteMixin,
28+
UpdateMixin[Document],
29+
PublishableMixin[Document],
30+
Service[Document],
31+
DocumentServiceConfig,
32+
):
33+
"""Document service."""
34+
35+
36+
class AsyncDocumentService(
37+
AsyncFileOperationsMixin[Document],
38+
AsyncDeleteMixin,
39+
AsyncUpdateMixin[Document],
40+
AsyncPublishableMixin[Document],
41+
AsyncService[Document],
42+
DocumentServiceConfig,
43+
):
44+
"""Document service."""

0 commit comments

Comments
 (0)