Skip to content

Commit 635f194

Browse files
committed
Refactor files handle
1 parent d1039a5 commit 635f194

File tree

15 files changed

+201
-112
lines changed

15 files changed

+201
-112
lines changed

mpt_api_client/http/async_client.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
HTTPStatusError,
99
)
1010

11+
from mpt_api_client.constants import APPLICATION_JSON
1112
from mpt_api_client.exceptions import MPTError, transform_http_status_exception
13+
from mpt_api_client.http.client import json_to_file_payload
1214
from mpt_api_client.http.types import (
1315
HeaderTypes,
1416
QueryParam,
@@ -65,6 +67,8 @@ async def request( # noqa: WPS211
6567
json: Any | None = None,
6668
query_params: QueryParam | None = None,
6769
headers: HeaderTypes | None = None,
70+
json_file_key: str = "_attachment_data",
71+
force_multipart: bool = False,
6872
) -> Response:
6973
"""Perform an HTTP request.
7074
@@ -75,6 +79,8 @@ async def request( # noqa: WPS211
7579
json: Request JSON data.
7680
query_params: Query parameters.
7781
headers: Request headers.
82+
json_file_key: json file name for data when sending a multipart request.
83+
force_multipart: force multipart request even if file is not provided.
7884
7985
Returns:
8086
Response object.
@@ -84,6 +90,10 @@ async def request( # noqa: WPS211
8490
MPTApiError: If the response contains an error.
8591
MPTHttpError: If the response contains an HTTP error.
8692
"""
93+
files = dict(files or {})
94+
if force_multipart or (files and json):
95+
files[json_file_key] = (None, json_to_file_payload(json), APPLICATION_JSON)
96+
json = None
8797
try:
8898
response = await self.httpx_client.request(
8999
method,

mpt_api_client/http/client.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json as json_package
12
import os
23
from typing import Any
34

@@ -8,6 +9,7 @@
89
HTTPTransport,
910
)
1011

12+
from mpt_api_client.constants import APPLICATION_JSON
1113
from mpt_api_client.exceptions import (
1214
MPTError,
1315
transform_http_status_exception,
@@ -18,6 +20,16 @@
1820
RequestFiles,
1921
Response,
2022
)
23+
from mpt_api_client.models import ResourceData
24+
25+
26+
def json_to_file_payload(resource_data: ResourceData | None) -> bytes:
27+
"""Convert resource data to file payload."""
28+
if resource_data is None:
29+
resource_data = {}
30+
return json_package.dumps(
31+
resource_data, ensure_ascii=False, separators=(",", ":"), allow_nan=False
32+
).encode("utf-8")
2133

2234

2335
class HTTPClient:
@@ -67,6 +79,8 @@ def request( # noqa: WPS211
6779
json: Any | None = None,
6880
query_params: QueryParam | None = None,
6981
headers: HeaderTypes | None = None,
82+
json_file_key: str = "_attachment_data",
83+
force_multipart: bool = False,
7084
) -> Response:
7185
"""Perform an HTTP request.
7286
@@ -77,6 +91,8 @@ def request( # noqa: WPS211
7791
json: Request JSON data.
7892
query_params: Query parameters.
7993
headers: Request headers.
94+
json_file_key: json file name for data when sending a multipart request.
95+
force_multipart: force multipart request even if file is not provided.
8096
8197
Returns:
8298
Response object.
@@ -86,6 +102,10 @@ def request( # noqa: WPS211
86102
MPTApiError: If the response contains an error.
87103
MPTHttpError: If the response contains an HTTP error.
88104
"""
105+
files = dict(files or {})
106+
if force_multipart or (files and json):
107+
files[json_file_key] = (None, json_to_file_payload(json), APPLICATION_JSON)
108+
json = None
89109
try:
90110
response = self.httpx_client.request(
91111
method,

mpt_api_client/http/mixins.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,14 @@
55

66
from mpt_api_client.constants import APPLICATION_JSON
77
from mpt_api_client.exceptions import MPTError
8+
from mpt_api_client.http.client import json_to_file_payload
89
from mpt_api_client.http.query_state import QueryState
910
from mpt_api_client.http.types import FileTypes, Response
1011
from mpt_api_client.models import Collection, FileModel, ResourceData
1112
from mpt_api_client.models import Model as BaseModel
1213
from mpt_api_client.rql import RQLQuery
1314

1415

15-
def _json_to_file_payload(resource_data: ResourceData) -> bytes:
16-
return json.dumps(
17-
resource_data, ensure_ascii=False, separators=(",", ":"), allow_nan=False
18-
).encode("utf-8")
19-
20-
2116
class CreateMixin[Model]:
2217
"""Create resource mixin."""
2318

@@ -110,7 +105,7 @@ def create(
110105
if resource_data:
111106
files[data_key] = (
112107
None,
113-
_json_to_file_payload(resource_data),
108+
json_to_file_payload(resource_data),
114109
APPLICATION_JSON,
115110
)
116111
response = self.http_client.request("post", self.path, files=files) # type: ignore[attr-defined]
@@ -282,7 +277,7 @@ async def create(
282277
if resource_data:
283278
files[data_key] = (
284279
None,
285-
_json_to_file_payload(resource_data),
280+
json_to_file_payload(resource_data),
286281
APPLICATION_JSON,
287282
)
288283

mpt_api_client/resources/catalog/mixins.py

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
from mpt_api_client.constants import APPLICATION_JSON
21
from mpt_api_client.http.mixins import (
32
AsyncDownloadFileMixin,
43
DownloadFileMixin,
5-
_json_to_file_payload,
64
)
75
from mpt_api_client.http.types import FileTypes
86
from mpt_api_client.models import ResourceData
@@ -83,14 +81,14 @@ async def unpublish(self, resource_id: str, resource_data: ResourceData | None =
8381
)
8482

8583

86-
class AsyncDocumentMixin[Model](
87-
AsyncDownloadFileMixin[Model],
88-
AsyncPublishableMixin[Model],
89-
):
90-
"""Async document mixin."""
84+
class AsyncCreateFileMixin[Model]:
85+
"""Create file mixin."""
86+
87+
_upload_file_key = "file"
88+
_upload_data_key = "document"
9189

9290
async def create(self, resource_data: ResourceData, file: FileTypes | None = None) -> Model:
93-
"""Creates document resource.
91+
"""Create document.
9492
9593
Creates a document resource by specifying a `file` or an `url`.
9694
@@ -100,27 +98,34 @@ async def create(self, resource_data: ResourceData, file: FileTypes | None = Non
10098
10199
Returns:
102100
Created resource.
103-
104101
"""
105102
files = {}
106-
107-
if resource_data:
108-
files["document"] = (
109-
None,
110-
_json_to_file_payload(resource_data),
111-
APPLICATION_JSON,
112-
)
113103
if file:
114-
files["file"] = file # type: ignore[assignment]
115-
response = await self.http_client.request("post", self.path, files=files) # type: ignore[attr-defined]
104+
files[self._upload_file_key] = file
105+
response = await self.http_client.request( # type: ignore[attr-defined]
106+
"post",
107+
self.path, # type: ignore[attr-defined]
108+
json=resource_data,
109+
files=files,
110+
json_file_key=self._upload_data_key,
111+
force_multipart=True,
112+
)
116113
return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]
117114

118115

119-
class DocumentMixin[Model](
120-
DownloadFileMixin[Model],
121-
PublishableMixin[Model],
116+
class AsyncDocumentMixin[Model](
117+
AsyncCreateFileMixin[Model],
118+
AsyncDownloadFileMixin[Model],
119+
AsyncPublishableMixin[Model],
122120
):
123-
"""Document mixin."""
121+
"""Async document mixin."""
122+
123+
124+
class CreateFileMixin[Model]:
125+
"""Create file mixin."""
126+
127+
_upload_file_key = "file"
128+
_upload_data_key = "document"
124129

125130
def create(self, resource_data: ResourceData, file: FileTypes | None = None) -> Model:
126131
"""Create document.
@@ -135,19 +140,41 @@ def create(self, resource_data: ResourceData, file: FileTypes | None = None) ->
135140
Created resource.
136141
"""
137142
files = {}
138-
139-
if resource_data:
140-
files["document"] = (
141-
None,
142-
_json_to_file_payload(resource_data),
143-
APPLICATION_JSON,
144-
)
145143
if file:
146-
files["file"] = file # type: ignore[assignment]
147-
response = self.http_client.request("post", self.path, files=files) # type: ignore[attr-defined]
144+
files[self._upload_file_key] = file
145+
response = self.http_client.request( # type: ignore[attr-defined]
146+
"post",
147+
self.path, # type: ignore[attr-defined]
148+
json=resource_data,
149+
files=files,
150+
json_file_key=self._upload_data_key,
151+
force_multipart=True,
152+
)
148153
return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]
149154

150155

156+
class DocumentMixin[Model](
157+
CreateFileMixin[Model],
158+
DownloadFileMixin[Model],
159+
PublishableMixin[Model],
160+
):
161+
"""Document mixin."""
162+
163+
_upload_file_key = "file"
164+
_upload_data_key = "document"
165+
166+
167+
class MediaMixin[Model](
168+
CreateFileMixin[Model],
169+
DownloadFileMixin[Model],
170+
PublishableMixin[Model],
171+
):
172+
"""Document mixin."""
173+
174+
_upload_file_key = "file"
175+
_upload_data_key = "media"
176+
177+
151178
class ActivatableMixin[Model]:
152179
"""Activatable mixin adds the ability to activate and deactivate."""
153180

mpt_api_client/resources/catalog/products_media.py

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@
88
AsyncFilesOperationsMixin,
99
AsyncModifiableResourceMixin,
1010
CollectionMixin,
11-
FilesOperationsMixin,
1211
ModifiableResourceMixin,
1312
)
1413
from mpt_api_client.models import Model, ResourceData
1514
from mpt_api_client.resources.catalog.mixins import (
1615
AsyncPublishableMixin,
17-
PublishableMixin,
16+
MediaMixin,
1817
)
1918

2019

@@ -31,57 +30,14 @@ class MediaServiceConfig:
3130

3231

3332
class MediaService(
34-
FilesOperationsMixin[Media],
35-
PublishableMixin[Media],
33+
MediaMixin[Media],
3634
ModifiableResourceMixin[Media],
3735
CollectionMixin[Media],
3836
Service[Media],
3937
MediaServiceConfig,
4038
):
4139
"""Media service."""
4240

43-
@override
44-
def create(
45-
self,
46-
resource_data: ResourceData | None = None,
47-
files: dict[str, FileTypes] | None = None,
48-
data_key: str = "_media_data",
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-
data_key: Key to use for the JSON data in the multipart form.
79-
80-
Returns:
81-
Media resource.
82-
"""
83-
return super().create(resource_data=resource_data, files=files, data_key=data_key)
84-
8541

8642
class AsyncMediaService(
8743
AsyncFilesOperationsMixin[Media],

mpt_api_client/resources/notifications/batches.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from httpx._types import FileTypes
22

33
from mpt_api_client.http import AsyncService, Service
4+
from mpt_api_client.http.client import json_to_file_payload
45
from mpt_api_client.http.mixins import (
56
AsyncCollectionMixin,
67
AsyncGetMixin,
78
CollectionMixin,
89
GetMixin,
9-
_json_to_file_payload,
1010
)
1111
from mpt_api_client.models import FileModel, Model, ResourceData
1212

@@ -52,7 +52,7 @@ def create(
5252
if resource_data:
5353
files[data_key] = (
5454
None,
55-
_json_to_file_payload(resource_data),
55+
json_to_file_payload(resource_data),
5656
"application/json",
5757
)
5858

@@ -105,7 +105,7 @@ async def create(
105105
if resource_data:
106106
files[data_key] = (
107107
None,
108-
_json_to_file_payload(resource_data),
108+
json_to_file_payload(resource_data),
109109
"application/json",
110110
)
111111

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ per-file-ignores =
3939
mpt_api_client/resources/accounts/*.py: WPS202 WPS215 WPS214 WPS235
4040
mpt_api_client/resources/billing/*.py: WPS202 WPS204 WPS214 WPS215
4141
mpt_api_client/resources/catalog/*.py: WPS110 WPS214 WPS215 WPS235
42+
mpt_api_client/resources/catalog/mixins.py: WPS110 WPS202 WPS214 WPS215 WPS235
4243
mpt_api_client/resources/catalog/products.py: WPS204 WPS214 WPS215 WPS235
4344
mpt_api_client/rql/query_builder.py: WPS110 WPS115 WPS210 WPS214
4445
tests/unit/http/test_async_service.py: WPS204 WPS202

tests/e2e/accounts/conftest.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import datetime as dt
2-
import pathlib
32

43
import pytest
54

@@ -10,9 +9,8 @@ def timestamp():
109

1110

1211
@pytest.fixture
13-
def account_icon():
14-
icon_path = pathlib.Path(__file__).parents[1] / "logo.png"
15-
return pathlib.Path.open(icon_path, "rb")
12+
def account_icon(logo_fd):
13+
return logo_fd
1614

1715

1816
@pytest.fixture

0 commit comments

Comments
 (0)