Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions mpt_api_client/http/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
HTTPStatusError,
)

from mpt_api_client.constants import APPLICATION_JSON
from mpt_api_client.exceptions import MPTError, transform_http_status_exception
from mpt_api_client.http.client import json_to_file_payload
from mpt_api_client.http.types import (
HeaderTypes,
QueryParam,
Expand Down Expand Up @@ -65,6 +67,8 @@ async def request( # noqa: WPS211
json: Any | None = None,
query_params: QueryParam | None = None,
headers: HeaderTypes | None = None,
json_file_key: str = "_attachment_data",
force_multipart: bool = False,
) -> Response:
"""Perform an HTTP request.

Expand All @@ -75,6 +79,8 @@ async def request( # noqa: WPS211
json: Request JSON data.
query_params: Query parameters.
headers: Request headers.
json_file_key: json file name for data when sending a multipart request.
force_multipart: force multipart request even if file is not provided.

Returns:
Response object.
Expand All @@ -84,6 +90,10 @@ async def request( # noqa: WPS211
MPTApiError: If the response contains an error.
MPTHttpError: If the response contains an HTTP error.
"""
files = dict(files or {})
if force_multipart or (files and json):
files[json_file_key] = (None, json_to_file_payload(json), APPLICATION_JSON)
json = None
try:
response = await self.httpx_client.request(
method,
Expand Down
20 changes: 20 additions & 0 deletions mpt_api_client/http/client.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json as json_package
import os
from typing import Any

Expand All @@ -8,6 +9,7 @@
HTTPTransport,
)

from mpt_api_client.constants import APPLICATION_JSON
from mpt_api_client.exceptions import (
MPTError,
transform_http_status_exception,
Expand All @@ -18,6 +20,16 @@
RequestFiles,
Response,
)
from mpt_api_client.models import ResourceData


def json_to_file_payload(resource_data: ResourceData | None) -> bytes:
"""Convert resource data to file payload."""
if resource_data is None:
resource_data = {}
return json_package.dumps(
resource_data, ensure_ascii=False, separators=(",", ":"), allow_nan=False
).encode("utf-8")


class HTTPClient:
Expand Down Expand Up @@ -67,6 +79,8 @@ def request( # noqa: WPS211
json: Any | None = None,
query_params: QueryParam | None = None,
headers: HeaderTypes | None = None,
json_file_key: str = "_attachment_data",
force_multipart: bool = False,
) -> Response:
"""Perform an HTTP request.

Expand All @@ -77,6 +91,8 @@ def request( # noqa: WPS211
json: Request JSON data.
query_params: Query parameters.
headers: Request headers.
json_file_key: json file name for data when sending a multipart request.
force_multipart: force multipart request even if file is not provided.

Returns:
Response object.
Expand All @@ -86,6 +102,10 @@ def request( # noqa: WPS211
MPTApiError: If the response contains an error.
MPTHttpError: If the response contains an HTTP error.
"""
files = dict(files or {})
if force_multipart or (files and json):
files[json_file_key] = (None, json_to_file_payload(json), APPLICATION_JSON)
json = None
try:
response = self.httpx_client.request(
method,
Expand Down
11 changes: 3 additions & 8 deletions mpt_api_client/http/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,14 @@

from mpt_api_client.constants import APPLICATION_JSON
from mpt_api_client.exceptions import MPTError
from mpt_api_client.http.client import json_to_file_payload
from mpt_api_client.http.query_state import QueryState
from mpt_api_client.http.types import FileTypes, Response
from mpt_api_client.models import Collection, FileModel, ResourceData
from mpt_api_client.models import Model as BaseModel
from mpt_api_client.rql import RQLQuery


def _json_to_file_payload(resource_data: ResourceData) -> bytes:
return json.dumps(
resource_data, ensure_ascii=False, separators=(",", ":"), allow_nan=False
).encode("utf-8")


class CreateMixin[Model]:
"""Create resource mixin."""

Expand Down Expand Up @@ -110,7 +105,7 @@ def create(
if resource_data:
files[data_key] = (
None,
_json_to_file_payload(resource_data),
json_to_file_payload(resource_data),
APPLICATION_JSON,
)
response = self.http_client.request("post", self.path, files=files) # type: ignore[attr-defined]
Expand Down Expand Up @@ -282,7 +277,7 @@ async def create(
if resource_data:
files[data_key] = (
None,
_json_to_file_payload(resource_data),
json_to_file_payload(resource_data),
APPLICATION_JSON,
)

Expand Down
89 changes: 58 additions & 31 deletions mpt_api_client/resources/catalog/mixins.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from mpt_api_client.constants import APPLICATION_JSON
from mpt_api_client.http.mixins import (
AsyncDownloadFileMixin,
DownloadFileMixin,
_json_to_file_payload,
)
from mpt_api_client.http.types import FileTypes
from mpt_api_client.models import ResourceData
Expand Down Expand Up @@ -83,14 +81,14 @@ async def unpublish(self, resource_id: str, resource_data: ResourceData | None =
)


class AsyncDocumentMixin[Model](
AsyncDownloadFileMixin[Model],
AsyncPublishableMixin[Model],
):
"""Async document mixin."""
class AsyncCreateFileMixin[Model]:
"""Create file mixin."""

_upload_file_key = "file"
_upload_data_key = "document"

async def create(self, resource_data: ResourceData, file: FileTypes | None = None) -> Model:
"""Creates document resource.
"""Create document.

Creates a document resource by specifying a `file` or an `url`.

Expand All @@ -100,27 +98,34 @@ async def create(self, resource_data: ResourceData, file: FileTypes | None = Non

Returns:
Created resource.

"""
files = {}

if resource_data:
files["document"] = (
None,
_json_to_file_payload(resource_data),
APPLICATION_JSON,
)
if file:
files["file"] = file # type: ignore[assignment]
response = await self.http_client.request("post", self.path, files=files) # type: ignore[attr-defined]
files[self._upload_file_key] = file
response = await self.http_client.request( # type: ignore[attr-defined]
"post",
self.path, # type: ignore[attr-defined]
json=resource_data,
files=files,
json_file_key=self._upload_data_key,
force_multipart=True,
)
return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]


class DocumentMixin[Model](
DownloadFileMixin[Model],
PublishableMixin[Model],
class AsyncDocumentMixin[Model](
AsyncCreateFileMixin[Model],
AsyncDownloadFileMixin[Model],
AsyncPublishableMixin[Model],
):
"""Document mixin."""
"""Async document mixin."""


class CreateFileMixin[Model]:
"""Create file mixin."""

_upload_file_key = "file"
_upload_data_key = "document"

def create(self, resource_data: ResourceData, file: FileTypes | None = None) -> Model:
"""Create document.
Expand All @@ -135,19 +140,41 @@ def create(self, resource_data: ResourceData, file: FileTypes | None = None) ->
Created resource.
"""
files = {}

if resource_data:
files["document"] = (
None,
_json_to_file_payload(resource_data),
APPLICATION_JSON,
)
if file:
files["file"] = file # type: ignore[assignment]
response = self.http_client.request("post", self.path, files=files) # type: ignore[attr-defined]
files[self._upload_file_key] = file
response = self.http_client.request( # type: ignore[attr-defined]
"post",
self.path, # type: ignore[attr-defined]
json=resource_data,
files=files,
json_file_key=self._upload_data_key,
force_multipart=True,
)
return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]


class DocumentMixin[Model](
CreateFileMixin[Model],
DownloadFileMixin[Model],
PublishableMixin[Model],
):
"""Document mixin."""

_upload_file_key = "file"
_upload_data_key = "document"


class MediaMixin[Model](
CreateFileMixin[Model],
DownloadFileMixin[Model],
PublishableMixin[Model],
):
"""Document mixin."""

_upload_file_key = "file"
_upload_data_key = "media"


class ActivatableMixin[Model]:
"""Activatable mixin adds the ability to activate and deactivate."""

Expand Down
48 changes: 2 additions & 46 deletions mpt_api_client/resources/catalog/products_media.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@
AsyncFilesOperationsMixin,
AsyncModifiableResourceMixin,
CollectionMixin,
FilesOperationsMixin,
ModifiableResourceMixin,
)
from mpt_api_client.models import Model, ResourceData
from mpt_api_client.resources.catalog.mixins import (
AsyncPublishableMixin,
PublishableMixin,
MediaMixin,
)


Expand All @@ -31,57 +30,14 @@ class MediaServiceConfig:


class MediaService(
FilesOperationsMixin[Media],
PublishableMixin[Media],
MediaMixin[Media],
ModifiableResourceMixin[Media],
CollectionMixin[Media],
Service[Media],
MediaServiceConfig,
):
"""Media service."""

@override
def create(
self,
resource_data: ResourceData | None = None,
files: dict[str, FileTypes] | None = None,
data_key: str = "_media_data",
) -> Media:
"""Create Media resource.

Currently are two types of media resources available image and video.

Video:
resource_data:
{
"name": "SomeMediaFile",
"description":"Some media description",
"mediaType": "Video",
"url": http://www.somemedia.com/somevideo.avi,
"displayOrder": 1
}
files: Add an image with the video thumbnail

Image:
resource_data:
{
"name": "SomeMediaFile",
"description":"Some media description",
"mediaType": "Video",
"displayOrder": 1
}
files: The image itself

Args:
resource_data: Resource data.
files: Files data.
data_key: Key to use for the JSON data in the multipart form.

Returns:
Media resource.
"""
return super().create(resource_data=resource_data, files=files, data_key=data_key)


class AsyncMediaService(
AsyncFilesOperationsMixin[Media],
Expand Down
6 changes: 3 additions & 3 deletions mpt_api_client/resources/notifications/batches.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from httpx._types import FileTypes

from mpt_api_client.http import AsyncService, Service
from mpt_api_client.http.client import json_to_file_payload
from mpt_api_client.http.mixins import (
AsyncCollectionMixin,
AsyncGetMixin,
CollectionMixin,
GetMixin,
_json_to_file_payload,
)
from mpt_api_client.models import FileModel, Model, ResourceData

Expand Down Expand Up @@ -52,7 +52,7 @@ def create(
if resource_data:
files[data_key] = (
None,
_json_to_file_payload(resource_data),
json_to_file_payload(resource_data),
"application/json",
)

Expand Down Expand Up @@ -105,7 +105,7 @@ async def create(
if resource_data:
files[data_key] = (
None,
_json_to_file_payload(resource_data),
json_to_file_payload(resource_data),
"application/json",
)

Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ per-file-ignores =
mpt_api_client/resources/accounts/*.py: WPS202 WPS215 WPS214 WPS235
mpt_api_client/resources/billing/*.py: WPS202 WPS204 WPS214 WPS215
mpt_api_client/resources/catalog/*.py: WPS110 WPS214 WPS215 WPS235
mpt_api_client/resources/catalog/mixins.py: WPS110 WPS202 WPS214 WPS215 WPS235
mpt_api_client/resources/catalog/products.py: WPS204 WPS214 WPS215 WPS235
mpt_api_client/rql/query_builder.py: WPS110 WPS115 WPS210 WPS214
tests/unit/http/test_async_service.py: WPS204 WPS202
Expand Down
6 changes: 2 additions & 4 deletions tests/e2e/accounts/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import datetime as dt
import pathlib

import pytest

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


@pytest.fixture
def account_icon():
icon_path = pathlib.Path(__file__).parents[1] / "logo.png"
return pathlib.Path.open(icon_path, "rb")
def account_icon(logo_fd):
return logo_fd


@pytest.fixture
Expand Down
Loading