Skip to content

Commit 17b751a

Browse files
committed
MPT-13324 Add catalog products terms variants
- Add catalog product terms variants - Refactored create and download into FileOperationsMixin
1 parent 8da8b0b commit 17b751a

16 files changed

+507
-838
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."""

mpt_api_client/resources/catalog/product_terms.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
)
88
from mpt_api_client.models import Model
99
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+
)
1014

1115

1216
class Term(Model):
@@ -31,6 +35,13 @@ class TermService(
3135
):
3236
"""Term service."""
3337

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+
3445

3546
class AsyncTermService(
3647
AsyncCreateMixin[Term],
@@ -41,3 +52,10 @@ class AsyncTermService(
4152
TermServiceConfig,
4253
):
4354
"""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+
)
Lines changed: 6 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,15 @@
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
1+
from mpt_api_client.http import AsyncService, DeleteMixin, Service
82
from mpt_api_client.http.mixins import (
9-
AsyncCreateMixin,
103
AsyncDeleteMixin,
4+
AsyncFileOperationsMixin,
115
AsyncUpdateMixin,
6+
FileOperationsMixin,
127
UpdateMixin,
138
)
14-
from mpt_api_client.models import FileModel, Model, ResourceData
9+
from mpt_api_client.models import Model
1510
from mpt_api_client.resources.catalog.mixins import AsyncPublishableMixin, PublishableMixin
1611

1712

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-
2413
class Document(Model):
2514
"""Document resource."""
2615

@@ -34,7 +23,7 @@ class DocumentServiceConfig:
3423

3524

3625
class DocumentService(
37-
CreateMixin[Document],
26+
FileOperationsMixin[Document],
3827
DeleteMixin,
3928
UpdateMixin[Document],
4029
PublishableMixin[Document],
@@ -43,101 +32,13 @@ class DocumentService(
4332
):
4433
"""Document service."""
4534

46-
@override
47-
def create(
48-
self,
49-
resource_data: ResourceData | None = None,
50-
files: dict[str, FileTypes] | None = None, # noqa: WPS221
51-
) -> Document:
52-
"""Create Document resource.
53-
54-
Include the document as a file or add an url in resource_data to be uploaded.
55-
56-
Args:
57-
resource_data: Resource data.
58-
files: Files data.
59-
60-
Returns:
61-
Document resource.
62-
"""
63-
files = files or {}
64-
65-
if resource_data:
66-
files["_attachment_data"] = (
67-
None,
68-
_json_to_file_payload(resource_data),
69-
"application/json",
70-
)
71-
72-
response = self.http_client.post(self.endpoint, files=files)
73-
response.raise_for_status()
74-
return Document.from_response(response)
75-
76-
def download(self, document_id: str) -> FileModel:
77-
"""Download the document file for the given document ID.
78-
79-
Args:
80-
document_id: Document ID.
81-
82-
Returns:
83-
Document file.
84-
"""
85-
response: Response = self._resource_do_request(
86-
document_id, method="GET", headers={"Accept": "*"}
87-
)
88-
return FileModel(response)
89-
9035

9136
class AsyncDocumentService(
92-
AsyncCreateMixin[Document],
37+
AsyncFileOperationsMixin[Document],
9338
AsyncDeleteMixin,
9439
AsyncUpdateMixin[Document],
9540
AsyncPublishableMixin[Document],
9641
AsyncService[Document],
9742
DocumentServiceConfig,
9843
):
9944
"""Document service."""
100-
101-
@override
102-
async def create(
103-
self,
104-
resource_data: ResourceData | None = None,
105-
files: dict[str, FileTypes] | None = None, # noqa: WPS221
106-
) -> Document:
107-
"""Create Document resource.
108-
109-
Include the document as a file or add an url in resource_data to be uploaded.
110-
111-
Args:
112-
resource_data: Resource data.
113-
files: Files data.
114-
115-
Returns:
116-
Document resource.
117-
"""
118-
files = files or {}
119-
120-
if resource_data:
121-
files["_attachment_data"] = (
122-
None,
123-
_json_to_file_payload(resource_data),
124-
"application/json",
125-
)
126-
127-
response = await self.http_client.post(self.endpoint, files=files)
128-
response.raise_for_status()
129-
return Document.from_response(response)
130-
131-
async def download(self, document_id: str) -> FileModel:
132-
"""Download the document file for the given document ID.
133-
134-
Args:
135-
document_id: Document ID.
136-
137-
Returns:
138-
Document file.
139-
"""
140-
response = await self._resource_do_request(
141-
document_id, method="GET", headers={"Accept": "*"}
142-
)
143-
return FileModel(response)

0 commit comments

Comments
 (0)