Skip to content

Commit 3483671

Browse files
committed
MPT-12840 Add agreements attachments
1 parent 6403e88 commit 3483671

File tree

18 files changed

+713
-82
lines changed

18 files changed

+713
-82
lines changed

mpt_api_client/http/async_client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def __init__(
3232
base_headers = {
3333
"User-Agent": "swo-marketplace-client/1.0",
3434
"Authorization": f"Bearer {api_token}",
35+
"Accept": "application/json",
3536
}
3637
super().__init__(
3738
base_url=base_url,

mpt_api_client/http/async_service.py

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,6 @@ async def get(self, resource_id: str, select: list[str] | str | None = None) ->
9191
select = ",".join(select) if select else None
9292
return await self._resource_action(resource_id=resource_id, query_params={"select": select})
9393

94-
async def update(self, resource_id: str, resource_data: ResourceData) -> Model:
95-
"""Update a resource using `PUT /endpoint/{resource_id}`.
96-
97-
Args:
98-
resource_id: Resource ID.
99-
resource_data: Resource data.
100-
101-
Returns:
102-
Resource object.
103-
104-
"""
105-
return await self._resource_action(resource_id, "PUT", json=resource_data)
106-
10794
async def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> httpx.Response:
10895
"""Fetch one page of resources.
10996
@@ -119,13 +106,14 @@ async def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> ht
119106

120107
return response
121108

122-
async def _resource_do_request(
109+
async def _resource_do_request( # noqa: WPS211
123110
self,
124111
resource_id: str,
125112
method: str = "GET",
126113
action: str | None = None,
127114
json: ResourceData | ResourceList | None = None,
128115
query_params: QueryParam | None = None,
116+
headers: dict[str, str] | None = None,
129117
) -> httpx.Response:
130118
"""Perform an action on a specific resource using.
131119
@@ -138,13 +126,16 @@ async def _resource_do_request(
138126
action: The action name to use.
139127
json: The updated resource data.
140128
query_params: Additional query parameters.
129+
headers: Additional headers.
141130
142131
Raises:
143132
HTTPError: If the action fails.
144133
"""
145-
resource_url = urljoin(f"{self._endpoint}/", resource_id)
134+
resource_url = urljoin(f"{self.endpoint}/", resource_id)
146135
url = urljoin(f"{resource_url}/", action) if action else resource_url
147-
response = await self.http_client.request(method, url, json=json, params=query_params)
136+
response = await self.http_client.request(
137+
method, url, json=json, params=query_params, headers=headers
138+
)
148139
response.raise_for_status()
149140
return response
150141

mpt_api_client/http/client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def __init__(
3232
base_headers = {
3333
"User-Agent": "swo-marketplace-client/1.0",
3434
"Authorization": f"Bearer {api_token}",
35+
"content-type": "application/json",
3536
}
3637
super().__init__(
3738
base_url=base_url,

mpt_api_client/http/mixins.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def create(self, resource_data: ResourceData) -> Model:
1212
Returns:
1313
New resource created.
1414
"""
15-
response = self.http_client.post(self._endpoint, json=resource_data) # type: ignore[attr-defined]
15+
response = self.http_client.post(self.endpoint, json=resource_data) # type: ignore[attr-defined]
1616
response.raise_for_status()
1717

1818
return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]
@@ -31,6 +31,23 @@ def delete(self, resource_id: str) -> None:
3131
response.raise_for_status()
3232

3333

34+
class UpdateMixin[Model]:
35+
"""Update resource mixin."""
36+
37+
def update(self, resource_id: str, resource_data: ResourceData) -> Model:
38+
"""Update a resource using `PUT /endpoint/{resource_id}`.
39+
40+
Args:
41+
resource_id: Resource ID.
42+
resource_data: Resource data.
43+
44+
Returns:
45+
Resource object.
46+
47+
"""
48+
return self._resource_action(resource_id, "PUT", json=resource_data) # type: ignore[attr-defined, no-any-return]
49+
50+
3451
class AsyncCreateMixin[Model]:
3552
"""Create resource mixin."""
3653

@@ -40,7 +57,7 @@ async def create(self, resource_data: ResourceData) -> Model:
4057
Returns:
4158
New resource created.
4259
"""
43-
response = await self.http_client.post(self._endpoint, json=resource_data) # type: ignore[attr-defined]
60+
response = await self.http_client.post(self.endpoint, json=resource_data) # type: ignore[attr-defined]
4461
response.raise_for_status()
4562

4663
return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]
@@ -55,6 +72,23 @@ async def delete(self, resource_id: str) -> None:
5572
Args:
5673
resource_id: Resource ID.
5774
"""
58-
url = urljoin(f"{self._endpoint}/", resource_id) # type: ignore[attr-defined]
75+
url = urljoin(f"{self.endpoint}/", resource_id) # type: ignore[attr-defined]
5976
response = await self.http_client.delete(url) # type: ignore[attr-defined]
6077
response.raise_for_status()
78+
79+
80+
class AsyncUpdateMixin[Model]:
81+
"""Update resource mixin."""
82+
83+
async def update(self, resource_id: str, resource_data: ResourceData) -> Model:
84+
"""Update a resource using `PUT /endpoint/{resource_id}`.
85+
86+
Args:
87+
resource_id: Resource ID.
88+
resource_data: Resource data.
89+
90+
Returns:
91+
Resource object.
92+
93+
"""
94+
return await self._resource_action(resource_id, "PUT", json=resource_data) # type: ignore[attr-defined, no-any-return]

mpt_api_client/http/service.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,6 @@ def get(self, resource_id: str, select: list[str] | str | None = None) -> Model:
9191

9292
return self._resource_action(resource_id=resource_id, query_params={"select": select})
9393

94-
def update(self, resource_id: str, resource_data: ResourceData) -> Model:
95-
"""Update a resource using `PUT /endpoint/{resource_id}`.
96-
97-
Args:
98-
resource_id: Resource ID.
99-
resource_data: Resource data.
100-
101-
Returns:
102-
Resource object.
103-
104-
"""
105-
return self._resource_action(resource_id, "PUT", json=resource_data)
106-
10794
def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> httpx.Response:
10895
"""Fetch one page of resources.
10996
@@ -119,13 +106,14 @@ def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> httpx.Re
119106

120107
return response
121108

122-
def _resource_do_request(
109+
def _resource_do_request( # noqa: WPS211
123110
self,
124111
resource_id: str,
125112
method: str = "GET",
126113
action: str | None = None,
127114
json: ResourceData | ResourceList | None = None,
128115
query_params: QueryParam | None = None,
116+
headers: dict[str, str] | None = None,
129117
) -> httpx.Response:
130118
"""Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`.
131119
@@ -135,16 +123,19 @@ def _resource_do_request(
135123
action: The action name to use.
136124
json: The updated resource data.
137125
query_params: Additional query parameters.
126+
headers: Additional headers.
138127
139128
Returns:
140129
HTTP response object.
141130
142131
Raises:
143132
HTTPError: If the action fails.
144133
"""
145-
resource_url = urljoin(f"{self._endpoint}/", resource_id)
134+
resource_url = urljoin(f"{self.endpoint}/", resource_id)
146135
url = urljoin(f"{resource_url}/", action) if action else resource_url
147-
response = self.http_client.request(method, url, json=json, params=query_params)
136+
response = self.http_client.request(
137+
method, url, json=json, params=query_params, headers=headers
138+
)
148139
response.raise_for_status()
149140
return response
150141

@@ -166,6 +157,11 @@ def _resource_action(
166157
query_params: Additional query parameters.
167158
"""
168159
response = self._resource_do_request(
169-
resource_id, method, action, json=json, query_params=query_params
160+
resource_id,
161+
method,
162+
action,
163+
json=json,
164+
query_params=query_params,
165+
headers={"Accept": "application/json"},
170166
)
171167
return self._model_class.from_response(response)

mpt_api_client/models/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from mpt_api_client.models.collection import Collection
2+
from mpt_api_client.models.download_file import DownloadFile
23
from mpt_api_client.models.meta import Meta, Pagination
34
from mpt_api_client.models.model import Model, ResourceData
45

5-
__all__ = ["Collection", "Meta", "Model", "Pagination", "ResourceData"] # noqa: WPS410
6+
__all__ = ["Collection", "DownloadFile", "Meta", "Model", "Pagination", "ResourceData"] # noqa: WPS410
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import re
2+
3+
from httpx import Response
4+
5+
6+
class DownloadFile:
7+
"""File resource."""
8+
9+
def __init__(self, response: Response):
10+
self.response = response
11+
12+
@property
13+
def filename(self) -> str | None:
14+
"""Filename from Content-Disposition header.
15+
16+
Returns:
17+
The filename if found in the Content-Disposition header, None otherwise.
18+
"""
19+
content_disposition = self.response.headers.get("content-disposition")
20+
if not content_disposition:
21+
return None
22+
23+
filename_match = re.search(
24+
r'filename\*=(?:UTF-8\'\')?([^;]+)|filename=(?:"([^"]+)"|([^;]+))',
25+
content_disposition,
26+
re.IGNORECASE,
27+
)
28+
29+
if filename_match:
30+
return filename_match.group(1) or filename_match.group(2) or filename_match.group(3)
31+
32+
return None
33+
34+
@property
35+
def file_contents(self) -> bytes:
36+
"""Returns the content of the attachment.
37+
38+
Returns:
39+
The content of the attachment in bytes
40+
41+
Raises:
42+
ResponseNotRead()
43+
44+
"""
45+
return self.response.content
46+
47+
@property
48+
def content_type(self) -> str | None:
49+
"""Returns the content type of the attachment.
50+
51+
Returns:
52+
The content type of the attachment.
53+
"""
54+
ctype = self.response.headers.get("content-type", "")
55+
return str(ctype)

mpt_api_client/resources/commerce/agreements.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
from mpt_api_client.http import AsyncService, Service
2+
from mpt_api_client.http.mixins import (
3+
AsyncCreateMixin,
4+
AsyncDeleteMixin,
5+
AsyncUpdateMixin,
6+
CreateMixin,
7+
DeleteMixin,
8+
UpdateMixin,
9+
)
210
from mpt_api_client.models import Model
11+
from mpt_api_client.resources.commerce.agreements_attachments import (
12+
AgreementsAttachmentService,
13+
AsyncAgreementsAttachmentService,
14+
)
315

416

517
class Agreement(Model):
@@ -14,7 +26,13 @@ class AgreementsServiceConfig:
1426
_collection_key = "data"
1527

1628

17-
class AgreementsService(Service[Agreement], AgreementsServiceConfig):
29+
class AgreementsService( # noqa: WPS215
30+
CreateMixin[Agreement],
31+
UpdateMixin[Agreement],
32+
DeleteMixin,
33+
Service[Agreement],
34+
AgreementsServiceConfig,
35+
):
1836
"""Agreements service."""
1937

2038
def template(self, agreement_id: str) -> str:
@@ -29,8 +47,28 @@ def template(self, agreement_id: str) -> str:
2947
response = self._resource_do_request(agreement_id, action="template")
3048
return response.text
3149

50+
def attachments(self, agreement_id: str) -> AgreementsAttachmentService:
51+
"""Get the attachments service for the given Agreement id.
3252
33-
class AsyncAgreementsService(AsyncService[Agreement], AgreementsServiceConfig):
53+
Args:
54+
agreement_id: Agreement ID.
55+
56+
Returns:
57+
Agreements Attachment service.
58+
"""
59+
return AgreementsAttachmentService(
60+
http_client=self.http_client,
61+
endpoint_params={"agreement_id": agreement_id},
62+
)
63+
64+
65+
class AsyncAgreementsService( # noqa: WPS215
66+
AsyncCreateMixin[Agreement],
67+
AsyncUpdateMixin[Agreement],
68+
AsyncDeleteMixin,
69+
AsyncService[Agreement],
70+
AgreementsServiceConfig,
71+
):
3472
"""Agreements service."""
3573

3674
async def template(self, agreement_id: str) -> str:
@@ -44,3 +82,17 @@ async def template(self, agreement_id: str) -> str:
4482
"""
4583
response = await self._resource_do_request(agreement_id, action="template")
4684
return response.text
85+
86+
def attachments(self, agreement_id: str) -> AsyncAgreementsAttachmentService:
87+
"""Get the attachments service for the given Agreement id.
88+
89+
Args:
90+
agreement_id: Agreement ID.
91+
92+
Returns:
93+
Agreements Attachment service.
94+
"""
95+
return AsyncAgreementsAttachmentService(
96+
http_client=self.http_client,
97+
endpoint_params={"agreement_id": agreement_id},
98+
)

0 commit comments

Comments
 (0)