From 9bdbf50c51edc805313049d5f86cd30b49e280fd Mon Sep 17 00:00:00 2001 From: Albert Sola Date: Mon, 30 Mar 2026 10:30:09 +0100 Subject: [PATCH] MPT-19660 Improve resource action implementation --- mpt_api_client/http/async_service.py | 71 +---- mpt_api_client/http/mixins/delete_mixin.py | 8 +- mpt_api_client/http/mixins/disable_mixin.py | 8 +- .../http/mixins/download_file_mixin.py | 14 +- mpt_api_client/http/mixins/enable_mixin.py | 8 +- mpt_api_client/http/mixins/get_mixin.py | 4 +- .../http/mixins/update_file_mixin.py | 7 +- mpt_api_client/http/mixins/update_mixin.py | 26 +- mpt_api_client/http/resource_accessor.py | 199 ++++++++++++++ mpt_api_client/http/service.py | 71 +---- mpt_api_client/http/url_utils.py | 26 ++ mpt_api_client/resources/accounts/buyers.py | 8 +- .../accounts/mixins/activatable_mixin.py | 52 +--- .../accounts/mixins/blockable_mixin.py | 16 +- .../accounts/mixins/invitable_mixin.py | 24 +- .../accounts/mixins/validate_mixin.py | 8 +- mpt_api_client/resources/accounts/sellers.py | 4 +- mpt_api_client/resources/accounts/users.py | 12 +- .../resources/billing/custom_ledgers.py | 6 +- mpt_api_client/resources/billing/journals.py | 7 +- .../billing/mixins/acceptable_mixin.py | 16 +- .../billing/mixins/issuable_mixin.py | 56 +--- .../billing/mixins/recalculatable_mixin.py | 24 +- .../billing/mixins/regeneratable_mixin.py | 32 +-- .../catalog/mixins/activatable_mixin.py | 48 +--- .../catalog/mixins/publishable_mixin.py | 24 +- .../resources/catalog/pricing_policies.py | 8 +- mpt_api_client/resources/catalog/products.py | 4 +- .../resources/commerce/mixins/render_mixin.py | 4 +- .../commerce/mixins/template_mixin.py | 5 +- .../commerce/mixins/terminate_mixin.py | 4 +- mpt_api_client/resources/commerce/orders.py | 28 +- mpt_api_client/resources/helpdesk/cases.py | 12 +- .../resources/helpdesk/chat_answers.py | 16 +- mpt_api_client/resources/helpdesk/forms.py | 8 +- mpt_api_client/resources/helpdesk/queues.py | 8 +- .../resources/notifications/categories.py | 8 +- .../resources/notifications/contacts.py | 8 +- pyproject.toml | 1 + tests/unit/http/test_resource_accessor.py | 242 ++++++++++++++++++ tests/unit/http/test_url_utils.py | 86 +++++++ 41 files changed, 745 insertions(+), 476 deletions(-) create mode 100644 mpt_api_client/http/resource_accessor.py create mode 100644 mpt_api_client/http/url_utils.py create mode 100644 tests/unit/http/test_resource_accessor.py create mode 100644 tests/unit/http/test_url_utils.py diff --git a/mpt_api_client/http/async_service.py b/mpt_api_client/http/async_service.py index 585b674d..7e83a446 100644 --- a/mpt_api_client/http/async_service.py +++ b/mpt_api_client/http/async_service.py @@ -1,12 +1,8 @@ -from urllib.parse import urljoin - -from mpt_api_client.constants import APPLICATION_JSON from mpt_api_client.http.async_client import AsyncHTTPClient from mpt_api_client.http.base_service import ServiceBase -from mpt_api_client.http.types import QueryParam, Response +from mpt_api_client.http.resource_accessor import AsyncResourceAccessor +from mpt_api_client.http.url_utils import join_url_path from mpt_api_client.models import Model as BaseModel -from mpt_api_client.models import ResourceData -from mpt_api_client.models.collection import ResourceList class AsyncService[Model: BaseModel](ServiceBase[AsyncHTTPClient, Model]): # noqa: WPS214 @@ -21,60 +17,15 @@ class AsyncService[Model: BaseModel](ServiceBase[AsyncHTTPClient, Model]): # no """ - async def _resource_do_request( # noqa: WPS211 - self, - resource_id: str, - method: str = "GET", - action: str | None = None, - json: ResourceData | ResourceList | None = None, - query_params: QueryParam | None = None, - headers: dict[str, str] | None = None, - ) -> Response: - """Perform an action on a specific resource using. - - Request with action: `HTTP_METHOD /endpoint/{resource_id}/{action}`. - Request without action: `HTTP_METHOD /endpoint/{resource_id}`. - - Args: - resource_id: The resource ID to operate on. - method: The HTTP method to use. - action: The action name to use. - json: The updated resource data. - query_params: Additional query parameters. - headers: Additional headers. - - Raises: - HTTPError: If the action fails. - """ - resource_url = urljoin(f"{self.path}/", resource_id) - url = urljoin(f"{resource_url}/", action) if action else resource_url - return await self.http_client.request( - method, url, json=json, query_params=query_params, headers=headers - ) + def _resource(self, resource_id: str) -> AsyncResourceAccessor[Model]: + """Return an :class:`AsyncResourceAccessor` bound to *resource_id*. - async def _resource_action( - self, - resource_id: str, - method: str = "GET", - action: str | None = None, - json: ResourceData | ResourceList | None = None, - query_params: QueryParam | None = None, - ) -> Model: - """Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`. + Usage:: - Args: - resource_id: The resource ID to operate on. - method: The HTTP method to use. - action: The action name to use. - json: The updated resource data. - query_params: Additional query parameters. + await self._resource("RES-123").post("complete", json=data) + await self._resource("RES-123").get() + await self._resource("RES-123").put(json=data) + await self._resource("RES-123").delete() """ - response = await self._resource_do_request( - resource_id, - method, - action, - json=json, - query_params=query_params, - headers={"Accept": APPLICATION_JSON}, - ) - return self._model_class.from_response(response) + resource_url = join_url_path(self.path, resource_id) + return AsyncResourceAccessor(self.http_client, resource_url, self._model_class) diff --git a/mpt_api_client/http/mixins/delete_mixin.py b/mpt_api_client/http/mixins/delete_mixin.py index edcaa2e0..a49fe059 100644 --- a/mpt_api_client/http/mixins/delete_mixin.py +++ b/mpt_api_client/http/mixins/delete_mixin.py @@ -1,6 +1,3 @@ -from urllib.parse import urljoin - - class DeleteMixin: """Delete resource mixin.""" @@ -10,7 +7,7 @@ def delete(self, resource_id: str) -> None: Args: resource_id: Resource ID. """ - self._resource_do_request(resource_id, "DELETE") # type: ignore[attr-defined] + self._resource(resource_id).delete() # type: ignore[attr-defined] class AsyncDeleteMixin: @@ -22,5 +19,4 @@ async def delete(self, resource_id: str) -> None: Args: resource_id: Resource ID. """ - url = urljoin(f"{self.path}/", resource_id) # type: ignore[attr-defined] - await self.http_client.request("delete", url) # type: ignore[attr-defined] + await self._resource(resource_id).delete() # type: ignore[attr-defined] diff --git a/mpt_api_client/http/mixins/disable_mixin.py b/mpt_api_client/http/mixins/disable_mixin.py index 1577f0e4..cfb0c6b0 100644 --- a/mpt_api_client/http/mixins/disable_mixin.py +++ b/mpt_api_client/http/mixins/disable_mixin.py @@ -7,9 +7,7 @@ class AsyncDisableMixin[Model: BaseModel]: async def disable(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Disable a specific resource.""" - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id=resource_id, method="POST", action="disable", json=resource_data - ) + return await self._resource(resource_id).post("disable", json=resource_data) # type: ignore[attr-defined, no-any-return] class DisableMixin[Model: BaseModel]: @@ -17,6 +15,4 @@ class DisableMixin[Model: BaseModel]: def disable(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Disable a specific resource.""" - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id=resource_id, method="POST", action="disable", json=resource_data - ) + return self._resource(resource_id).post("disable", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/http/mixins/download_file_mixin.py b/mpt_api_client/http/mixins/download_file_mixin.py index be2e85b5..e94112be 100644 --- a/mpt_api_client/http/mixins/download_file_mixin.py +++ b/mpt_api_client/http/mixins/download_file_mixin.py @@ -17,14 +17,13 @@ def download(self, resource_id: str, accept: str | None = None) -> FileModel: Returns: File model containing the downloaded file. """ + accessor = self._resource(resource_id) # type: ignore[attr-defined] if not accept: - resource: Model = self._resource_action(resource_id, method="GET") # type: ignore[attr-defined] + resource: Model = accessor.get() accept = resource.content_type # type: ignore[attr-defined] if not accept: raise MPTError("Unable to download file. Content type not found in resource") - response: Response = self._resource_do_request( # type: ignore[attr-defined] - resource_id, method="GET", headers={"Accept": accept} - ) + response: Response = accessor.do_request("GET", headers={"Accept": accept}) return FileModel(response) @@ -42,12 +41,11 @@ async def download(self, resource_id: str, accept: str | None = None) -> FileMod Returns: File model containing the downloaded file. """ + accessor = self._resource(resource_id) # type: ignore[attr-defined] if not accept: - resource: Model = await self._resource_action(resource_id, method="GET") # type: ignore[attr-defined] + resource: Model = await accessor.get() accept = resource.content_type # type: ignore[attr-defined] if not accept: raise MPTError("Unable to download file. Content type not found in resource") - response = await self._resource_do_request( # type: ignore[attr-defined] - resource_id, method="GET", headers={"Accept": accept} - ) + response = await accessor.do_request("GET", headers={"Accept": accept}) return FileModel(response) diff --git a/mpt_api_client/http/mixins/enable_mixin.py b/mpt_api_client/http/mixins/enable_mixin.py index 9f4346fa..0b313113 100644 --- a/mpt_api_client/http/mixins/enable_mixin.py +++ b/mpt_api_client/http/mixins/enable_mixin.py @@ -7,9 +7,7 @@ class AsyncEnableMixin[Model: BaseModel]: async def enable(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Enable a specific resource.""" - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id=resource_id, method="POST", action="enable", json=resource_data - ) + return await self._resource(resource_id).post("enable", json=resource_data) # type: ignore[attr-defined, no-any-return] class EnableMixin[Model: BaseModel]: @@ -17,6 +15,4 @@ class EnableMixin[Model: BaseModel]: def enable(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Enable a specific resource.""" - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id=resource_id, method="POST", action="enable", json=resource_data - ) + return self._resource(resource_id).post("enable", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/http/mixins/get_mixin.py b/mpt_api_client/http/mixins/get_mixin.py index 69640a21..3bdc16ca 100644 --- a/mpt_api_client/http/mixins/get_mixin.py +++ b/mpt_api_client/http/mixins/get_mixin.py @@ -14,7 +14,7 @@ def get(self, resource_id: str, select: list[str] | str | None = None) -> Model: if isinstance(select, list): select = ",".join(select) if select else None - return self._resource_action(resource_id=resource_id, query_params={"select": select}) # type: ignore[attr-defined, no-any-return] + return self._resource(resource_id).get(query_params={"select": select}) # type: ignore[attr-defined, no-any-return] class AsyncGetMixin[Model]: @@ -32,4 +32,4 @@ async def get(self, resource_id: str, select: list[str] | str | None = None) -> """ if isinstance(select, list): select = ",".join(select) if select else None - return await self._resource_action(resource_id=resource_id, query_params={"select": select}) # type: ignore[attr-defined, no-any-return] + return await self._resource(resource_id).get(query_params={"select": select}) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/http/mixins/update_file_mixin.py b/mpt_api_client/http/mixins/update_file_mixin.py index ce76df71..60b8d7d6 100644 --- a/mpt_api_client/http/mixins/update_file_mixin.py +++ b/mpt_api_client/http/mixins/update_file_mixin.py @@ -1,6 +1,5 @@ -from urllib.parse import urljoin - from mpt_api_client.http.types import FileTypes +from mpt_api_client.http.url_utils import join_url_path from mpt_api_client.models import ResourceData @@ -27,7 +26,7 @@ def update( """ files = {} - url = urljoin(f"{self.path}/", resource_id) # type: ignore[attr-defined] + url = join_url_path(self.path, resource_id) # type: ignore[attr-defined] if file: files[self._upload_file_key] = file # type: ignore[attr-defined] @@ -67,7 +66,7 @@ async def update( """ files = {} - url = urljoin(f"{self.path}/", resource_id) # type: ignore[attr-defined] + url = join_url_path(self.path, resource_id) # type: ignore[attr-defined] if file: files[self._upload_file_key] = file # type: ignore[attr-defined] diff --git a/mpt_api_client/http/mixins/update_mixin.py b/mpt_api_client/http/mixins/update_mixin.py index 9d9a7c4a..c65ec0bc 100644 --- a/mpt_api_client/http/mixins/update_mixin.py +++ b/mpt_api_client/http/mixins/update_mixin.py @@ -5,31 +5,13 @@ class UpdateMixin[Model]: """Update resource mixin.""" def update(self, resource_id: str, resource_data: ResourceData) -> Model: - """Update a resource using `PUT /endpoint/{resource_id}`. - - Args: - resource_id: Resource ID. - resource_data: Resource data. - - Returns: - Resource object. - - """ - return self._resource_action(resource_id, "PUT", json=resource_data) # type: ignore[attr-defined, no-any-return] + """Update a resource using `PUT /endpoint/{resource_id}`.""" + return self._resource(resource_id).put(json=resource_data) # type: ignore[attr-defined, no-any-return] class AsyncUpdateMixin[Model]: """Update resource mixin.""" async def update(self, resource_id: str, resource_data: ResourceData) -> Model: - """Update a resource using `PUT /endpoint/{resource_id}`. - - Args: - resource_id: Resource ID. - resource_data: Resource data. - - Returns: - Resource object. - - """ - return await self._resource_action(resource_id, "PUT", json=resource_data) # type: ignore[attr-defined, no-any-return] + """Update a resource using `PUT /endpoint/{resource_id}`.""" + return await self._resource(resource_id).put(json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/http/resource_accessor.py b/mpt_api_client/http/resource_accessor.py new file mode 100644 index 00000000..4d007d6a --- /dev/null +++ b/mpt_api_client/http/resource_accessor.py @@ -0,0 +1,199 @@ +from mpt_api_client.constants import APPLICATION_JSON +from mpt_api_client.http.async_client import AsyncHTTPClient +from mpt_api_client.http.client import HTTPClient +from mpt_api_client.http.types import QueryParam, Response +from mpt_api_client.http.url_utils import join_url_path +from mpt_api_client.models.collection import ResourceList +from mpt_api_client.models.model import Model, ResourceData # NOSONAR + +_JsonPayload = ResourceData | ResourceList | None + + +class ResourceAccessor[ResourceModel: Model]: # NOSONAR + """Synchronous accessor bound to a single resource URL. + + Provides ``.get()``, ``.post()``, ``.put()``, ``.delete()`` helpers that + deserialize the response into a ``Model``, and a ``.do_request()`` escape + hatch that returns the raw ``Response``. + """ + + def __init__( + self, + http_client: HTTPClient, + resource_url: str, + model_class: type[ResourceModel], + ) -> None: + self._http_client = http_client + self._resource_url = resource_url + self._model_class = model_class + + # -- raw request --------------------------------------------------------- + + def do_request( # noqa: WPS211 + self, + method: str, + action: str | None = None, + *, + json: _JsonPayload = None, + query_params: QueryParam | None = None, + headers: dict[str, str] | None = None, + ) -> Response: + """Perform an HTTP request and return the raw ``Response``. + + Args: + method: HTTP method (GET, POST, PUT, DELETE …). + action: Optional sub-path appended after the resource id. + json: JSON body payload. + query_params: Query-string parameters. + headers: Extra HTTP headers. + """ + url = join_url_path(self._resource_url, action) if action else self._resource_url + return self._http_client.request( + method, url, json=json, query_params=query_params, headers=headers + ) + + # -- model-returning helpers --------------------------------------------- + + def get( + self, + action: str | None = None, + *, + query_params: QueryParam | None = None, + ) -> ResourceModel: + """``GET`` the resource (optionally with a sub-action).""" + return self._action("GET", action, query_params=query_params) + + def post( + self, + action: str | None = None, + *, + json: _JsonPayload = None, + query_params: QueryParam | None = None, + ) -> ResourceModel: + """``POST`` to the resource (optionally with a sub-action).""" + return self._action("POST", action, json=json, query_params=query_params) + + def put( + self, + action: str | None = None, + *, + json: _JsonPayload = None, + query_params: QueryParam | None = None, + ) -> ResourceModel: + """``PUT`` to the resource (optionally with a sub-action).""" + return self._action("PUT", action, json=json, query_params=query_params) + + def delete(self) -> None: + """``DELETE`` the resource.""" + self.do_request("DELETE") + + def _action( + self, + method: str, + action: str | None = None, + *, + json: _JsonPayload = None, + query_params: QueryParam | None = None, + ) -> ResourceModel: + response = self.do_request( + method, + action, + json=json, + query_params=query_params, + headers={"Accept": APPLICATION_JSON}, + ) + return self._model_class.from_response(response) + + +class AsyncResourceAccessor[ResourceModel: Model]: # NOSONAR + """Asynchronous accessor bound to a single resource URL. + + Async counterpart of :class:`ResourceAccessor`. + """ + + def __init__( + self, + http_client: AsyncHTTPClient, + resource_url: str, + model_class: type[ResourceModel], + ) -> None: + self._http_client = http_client + self._resource_url = resource_url + self._model_class = model_class + + # -- raw request --------------------------------------------------------- + + async def do_request( # noqa: WPS211 + self, + method: str, + action: str | None = None, + *, + json: _JsonPayload = None, + query_params: QueryParam | None = None, + headers: dict[str, str] | None = None, + ) -> Response: + """Perform an HTTP request and return the raw ``Response``. + + Args: + method: HTTP method (GET, POST, PUT, DELETE …). + action: Optional sub-path appended after the resource id. + json: JSON body payload. + query_params: Query-string parameters. + headers: Extra HTTP headers. + """ + url = join_url_path(self._resource_url, action) if action else self._resource_url + return await self._http_client.request( + method, url, json=json, query_params=query_params, headers=headers + ) + + # -- model-returning helpers --------------------------------------------- + + async def get( + self, + action: str | None = None, + *, + query_params: QueryParam | None = None, + ) -> ResourceModel: + """``GET`` the resource (optionally with a sub-action).""" + return await self._action("GET", action, query_params=query_params) + + async def post( + self, + action: str | None = None, + *, + json: _JsonPayload = None, + query_params: QueryParam | None = None, + ) -> ResourceModel: + """``POST`` to the resource (optionally with a sub-action).""" + return await self._action("POST", action, json=json, query_params=query_params) + + async def put( + self, + action: str | None = None, + *, + json: _JsonPayload = None, + query_params: QueryParam | None = None, + ) -> ResourceModel: + """``PUT`` to the resource (optionally with a sub-action).""" + return await self._action("PUT", action, json=json, query_params=query_params) + + async def delete(self) -> None: + """``DELETE`` the resource.""" + await self.do_request("DELETE") + + async def _action( + self, + method: str, + action: str | None = None, + *, + json: _JsonPayload = None, + query_params: QueryParam | None = None, + ) -> ResourceModel: + response = await self.do_request( + method, + action, + json=json, + query_params=query_params, + headers={"Accept": APPLICATION_JSON}, + ) + return self._model_class.from_response(response) diff --git a/mpt_api_client/http/service.py b/mpt_api_client/http/service.py index 45ae6ac8..af729878 100644 --- a/mpt_api_client/http/service.py +++ b/mpt_api_client/http/service.py @@ -1,12 +1,8 @@ -from urllib.parse import urljoin - -from mpt_api_client.constants import APPLICATION_JSON from mpt_api_client.http.base_service import ServiceBase from mpt_api_client.http.client import HTTPClient -from mpt_api_client.http.types import QueryParam, Response +from mpt_api_client.http.resource_accessor import ResourceAccessor +from mpt_api_client.http.url_utils import join_url_path from mpt_api_client.models import Model as BaseModel -from mpt_api_client.models import ResourceData -from mpt_api_client.models.collection import ResourceList class Service[Model: BaseModel](ServiceBase[HTTPClient, Model]): # noqa: WPS214 @@ -21,60 +17,15 @@ class Service[Model: BaseModel](ServiceBase[HTTPClient, Model]): # noqa: WPS214 """ - def _resource_do_request( # noqa: WPS211 - self, - resource_id: str, - method: str = "GET", - action: str | None = None, - json: ResourceData | ResourceList | None = None, - query_params: QueryParam | None = None, - headers: dict[str, str] | None = None, - ) -> Response: - """Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`. - - Args: - resource_id: The resource ID to operate on. - method: The HTTP method to use. - action: The action name to use. - json: The updated resource data. - query_params: Additional query parameters. - headers: Additional headers. - - Returns: - HTTP response object. - - Raises: - HTTPError: If the action fails. - """ - resource_url = urljoin(f"{self.path}/", resource_id) - url = urljoin(f"{resource_url}/", action) if action else resource_url - return self.http_client.request( - method, url, json=json, query_params=query_params, headers=headers - ) + def _resource(self, resource_id: str) -> ResourceAccessor[Model]: + """Return a :class:`ResourceAccessor` bound to *resource_id*. - def _resource_action( - self, - resource_id: str, - method: str = "GET", - action: str | None = None, - json: ResourceData | ResourceList | None = None, - query_params: QueryParam | None = None, - ) -> Model: - """Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`. + Usage:: - Args: - resource_id: The resource ID to operate on. - method: The HTTP method to use. - action: The action name to use. - json: The updated resource data. - query_params: Additional query parameters. + self._resource("RES-123").post("complete", json=data) + self._resource("RES-123").get() + self._resource("RES-123").put(json=data) + self._resource("RES-123").delete() """ - response = self._resource_do_request( - resource_id, - method, - action, - json=json, - query_params=query_params, - headers={"Accept": APPLICATION_JSON}, - ) - return self._model_class.from_response(response) + resource_url = join_url_path(self.path, resource_id) + return ResourceAccessor(self.http_client, resource_url, self._model_class) diff --git a/mpt_api_client/http/url_utils.py b/mpt_api_client/http/url_utils.py new file mode 100644 index 00000000..6e9e4c1e --- /dev/null +++ b/mpt_api_client/http/url_utils.py @@ -0,0 +1,26 @@ +def join_url_path(base: str, *segments: str) -> str: + """Join *segments* onto *base* as literal path components. + + Unlike :func:`urllib.parse.urljoin`, this function **never** interprets a + segment as an absolute path or URL it always appends. + + Args: + base: The base URL path (e.g. ``/public/v1/billing/journals``). + *segments: One or more path segments to append + (e.g. ``"JRN-123"``, ``"upload"``). + + Returns: + The joined path with exactly one ``/`` between each part and no + trailing slash. + + Examples: + >>> join_url_path("/api/v1/orders", "ORD-001") + '/api/v1/orders/ORD-001' + >>> join_url_path("/api/v1/orders", "ORD-001", "complete") + '/api/v1/orders/ORD-001/complete' + >>> join_url_path("/api/v1/orders/", "/ORD-001/") + '/api/v1/orders/ORD-001' + """ + parts = [base.rstrip("/")] + parts.extend(seg.strip("/") for seg in segments if seg) + return "/".join(parts) diff --git a/mpt_api_client/resources/accounts/buyers.py b/mpt_api_client/resources/accounts/buyers.py index 5d9fbcb9..ff61d0d6 100644 --- a/mpt_api_client/resources/accounts/buyers.py +++ b/mpt_api_client/resources/accounts/buyers.py @@ -61,7 +61,7 @@ def synchronize(self, resource_id: str, resource_data: ResourceData | None = Non resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action(resource_id, "POST", "synchronize", json=resource_data) + return self._resource(resource_id).post("synchronize", json=resource_data) def transfer(self, resource_id: str, resource_data: ResourceData | None = None) -> Buyer: """Transfer a buyer. @@ -70,7 +70,7 @@ def transfer(self, resource_id: str, resource_data: ResourceData | None = None) resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action(resource_id, "POST", "transfer", json=resource_data) + return self._resource(resource_id).post("transfer", json=resource_data) class AsyncBuyersService( @@ -97,7 +97,7 @@ async def synchronize( resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action(resource_id, "POST", "synchronize", json=resource_data) + return await self._resource(resource_id).post("synchronize", json=resource_data) async def transfer(self, resource_id: str, resource_data: ResourceData | None = None) -> Buyer: """Transfer a buyer. @@ -106,4 +106,4 @@ async def transfer(self, resource_id: str, resource_data: ResourceData | None = resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action(resource_id, "POST", "transfer", json=resource_data) + return await self._resource(resource_id).post("transfer", json=resource_data) diff --git a/mpt_api_client/resources/accounts/mixins/activatable_mixin.py b/mpt_api_client/resources/accounts/mixins/activatable_mixin.py index 8781ac8c..2f9c608c 100644 --- a/mpt_api_client/resources/accounts/mixins/activatable_mixin.py +++ b/mpt_api_client/resources/accounts/mixins/activatable_mixin.py @@ -2,54 +2,28 @@ class ActivatableMixin[Model]: - """Activatable mixin for activating, enabling, disabling and deactivating resources.""" + """Activatable mixin for activating and deactivating resources.""" def activate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Activate a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "activate", json=resource_data - ) + """Activate a resource.""" + return self._resource(resource_id).post("activate", json=resource_data) # type: ignore[attr-defined, no-any-return] def deactivate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Deactivate a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "deactivate", json=resource_data - ) + """Deactivate a resource.""" + return self._resource(resource_id).post("deactivate", json=resource_data) # type: ignore[attr-defined, no-any-return] class AsyncActivatableMixin[Model]: - """Async activatable mixin for activating, enabling, disabling and deactivating resources.""" + """Async activatable mixin for activating and deactivating resources.""" async def activate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Activate a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "activate", json=resource_data - ) + """Activate a resource.""" + return await self._resource(resource_id).post("activate", json=resource_data) # type: ignore[attr-defined, no-any-return] async def deactivate( - self, resource_id: str, resource_data: ResourceData | None = None + self, + resource_id: str, + resource_data: ResourceData | None = None, ) -> Model: - """Deactivate a resource. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "deactivate", json=resource_data - ) + """Deactivate a resource.""" + return await self._resource(resource_id).post("deactivate", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/resources/accounts/mixins/blockable_mixin.py b/mpt_api_client/resources/accounts/mixins/blockable_mixin.py index d4a05abb..abcded6a 100644 --- a/mpt_api_client/resources/accounts/mixins/blockable_mixin.py +++ b/mpt_api_client/resources/accounts/mixins/blockable_mixin.py @@ -11,9 +11,7 @@ def block(self, resource_id: str, resource_data: ResourceData | None = None) -> resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "block", json=resource_data - ) + return self._resource(resource_id).post("block", json=resource_data) # type: ignore[attr-defined, no-any-return] def unblock(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Unblock a resource. @@ -22,9 +20,7 @@ def unblock(self, resource_id: str, resource_data: ResourceData | None = None) - resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "unblock", json=resource_data - ) + return self._resource(resource_id).post("unblock", json=resource_data) # type: ignore[attr-defined, no-any-return] class AsyncBlockableMixin[Model]: @@ -37,9 +33,7 @@ async def block(self, resource_id: str, resource_data: ResourceData | None = Non resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "block", json=resource_data - ) + return await self._resource(resource_id).post("block", json=resource_data) # type: ignore[attr-defined, no-any-return] async def unblock(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Unblock a resource. @@ -48,6 +42,4 @@ async def unblock(self, resource_id: str, resource_data: ResourceData | None = N resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "unblock", json=resource_data - ) + return await self._resource(resource_id).post("unblock", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/resources/accounts/mixins/invitable_mixin.py b/mpt_api_client/resources/accounts/mixins/invitable_mixin.py index a4a22e14..6ae3b2d5 100644 --- a/mpt_api_client/resources/accounts/mixins/invitable_mixin.py +++ b/mpt_api_client/resources/accounts/mixins/invitable_mixin.py @@ -11,9 +11,7 @@ def accept_invite(self, resource_id: str, resource_data: ResourceData | None = N resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "accept-invite", json=resource_data - ) + return self._resource(resource_id).post("accept-invite", json=resource_data) # type: ignore[attr-defined, no-any-return] def resend_invite(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Resend an invite to a resource. @@ -22,9 +20,7 @@ def resend_invite(self, resource_id: str, resource_data: ResourceData | None = N resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "resend-invite", json=resource_data - ) + return self._resource(resource_id).post("resend-invite", json=resource_data) # type: ignore[attr-defined, no-any-return] def send_new_invite(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Send a new invite to a resource. @@ -33,9 +29,7 @@ def send_new_invite(self, resource_id: str, resource_data: ResourceData | None = resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "send-new-invite", json=resource_data - ) + return self._resource(resource_id).post("send-new-invite", json=resource_data) # type: ignore[attr-defined, no-any-return] class AsyncInvitableMixin[Model]: @@ -50,9 +44,7 @@ async def accept_invite( resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "accept-invite", json=resource_data - ) + return await self._resource(resource_id).post("accept-invite", json=resource_data) # type: ignore[attr-defined, no-any-return] async def resend_invite( self, resource_id: str, resource_data: ResourceData | None = None @@ -63,9 +55,7 @@ async def resend_invite( resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "resend-invite", json=resource_data - ) + return await self._resource(resource_id).post("resend-invite", json=resource_data) # type: ignore[attr-defined, no-any-return] async def send_new_invite( self, resource_id: str, resource_data: ResourceData | None = None @@ -76,6 +66,4 @@ async def send_new_invite( resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "send-new-invite", json=resource_data - ) + return await self._resource(resource_id).post("send-new-invite", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/resources/accounts/mixins/validate_mixin.py b/mpt_api_client/resources/accounts/mixins/validate_mixin.py index 3fbffc7c..aa7d60a3 100644 --- a/mpt_api_client/resources/accounts/mixins/validate_mixin.py +++ b/mpt_api_client/resources/accounts/mixins/validate_mixin.py @@ -11,9 +11,7 @@ def validate(self, resource_id: str, resource_data: ResourceData | None = None) resource_id: Resource ID resource_data: Resource data will be validated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "validate", json=resource_data - ) + return self._resource(resource_id).post("validate", json=resource_data) # type: ignore[attr-defined, no-any-return] class AsyncValidateMixin[Model]: @@ -26,6 +24,4 @@ async def validate(self, resource_id: str, resource_data: ResourceData | None = resource_id: Resource ID resource_data: Resource data will be validated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "validate", json=resource_data - ) + return await self._resource(resource_id).post("validate", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/resources/accounts/sellers.py b/mpt_api_client/resources/accounts/sellers.py index 69dbea7d..4e52a8b6 100644 --- a/mpt_api_client/resources/accounts/sellers.py +++ b/mpt_api_client/resources/accounts/sellers.py @@ -41,7 +41,7 @@ def disable(self, resource_id: str, resource_data: ResourceData | None = None) - resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action(resource_id, "POST", "disable", json=resource_data) + return self._resource(resource_id).post("disable", json=resource_data) class AsyncSellersService( @@ -60,4 +60,4 @@ async def disable(self, resource_id: str, resource_data: ResourceData | None = N resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action(resource_id, "POST", "disable", json=resource_data) + return await self._resource(resource_id).post("disable", json=resource_data) diff --git a/mpt_api_client/resources/accounts/users.py b/mpt_api_client/resources/accounts/users.py index d8c322a2..1654fdf4 100644 --- a/mpt_api_client/resources/accounts/users.py +++ b/mpt_api_client/resources/accounts/users.py @@ -49,7 +49,7 @@ def sso(self, resource_id: str, resource_data: ResourceData | None = None) -> Us resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action(resource_id, "POST", "sso", json=resource_data) + return self._resource(resource_id).post("sso", json=resource_data) def sso_check(self, resource_id: str, resource_data: ResourceData | None = None) -> User: """Perform SSO check action for a user. @@ -58,7 +58,7 @@ def sso_check(self, resource_id: str, resource_data: ResourceData | None = None) resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action(resource_id, "POST", "sso-check", json=resource_data) + return self._resource(resource_id).post("sso-check", json=resource_data) def set_password(self, resource_id: str, password: str) -> User: """Set password for a user. @@ -69,7 +69,7 @@ def set_password(self, resource_id: str, password: str) -> User: """ resource_data = {"password": password} - return self._resource_action(resource_id, "POST", "set-password", json=resource_data) + return self._resource(resource_id).post("set-password", json=resource_data) class AsyncUsersService( @@ -90,7 +90,7 @@ async def sso(self, resource_id: str, resource_data: ResourceData | None = None) resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action(resource_id, "POST", "sso", json=resource_data) + return await self._resource(resource_id).post("sso", json=resource_data) async def sso_check(self, resource_id: str, resource_data: ResourceData | None = None) -> User: """Perform SSO check action for a user. @@ -99,7 +99,7 @@ async def sso_check(self, resource_id: str, resource_data: ResourceData | None = resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action(resource_id, "POST", "sso-check", json=resource_data) + return await self._resource(resource_id).post("sso-check", json=resource_data) async def set_password(self, resource_id: str, password: str) -> User: """Set password for a user. @@ -110,4 +110,4 @@ async def set_password(self, resource_id: str, password: str) -> User: """ resource_data = {"password": password} - return await self._resource_action(resource_id, "POST", "set-password", json=resource_data) + return await self._resource(resource_id).post("set-password", json=resource_data) diff --git a/mpt_api_client/resources/billing/custom_ledgers.py b/mpt_api_client/resources/billing/custom_ledgers.py index 1d1b45f7..1ad473d1 100644 --- a/mpt_api_client/resources/billing/custom_ledgers.py +++ b/mpt_api_client/resources/billing/custom_ledgers.py @@ -1,6 +1,5 @@ import pathlib from typing import cast -from urllib.parse import urljoin from mpt_api_client.constants import MIMETYPE_EXCEL_XLSX from mpt_api_client.http import AsyncService, Service @@ -11,6 +10,7 @@ ManagedResourceMixin, ) from mpt_api_client.http.types import FileContent, FileTypes +from mpt_api_client.http.url_utils import join_url_path from mpt_api_client.models import Model from mpt_api_client.resources.billing.custom_ledger_attachments import ( AsyncCustomLedgerAttachmentsService, @@ -68,7 +68,7 @@ def upload(self, custom_ledger_id: str, file: FileTypes) -> CustomLedger: ) # UNUSED type: ignore[attr-defined] files[self._upload_data_key] = custom_ledger_id # UNUSED type: ignore - path = urljoin(f"{self.path}/", f"{custom_ledger_id}/upload") + path = join_url_path(self.path, custom_ledger_id, "upload") response = self.http_client.request( # UNUSED type: ignore[attr-defined] "post", @@ -127,7 +127,7 @@ async def upload(self, custom_ledger_id: str, file: FileTypes) -> CustomLedger: ) # UNUSED type: ignore[attr-defined] files[self._upload_data_key] = custom_ledger_id # UNUSED type: ignore - path = urljoin(f"{self.path}/", f"{custom_ledger_id}/upload") + path = join_url_path(self.path, custom_ledger_id, "upload") response = await self.http_client.request( # UNUSED type: ignore[attr-defined] "post", diff --git a/mpt_api_client/resources/billing/journals.py b/mpt_api_client/resources/billing/journals.py index e4377277..6941b308 100644 --- a/mpt_api_client/resources/billing/journals.py +++ b/mpt_api_client/resources/billing/journals.py @@ -1,5 +1,3 @@ -from urllib.parse import urljoin - from mpt_api_client.http import AsyncService, Service from mpt_api_client.http.mixins import ( AsyncCollectionMixin, @@ -8,6 +6,7 @@ ManagedResourceMixin, ) from mpt_api_client.http.types import FileTypes +from mpt_api_client.http.url_utils import join_url_path from mpt_api_client.models import Model from mpt_api_client.resources.billing.journal_attachments import ( AsyncJournalAttachmentsService, @@ -63,7 +62,7 @@ def upload(self, journal_id: str, file: FileTypes | None = None) -> Journal: # files[self._upload_file_key] = file # UNUSED type: ignore[attr-defined] files[self._upload_data_key] = journal_id # UNUSED type: ignore - path = urljoin(f"{self.path}/", f"{journal_id}/upload") + path = join_url_path(self.path, journal_id, "upload") response = self.http_client.request( # UNUSED type: ignore[attr-defined] "post", @@ -121,7 +120,7 @@ async def upload(self, journal_id: str, file: FileTypes | None = None) -> Journa files[self._upload_file_key] = file # UNUSED type: ignore[attr-defined] files[self._upload_data_key] = journal_id # UNUSED type: ignore - path = urljoin(f"{self.path}/", f"{journal_id}/upload") + path = join_url_path(self.path, journal_id, "upload") response = await self.http_client.request( # UNUSED type: ignore[attr-defined] "post", diff --git a/mpt_api_client/resources/billing/mixins/acceptable_mixin.py b/mpt_api_client/resources/billing/mixins/acceptable_mixin.py index 83a0f511..5d4171b9 100644 --- a/mpt_api_client/resources/billing/mixins/acceptable_mixin.py +++ b/mpt_api_client/resources/billing/mixins/acceptable_mixin.py @@ -11,9 +11,7 @@ def accept(self, resource_id: str, resource_data: ResourceData | None = None) -> resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "accept", json=resource_data - ) + return self._resource(resource_id).post("accept", json=resource_data) # type: ignore[attr-defined, no-any-return] def queue(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Queue resource. @@ -22,9 +20,7 @@ def queue(self, resource_id: str, resource_data: ResourceData | None = None) -> resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "queue", json=resource_data - ) + return self._resource(resource_id).post("queue", json=resource_data) # type: ignore[attr-defined, no-any-return] class AsyncAcceptableMixin[Model]: @@ -37,9 +33,7 @@ async def accept(self, resource_id: str, resource_data: ResourceData | None = No resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "accept", json=resource_data - ) + return await self._resource(resource_id).post("accept", json=resource_data) # type: ignore[attr-defined, no-any-return] async def queue(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Queue resource. @@ -48,6 +42,4 @@ async def queue(self, resource_id: str, resource_data: ResourceData | None = Non resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "queue", json=resource_data - ) + return await self._resource(resource_id).post("queue", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/resources/billing/mixins/issuable_mixin.py b/mpt_api_client/resources/billing/mixins/issuable_mixin.py index 723856b2..e5ca9bde 100644 --- a/mpt_api_client/resources/billing/mixins/issuable_mixin.py +++ b/mpt_api_client/resources/billing/mixins/issuable_mixin.py @@ -11,9 +11,7 @@ def issue(self, resource_id: str, resource_data: ResourceData | None = None) -> resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "issue", json=resource_data - ) + return self._resource(resource_id).post("issue", json=resource_data) # type: ignore[attr-defined, no-any-return] def cancel(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Cancel resource. @@ -22,9 +20,7 @@ def cancel(self, resource_id: str, resource_data: ResourceData | None = None) -> resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "cancel", json=resource_data - ) + return self._resource(resource_id).post("cancel", json=resource_data) # type: ignore[attr-defined, no-any-return] def error(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Error resource. @@ -33,9 +29,7 @@ def error(self, resource_id: str, resource_data: ResourceData | None = None) -> resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "error", json=resource_data - ) + return self._resource(resource_id).post("error", json=resource_data) # type: ignore[attr-defined, no-any-return] def pending(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Pending resource. @@ -44,9 +38,7 @@ def pending(self, resource_id: str, resource_data: ResourceData | None = None) - resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "pending", json=resource_data - ) + return self._resource(resource_id).post("pending", json=resource_data) # type: ignore[attr-defined, no-any-return] def queue(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Queue resource. @@ -55,9 +47,7 @@ def queue(self, resource_id: str, resource_data: ResourceData | None = None) -> resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "queue", json=resource_data - ) + return self._resource(resource_id).post("queue", json=resource_data) # type: ignore[attr-defined, no-any-return] def retry(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Retry resource. @@ -66,9 +56,7 @@ def retry(self, resource_id: str, resource_data: ResourceData | None = None) -> resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "retry", json=resource_data - ) + return self._resource(resource_id).post("retry", json=resource_data) # type: ignore[attr-defined, no-any-return] def recalculate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Recalculate resource. @@ -77,9 +65,7 @@ def recalculate(self, resource_id: str, resource_data: ResourceData | None = Non resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "recalculate", json=resource_data - ) + return self._resource(resource_id).post("recalculate", json=resource_data) # type: ignore[attr-defined, no-any-return] class AsyncIssuableMixin[Model]: @@ -92,9 +78,7 @@ async def issue(self, resource_id: str, resource_data: ResourceData | None = Non resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "issue", json=resource_data - ) + return await self._resource(resource_id).post("issue", json=resource_data) # type: ignore[attr-defined, no-any-return] async def cancel(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Cancel resource. @@ -103,9 +87,7 @@ async def cancel(self, resource_id: str, resource_data: ResourceData | None = No resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "cancel", json=resource_data - ) + return await self._resource(resource_id).post("cancel", json=resource_data) # type: ignore[attr-defined, no-any-return] async def error(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Error resource. @@ -114,9 +96,7 @@ async def error(self, resource_id: str, resource_data: ResourceData | None = Non resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "error", json=resource_data - ) + return await self._resource(resource_id).post("error", json=resource_data) # type: ignore[attr-defined, no-any-return] async def pending(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Pending resource. @@ -125,9 +105,7 @@ async def pending(self, resource_id: str, resource_data: ResourceData | None = N resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "pending", json=resource_data - ) + return await self._resource(resource_id).post("pending", json=resource_data) # type: ignore[attr-defined, no-any-return] async def queue(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Queue resource. @@ -136,9 +114,7 @@ async def queue(self, resource_id: str, resource_data: ResourceData | None = Non resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "queue", json=resource_data - ) + return await self._resource(resource_id).post("queue", json=resource_data) # type: ignore[attr-defined, no-any-return] async def retry(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Retry resource. @@ -147,9 +123,7 @@ async def retry(self, resource_id: str, resource_data: ResourceData | None = Non resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "retry", json=resource_data - ) + return await self._resource(resource_id).post("retry", json=resource_data) # type: ignore[attr-defined, no-any-return] async def recalculate( self, resource_id: str, resource_data: ResourceData | None = None @@ -160,6 +134,4 @@ async def recalculate( resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "recalculate", json=resource_data - ) + return await self._resource(resource_id).post("recalculate", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/resources/billing/mixins/recalculatable_mixin.py b/mpt_api_client/resources/billing/mixins/recalculatable_mixin.py index 44163824..06d54f0d 100644 --- a/mpt_api_client/resources/billing/mixins/recalculatable_mixin.py +++ b/mpt_api_client/resources/billing/mixins/recalculatable_mixin.py @@ -11,9 +11,7 @@ def recalculate(self, resource_id: str, resource_data: ResourceData | None = Non resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "recalculate", json=resource_data - ) + return self._resource(resource_id).post("recalculate", json=resource_data) # type: ignore[attr-defined, no-any-return] def accept(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Accept resource. @@ -22,9 +20,7 @@ def accept(self, resource_id: str, resource_data: ResourceData | None = None) -> resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "accept", json=resource_data - ) + return self._resource(resource_id).post("accept", json=resource_data) # type: ignore[attr-defined, no-any-return] def queue(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Queue resource. @@ -33,9 +29,7 @@ def queue(self, resource_id: str, resource_data: ResourceData | None = None) -> resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "queue", json=resource_data - ) + return self._resource(resource_id).post("queue", json=resource_data) # type: ignore[attr-defined, no-any-return] class AsyncRecalculatableMixin[Model]: @@ -50,9 +44,7 @@ async def recalculate( resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "recalculate", json=resource_data - ) + return await self._resource(resource_id).post("recalculate", json=resource_data) # type: ignore[attr-defined, no-any-return] async def accept(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Accept resource. @@ -61,9 +53,7 @@ async def accept(self, resource_id: str, resource_data: ResourceData | None = No resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "accept", json=resource_data - ) + return await self._resource(resource_id).post("accept", json=resource_data) # type: ignore[attr-defined, no-any-return] async def queue(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Queue resource. @@ -72,6 +62,4 @@ async def queue(self, resource_id: str, resource_data: ResourceData | None = Non resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "queue", json=resource_data - ) + return await self._resource(resource_id).post("queue", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/resources/billing/mixins/regeneratable_mixin.py b/mpt_api_client/resources/billing/mixins/regeneratable_mixin.py index 74061975..417d7cfd 100644 --- a/mpt_api_client/resources/billing/mixins/regeneratable_mixin.py +++ b/mpt_api_client/resources/billing/mixins/regeneratable_mixin.py @@ -11,9 +11,7 @@ def regenerate(self, resource_id: str, resource_data: ResourceData | None = None resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "regenerate", json=resource_data - ) + return self._resource(resource_id).post("regenerate", json=resource_data) # type: ignore[attr-defined, no-any-return] def submit(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Submit resource. @@ -22,9 +20,7 @@ def submit(self, resource_id: str, resource_data: ResourceData | None = None) -> resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "submit", json=resource_data - ) + return self._resource(resource_id).post("submit", json=resource_data) # type: ignore[attr-defined, no-any-return] def enquiry(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Enquiry resource. @@ -33,9 +29,7 @@ def enquiry(self, resource_id: str, resource_data: ResourceData | None = None) - resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "enquiry", json=resource_data - ) + return self._resource(resource_id).post("enquiry", json=resource_data) # type: ignore[attr-defined, no-any-return] def accept(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Accept resource. @@ -44,9 +38,7 @@ def accept(self, resource_id: str, resource_data: ResourceData | None = None) -> resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "accept", json=resource_data - ) + return self._resource(resource_id).post("accept", json=resource_data) # type: ignore[attr-defined, no-any-return] class AsyncRegeneratableMixin[Model]: @@ -61,9 +53,7 @@ async def regenerate( resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "regenerate", json=resource_data - ) + return await self._resource(resource_id).post("regenerate", json=resource_data) # type: ignore[attr-defined, no-any-return] async def submit(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Submit resource. @@ -72,9 +62,7 @@ async def submit(self, resource_id: str, resource_data: ResourceData | None = No resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "submit", json=resource_data - ) + return await self._resource(resource_id).post("submit", json=resource_data) # type: ignore[attr-defined, no-any-return] async def enquiry(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Enquiry resource. @@ -83,9 +71,7 @@ async def enquiry(self, resource_id: str, resource_data: ResourceData | None = N resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "enquiry", json=resource_data - ) + return await self._resource(resource_id).post("enquiry", json=resource_data) # type: ignore[attr-defined, no-any-return] async def accept(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Accept resource. @@ -94,6 +80,4 @@ async def accept(self, resource_id: str, resource_data: ResourceData | None = No resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "accept", json=resource_data - ) + return await self._resource(resource_id).post("accept", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/resources/catalog/mixins/activatable_mixin.py b/mpt_api_client/resources/catalog/mixins/activatable_mixin.py index a8ad9ff3..645608bd 100644 --- a/mpt_api_client/resources/catalog/mixins/activatable_mixin.py +++ b/mpt_api_client/resources/catalog/mixins/activatable_mixin.py @@ -5,51 +5,25 @@ class ActivatableMixin[Model]: """Activatable mixin adds the ability to activate and deactivate.""" def activate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Update state to Active. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "activate", json=resource_data - ) + """Update state to Active.""" + return self._resource(resource_id).post("activate", json=resource_data) # type: ignore[attr-defined, no-any-return] def deactivate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Update state to Inactive. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "deactivate", json=resource_data - ) + """Update state to Inactive.""" + return self._resource(resource_id).post("deactivate", json=resource_data) # type: ignore[attr-defined, no-any-return] class AsyncActivatableMixin[Model]: """Activatable mixin adds the ability to activate and deactivate.""" async def activate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: - """Update state to Active. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "activate", json=resource_data - ) + """Update state to Active.""" + return await self._resource(resource_id).post("activate", json=resource_data) # type: ignore[attr-defined, no-any-return] async def deactivate( - self, resource_id: str, resource_data: ResourceData | None = None + self, + resource_id: str, + resource_data: ResourceData | None = None, ) -> Model: - """Update state to Inactive. - - Args: - resource_id: Resource ID - resource_data: Resource data will be updated - """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "deactivate", json=resource_data - ) + """Update state to Inactive.""" + return await self._resource(resource_id).post("deactivate", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/resources/catalog/mixins/publishable_mixin.py b/mpt_api_client/resources/catalog/mixins/publishable_mixin.py index 05ce4aed..b77ebf35 100644 --- a/mpt_api_client/resources/catalog/mixins/publishable_mixin.py +++ b/mpt_api_client/resources/catalog/mixins/publishable_mixin.py @@ -11,9 +11,7 @@ def review(self, resource_id: str, resource_data: ResourceData | None = None) -> resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "review", json=resource_data - ) + return self._resource(resource_id).post("review", json=resource_data) # type: ignore[attr-defined, no-any-return] def publish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Update state to Published. @@ -22,9 +20,7 @@ def publish(self, resource_id: str, resource_data: ResourceData | None = None) - resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "publish", json=resource_data - ) + return self._resource(resource_id).post("publish", json=resource_data) # type: ignore[attr-defined, no-any-return] def unpublish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Update state to Unpublished. @@ -33,9 +29,7 @@ def unpublish(self, resource_id: str, resource_data: ResourceData | None = None) resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "unpublish", json=resource_data - ) + return self._resource(resource_id).post("unpublish", json=resource_data) # type: ignore[attr-defined, no-any-return] class AsyncPublishableMixin[Model]: @@ -48,9 +42,7 @@ async def review(self, resource_id: str, resource_data: ResourceData | None = No resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "review", json=resource_data - ) + return await self._resource(resource_id).post("review", json=resource_data) # type: ignore[attr-defined, no-any-return] async def publish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Update state to Published. @@ -59,9 +51,7 @@ async def publish(self, resource_id: str, resource_data: ResourceData | None = N resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "publish", json=resource_data - ) + return await self._resource(resource_id).post("publish", json=resource_data) # type: ignore[attr-defined, no-any-return] async def unpublish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Update state to Unpublished. @@ -70,6 +60,4 @@ async def unpublish(self, resource_id: str, resource_data: ResourceData | None = resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action( # type: ignore[attr-defined, no-any-return] - resource_id, "POST", "unpublish", json=resource_data - ) + return await self._resource(resource_id).post("unpublish", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/resources/catalog/pricing_policies.py b/mpt_api_client/resources/catalog/pricing_policies.py index 80c16bfe..075a11ea 100644 --- a/mpt_api_client/resources/catalog/pricing_policies.py +++ b/mpt_api_client/resources/catalog/pricing_policies.py @@ -81,7 +81,7 @@ def activate( Returns: Activated pricing policy. """ - return self._resource_action(resource_id, "POST", "activate", json=resource_data) + return self._resource(resource_id).post("activate", json=resource_data) def disable(self, resource_id: str, resource_data: ResourceData | None = None) -> PricingPolicy: """Disable pricing policy. @@ -93,7 +93,7 @@ def disable(self, resource_id: str, resource_data: ResourceData | None = None) - Returns: Disabled pricing policy. """ - return self._resource_action(resource_id, "POST", "disable", json=resource_data) + return self._resource(resource_id).post("disable", json=resource_data) class AsyncPricingPoliciesService( @@ -123,7 +123,7 @@ async def activate( Returns: Activated pricing policy. """ - return await self._resource_action(resource_id, "POST", "activate", json=resource_data) + return await self._resource(resource_id).post("activate", json=resource_data) async def disable( self, resource_id: str, resource_data: ResourceData | None = None @@ -137,4 +137,4 @@ async def disable( Returns: Disabled pricing policy. """ - return await self._resource_action(resource_id, "POST", "disable", json=resource_data) + return await self._resource(resource_id).post("disable", json=resource_data) diff --git a/mpt_api_client/resources/catalog/products.py b/mpt_api_client/resources/catalog/products.py index b6c6f573..6244e6a8 100644 --- a/mpt_api_client/resources/catalog/products.py +++ b/mpt_api_client/resources/catalog/products.py @@ -151,7 +151,7 @@ def terms(self, product_id: str) -> TermService: def update_settings(self, product_id: str, settings: ResourceData) -> Product: """Update product settings.""" - return self._resource_action(product_id, "PUT", "settings", settings) + return self._resource(product_id).put("settings", json=settings) class AsyncProductsService( @@ -216,4 +216,4 @@ def terms(self, product_id: str) -> AsyncTermService: async def update_settings(self, product_id: str, settings: ResourceData) -> Product: """Update product settings.""" - return await self._resource_action(product_id, "PUT", "settings", settings) + return await self._resource(product_id).put("settings", json=settings) diff --git a/mpt_api_client/resources/commerce/mixins/render_mixin.py b/mpt_api_client/resources/commerce/mixins/render_mixin.py index 9b20462b..83c94750 100644 --- a/mpt_api_client/resources/commerce/mixins/render_mixin.py +++ b/mpt_api_client/resources/commerce/mixins/render_mixin.py @@ -10,7 +10,7 @@ def render(self, resource_id: str) -> str: Returns: Rendered resource. """ - response = self._resource_do_request(resource_id, action="render") # type: ignore[attr-defined] + response = self._resource(resource_id).do_request("GET", "render") # type: ignore[attr-defined] return response.text # type: ignore[no-any-return] @@ -26,5 +26,5 @@ async def render(self, resource_id: str) -> str: Returns: Rendered resource. """ - response = await self._resource_do_request(resource_id, action="render") # type: ignore[attr-defined] + response = await self._resource(resource_id).do_request("GET", "render") # type: ignore[attr-defined] return response.text # type: ignore[no-any-return] diff --git a/mpt_api_client/resources/commerce/mixins/template_mixin.py b/mpt_api_client/resources/commerce/mixins/template_mixin.py index b9a7f950..004a17e8 100644 --- a/mpt_api_client/resources/commerce/mixins/template_mixin.py +++ b/mpt_api_client/resources/commerce/mixins/template_mixin.py @@ -10,7 +10,7 @@ def template(self, resource_id: str) -> str: Returns: Resource template. """ - response = self._resource_do_request(resource_id, action="template") # type: ignore[attr-defined] + response = self._resource(resource_id).do_request("GET", "template") # type: ignore[attr-defined] return response.text # type: ignore[no-any-return] @@ -26,6 +26,5 @@ async def template(self, resource_id: str) -> str: Returns: Resource template. """ - # pylint: disable=duplicate-code - response = await self._resource_do_request(resource_id, action="template") # type: ignore[attr-defined] + response = await self._resource(resource_id).do_request("GET", "template") # type: ignore[attr-defined] return response.text # type: ignore[no-any-return] diff --git a/mpt_api_client/resources/commerce/mixins/terminate_mixin.py b/mpt_api_client/resources/commerce/mixins/terminate_mixin.py index 856ca76c..920b7095 100644 --- a/mpt_api_client/resources/commerce/mixins/terminate_mixin.py +++ b/mpt_api_client/resources/commerce/mixins/terminate_mixin.py @@ -14,7 +14,7 @@ def terminate(self, resource_id: str, resource_data: ResourceData | None = None) Returns: Terminated resource. """ - return self._resource_action(resource_id, "POST", "terminate", json=resource_data) # type: ignore[attr-defined, no-any-return] + return self._resource(resource_id).post("terminate", json=resource_data) # type: ignore[attr-defined, no-any-return] class AsyncTerminateMixin[Model]: @@ -30,4 +30,4 @@ async def terminate(self, resource_id: str, resource_data: ResourceData | None = Returns: Terminated resource. """ - return await self._resource_action(resource_id, "POST", "terminate", json=resource_data) # type: ignore[attr-defined, no-any-return] + return await self._resource(resource_id).post("terminate", json=resource_data) # type: ignore[attr-defined, no-any-return] diff --git a/mpt_api_client/resources/commerce/orders.py b/mpt_api_client/resources/commerce/orders.py index 3203edc9..5f853393 100644 --- a/mpt_api_client/resources/commerce/orders.py +++ b/mpt_api_client/resources/commerce/orders.py @@ -117,7 +117,7 @@ def validate(self, resource_id: str, resource_data: ResourceData | None = None) resource_id: Order resource ID resource_data: Order data will be updated """ - return self._resource_action(resource_id, "POST", "validate", json=resource_data) + return self._resource(resource_id).post("validate", json=resource_data) def process(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: """Switch order to process state. @@ -126,7 +126,7 @@ def process(self, resource_id: str, resource_data: ResourceData | None = None) - resource_id: Order resource ID resource_data: Order data will be updated """ - return self._resource_action(resource_id, "POST", "process", json=resource_data) + return self._resource(resource_id).post("process", json=resource_data) def query(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: """Switch order to query state. @@ -135,7 +135,7 @@ def query(self, resource_id: str, resource_data: ResourceData | None = None) -> resource_id: Order resource ID resource_data: Order data will be updated """ - return self._resource_action(resource_id, "POST", "query", json=resource_data) + return self._resource(resource_id).post("query", json=resource_data) def complete(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: """Switch order to complete state. @@ -144,7 +144,7 @@ def complete(self, resource_id: str, resource_data: ResourceData | None = None) resource_id: Order resource ID resource_data: Order data will be updated """ - return self._resource_action(resource_id, "POST", "complete", json=resource_data) + return self._resource(resource_id).post("complete", json=resource_data) def fail(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: """Switch order to fail state. @@ -153,7 +153,7 @@ def fail(self, resource_id: str, resource_data: ResourceData | None = None) -> O resource_id: Order resource ID resource_data: Order data will be updated """ - return self._resource_action(resource_id, "POST", "fail", json=resource_data) + return self._resource(resource_id).post("fail", json=resource_data) def notify(self, resource_id: str, user: ResourceData) -> None: """Notify user about order status. @@ -162,7 +162,7 @@ def notify(self, resource_id: str, user: ResourceData) -> None: resource_id: Order resource ID user: User data """ - self._resource_do_request(resource_id, "POST", "notify", json=user) + self._resource(resource_id).do_request("POST", "notify", json=user) def quote(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: """Quote the order. @@ -174,7 +174,7 @@ def quote(self, resource_id: str, resource_data: ResourceData | None = None) -> Returns: Quoted order resource. """ - return self._resource_action(resource_id, "POST", "quote", json=resource_data) + return self._resource(resource_id).post("quote", json=resource_data) def subscriptions(self, order_id: str) -> OrderSubscriptionsService: """Get the subscription service for the given Order id. @@ -225,7 +225,7 @@ async def validate(self, resource_id: str, resource_data: ResourceData | None = Returns: Updated order resource """ - return await self._resource_action(resource_id, "POST", "validate", json=resource_data) + return await self._resource(resource_id).post("validate", json=resource_data) async def process(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: """Switch order to process state. @@ -237,7 +237,7 @@ async def process(self, resource_id: str, resource_data: ResourceData | None = N Returns: Updated order resource """ - return await self._resource_action(resource_id, "POST", "process", json=resource_data) + return await self._resource(resource_id).post("process", json=resource_data) async def query(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: """Switch order to query state. @@ -249,7 +249,7 @@ async def query(self, resource_id: str, resource_data: ResourceData | None = Non Returns: Updated order resource """ - return await self._resource_action(resource_id, "POST", "query", json=resource_data) + return await self._resource(resource_id).post("query", json=resource_data) async def complete(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: """Switch order to complete state. @@ -261,7 +261,7 @@ async def complete(self, resource_id: str, resource_data: ResourceData | None = Returns: Updated order resource """ - return await self._resource_action(resource_id, "POST", "complete", json=resource_data) + return await self._resource(resource_id).post("complete", json=resource_data) async def fail(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: """Switch order to fail state. @@ -273,7 +273,7 @@ async def fail(self, resource_id: str, resource_data: ResourceData | None = None Returns: Updated order resource """ - return await self._resource_action(resource_id, "POST", "fail", json=resource_data) + return await self._resource(resource_id).post("fail", json=resource_data) async def notify(self, resource_id: str, resource_data: ResourceData) -> None: """Notify user about order status. @@ -282,7 +282,7 @@ async def notify(self, resource_id: str, resource_data: ResourceData) -> None: resource_id: Order resource ID resource_data: User data to notify """ - await self._resource_do_request(resource_id, "POST", "notify", json=resource_data) + await self._resource(resource_id).do_request("POST", "notify", json=resource_data) async def quote(self, resource_id: str, resource_data: ResourceData | None = None) -> Order: """Quote the order. @@ -294,7 +294,7 @@ async def quote(self, resource_id: str, resource_data: ResourceData | None = Non Returns: Quoted order resource. """ - return await self._resource_action(resource_id, "POST", "quote", json=resource_data) + return await self._resource(resource_id).post("quote", json=resource_data) def subscriptions(self, order_id: str) -> AsyncOrderSubscriptionsService: """Get the subscription service for the given Order id. diff --git a/mpt_api_client/resources/helpdesk/cases.py b/mpt_api_client/resources/helpdesk/cases.py index 1b150dca..0c94cf0c 100644 --- a/mpt_api_client/resources/helpdesk/cases.py +++ b/mpt_api_client/resources/helpdesk/cases.py @@ -36,15 +36,15 @@ class CasesService( def query(self, resource_id: str, resource_data: ResourceData | None = None) -> Case: """Switch case to query state.""" - return self._resource_action(resource_id, "POST", "query", json=resource_data) + return self._resource(resource_id).post("query", json=resource_data) def process(self, resource_id: str, resource_data: ResourceData | None = None) -> Case: """Switch case to process state.""" - return self._resource_action(resource_id, "POST", "process", json=resource_data) + return self._resource(resource_id).post("process", json=resource_data) def complete(self, resource_id: str, resource_data: ResourceData | None = None) -> Case: """Switch case to complete state.""" - return self._resource_action(resource_id, "POST", "complete", json=resource_data) + return self._resource(resource_id).post("complete", json=resource_data) class AsyncCasesService( @@ -59,12 +59,12 @@ class AsyncCasesService( async def query(self, resource_id: str, resource_data: ResourceData | None = None) -> Case: """Switch case to query state.""" - return await self._resource_action(resource_id, "POST", "query", json=resource_data) + return await self._resource(resource_id).post("query", json=resource_data) async def process(self, resource_id: str, resource_data: ResourceData | None = None) -> Case: """Switch case to process state.""" - return await self._resource_action(resource_id, "POST", "process", json=resource_data) + return await self._resource(resource_id).post("process", json=resource_data) async def complete(self, resource_id: str, resource_data: ResourceData | None = None) -> Case: """Switch case to complete state.""" - return await self._resource_action(resource_id, "POST", "complete", json=resource_data) + return await self._resource(resource_id).post("complete", json=resource_data) diff --git a/mpt_api_client/resources/helpdesk/chat_answers.py b/mpt_api_client/resources/helpdesk/chat_answers.py index 7f059359..eb187a6d 100644 --- a/mpt_api_client/resources/helpdesk/chat_answers.py +++ b/mpt_api_client/resources/helpdesk/chat_answers.py @@ -31,19 +31,19 @@ class ChatAnswersService( def submit(self, resource_id: str, resource_data: ResourceData | None = None) -> ChatAnswer: """Switch answer to submitted state.""" - return self._resource_action(resource_id, "POST", "submit", json=resource_data) + return self._resource(resource_id).post("submit", json=resource_data) def accept(self, resource_id: str, resource_data: ResourceData | None = None) -> ChatAnswer: """Switch answer to accepted state.""" - return self._resource_action(resource_id, "POST", "accept", json=resource_data) + return self._resource(resource_id).post("accept", json=resource_data) def query(self, resource_id: str, resource_data: ResourceData | None = None) -> ChatAnswer: """Switch answer to query state.""" - return self._resource_action(resource_id, "POST", "query", json=resource_data) + return self._resource(resource_id).post("query", json=resource_data) def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> ChatAnswer: """Validate answer.""" - return self._resource_action(resource_id, "POST", "validate", json=resource_data) + return self._resource(resource_id).post("validate", json=resource_data) def parameters(self, answer_id: str) -> ChatAnswerParametersService: # noqa: WPS110 """Return chat answer parameters service.""" @@ -73,7 +73,7 @@ async def submit( resource_data: ResourceData | None = None, ) -> ChatAnswer: """Switch answer to submitted state.""" - return await self._resource_action(resource_id, "POST", "submit", json=resource_data) + return await self._resource(resource_id).post("submit", json=resource_data) async def accept( self, @@ -81,7 +81,7 @@ async def accept( resource_data: ResourceData | None = None, ) -> ChatAnswer: """Switch answer to accepted state.""" - return await self._resource_action(resource_id, "POST", "accept", json=resource_data) + return await self._resource(resource_id).post("accept", json=resource_data) async def query( self, @@ -89,7 +89,7 @@ async def query( resource_data: ResourceData | None = None, ) -> ChatAnswer: """Switch answer to query state.""" - return await self._resource_action(resource_id, "POST", "query", json=resource_data) + return await self._resource(resource_id).post("query", json=resource_data) async def validate( self, @@ -97,7 +97,7 @@ async def validate( resource_data: ResourceData | None = None, ) -> ChatAnswer: """Validate answer.""" - return await self._resource_action(resource_id, "POST", "validate", json=resource_data) + return await self._resource(resource_id).post("validate", json=resource_data) def parameters(self, answer_id: str) -> AsyncChatAnswerParametersService: # noqa: WPS110 """Return async chat answer parameters service.""" diff --git a/mpt_api_client/resources/helpdesk/forms.py b/mpt_api_client/resources/helpdesk/forms.py index f1afa033..15d2e188 100644 --- a/mpt_api_client/resources/helpdesk/forms.py +++ b/mpt_api_client/resources/helpdesk/forms.py @@ -30,11 +30,11 @@ class FormsService( def publish(self, resource_id: str, resource_data: ResourceData | None = None) -> Form: """Switch form to published state.""" - return self._resource_action(resource_id, "POST", "publish", json=resource_data) + return self._resource(resource_id).post("publish", json=resource_data) def unpublish(self, resource_id: str, resource_data: ResourceData | None = None) -> Form: """Switch form to unpublished state.""" - return self._resource_action(resource_id, "POST", "unpublish", json=resource_data) + return self._resource(resource_id).post("unpublish", json=resource_data) class AsyncFormsService( @@ -47,8 +47,8 @@ class AsyncFormsService( async def publish(self, resource_id: str, resource_data: ResourceData | None = None) -> Form: """Switch form to published state.""" - return await self._resource_action(resource_id, "POST", "publish", json=resource_data) + return await self._resource(resource_id).post("publish", json=resource_data) async def unpublish(self, resource_id: str, resource_data: ResourceData | None = None) -> Form: """Switch form to unpublished state.""" - return await self._resource_action(resource_id, "POST", "unpublish", json=resource_data) + return await self._resource(resource_id).post("unpublish", json=resource_data) diff --git a/mpt_api_client/resources/helpdesk/queues.py b/mpt_api_client/resources/helpdesk/queues.py index 888d0d46..7586b974 100644 --- a/mpt_api_client/resources/helpdesk/queues.py +++ b/mpt_api_client/resources/helpdesk/queues.py @@ -30,11 +30,11 @@ class QueuesService( def activate(self, resource_id: str, resource_data: ResourceData | None = None) -> Queue: """Switch queue to active state.""" - return self._resource_action(resource_id, "POST", "activate", json=resource_data) + return self._resource(resource_id).post("activate", json=resource_data) def disable(self, resource_id: str, resource_data: ResourceData | None = None) -> Queue: """Switch queue to disabled state.""" - return self._resource_action(resource_id, "POST", "disable", json=resource_data) + return self._resource(resource_id).post("disable", json=resource_data) class AsyncQueuesService( @@ -47,8 +47,8 @@ class AsyncQueuesService( async def activate(self, resource_id: str, resource_data: ResourceData | None = None) -> Queue: """Switch queue to active state.""" - return await self._resource_action(resource_id, "POST", "activate", json=resource_data) + return await self._resource(resource_id).post("activate", json=resource_data) async def disable(self, resource_id: str, resource_data: ResourceData | None = None) -> Queue: """Switch queue to disabled state.""" - return await self._resource_action(resource_id, "POST", "disable", json=resource_data) + return await self._resource(resource_id).post("disable", json=resource_data) diff --git a/mpt_api_client/resources/notifications/categories.py b/mpt_api_client/resources/notifications/categories.py index 3d9f1482..e69f6235 100644 --- a/mpt_api_client/resources/notifications/categories.py +++ b/mpt_api_client/resources/notifications/categories.py @@ -35,7 +35,7 @@ def publish(self, resource_id: str, resource_data: ResourceData | None = None) - resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action(resource_id, "POST", "publish", json=resource_data) + return self._resource(resource_id).post("publish", json=resource_data) def unpublish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Update state to Unpublished. @@ -44,7 +44,7 @@ def unpublish(self, resource_id: str, resource_data: ResourceData | None = None) resource_id: Resource ID resource_data: Resource data will be updated """ - return self._resource_action(resource_id, "POST", "unpublish", json=resource_data) + return self._resource(resource_id).post("unpublish", json=resource_data) class AsyncCategoriesService( @@ -62,7 +62,7 @@ async def publish(self, resource_id: str, resource_data: ResourceData | None = N resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action(resource_id, "POST", "publish", json=resource_data) + return await self._resource(resource_id).post("publish", json=resource_data) async def unpublish(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Update state to Unpublished. @@ -71,4 +71,4 @@ async def unpublish(self, resource_id: str, resource_data: ResourceData | None = resource_id: Resource ID resource_data: Resource data will be updated """ - return await self._resource_action(resource_id, "POST", "unpublish", json=resource_data) + return await self._resource(resource_id).post("unpublish", json=resource_data) diff --git a/mpt_api_client/resources/notifications/contacts.py b/mpt_api_client/resources/notifications/contacts.py index 7a4a8d9f..4e43bb5c 100644 --- a/mpt_api_client/resources/notifications/contacts.py +++ b/mpt_api_client/resources/notifications/contacts.py @@ -30,11 +30,11 @@ class ContactsService( def block(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Block a contact.""" - return self._resource_action(resource_id, "POST", "block", json=resource_data) + return self._resource(resource_id).post("block", json=resource_data) def unblock(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Unblock a contact.""" - return self._resource_action(resource_id, "POST", "unblock", json=resource_data) + return self._resource(resource_id).post("unblock", json=resource_data) class AsyncContactsService( @@ -47,8 +47,8 @@ class AsyncContactsService( async def block(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Block a contact.""" - return await self._resource_action(resource_id, "POST", "block", json=resource_data) + return await self._resource(resource_id).post("block", json=resource_data) async def unblock(self, resource_id: str, resource_data: ResourceData | None = None) -> Model: """Unblock a contact.""" - return await self._resource_action(resource_id, "POST", "unblock", json=resource_data) + return await self._resource(resource_id).post("unblock", json=resource_data) diff --git a/pyproject.toml b/pyproject.toml index 47a4c53b..14d74458 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -135,6 +135,7 @@ per-file-ignores = [ "tests/e2e/commerce/order/asset/*.py: WPS211 WPS202", "tests/e2e/commerce/subscription/*.py: WPS202", "tests/unit/http/test_async_service.py: WPS204 WPS202", + "tests/unit/http/test_resource_accessor.py: WPS204 WPS202 WPS210 WPS219", "tests/unit/http/test_service.py: WPS204 WPS202", "tests/unit/http/mixins/*: WPS204 WPS202 WPS210", "tests/unit/resources/accounts/*.py: WPS204 WPS202 WPS210", diff --git a/tests/unit/http/test_resource_accessor.py b/tests/unit/http/test_resource_accessor.py new file mode 100644 index 00000000..383eecd6 --- /dev/null +++ b/tests/unit/http/test_resource_accessor.py @@ -0,0 +1,242 @@ +import json + +import httpx +import pytest +import respx + +from mpt_api_client.http.resource_accessor import AsyncResourceAccessor, ResourceAccessor +from tests.unit.conftest import API_URL, DummyModel + +RESOURCE_URL = "/api/v1/test/RES-123" +FULL_URL = f"{API_URL}{RESOURCE_URL}" + + +@pytest.mark.parametrize( + ("action", "expected_url"), + [ + (None, FULL_URL), + ("complete", f"{FULL_URL}/complete"), + ], + ids=["without-action", "with-action"], +) +def test_do_request(http_client, action, expected_url): + response_data = {"id": "RES-123"} + with respx.mock: + mock_route = respx.post(expected_url).mock( + return_value=httpx.Response(httpx.codes.OK, json=response_data) + ) + accessor = ResourceAccessor(http_client, RESOURCE_URL, DummyModel) + + result = accessor.do_request("POST", action) + + assert result.json() == response_data + assert mock_route.call_count == 1 + + +def test_do_request_forwards_json(http_client): + payload = {"name": "Updated"} + response_data = {"id": "RES-123", "name": "Updated"} + with respx.mock: + mock_route = respx.put(FULL_URL).mock( + return_value=httpx.Response(httpx.codes.OK, json=response_data) + ) + accessor = ResourceAccessor(http_client, RESOURCE_URL, DummyModel) + + result = accessor.do_request("PUT", json=payload) + + assert result.json() == response_data + assert json.loads(mock_route.calls[0].request.content.decode()) == payload + + +def test_do_request_forwards_query_params(http_client): + response_data = {"id": "RES-123"} + with respx.mock: + mock_route = respx.get(FULL_URL, params={"select": "id"}).mock( + return_value=httpx.Response(httpx.codes.OK, json=response_data) + ) + accessor = ResourceAccessor(http_client, RESOURCE_URL, DummyModel) + + result = accessor.do_request("GET", query_params={"select": "id"}) + + assert result.json() == response_data + assert mock_route.call_count == 1 + + +def test_do_request_forwards_headers(http_client): + response_data = {"id": "RES-123"} + with respx.mock: + mock_route = respx.get(FULL_URL).mock( + return_value=httpx.Response(httpx.codes.OK, json=response_data) + ) + accessor = ResourceAccessor(http_client, RESOURCE_URL, DummyModel) + + result = accessor.do_request("GET", headers={"X-Custom": "value"}) + + assert result.json() == response_data + assert mock_route.calls[0].request.headers["X-Custom"] == "value" + + +@pytest.mark.parametrize( + ("action", "expected_url"), + [ + (None, FULL_URL), + ("status", f"{FULL_URL}/status"), + ], + ids=["without-action", "with-action"], +) +def test_get(http_client, action, expected_url): + response_data = {"id": "RES-123", "name": "Test Resource"} + ok_response = httpx.Response(httpx.codes.OK, json=response_data) + with respx.mock: + mock_route = respx.get(expected_url).mock(return_value=ok_response) + accessor = ResourceAccessor(http_client, RESOURCE_URL, DummyModel) + + result = accessor.get(action) + + assert result.to_dict() == response_data + assert mock_route.calls[0].request.headers["Accept"] == "application/json" + + +@pytest.mark.parametrize( + ("action", "expected_url"), + [ + (None, FULL_URL), + ("complete", f"{FULL_URL}/complete"), + ], + ids=["without-action", "with-action"], +) +def test_post(http_client, action, expected_url): + payload = {"name": "New"} + response_data = {"id": "RES-123", "name": "New"} + with respx.mock: + mock_route = respx.post(expected_url).mock( + return_value=httpx.Response(httpx.codes.OK, json=response_data) + ) + accessor = ResourceAccessor(http_client, RESOURCE_URL, DummyModel) + + result = accessor.post(action, json=payload) + + assert result.to_dict() == response_data + assert json.loads(mock_route.calls[0].request.content.decode()) == payload + + +def test_put(http_client): + payload = {"name": "Updated"} + response_data = {"id": "RES-123", "name": "Updated"} + with respx.mock: + mock_route = respx.put(FULL_URL).mock( + return_value=httpx.Response(httpx.codes.OK, json=response_data) + ) + accessor = ResourceAccessor(http_client, RESOURCE_URL, DummyModel) + + result = accessor.put(json=payload) + + assert result.to_dict() == response_data + assert json.loads(mock_route.calls[0].request.content.decode()) == payload + + +def test_delete(http_client): # noqa: AAA01 + with respx.mock: + mock_route = respx.delete(FULL_URL).mock( + return_value=httpx.Response(httpx.codes.NO_CONTENT) + ) + accessor = ResourceAccessor(http_client, RESOURCE_URL, DummyModel) + + accessor.delete() + + assert mock_route.call_count == 1 + assert mock_route.calls[0].request.method == "DELETE" + + +@pytest.mark.parametrize( + ("action", "expected_url"), + [ + (None, FULL_URL), + ("complete", f"{FULL_URL}/complete"), + ], + ids=["without-action", "with-action"], +) +async def test_async_do_request(async_http_client, action, expected_url): + response_data = {"id": "RES-123"} + with respx.mock: + mock_route = respx.post(expected_url).mock( + return_value=httpx.Response(httpx.codes.OK, json=response_data) + ) + accessor = AsyncResourceAccessor(async_http_client, RESOURCE_URL, DummyModel) + + result = await accessor.do_request("POST", action) + + assert result.json() == response_data + assert mock_route.call_count == 1 + + +@pytest.mark.parametrize( + ("action", "expected_url"), + [ + (None, FULL_URL), + ("status", f"{FULL_URL}/status"), + ], + ids=["without-action", "with-action"], +) +async def test_async_get(async_http_client, action, expected_url): + response_data = {"id": "RES-123", "name": "Test Resource"} + ok_response = httpx.Response(httpx.codes.OK, json=response_data) + with respx.mock: + mock_route = respx.get(expected_url).mock(return_value=ok_response) + accessor = AsyncResourceAccessor(async_http_client, RESOURCE_URL, DummyModel) + + result = await accessor.get(action) + + assert result.to_dict() == response_data + assert mock_route.calls[0].request.headers["Accept"] == "application/json" + + +@pytest.mark.parametrize( + ("action", "expected_url"), + [ + (None, FULL_URL), + ("complete", f"{FULL_URL}/complete"), + ], + ids=["without-action", "with-action"], +) +async def test_async_post(async_http_client, action, expected_url): + payload = {"name": "New"} + response_data = {"id": "RES-123", "name": "New"} + with respx.mock: + mock_route = respx.post(expected_url).mock( + return_value=httpx.Response(httpx.codes.OK, json=response_data) + ) + accessor = AsyncResourceAccessor(async_http_client, RESOURCE_URL, DummyModel) + + result = await accessor.post(action, json=payload) + + assert result.to_dict() == response_data + assert json.loads(mock_route.calls[0].request.content.decode()) == payload + + +async def test_async_put(async_http_client): + payload = {"name": "Updated"} + response_data = {"id": "RES-123", "name": "Updated"} + with respx.mock: + mock_route = respx.put(FULL_URL).mock( + return_value=httpx.Response(httpx.codes.OK, json=response_data) + ) + accessor = AsyncResourceAccessor(async_http_client, RESOURCE_URL, DummyModel) + + result = await accessor.put(json=payload) + + assert result.to_dict() == response_data + assert json.loads(mock_route.calls[0].request.content.decode()) == payload + + +async def test_async_delete(async_http_client): # noqa: AAA01 + with respx.mock: + mock_route = respx.delete(FULL_URL).mock( + return_value=httpx.Response(httpx.codes.NO_CONTENT) + ) + accessor = AsyncResourceAccessor(async_http_client, RESOURCE_URL, DummyModel) + + await accessor.delete() + + assert mock_route.call_count == 1 + assert mock_route.calls[0].request.method == "DELETE" diff --git a/tests/unit/http/test_url_utils.py b/tests/unit/http/test_url_utils.py new file mode 100644 index 00000000..27dd4551 --- /dev/null +++ b/tests/unit/http/test_url_utils.py @@ -0,0 +1,86 @@ +import pytest + +from mpt_api_client.http.url_utils import join_url_path + + +def test_simple_segment(): + result = join_url_path("/api/v1/orders", "ORD-001") + + assert result == "/api/v1/orders/ORD-001" + + +def test_multiple_segments(): + result = join_url_path("/api/v1/orders", "ORD-001", "complete") + + assert result == "/api/v1/orders/ORD-001/complete" + + +def test_base_with_trailing_slash(): + result = join_url_path("/api/v1/orders/", "ORD-001") + + assert result == "/api/v1/orders/ORD-001" + + +def test_segment_with_leading_slash(): + result = join_url_path("/api/v1/orders", "/ORD-001") + + assert result == "/api/v1/orders/ORD-001" + + +def test_segment_with_surrounding_slashes(): + result = join_url_path("/api/v1/orders/", "/ORD-001/") + + assert result == "/api/v1/orders/ORD-001" + + +def test_absolute_segment(): + result = join_url_path("/api/v1/orders", "https://evil.com") + + assert result == "/api/v1/orders/https://evil.com" + + +def test_dotdot_segment_is_literal(): + result = join_url_path("/api/v1/orders", "../other") + + assert result == "/api/v1/orders/../other" + + +def test_no_segments(): + result = join_url_path("/api/v1/orders") + + assert result == "/api/v1/orders" + + +def test_empty_segment_is_skipped(): + result = join_url_path("/api/v1/orders", "", "upload") + + assert result == "/api/v1/orders/upload" + + +def test_none_segment_is_skipped(): + result = join_url_path("/api/v1/orders", None, "upload") # type: ignore[arg-type] + + assert result == "/api/v1/orders/upload" + + +@pytest.mark.parametrize( + ("base", "segments", "expected"), + [ + ("/public/v1/billing/journals", ("JRN-123",), "/public/v1/billing/journals/JRN-123"), + ( + "/public/v1/billing/journals", + ("JRN-123", "upload"), + "/public/v1/billing/journals/JRN-123/upload", + ), + ( + "/public/v1/billing/custom-ledgers", + ("CL-456", "upload"), + "/public/v1/billing/custom-ledgers/CL-456/upload", + ), + ], + ids=["resource-id", "resource-id-and-action", "custom-ledger-upload"], +) +def test_real_world_paths(base, segments, expected): + result = join_url_path(base, *segments) + + assert result == expected