diff --git a/mollie/api/objects/balance.py b/mollie/api/objects/balance.py index 501908d8..a2ea543a 100644 --- a/mollie/api/objects/balance.py +++ b/mollie/api/objects/balance.py @@ -2,16 +2,10 @@ from .balance_report import BalanceReport from .base import ObjectBase -from .list import ObjectList +from .list import PaginationList class Balance(ObjectBase): - @classmethod - def get_resource_class(cls, client): - from ..resources import Balances - - return Balances(client) - @property def resource(self): return self._get_property("resource") @@ -65,7 +59,7 @@ def get_report(self, **params: Any) -> BalanceReport: return BalanceReports(self.client, self).get_report(params=params) - def get_transactions(self, **params: Any) -> ObjectList: + def get_transactions(self, **params: Any) -> PaginationList: from ..resources import BalanceTransactions return BalanceTransactions(self.client, self).list(params=params) diff --git a/mollie/api/objects/balance_report.py b/mollie/api/objects/balance_report.py index 5b2adf93..dd88147b 100644 --- a/mollie/api/objects/balance_report.py +++ b/mollie/api/objects/balance_report.py @@ -1,20 +1,7 @@ -from typing import TYPE_CHECKING, Any - from .base import ObjectBase -if TYPE_CHECKING: - from ..client import Client - from ..resources import BalanceReports - class BalanceReport(ObjectBase): - @classmethod - def get_resource_class(cls, client: "Client", **kwargs: Any) -> "BalanceReports": - from ..resources import BalanceReports - - balance = kwargs["balance"] - return BalanceReports(client, balance) - @property def resource(self): return self._get_property("resource") diff --git a/mollie/api/objects/balance_transaction.py b/mollie/api/objects/balance_transaction.py index e3730d44..3af09447 100644 --- a/mollie/api/objects/balance_transaction.py +++ b/mollie/api/objects/balance_transaction.py @@ -1,20 +1,7 @@ -from typing import TYPE_CHECKING, Any - from .base import ObjectBase -if TYPE_CHECKING: - from ..client import Client - from ..resources import BalanceTransactions - class BalanceTransaction(ObjectBase): - @classmethod - def get_resource_class(cls, client: "Client", **kwargs: Any) -> "BalanceTransactions": - from ..resources import BalanceTransactions - - balance = kwargs["balance"] - return BalanceTransactions(client, balance) - @classmethod def get_object_name(cls): # Overwrite get_object_name since BalanceTransactions gets returned by Mollie as balance_transactions. diff --git a/mollie/api/objects/base.py b/mollie/api/objects/base.py index 856d6615..ac8493a0 100644 --- a/mollie/api/objects/base.py +++ b/mollie/api/objects/base.py @@ -1,10 +1,5 @@ -from typing import TYPE_CHECKING, Any - from ..error import EmbedNotFound -if TYPE_CHECKING: - from ..client import Client - class ObjectBase(dict): def __init__(self, data, client): @@ -45,7 +40,3 @@ def get_embedded(self, name: str) -> dict: def get_object_name(cls): name = cls.__name__.lower() return f"{name}s" - - @classmethod - def get_resource_class(cls, client: "Client", **kwargs: Any) -> Any: - raise NotImplementedError # pragma: no cover diff --git a/mollie/api/objects/capture.py b/mollie/api/objects/capture.py index 331aeafb..d040f405 100644 --- a/mollie/api/objects/capture.py +++ b/mollie/api/objects/capture.py @@ -1,20 +1,7 @@ -from typing import TYPE_CHECKING, Any - from .base import ObjectBase -if TYPE_CHECKING: - from ..client import Client - from ..resources import PaymentCaptures - class Capture(ObjectBase): - @classmethod - def get_resource_class(cls, client: "Client", **kwargs: Any) -> "PaymentCaptures": - from ..resources import PaymentCaptures - - payment = kwargs["payment"] - return PaymentCaptures(client, payment) - @property def id(self): return self._get_property("id") diff --git a/mollie/api/objects/chargeback.py b/mollie/api/objects/chargeback.py index 40f1d6e9..52643975 100644 --- a/mollie/api/objects/chargeback.py +++ b/mollie/api/objects/chargeback.py @@ -4,12 +4,6 @@ class Chargeback(ObjectBase): - @classmethod - def get_resource_class(cls, client): - from ..resources import Chargebacks - - return Chargebacks(client) - @property def id(self): return self._get_property("id") diff --git a/mollie/api/objects/client.py b/mollie/api/objects/client.py index e9908eb8..7e7928f0 100644 --- a/mollie/api/objects/client.py +++ b/mollie/api/objects/client.py @@ -2,12 +2,6 @@ class Client(ObjectBase): - @classmethod - def get_resource_class(cls, client): - from ..resources import Clients - - return Clients(client) - # Documented properties @property diff --git a/mollie/api/objects/customer.py b/mollie/api/objects/customer.py index 2a52876b..fe3602fc 100644 --- a/mollie/api/objects/customer.py +++ b/mollie/api/objects/customer.py @@ -2,12 +2,6 @@ class Customer(ObjectBase): - @classmethod - def get_resource_class(cls, client): - from ..resources import Customers - - return Customers(client) - @property def id(self): return self._get_property("id") diff --git a/mollie/api/objects/invoice.py b/mollie/api/objects/invoice.py index e01f9f66..4f6d2070 100644 --- a/mollie/api/objects/invoice.py +++ b/mollie/api/objects/invoice.py @@ -2,12 +2,6 @@ class Invoice(ObjectBase): - @classmethod - def get_resource_class(cls, client): - from ..resources import Invoices - - return Invoices(client) - @property def id(self): return self._get_property("id") diff --git a/mollie/api/objects/list.py b/mollie/api/objects/list.py index a1919553..45598a16 100644 --- a/mollie/api/objects/list.py +++ b/mollie/api/objects/list.py @@ -1,29 +1,16 @@ -from .base import ObjectBase - +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any, Optional, Type -class UnknownObject(ObjectBase): - """Mock object for empty lists.""" +from .base import ObjectBase - @classmethod - def get_object_name(cls): - return "unknown" +if TYPE_CHECKING: + from mollie.api.client import Client + from mollie.api.resources.base import ResourceBase -class ObjectList(ObjectBase): +class ListBase(ObjectBase, ABC): current = None - def __init__(self, result, object_type, client=None): - # If an empty dataset was injected, we mock the structure that the remainder of the clas expects. - # TODO: it would be better if the ObjectList was initiated with a list of results, rather than with - # the full datastructure as it is now, so we can remove all this mucking around with fake data, - # mocked result objects, and loads of lengthy accessor workarounds everywhere in the ObjectList. - if result == {}: - result = {"_embedded": {"unknown": []}, "count": 0} - object_type = UnknownObject - - super().__init__(result, client) - self.object_type = object_type - def __len__(self): """Return the count field.""" return self.count @@ -66,7 +53,7 @@ def __getitem__(self, key): }, "count": len(sliced_data), } - return ObjectList(sliced_result, self.object_type, self.client) + return self.new(sliced_result) return super().__getitem__(key) @@ -84,16 +71,101 @@ def has_previous(self): """Return True if the ObjectList contains an url for the previous set.""" return self._get_link("previous") is not None + @abstractmethod def get_next(self): - """Return the next set of objects in an ObjectList.""" + ... + + @abstractmethod + def get_previous(self): + ... + + @property + @abstractmethod + def object_type(self): + ... + + @abstractmethod + def new(self, result): + ... + + +class PaginationList(ListBase): + """ + Pagination lists are used to return a paginated list of Objects. + + You can use the `has_next` and `get_next` methods to get the next page of result data from the API. + The `has_previous` and `get_previous` methods return the previous page. + """ + + _parent: "ResourceBase" + + def __init__(self, result: Any, parent: "ResourceBase", client: "Client"): + # If an empty dataset was injected, we mock the structure that the remainder of the clas expects. + # TODO: it would be better if the ObjectList was initiated with a list of results, rather than with + # the full datastructure as it is now, so we can remove all this mucking around with fake data, + # mocked result objects, and loads of lengthy accessor workarounds everywhere in the ObjectList. + self._parent = parent + + if result == {}: + result = {"_embedded": {f"{self._parent.object_type.get_object_name()}": []}, "count": 0} + + super().__init__(result, client) + + def get_next(self): + """Return the next set of objects in the paginated list.""" url = self._get_link("next") - resource = self.object_type.get_resource_class(self.client) - resp = resource.perform_api_call(resource.REST_READ, url) - return ObjectList(resp, self.object_type, self.client) + if url is None: + return None + resp = self._parent.perform_api_call(self._parent.REST_READ, url) + return PaginationList(resp, self._parent, self.client) def get_previous(self): - """Return the previous set of objects in an ObjectList.""" + """Return the previous set of objects in the paginated list.""" url = self._get_link("previous") - resource = self.object_type.get_resource_class(self.client) - resp = resource.perform_api_call(resource.REST_READ, url) - return ObjectList(resp, self.object_type, self.client) + if url is None: + return None + resp = self._parent.perform_api_call(self._parent.REST_READ, url) + return PaginationList(resp, self._parent, self.client) + + @property + def object_type(self): + return self._parent.object_type + + def new(self, result): + return PaginationList(result, self._parent, self.client) + + +class ObjectList(ListBase): + """ + Object lists are used to return an embedded list on an object. + + It works to similar to PaginationList, but has no pagination (as all data is already embedded). + """ + + _object_type: Type[ObjectBase] + + def __init__(self, result: Any, object_type: Type[ObjectBase], client: Optional["Client"] = None): + # If an empty dataset was injected, we mock the structure that the remainder of the clas expects. + # TODO: it would be better if the ObjectList was initiated with a list of results, rather than with + # the full datastructure as it is now, so we can remove all this mucking around with fake data, + # mocked result objects, and loads of lengthy accessor workarounds everywhere in the ObjectList. + self._object_type = object_type + + if result == {}: + result = {"_embedded": {f"{self._object_type.get_object_name()}": []}, "count": 0} + + super().__init__(result, client) + + def get_next(self): + """Return the next set of objects in an ObjectList.""" + return None + + def get_previous(self): + return None + + @property + def object_type(self): + return self._object_type + + def new(self, result): + return ObjectList(result, self._object_type, self.client) diff --git a/mollie/api/objects/mandate.py b/mollie/api/objects/mandate.py index 87405565..0f5f3d62 100644 --- a/mollie/api/objects/mandate.py +++ b/mollie/api/objects/mandate.py @@ -1,20 +1,7 @@ -from typing import TYPE_CHECKING, Any - from .base import ObjectBase -if TYPE_CHECKING: - from ..client import Client - from ..resources import CustomerMandates - class Mandate(ObjectBase): - @classmethod - def get_resource_class(cls, client: "Client", **kwargs: Any) -> "CustomerMandates": - from ..resources import CustomerMandates - - customer = kwargs["customer"] - return CustomerMandates(client, customer) - STATUS_PENDING = "pending" STATUS_VALID = "valid" STATUS_INVALID = "invalid" diff --git a/mollie/api/objects/method.py b/mollie/api/objects/method.py index 1ef9095b..c873c9a7 100644 --- a/mollie/api/objects/method.py +++ b/mollie/api/objects/method.py @@ -4,12 +4,6 @@ class Method(ObjectBase): - @classmethod - def get_resource_class(cls, client): - from ..resources import Methods - - return Methods(client) - # Payment methods for Payments and Orders APPLEPAY = "applepay" BANCONTACT = "bancontact" diff --git a/mollie/api/objects/onboarding.py b/mollie/api/objects/onboarding.py index da9f5646..2db7febe 100644 --- a/mollie/api/objects/onboarding.py +++ b/mollie/api/objects/onboarding.py @@ -2,12 +2,6 @@ class Onboarding(ObjectBase): - @classmethod - def get_resource_class(cls, client): - from ..resources import Onboarding as OnboardingResource - - return OnboardingResource(client) - STATUS_NEEDS_DATA = "needs-data" STATUS_IN_REVIEW = "in-review" # Waiting for a valid mandate. STATUS_COMPLETED = "completed" diff --git a/mollie/api/objects/order.py b/mollie/api/objects/order.py index 42497354..04cf9463 100644 --- a/mollie/api/objects/order.py +++ b/mollie/api/objects/order.py @@ -5,12 +5,6 @@ class Order(ObjectBase): def __init__(self, data, client): super().__init__(data, client) - @classmethod - def get_resource_class(cls, client): - from ..resources import Orders - - return Orders(client) - STATUS_CREATED = "created" STATUS_PAID = "paid" STATUS_AUTHORIZED = "authorized" diff --git a/mollie/api/objects/order_line.py b/mollie/api/objects/order_line.py index 086465a0..f7cf4cd9 100644 --- a/mollie/api/objects/order_line.py +++ b/mollie/api/objects/order_line.py @@ -1,20 +1,7 @@ -from typing import TYPE_CHECKING, Any - from .base import ObjectBase -if TYPE_CHECKING: - from ..client import Client - from ..resources import OrderLines - class OrderLine(ObjectBase): - @classmethod - def get_resource_class(cls, client: "Client", **kwargs: Any) -> "OrderLines": - from ..resources import OrderLines - - order = kwargs["order"] - return OrderLines(client, order) - STATUS_CREATED = "created" STATUS_AUTHORIZED = "authorized" STATUS_PAID = "paid" diff --git a/mollie/api/objects/organization.py b/mollie/api/objects/organization.py index 085e009e..4eea7d90 100644 --- a/mollie/api/objects/organization.py +++ b/mollie/api/objects/organization.py @@ -2,12 +2,6 @@ class Organization(ObjectBase): - @classmethod - def get_resource_class(cls, client): - from ..resources import Organizations - - return Organizations(client) - @property def id(self): return self._get_property("id") diff --git a/mollie/api/objects/payment.py b/mollie/api/objects/payment.py index 23bf95e0..ed361d54 100644 --- a/mollie/api/objects/payment.py +++ b/mollie/api/objects/payment.py @@ -3,12 +3,6 @@ class Payment(ObjectBase): - @classmethod - def get_resource_class(cls, client): - from ..resources import Payments - - return Payments(client) - STATUS_OPEN = "open" STATUS_PENDING = "pending" STATUS_CANCELED = "canceled" diff --git a/mollie/api/objects/payment_link.py b/mollie/api/objects/payment_link.py index 9c6327d1..94b0ca9c 100644 --- a/mollie/api/objects/payment_link.py +++ b/mollie/api/objects/payment_link.py @@ -2,12 +2,6 @@ class PaymentLink(ObjectBase): - @classmethod - def get_resource_class(cls, client): - from ..resources import PaymentLinks - - return PaymentLinks(client) - @classmethod def get_object_name(cls): return "payment_links" diff --git a/mollie/api/objects/permission.py b/mollie/api/objects/permission.py index 8015736c..a99e67ce 100644 --- a/mollie/api/objects/permission.py +++ b/mollie/api/objects/permission.py @@ -2,12 +2,6 @@ class Permission(ObjectBase): - @classmethod - def get_resource_class(cls, client): - from ..resources import Permissions - - return Permissions(client) - @property def id(self): return self._get_property("id") diff --git a/mollie/api/objects/profile.py b/mollie/api/objects/profile.py index 86a2d6d4..fffc6d5c 100644 --- a/mollie/api/objects/profile.py +++ b/mollie/api/objects/profile.py @@ -9,12 +9,6 @@ class Profile(ObjectBase): STATUS_VERIFIED = "verified" STATUS_BLOCKED = "blocked" - @classmethod - def get_resource_class(cls, client): - from ..resources import Profiles - - return Profiles(client) - @property def id(self): return self._get_property("id") diff --git a/mollie/api/objects/refund.py b/mollie/api/objects/refund.py index 56ca1e30..420249cb 100644 --- a/mollie/api/objects/refund.py +++ b/mollie/api/objects/refund.py @@ -4,12 +4,6 @@ class Refund(ObjectBase): - @classmethod - def get_resource_class(cls, client): - from ..resources import Refunds - - return Refunds(client) - STATUS_QUEUED = "queued" STATUS_PENDING = "pending" STATUS_PROCESSING = "processing" diff --git a/mollie/api/objects/settlement.py b/mollie/api/objects/settlement.py index e82859c4..1fe3b176 100644 --- a/mollie/api/objects/settlement.py +++ b/mollie/api/objects/settlement.py @@ -10,12 +10,6 @@ class Settlement(ObjectBase): STATUS_PAIDOUT = "paidout" STATUS_FAILED = "failed" - @classmethod - def get_resource_class(cls, client): - from ..resources import Settlements - - return Settlements(client) - @property def id(self): return self._get_property("id") diff --git a/mollie/api/objects/subscription.py b/mollie/api/objects/subscription.py index 2d7c91c5..02572706 100644 --- a/mollie/api/objects/subscription.py +++ b/mollie/api/objects/subscription.py @@ -5,13 +5,6 @@ class Subscription(ObjectBase): - @classmethod - def get_resource_class(cls, client, **kwargs): - from ..resources import CustomerSubscriptions - - customer = kwargs["customer"] - return CustomerSubscriptions(client, customer) - STATUS_ACTIVE = "active" STATUS_PENDING = "pending" # Waiting for a valid mandate. STATUS_CANCELED = "canceled" diff --git a/mollie/api/resources/balances.py b/mollie/api/resources/balances.py index 61438234..b342c437 100644 --- a/mollie/api/resources/balances.py +++ b/mollie/api/resources/balances.py @@ -11,6 +11,7 @@ class Balances(ResourceGetMixin, ResourceListMixin): RESOURCE_ID_PREFIX: str = "bal_" + object_type = Balance @classmethod def validate_resource_id(cls, resource_id: str, name: str = "", message: str = "") -> None: @@ -31,11 +32,6 @@ def validate_resource_id(cls, resource_id: str, name: str = "", message: str = " else: super().validate_resource_id(resource_id, message=exc_message) - def get_resource_object(self, result: dict) -> Balance: - from ..objects.balance import Balance - - return Balance(result, self.client) - def get(self, resource_id: str, **params: Any) -> Balance: self.validate_resource_id(resource_id) return super().get(resource_id, **params) @@ -43,36 +39,28 @@ def get(self, resource_id: str, **params: Any) -> Balance: class BalanceReports(ResourceBase): _balance: "Balance" + object_type = BalanceReport def __init__(self, client: "Client", balance: "Balance") -> None: self._balance = balance super().__init__(client) - def get_resource_object(self, result: dict) -> BalanceReport: - from ..objects.balance_report import BalanceReport - - return BalanceReport(result, self.client) - def get_resource_path(self) -> str: return f"balances/{self._balance.id}/report" def get_report(self, **params: Any) -> BalanceReport: path = self.get_resource_path() result = self.perform_api_call(self.REST_READ, path, params=params) - return self.get_resource_object(result) + return BalanceReport(result, self.client) class BalanceTransactions(ResourceListMixin): _balance: "Balance" + object_type = BalanceTransaction def __init__(self, client: "Client", balance: "Balance") -> None: self._balance = balance super().__init__(client) - def get_resource_object(self, result: dict) -> BalanceTransaction: - from ..objects.balance_transaction import BalanceTransaction - - return BalanceTransaction(result, self.client) - def get_resource_path(self) -> str: return f"balances/{self._balance.id}/transactions" diff --git a/mollie/api/resources/base.py b/mollie/api/resources/base.py index aa1303d9..7b10983e 100644 --- a/mollie/api/resources/base.py +++ b/mollie/api/resources/base.py @@ -1,9 +1,11 @@ import logging import uuid -from typing import TYPE_CHECKING, Any, Dict, Optional +from typing import TYPE_CHECKING, Any, Dict, Optional, Type + +from mollie.api.objects.base import ObjectBase from ..error import IdentifierError, ResponseError, ResponseHandlingError -from ..objects.list import ObjectList +from ..objects.list import PaginationList if TYPE_CHECKING: from ..client import Client @@ -20,18 +22,11 @@ class ResourceBase: RESOURCE_ID_PREFIX: str = "" + object_type: Type[ObjectBase] + def __init__(self, client: "Client") -> None: self.client = client - def get_resource_object(self, result: dict) -> Any: - """ - Return an instantiated result class for this resource. Should be overriden by a subclass. - - :param result: The API response that the object should hold. - :type result: dict - """ - raise NotImplementedError() # pragma: no cover - def get_resource_path(self) -> str: """Return the base URL path in the API for this resource.""" return self.__class__.__name__.lower() @@ -92,7 +87,7 @@ def create(self, data: Optional[Dict[str, Any]] = None, idempotency_key: str = " idempotency_key = idempotency_key or self._generate_idempotency_key() path = self.get_resource_path() result = self.perform_api_call(self.REST_CREATE, path, data, params, idempotency_key=idempotency_key) - return self.get_resource_object(result) + return self.object_type(result, self.client) class ResourceGetMixin(ResourceBase): @@ -100,7 +95,7 @@ def get(self, resource_id: str, **params: Any) -> Any: resource_path = self.get_resource_path() path = f"{resource_path}/{resource_id}" result = self.perform_api_call(self.REST_READ, path, params=params) - return self.get_resource_object(result) + return self.object_type(result, self.client) def from_url(self, url: str, params: Optional[Dict[str, Any]] = None) -> Any: """Utility method to return an object from a full URL (such as from _links). @@ -108,14 +103,14 @@ def from_url(self, url: str, params: Optional[Dict[str, Any]] = None) -> Any: This method always does a GET request and returns a single Object. """ result = self.perform_api_call(self.REST_READ, url, params=params) - return self.get_resource_object(result) + return self.object_type(result, self.client) class ResourceListMixin(ResourceBase): - def list(self, **params: Any) -> ObjectList: + def list(self, **params: Any) -> PaginationList: path = self.get_resource_path() result = self.perform_api_call(self.REST_LIST, path, params=params) - return ObjectList(result, self.get_resource_object({}).__class__, self.client) + return PaginationList(result, self, self.client) class ResourceUpdateMixin(ResourceBase): @@ -126,7 +121,7 @@ def update( resource_path = self.get_resource_path() path = f"{resource_path}/{resource_id}" result = self.perform_api_call(self.REST_UPDATE, path, data, params, idempotency_key=idempotency_key) - return self.get_resource_object(result) + return self.object_type(result, self.client) class ResourceDeleteMixin(ResourceBase): diff --git a/mollie/api/resources/captures.py b/mollie/api/resources/captures.py index 4a3af95e..132b12ac 100644 --- a/mollie/api/resources/captures.py +++ b/mollie/api/resources/captures.py @@ -16,9 +16,7 @@ class CapturesBase(ResourceBase): RESOURCE_ID_PREFIX: str = "cpt_" - - def get_resource_object(self, result: dict) -> Capture: - return Capture(result, self.client) + object_type = Capture class PaymentCaptures(CapturesBase, ResourceGetMixin, ResourceListMixin, ResourceCreateMixin): diff --git a/mollie/api/resources/chargebacks.py b/mollie/api/resources/chargebacks.py index 7e8cb166..7d0a0ca3 100644 --- a/mollie/api/resources/chargebacks.py +++ b/mollie/api/resources/chargebacks.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING, Any from ..objects.chargeback import Chargeback -from ..objects.list import ObjectList +from ..objects.list import PaginationList from .base import ResourceBase, ResourceGetMixin, ResourceListMixin if TYPE_CHECKING: @@ -20,9 +20,7 @@ class ChargebacksBase(ResourceBase): RESOURCE_ID_PREFIX: str = "chb_" - - def get_resource_object(self, result: dict) -> Chargeback: - return Chargeback(result, self.client) + object_type = Chargeback class Chargebacks(ChargebacksBase, ResourceListMixin): @@ -74,7 +72,7 @@ def __init__(self, client: "Client", profile: "Profile") -> None: self._profile = profile super().__init__(client) - def list(self, **params: Any) -> ObjectList: + def list(self, **params: Any) -> PaginationList: # Set the profileId in the query params params.update({"profileId": self._profile.id}) return Chargebacks(self.client).list(**params) diff --git a/mollie/api/resources/clients.py b/mollie/api/resources/clients.py index f48c466f..8a29af8e 100644 --- a/mollie/api/resources/clients.py +++ b/mollie/api/resources/clients.py @@ -16,9 +16,7 @@ class Clients(ResourceListMixin, ResourceGetMixin): """ RESOURCE_ID_PREFIX: str = "org_" - - def get_resource_object(self, result: dict) -> Client: - return Client(result, self.client) + object_type = Client def get(self, resource_id: str, **params: Any) -> Client: """Retrieve a single client, linked to your partner account, by its ID.""" diff --git a/mollie/api/resources/customers.py b/mollie/api/resources/customers.py index 3eddd945..14b54443 100644 --- a/mollie/api/resources/customers.py +++ b/mollie/api/resources/customers.py @@ -8,9 +8,7 @@ class Customers(ResourceCreateMixin, ResourceDeleteMixin, ResourceGetMixin, Reso """Resource handler for the `/customers` endpoint.""" RESOURCE_ID_PREFIX: str = "cst_" - - def get_resource_object(self, result: dict) -> Customer: - return Customer(result, self.client) + object_type = Customer def get(self, resource_id: str, **params: Any) -> Customer: self.validate_resource_id(resource_id, "customer ID") diff --git a/mollie/api/resources/invoices.py b/mollie/api/resources/invoices.py index 69bec70b..6f798455 100644 --- a/mollie/api/resources/invoices.py +++ b/mollie/api/resources/invoices.py @@ -12,9 +12,7 @@ class Invoices(ResourceGetMixin, ResourceListMixin): """Resource handler for the `/invoices` endpoint.""" RESOURCE_ID_PREFIX: str = "inv_" - - def get_resource_object(self, result: dict) -> Invoice: - return Invoice(result, self.client) + object_type = Invoice def get(self, resource_id: str, **params: Any) -> Invoice: self.validate_resource_id(resource_id, "invoice ID") diff --git a/mollie/api/resources/mandates.py b/mollie/api/resources/mandates.py index 779be3f8..90412a7a 100644 --- a/mollie/api/resources/mandates.py +++ b/mollie/api/resources/mandates.py @@ -19,6 +19,7 @@ class CustomerMandates(ResourceCreateMixin, ResourceDeleteMixin, ResourceGetMixi RESOURCE_ID_PREFIX = "mdt_" _customer: Customer + object_type = Mandate def __init__(self, client: "Client", customer: Customer) -> None: self._customer = customer @@ -27,9 +28,6 @@ def __init__(self, client: "Client", customer: Customer) -> None: def get_resource_path(self) -> str: return f"customers/{self._customer.id}/mandates" - def get_resource_object(self, result: dict) -> Mandate: - return Mandate(result, self.client) - def get(self, resource_id: str, **params: Any) -> Mandate: self.validate_resource_id(resource_id, "mandate ID") return super().get(resource_id, **params) diff --git a/mollie/api/resources/methods.py b/mollie/api/resources/methods.py index 2b1c064e..d74b8405 100644 --- a/mollie/api/resources/methods.py +++ b/mollie/api/resources/methods.py @@ -2,7 +2,7 @@ from ..error import IdentifierError from ..objects.issuer import Issuer -from ..objects.list import ObjectList +from ..objects.list import PaginationList from ..objects.method import Method from .base import ResourceBase, ResourceGetMixin, ResourceListMixin @@ -18,19 +18,18 @@ class MethodsBase(ResourceBase): - def get_resource_object(self, result: dict) -> Method: - return Method(result, self.client) + object_type = Method class Methods(MethodsBase, ResourceGetMixin, ResourceListMixin): """Resource handler for the `/methods` endpoint.""" - def all(self, **params: Any) -> ObjectList: + def all(self, **params: Any) -> PaginationList: """List all mollie payment methods, including methods that aren't activated in your profile.""" resource_path = self.get_resource_path() path = f"{resource_path}/all" result = self.perform_api_call(self.REST_LIST, path, params=params) - return ObjectList(result, Method, self.client) + return PaginationList(result, self, self.client) class ProfileMethods(MethodsBase): @@ -66,7 +65,7 @@ def enable(self, method_id: str, **params: Any) -> Method: resource_path = self.get_resource_path() path = f"{resource_path}/{method_id}" result = self.perform_api_call(self.REST_CREATE, path, params=params) - return self.get_resource_object(result) + return Method(result, self.client) def disable(self, method_id: str, **params: Any) -> Method: """ @@ -83,9 +82,9 @@ def disable(self, method_id: str, **params: Any) -> Method: resource_path = self.get_resource_path() path = f"{resource_path}/{method_id}" result = self.perform_api_call(self.REST_DELETE, path, params=params) - return self.get_resource_object(result) + return Method(result, self.client) - def list(self, **params: Any) -> ObjectList: + def list(self, **params: Any) -> PaginationList: """List the payment methods for the profile.""" params.update({"profileId": self._profile.id}) # Divert the API call to the general Methods resource diff --git a/mollie/api/resources/onboarding.py b/mollie/api/resources/onboarding.py index 97d2fc8f..c60ad16f 100644 --- a/mollie/api/resources/onboarding.py +++ b/mollie/api/resources/onboarding.py @@ -13,8 +13,7 @@ class Onboarding(ResourceGetMixin): """Resource handler for the `/onboarding` endpoint.""" - def get_resource_object(self, result: dict) -> OnboardingObject: - return OnboardingObject(result, self.client) + object_type = OnboardingObject def get(self, resource_id: str, **params: Any) -> OnboardingObject: if resource_id != "me": @@ -31,4 +30,4 @@ def create(self, data: Dict[str, Any], **params: Any) -> OnboardingObject: resource_path = self.get_resource_path() path = f"{resource_path}/me" result = self.perform_api_call(self.REST_CREATE, path, data, params) - return self.get_resource_object(result) + return OnboardingObject(result, self.client) diff --git a/mollie/api/resources/order_lines.py b/mollie/api/resources/order_lines.py index e99ab010..55f0893c 100644 --- a/mollie/api/resources/order_lines.py +++ b/mollie/api/resources/order_lines.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional from ..error import DataConsistencyError -from ..objects.list import ObjectList +from ..objects.list import PaginationList from ..objects.order_line import OrderLine from .base import ResourceBase @@ -25,6 +25,7 @@ class OrderLines(ResourceBase): """ RESOURCE_ID_PREFIX: str = "odl_" + object_type = OrderLine _order: "Order" @@ -35,9 +36,6 @@ def __init__(self, client: "Client", order: "Order") -> None: def get_resource_path(self) -> str: return f"orders/{self._order.id}/lines" - def get_resource_object(self, result: dict) -> OrderLine: - return OrderLine(result, self.client) - def delete_lines(self, data: Optional[Dict[str, Any]] = None, **params: Any) -> dict: """ Cancel multiple orderlines. @@ -98,11 +96,11 @@ def update(self, order_line_id: str, data: Optional[Dict[str, Any]] = None, **pa for line in result["lines"]: if line["id"] == order_line_id: - return self.get_resource_object(line) + return OrderLine(line, self.client) raise DataConsistencyError(f"OrderLine with id '{order_line_id}' not found in response.") - def list(self, **params: Any) -> ObjectList: + def list(self, **params: Any) -> PaginationList: """Return the orderline data from the related order.""" lines = self._order._get_property("lines") or [] data = { @@ -111,4 +109,4 @@ def list(self, **params: Any) -> ObjectList: }, "count": len(lines), } - return ObjectList(data, OrderLine, self.client) + return PaginationList(data, self, self.client) diff --git a/mollie/api/resources/orders.py b/mollie/api/resources/orders.py index 70bac22d..7e552ead 100644 --- a/mollie/api/resources/orders.py +++ b/mollie/api/resources/orders.py @@ -12,9 +12,7 @@ class Orders(ResourceCreateMixin, ResourceDeleteMixin, ResourceGetMixin, Resourc """Resource handler for the `/orders` endpoint.""" RESOURCE_ID_PREFIX: str = "ord_" - - def get_resource_object(self, result: dict) -> Order: - return Order(result, self.client) + object_type = Order def get(self, resource_id: str, **params: Any) -> Order: self.validate_resource_id(resource_id, "order ID") @@ -28,7 +26,7 @@ def delete(self, resource_id: str, idempotency_key: str = "", **params: Any) -> """ self.validate_resource_id(resource_id, "order ID") result = super().delete(resource_id, **params) - return self.get_resource_object(result) + return Order(result, self.client) def update( self, resource_id: str, data: Optional[Dict[str, Any]] = None, idempotency_key: str = "", **params: Any diff --git a/mollie/api/resources/organizations.py b/mollie/api/resources/organizations.py index a64d9eba..b9756d23 100644 --- a/mollie/api/resources/organizations.py +++ b/mollie/api/resources/organizations.py @@ -12,9 +12,7 @@ class Organizations(ResourceGetMixin): """Resource handler for the `/organizations` endpoint.""" RESOURCE_ID_PREFIX: str = "org_" - - def get_resource_object(self, result: dict) -> Organization: - return Organization(result, self.client) + object_type = Organization def get(self, resource_id: str, **params: Any) -> Organization: if resource_id != "me": diff --git a/mollie/api/resources/payment_links.py b/mollie/api/resources/payment_links.py index 94579e8d..dfe3ebd7 100644 --- a/mollie/api/resources/payment_links.py +++ b/mollie/api/resources/payment_links.py @@ -12,13 +12,11 @@ class PaymentLinks(ResourceCreateMixin, ResourceGetMixin, ResourceListMixin): """Resource handler for the `/payment_links` endpoint.""" RESOURCE_ID_PREFIX: str = "pl_" + object_type = PaymentLink def get_resource_path(self) -> str: return "payment-links" - def get_resource_object(self, result: dict) -> PaymentLink: - return PaymentLink(result, self.client) - def get(self, resource_id: str, **params: Any) -> PaymentLink: self.validate_resource_id(resource_id, "payment link ID") return super().get(resource_id, **params) diff --git a/mollie/api/resources/payments.py b/mollie/api/resources/payments.py index fe005048..11c0be2d 100644 --- a/mollie/api/resources/payments.py +++ b/mollie/api/resources/payments.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional from ..objects.customer import Customer -from ..objects.list import ObjectList +from ..objects.list import PaginationList from ..objects.order import Order from ..objects.payment import Payment from ..objects.profile import Profile @@ -31,11 +31,7 @@ class PaymentsBase(ResourceBase): RESOURCE_ID_PREFIX: str = "tr_" - - def get_resource_object(self, result: dict) -> Payment: - from ..objects.payment import Payment - - return Payment(result, self.client) + object_type = Payment class Payments( @@ -55,7 +51,7 @@ def delete(self, resource_id: str, idempotency_key: str = "", **params: Any) -> """ self.validate_resource_id(resource_id, "payment ID") result = super().delete(resource_id, idempotency_key, **params) - return self.get_resource_object(result) + return Payment(result, self.client) def update( self, resource_id: str, data: Optional[Dict[str, Any]] = None, idempotency_key: str = "", **params: Any @@ -76,7 +72,7 @@ def __init__(self, client: "Client", order: Order) -> None: def get_resource_path(self) -> str: return f"orders/{self._order.id}/payments" - def list(self) -> ObjectList: + def list(self) -> PaginationList: """ List the payments that might have been embedded in the related order. @@ -90,7 +86,7 @@ def list(self) -> ObjectList: }, "count": len(payments), } - return ObjectList(data, Payment, self.client) + return PaginationList(data, self, self.client) class CustomerPayments(PaymentsBase, ResourceCreateMixin, ResourceListMixin): @@ -147,7 +143,7 @@ def __init__(self, client: "Client", profile: Profile) -> None: self._profile = profile super().__init__(client) - def list(self, **params: Any) -> ObjectList: + def list(self, **params: Any) -> PaginationList: # Set the profileId in the query params params.update({"profileId": self._profile.id}) return Payments(self.client).list(**params) diff --git a/mollie/api/resources/permissions.py b/mollie/api/resources/permissions.py index 0ba84425..7159743e 100644 --- a/mollie/api/resources/permissions.py +++ b/mollie/api/resources/permissions.py @@ -13,8 +13,7 @@ class Permissions(ResourceGetMixin, ResourceListMixin): """Resource handler for the `/permissions` endpoint.""" - def get_resource_object(self, result: dict) -> Permission: - return Permission(result, self.client) + object_type = Permission @staticmethod def validate_permission_id(permission_id: str) -> None: diff --git a/mollie/api/resources/profiles.py b/mollie/api/resources/profiles.py index 85e7f36e..35952da7 100644 --- a/mollie/api/resources/profiles.py +++ b/mollie/api/resources/profiles.py @@ -12,9 +12,7 @@ class Profiles(ResourceCreateMixin, ResourceDeleteMixin, ResourceGetMixin, Resou """Resource handler for the `/profiles` endpoint.""" RESOURCE_ID_PREFIX: str = "pfl_" - - def get_resource_object(self, result: dict) -> Profile: - return Profile(result, self.client) + object_type = Profile def get(self, resource_id: str, **params: Any) -> Profile: if resource_id != "me": diff --git a/mollie/api/resources/refunds.py b/mollie/api/resources/refunds.py index 3d25eccc..3b53c0dc 100644 --- a/mollie/api/resources/refunds.py +++ b/mollie/api/resources/refunds.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING, Any, Dict, Optional -from ..objects.list import ObjectList +from ..objects.list import PaginationList from ..objects.order import Order from ..objects.payment import Payment from ..objects.profile import Profile @@ -22,9 +22,7 @@ class RefundsBase(ResourceBase): RESOURCE_ID_PREFIX: str = "re_" - - def get_resource_object(self, result: dict) -> Refund: - return Refund(result, self.client) + object_type = Refund class Refunds(RefundsBase, ResourceListMixin): @@ -99,7 +97,7 @@ def __init__(self, client: "Client", profile: Profile) -> None: self._profile = profile super().__init__(client) - def list(self, **params: Any) -> ObjectList: + def list(self, **params: Any) -> PaginationList: # Set the profileId in the query params params.update({"profileId": self._profile.id}) return Refunds(self.client).list(**params) diff --git a/mollie/api/resources/settlements.py b/mollie/api/resources/settlements.py index 71787fa4..8282c30e 100644 --- a/mollie/api/resources/settlements.py +++ b/mollie/api/resources/settlements.py @@ -9,6 +9,7 @@ class Settlements(ResourceGetMixin, ResourceListMixin): """Resource handler for the `/settlements` endpoint.""" RESOURCE_ID_PREFIX: str = "stl_" + object_type = Settlement # According to Mollie, the bank reference is formatted as: # - The Mollie merchant ID, 4 to 8 digits, might grow when the number of merchants increases @@ -17,9 +18,6 @@ class Settlements(ResourceGetMixin, ResourceListMixin): # The components are separated by a dot. BANK_REFERENCE_REGEX: Pattern[str] = re.compile(r"^\d{4,}\.\d{4}\.\d{2}$", re.ASCII) - def get_resource_object(self, result: dict) -> Settlement: - return Settlement(result, self.client) - @classmethod def validate_resource_id(cls, resource_id: str, name: str = "", message: str = "") -> None: """ diff --git a/mollie/api/resources/shipments.py b/mollie/api/resources/shipments.py index f1c9ca6e..ca28f62b 100644 --- a/mollie/api/resources/shipments.py +++ b/mollie/api/resources/shipments.py @@ -16,6 +16,7 @@ class OrderShipments(ResourceCreateMixin, ResourceGetMixin, ResourceListMixin, R """Resource handler for the `/orders/:order_id:/shipments` endpoint.""" RESOURCE_ID_PREFIX: str = "shp_" + object_type = Shipment _order: Order @@ -23,9 +24,6 @@ def __init__(self, client: "Client", order: Order) -> None: self._order = order super().__init__(client) - def get_resource_object(self, result: dict) -> Shipment: - return Shipment(result, self.client) - def get_resource_path(self) -> str: return f"orders/{self._order.id}/shipments" diff --git a/mollie/api/resources/subscriptions.py b/mollie/api/resources/subscriptions.py index 2d9dd93a..bdca20cb 100644 --- a/mollie/api/resources/subscriptions.py +++ b/mollie/api/resources/subscriptions.py @@ -22,9 +22,7 @@ class SubscriptionsBase(ResourceBase): RESOURCE_ID_PREFIX: str = "sub_" - - def get_resource_object(self, result: dict) -> Subscription: - return Subscription(result, self.client) + object_type = Subscription class Subscriptions(SubscriptionsBase, ResourceListMixin): @@ -65,4 +63,4 @@ def update( def delete(self, resource_id: str, idempotency_key: str = "", **params: Any) -> dict: self.validate_resource_id(resource_id, "subscription ID") resp = super().delete(resource_id, idempotency_key, **params) - return self.get_resource_object(resp) + return Subscription(resp, self.client) diff --git a/tests/responses/.coverage b/tests/responses/.coverage new file mode 100644 index 00000000..6089ef87 Binary files /dev/null and b/tests/responses/.coverage differ diff --git a/tests/responses/chargebacks_list.json b/tests/responses/chargebacks_list.json index 3290f82a..3840b1a1 100644 --- a/tests/responses/chargebacks_list.json +++ b/tests/responses/chargebacks_list.json @@ -26,7 +26,7 @@ "type": "application/hal+json" }, "documentation": { - "href": "https://docs.mollie.com/reference/v2/chargebacks-api/get-chargeback", + "href": "https://docs.mollie.com/reference/v2/chargebacks-api/get-payment-chargeback", "type": "text/html" } } @@ -35,7 +35,11 @@ }, "_links": { "self": { - "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS/chargebacks", + "href": "https://api.mollie.com/v2/chargebacks", + "type": "application/hal+json" + }, + "next": { + "href": "https://api.mollie.com/v2/chargebacks?from=chb_n9z0tq", "type": "application/hal+json" }, "documentation": { diff --git a/tests/responses/chargebacks_list_more.json b/tests/responses/chargebacks_list_more.json new file mode 100644 index 00000000..db4a702f --- /dev/null +++ b/tests/responses/chargebacks_list_more.json @@ -0,0 +1,50 @@ +{ + "count": 1, + "_embedded": { + "chargebacks": [ + { + "resource": "chargeback", + "id": "chb_n9z0tq", + "amount": { + "currency": "USD", + "value": "43.38" + }, + "settlementAmount": { + "currency": "EUR", + "value": "35.07" + }, + "createdAt": "2018-03-14T17:00:52.0Z", + "reversedAt": null, + "paymentId": "tr_7UhSN1zuXS", + "_links": { + "self": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXT/chargebacks/chb_n9z0tq", + "type": "application/hal+json" + }, + "payment": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXT", + "type": "application/hal+json" + }, + "documentation": { + "href": "https://docs.mollie.com/reference/v2/chargebacks-api/get-payment-chargeback", + "type": "text/html" + } + } + } + ] + }, + "_links": { + "self": { + "href": "https://api.mollie.com/v2/chargebacks?from=chb_n9z0tq", + "type": "application/hal+json" + }, + "previous": { + "href": "https://api.mollie.com/v2/chargebacks", + "type": "application/hal+json" + }, + "documentation": { + "href": "https://docs.mollie.com/reference/v2/chargebacks-api/list-chargebacks", + "type": "text/html" + } + } +} diff --git a/tests/responses/subscriptions_customer_list.json b/tests/responses/customer_subscriptions_list.json similarity index 98% rename from tests/responses/subscriptions_customer_list.json rename to tests/responses/customer_subscriptions_list.json index e8e8ff96..f4e52217 100644 --- a/tests/responses/subscriptions_customer_list.json +++ b/tests/responses/customer_subscriptions_list.json @@ -89,7 +89,7 @@ }, "previous": null, "next": { - "href": "https://api.mollie.com/v2/customers/cst_8wmqcHMN4U/subscriptions?from=sub_mnfbwhMfvo", + "href": "https://api.mollie.com/v2/customers/cst_8wmqcHMN4U/subscriptions?from=sub_rVKGtNd6s6", "type": "application/hal+json" }, "documentation": { diff --git a/tests/responses/customer_subscriptions_list_more.json b/tests/responses/customer_subscriptions_list_more.json new file mode 100644 index 00000000..12dc0f3a --- /dev/null +++ b/tests/responses/customer_subscriptions_list_more.json @@ -0,0 +1,48 @@ +{ + "count": 1, + "_embedded": { + "subscriptions": [ + { + "resource": "subscription", + "id": "sub_rVKGtNd6s6", + "mode": "live", + "createdAt": "2018-06-01T12:23:34+00:00", + "status": "active", + "amount": { + "value": "35.00", + "currency": "EUR" + }, + "times": 4, + "interval": "3 months", + "description": "Quarterly payment", + "method": "ideal", + "webhookUrl": "https://webshop.example.org/subscriptions/webhook", + "_links": { + "self": { + "href": "https://api.mollie.com/v2/customers/cst_8wmqcHMN4U/subscriptions/sub_rVKGtNd6s6", + "type": "application/hal+json" + }, + "customer": { + "href": "https://api.mollie.com/v2/customers/cst_8wmqcHMN4U", + "type": "application/hal+json" + } + } + } + ] + }, + "_links": { + "self": { + "href": "https://api.mollie.com/v2/customers/cst_8wmqcHMN4U/subscriptions?from=sub_rVKGtNd6s6", + "type": "application/hal+json" + }, + "previous": { + "href": "https://api.mollie.com/v2/customers/cst_8wmqcHMN4U/subscriptions", + "type": "application/hal+json" + }, + "next": null, + "documentation": { + "href": "https://docs.mollie.com/reference/v2/subscriptions-api/list-subscriptions", + "type": "text/html" + } + } +} diff --git a/tests/responses/chargeback_single.json b/tests/responses/payment_chargeback_single.json similarity index 96% rename from tests/responses/chargeback_single.json rename to tests/responses/payment_chargeback_single.json index e44c935c..8bae229a 100644 --- a/tests/responses/chargeback_single.json +++ b/tests/responses/payment_chargeback_single.json @@ -26,7 +26,7 @@ "type": "application/hal+json" }, "documentation": { - "href": "https://docs.mollie.com/reference/v2/chargebacks-api/get-chargeback", + "href": "https://docs.mollie.com/reference/v2/chargebacks-api/get-payment-chargeback", "type": "text/html" } } diff --git a/tests/responses/payment_chargebacks_list.json b/tests/responses/payment_chargebacks_list.json new file mode 100644 index 00000000..95f3abf8 --- /dev/null +++ b/tests/responses/payment_chargebacks_list.json @@ -0,0 +1,50 @@ +{ + "count": 1, + "_embedded": { + "chargebacks": [ + { + "resource": "chargeback", + "id": "chb_n9z0tp", + "amount": { + "currency": "USD", + "value": "43.38" + }, + "settlementAmount": { + "currency": "EUR", + "value": "35.07" + }, + "createdAt": "2018-03-14T17:00:52.0Z", + "reversedAt": null, + "paymentId": "tr_7UhSN1zuXS", + "_links": { + "self": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS/chargebacks/chb_n9z0tp", + "type": "application/hal+json" + }, + "payment": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS", + "type": "application/hal+json" + }, + "documentation": { + "href": "https://docs.mollie.com/reference/v2/chargebacks-api/get-payment-chargeback", + "type": "text/html" + } + } + } + ] + }, + "_links": { + "self": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS/chargebacks", + "type": "application/hal+json" + }, + "next": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS/chargebacks?from=chb_n9z0tq", + "type": "application/hal+json" + }, + "documentation": { + "href": "https://docs.mollie.com/reference/v2/chargebacks-api/list-payment-chargebacks", + "type": "text/html" + } + } +} diff --git a/tests/responses/payment_chargebacks_list_more.json b/tests/responses/payment_chargebacks_list_more.json new file mode 100644 index 00000000..8fc25b82 --- /dev/null +++ b/tests/responses/payment_chargebacks_list_more.json @@ -0,0 +1,50 @@ +{ + "count": 1, + "_embedded": { + "chargebacks": [ + { + "resource": "chargeback", + "id": "chb_n9z0tq", + "amount": { + "currency": "USD", + "value": "43.38" + }, + "settlementAmount": { + "currency": "EUR", + "value": "35.07" + }, + "createdAt": "2018-03-14T17:00:52.0Z", + "reversedAt": null, + "paymentId": "tr_7UhSN1zuXS", + "_links": { + "self": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS/chargebacks/chb_n9z0tq", + "type": "application/hal+json" + }, + "payment": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS", + "type": "application/hal+json" + }, + "documentation": { + "href": "https://docs.mollie.com/reference/v2/chargebacks-api/get-payment-chargeback", + "type": "text/html" + } + } + } + ] + }, + "_links": { + "self": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS/chargebacks?from=chb_n9z0tq", + "type": "application/hal+json" + }, + "previous": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS/chargebacks", + "type": "application/hal+json" + }, + "documentation": { + "href": "https://docs.mollie.com/reference/v2/chargebacks-api/list-payment-chargebacks", + "type": "text/html" + } + } +} diff --git a/tests/responses/profile_chargebacks_list.json b/tests/responses/profile_chargebacks_list.json new file mode 100644 index 00000000..9ac6529a --- /dev/null +++ b/tests/responses/profile_chargebacks_list.json @@ -0,0 +1,50 @@ +{ + "count": 1, + "_embedded": { + "chargebacks": [ + { + "resource": "chargeback", + "id": "chb_n9z0tp", + "amount": { + "currency": "USD", + "value": "43.38" + }, + "settlementAmount": { + "currency": "EUR", + "value": "35.07" + }, + "createdAt": "2018-03-14T17:00:52.0Z", + "reversedAt": null, + "paymentId": "tr_7UhSN1zuXS", + "_links": { + "self": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS/chargebacks/chb_n9z0tp?profileId=pfl_v9hTwCvYqw", + "type": "application/hal+json" + }, + "payment": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS", + "type": "application/hal+json" + }, + "documentation": { + "href": "https://docs.mollie.com/reference/v2/chargebacks-api/get-payment-chargeback", + "type": "text/html" + } + } + } + ] + }, + "_links": { + "self": { + "href": "https://api.mollie.com/v2/chargebacks?profileId=pfl_v9hTwCvYqw", + "type": "application/hal+json" + }, + "next": { + "href": "https://api.mollie.com/v2/chargebacks?profileId=pfl_v9hTwCvYqw&from=chb_n9z0tq", + "type": "application/hal+json" + }, + "documentation": { + "href": "https://docs.mollie.com/reference/v2/chargebacks-api/list-chargebacks", + "type": "text/html" + } + } +} diff --git a/tests/responses/profile_chargebacks_list_more.json b/tests/responses/profile_chargebacks_list_more.json new file mode 100644 index 00000000..b4b1bc09 --- /dev/null +++ b/tests/responses/profile_chargebacks_list_more.json @@ -0,0 +1,50 @@ +{ + "count": 1, + "_embedded": { + "chargebacks": [ + { + "resource": "chargeback", + "id": "chb_n9z0tq", + "amount": { + "currency": "USD", + "value": "43.38" + }, + "settlementAmount": { + "currency": "EUR", + "value": "35.07" + }, + "createdAt": "2018-03-14T17:00:52.0Z", + "reversedAt": null, + "paymentId": "tr_7UhSN1zuXS", + "_links": { + "self": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXT/chargebacks/chb_n9z0tq?profileId=pfl_v9hTwCvYqw", + "type": "application/hal+json" + }, + "payment": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXT", + "type": "application/hal+json" + }, + "documentation": { + "href": "https://docs.mollie.com/reference/v2/chargebacks-api/get-payment-chargeback", + "type": "text/html" + } + } + } + ] + }, + "_links": { + "self": { + "href": "https://api.mollie.com/v2/chargebacks?profileId=pfl_v9hTwCvYqw&from=chb_n9z0tq", + "type": "application/hal+json" + }, + "previous": { + "href": "https://api.mollie.com/v2/chargebacks?profileId=pfl_v9hTwCvYqw", + "type": "application/hal+json" + }, + "documentation": { + "href": "https://docs.mollie.com/reference/v2/chargebacks-api/list-chargebacks", + "type": "text/html" + } + } +} diff --git a/tests/responses/settlement_chargebacks_list.json b/tests/responses/settlement_chargebacks_list.json new file mode 100644 index 00000000..5f8ddba3 --- /dev/null +++ b/tests/responses/settlement_chargebacks_list.json @@ -0,0 +1,50 @@ +{ + "count": 1, + "_embedded": { + "chargebacks": [ + { + "resource": "chargeback", + "id": "chb_n9z0tp", + "amount": { + "currency": "USD", + "value": "43.38" + }, + "settlementAmount": { + "currency": "EUR", + "value": "35.07" + }, + "createdAt": "2018-03-14T17:00:52.0Z", + "reversedAt": null, + "paymentId": "tr_7UhSN1zuXS", + "_links": { + "self": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS/chargebacks/chb_n9z0tp", + "type": "application/hal+json" + }, + "payment": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS", + "type": "application/hal+json" + }, + "documentation": { + "href": "https://docs.mollie.com/reference/v2/chargebacks-api/get-payment-chargeback", + "type": "text/html" + } + } + } + ] + }, + "_links": { + "self": { + "href": "https://api.mollie.com/v2/settlements/stl_jDk30akdN/chargebacks", + "type": "application/hal+json" + }, + "next": { + "href": "https://api.mollie.com/v2/settlements/stl_jDk30akdN/chargebacks?from=chb_n9z0tq", + "type": "application/hal+json" + }, + "documentation": { + "href": "https://docs.mollie.com/reference/v2/settlements-api/list-settlement-chargebacks", + "type": "text/html" + } + } +} diff --git a/tests/responses/settlement_chargebacks_list_more.json b/tests/responses/settlement_chargebacks_list_more.json new file mode 100644 index 00000000..c63e8719 --- /dev/null +++ b/tests/responses/settlement_chargebacks_list_more.json @@ -0,0 +1,50 @@ +{ + "count": 1, + "_embedded": { + "chargebacks": [ + { + "resource": "chargeback", + "id": "chb_n9z0tq", + "amount": { + "currency": "USD", + "value": "43.38" + }, + "settlementAmount": { + "currency": "EUR", + "value": "35.07" + }, + "createdAt": "2018-03-14T17:00:52.0Z", + "reversedAt": null, + "paymentId": "tr_7UhSN1zuXS", + "_links": { + "self": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS/chargebacks/chb_n9z0tq", + "type": "application/hal+json" + }, + "payment": { + "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS", + "type": "application/hal+json" + }, + "documentation": { + "href": "https://docs.mollie.com/reference/v2/chargebacks-api/get-payment-chargeback", + "type": "text/html" + } + } + } + ] + }, + "_links": { + "self": { + "href": "https://api.mollie.com/v2/settlements/stl_jDk30akdN/chargebacks?from=chb_n9z0tq", + "type": "application/hal+json" + }, + "previous": { + "href": "https://api.mollie.com/v2/settlements/stl_jDk30akdN/chargebacks", + "type": "application/hal+json" + }, + "documentation": { + "href": "https://docs.mollie.com/reference/v2/settlements-api/list-settlement-chargebacks", + "type": "text/html" + } + } +} diff --git a/tests/responses/subscriptions_list.json b/tests/responses/subscriptions_list.json index 3c928e58..75c1edde 100644 --- a/tests/responses/subscriptions_list.json +++ b/tests/responses/subscriptions_list.json @@ -101,7 +101,7 @@ }, "previous": null, "next": { - "href": "https://api.mollie.com/v2/subscriptions?from=sub_mnfbwhMfvo", + "href": "https://api.mollie.com/v2/subscriptions?from=sub_rVKGtNd6s6", "type": "application/hal+json" }, "documentation": { diff --git a/tests/responses/subscriptions_list_more.json b/tests/responses/subscriptions_list_more.json new file mode 100644 index 00000000..a5f95d6c --- /dev/null +++ b/tests/responses/subscriptions_list_more.json @@ -0,0 +1,52 @@ +{ + "count": 1, + "_embedded": { + "subscriptions": [ + { + "resource": "subscription", + "id": "sub_rVKGtNd6s6", + "mode": "live", + "createdAt": "2018-06-01T12:23:34+00:00", + "status": "active", + "amount": { + "value": "35.00", + "currency": "EUR" + }, + "times": 4, + "interval": "3 months", + "description": "Quarterly payment", + "method": "ideal", + "webhookUrl": "https://webshop.example.org/subscriptions/webhook", + "_links": { + "self": { + "href": "https://api.mollie.com/v2/customers/cst_8wmqcHMN4U/subscriptions/sub_rVKGtNd6s6", + "type": "application/hal+json" + }, + "profile": { + "href": "https://api.mollie.com/v2/profiles/pfl_URR55HPMGo", + "type": "application/hal+json" + }, + "customer": { + "href": "https://api.mollie.com/v2/customers/cst_8wmqcHMN4U", + "type": "application/hal+json" + } + } + } + ] + }, + "_links": { + "self": { + "href": "https://api.mollie.com/v2/subscriptions?from=sub_rVKGtNd6s6", + "type": "application/hal+json" + }, + "previous": { + "href": "https://api.mollie.com/v2/subscriptions", + "type": "application/hal+json" + }, + "next": null, + "documentation": { + "href": "https://docs.mollie.com/reference/v2/subscriptions-api/list-all-subscriptions", + "type": "text/html" + } + } +} diff --git a/tests/test_chargebacks.py b/tests/test_chargebacks.py index ca92963f..fe5e0575 100644 --- a/tests/test_chargebacks.py +++ b/tests/test_chargebacks.py @@ -1,3 +1,5 @@ +from responses import matchers + from mollie.api.objects.chargeback import Chargeback from .utils import assert_list_object @@ -9,3 +11,25 @@ def test_list_chargebacks(client, response): chargebacks = client.chargebacks.list() assert_list_object(chargebacks, Chargeback) + + +def test_list_chargeback_pagination(client, response): + """Retrieve a list of paginated chargebacks.""" + response.get( + "https://api.mollie.com/v2/chargebacks", "chargebacks_list", match=[matchers.query_string_matcher("")] + ) + response.get( + "https://api.mollie.com/v2/chargebacks", + "chargebacks_list_more", + match=[matchers.query_string_matcher("from=chb_n9z0tq")], + ) + + first_chargebacks_page = client.chargebacks.list() + assert first_chargebacks_page.has_previous() is False + assert first_chargebacks_page.has_next() is True + + second_chargebacks_page = first_chargebacks_page.get_next() + assert_list_object(second_chargebacks_page, Chargeback) + + subscription = next(second_chargebacks_page) + assert subscription.id == "chb_n9z0tq" diff --git a/tests/test_customer_subscriptions.py b/tests/test_customer_subscriptions.py index fa50cee7..01f42553 100644 --- a/tests/test_customer_subscriptions.py +++ b/tests/test_customer_subscriptions.py @@ -1,4 +1,5 @@ import pytest +from responses import matchers from mollie.api.error import IdentifierError from mollie.api.objects.customer import Customer @@ -19,13 +20,39 @@ def test_list_customer_subscriptions(client, response): """Retrieve a list of subscriptions.""" response.get(f"https://api.mollie.com/v2/customers/{CUSTOMER_ID}", "customer_single") - response.get(f"https://api.mollie.com/v2/customers/{CUSTOMER_ID}/subscriptions", "subscriptions_customer_list") + response.get(f"https://api.mollie.com/v2/customers/{CUSTOMER_ID}/subscriptions", "customer_subscriptions_list") customer = client.customers.get(CUSTOMER_ID) subscriptions = customer.subscriptions.list() assert_list_object(subscriptions, Subscription) +def test_list_customer_subscription_pagination(client, response): + """Retrieve a list of paginated subscriptions.""" + response.get(f"https://api.mollie.com/v2/customers/{CUSTOMER_ID}", "customer_single") + response.get( + f"https://api.mollie.com/v2/customers/{CUSTOMER_ID}/subscriptions", + "customer_subscriptions_list", + match=[matchers.query_string_matcher("")], + ) + response.get( + f"https://api.mollie.com/v2/customers/{CUSTOMER_ID}/subscriptions", + "customer_subscriptions_list_more", + match=[matchers.query_string_matcher("from=sub_rVKGtNd6s6")], + ) + + customer = client.customers.get(CUSTOMER_ID) + first_subscriptions_page = customer.subscriptions.list() + assert first_subscriptions_page.has_previous() is False + assert first_subscriptions_page.has_next() is True + + second_subscriptions_page = first_subscriptions_page.get_next() + assert_list_object(second_subscriptions_page, Subscription) + + subscription = next(second_subscriptions_page) + assert subscription.id == "sub_rVKGtNd6s6" + + def test_get_customer_subscription(client, response): response.get(f"https://api.mollie.com/v2/customers/{CUSTOMER_ID}", "customer_single") response.get( diff --git a/tests/test_customers.py b/tests/test_customers.py index f52b8536..da502229 100644 --- a/tests/test_customers.py +++ b/tests/test_customers.py @@ -109,7 +109,7 @@ def test_customer_get_related_mandates(client, response): def test_customer_get_related_subscriptions(client, response): """Retrieve related subscriptions for a customer.""" response.get(f"https://api.mollie.com/v2/customers/{CUSTOMER_ID}", "customer_single") - response.get(f"https://api.mollie.com/v2/customers/{CUSTOMER_ID}/subscriptions", "subscriptions_customer_list") + response.get(f"https://api.mollie.com/v2/customers/{CUSTOMER_ID}/subscriptions", "customer_subscriptions_list") customer = client.customers.get(CUSTOMER_ID) subscriptions = customer.subscriptions.list() diff --git a/tests/test_list.py b/tests/test_list.py index 8c410730..29db795a 100644 --- a/tests/test_list.py +++ b/tests/test_list.py @@ -1,6 +1,6 @@ import pytest -from mollie.api.objects.list import ObjectList +from mollie.api.objects.list import PaginationList from mollie.api.objects.method import Method from .utils import assert_list_object @@ -11,7 +11,7 @@ def test_list_iterator_behaviour(client, response): response.get("https://api.mollie.com/v2/methods", "methods_list") methods = client.methods.list() - assert isinstance(methods, ObjectList) + assert isinstance(methods, PaginationList) # walk the list using next() iterated = 0 diff --git a/tests/test_payment_chargebacks.py b/tests/test_payment_chargebacks.py index f738000c..a335d215 100644 --- a/tests/test_payment_chargebacks.py +++ b/tests/test_payment_chargebacks.py @@ -1,4 +1,5 @@ import pytest +from responses import matchers from mollie.api.error import IdentifierError from mollie.api.objects.chargeback import Chargeback @@ -15,17 +16,45 @@ def test_list_payment_chargebacks(client, response): """Get chargebacks relevant to a payment.""" response.get(f"https://api.mollie.com/v2/payments/{PAYMENT_ID}", "payment_single") - response.get(f"https://api.mollie.com/v2/payments/{PAYMENT_ID}/chargebacks", "chargebacks_list") + response.get(f"https://api.mollie.com/v2/payments/{PAYMENT_ID}/chargebacks", "payment_chargebacks_list") payment = client.payments.get(PAYMENT_ID) chargebacks = payment.chargebacks.list() assert_list_object(chargebacks, Chargeback) +def test_list_payment_chargebacks_pagination(client, response): + """Retrieve a list of paginated payment chargebacks.""" + response.get(f"https://api.mollie.com/v2/payments/{PAYMENT_ID}", "payment_single") + response.get( + f"https://api.mollie.com/v2/payments/{PAYMENT_ID}/chargebacks", + "payment_chargebacks_list", + match=[matchers.query_string_matcher("")], + ) + response.get( + f"https://api.mollie.com/v2/payments/{PAYMENT_ID}/chargebacks", + "payment_chargebacks_list_more", + match=[matchers.query_string_matcher("from=chb_n9z0tq")], + ) + + payment = client.payments.get(PAYMENT_ID) + first_chargebacks_page = payment.chargebacks.list() + assert first_chargebacks_page.has_previous() is False + assert first_chargebacks_page.has_next() is True + + second_chargebacks_page = first_chargebacks_page.get_next() + assert_list_object(second_chargebacks_page, Chargeback) + + subscription = next(second_chargebacks_page) + assert subscription.id == "chb_n9z0tq" + + def test_get_payment_chargeback(client, response): """Get a single chargeback relevant to a payment.""" response.get(f"https://api.mollie.com/v2/payments/{PAYMENT_ID}", "payment_single") - response.get(f"https://api.mollie.com/v2/payments/{PAYMENT_ID}/chargebacks/{CHARGEBACK_ID}", "chargeback_single") + response.get( + f"https://api.mollie.com/v2/payments/{PAYMENT_ID}/chargebacks/{CHARGEBACK_ID}", "payment_chargeback_single" + ) payment = client.payments.get(PAYMENT_ID) chargeback = payment.chargebacks.get(CHARGEBACK_ID) @@ -51,7 +80,9 @@ def test_get_payment_chargeback_invalid_id(client, response): def test_payment_chargeback_get_related_payment(client, response): response.get(f"https://api.mollie.com/v2/payments/{PAYMENT_ID}", "payment_single") - response.get(f"https://api.mollie.com/v2/payments/{PAYMENT_ID}/chargebacks/{CHARGEBACK_ID}", "chargeback_single") + response.get( + f"https://api.mollie.com/v2/payments/{PAYMENT_ID}/chargebacks/{CHARGEBACK_ID}", "payment_chargeback_single" + ) payment = client.payments.get(PAYMENT_ID) chargeback = payment.chargebacks.get(CHARGEBACK_ID) @@ -62,7 +93,9 @@ def test_payment_chargeback_get_related_payment(client, response): def test_payment_chargeback_get_related_settlement(client, response): response.get(f"https://api.mollie.com/v2/payments/{PAYMENT_ID}", "payment_single") - response.get(f"https://api.mollie.com/v2/payments/{PAYMENT_ID}/chargebacks/{CHARGEBACK_ID}", "chargeback_single") + response.get( + f"https://api.mollie.com/v2/payments/{PAYMENT_ID}/chargebacks/{CHARGEBACK_ID}", "payment_chargeback_single" + ) response.get(f"https://api.mollie.com/v2/settlements/{SETTLEMENT_ID}", "settlement_single") payment = client.payments.get(PAYMENT_ID) diff --git a/tests/test_profile_chargebacks.py b/tests/test_profile_chargebacks.py index 98039370..185047a3 100644 --- a/tests/test_profile_chargebacks.py +++ b/tests/test_profile_chargebacks.py @@ -1,3 +1,5 @@ +from responses import matchers + from mollie.api.objects.chargeback import Chargeback from .utils import assert_list_object @@ -8,8 +10,38 @@ def test_get_profile_chargebacks(oauth_client, response): """Get chargebacks relevant to profile by profile id.""" response.get(f"https://api.mollie.com/v2/profiles/{PROFILE_ID}", "profile_single") - response.get(f"https://api.mollie.com/v2/chargebacks?profileId={PROFILE_ID}", "chargebacks_list") + response.get( + "https://api.mollie.com/v2/chargebacks", + "profile_chargebacks_list", + match=[matchers.query_string_matcher(f"profileId={PROFILE_ID}")], + ) profile = oauth_client.profiles.get(PROFILE_ID) chargebacks = profile.chargebacks.list() assert_list_object(chargebacks, Chargeback) + + +def test_list_profile_chargebacks_pagination(oauth_client, response): + """Retrieve a list of paginated profile chargebacks.""" + response.get(f"https://api.mollie.com/v2/profiles/{PROFILE_ID}", "profile_single") + response.get( + "https://api.mollie.com/v2/chargebacks", + "profile_chargebacks_list", + match=[matchers.query_string_matcher(f"profileId={PROFILE_ID}")], + ) + response.get( + "https://api.mollie.com/v2/chargebacks", + "profile_chargebacks_list_more", + match=[matchers.query_string_matcher(f"profileId={PROFILE_ID}&from=chb_n9z0tq")], + ) + + profile = oauth_client.profiles.get(PROFILE_ID) + first_chargebacks_page = profile.chargebacks.list() + assert first_chargebacks_page.has_previous() is False + assert first_chargebacks_page.has_next() is True + + second_chargebacks_page = first_chargebacks_page.get_next() + assert_list_object(second_chargebacks_page, Chargeback) + + subscription = next(second_chargebacks_page) + assert subscription.id == "chb_n9z0tq" diff --git a/tests/test_settlement_chargebacks.py b/tests/test_settlement_chargebacks.py index 71fc9bc8..6a1180a7 100644 --- a/tests/test_settlement_chargebacks.py +++ b/tests/test_settlement_chargebacks.py @@ -1,17 +1,45 @@ +from responses import matchers + from mollie.api.objects.chargeback import Chargeback from .utils import assert_list_object PAYMENT_ID = "tr_7UhSN1zuXS" SETTLEMENT_ID = "stl_jDk30akdN" -CHARGEBACK_ID = "chb_n9z0tp" def test_list_settlement_chargebacks(oauth_client, response): """Get a list of chargebacks related to a settlement.""" response.get(f"https://api.mollie.com/v2/settlements/{SETTLEMENT_ID}", "settlement_single") - response.get(f"https://api.mollie.com/v2/settlements/{SETTLEMENT_ID}/chargebacks", "chargebacks_list") + response.get(f"https://api.mollie.com/v2/settlements/{SETTLEMENT_ID}/chargebacks", "settlement_chargebacks_list") settlement = oauth_client.settlements.get(SETTLEMENT_ID) chargebacks = settlement.chargebacks.list() assert_list_object(chargebacks, Chargeback) + + +def test_list_settlement_chargebacks_pagination(oauth_client, response): + """Retrieve a list of paginated settlement chargebacks.""" + response.get(f"https://api.mollie.com/v2/settlements/{SETTLEMENT_ID}", "settlement_single") + + response.get( + f"https://api.mollie.com/v2/settlements/{SETTLEMENT_ID}/chargebacks", + "settlement_chargebacks_list", + match=[matchers.query_string_matcher("")], + ) + response.get( + f"https://api.mollie.com/v2/settlements/{SETTLEMENT_ID}/chargebacks", + "settlement_chargebacks_list_more", + match=[matchers.query_string_matcher("from=chb_n9z0tq")], + ) + + settlement = oauth_client.settlements.get(SETTLEMENT_ID) + first_chargebacks_page = settlement.chargebacks.list() + assert first_chargebacks_page.has_previous() is False + assert first_chargebacks_page.has_next() is True + + second_chargebacks_page = first_chargebacks_page.get_next() + assert_list_object(second_chargebacks_page, Chargeback) + + subscription = next(second_chargebacks_page) + assert subscription.id == "chb_n9z0tq" diff --git a/tests/test_subscriptions.py b/tests/test_subscriptions.py index 0f9d3e5a..c7fdbf32 100644 --- a/tests/test_subscriptions.py +++ b/tests/test_subscriptions.py @@ -1,11 +1,35 @@ +from responses import matchers + from mollie.api.objects.subscription import Subscription from .utils import assert_list_object -def test_list_customers(client, response): +def test_list_subscriptions(client, response): """Retrieve a list of existing subscriptions.""" response.get("https://api.mollie.com/v2/subscriptions", "subscriptions_list") subscriptions = client.subscriptions.list() assert_list_object(subscriptions, Subscription) + + +def test_list_subscription_pagination(client, response): + """Retrieve a list of paginated subscriptions.""" + response.get( + "https://api.mollie.com/v2/subscriptions", "subscriptions_list", match=[matchers.query_string_matcher("")] + ) + response.get( + "https://api.mollie.com/v2/subscriptions", + "subscriptions_list_more", + match=[matchers.query_string_matcher("from=sub_rVKGtNd6s6")], + ) + + first_subscriptions_page = client.subscriptions.list() + assert first_subscriptions_page.has_previous() is False + assert first_subscriptions_page.has_next() is True + + second_subscriptions_page = first_subscriptions_page.get_next() + assert_list_object(second_subscriptions_page, Subscription) + + subscription = next(second_subscriptions_page) + assert subscription.id == "sub_rVKGtNd6s6" diff --git a/tests/utils.py b/tests/utils.py index d37430a4..f01c99ef 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,9 +1,9 @@ -from mollie.api.objects.list import ObjectList +from mollie.api.objects.list import ListBase def assert_list_object(obj, object_type, count=None): """Assert that a List object is correctly working, and has sane contents.""" - assert isinstance(obj, ObjectList), f"Object {obj} is not a ObjectList instance." + assert isinstance(obj, ListBase), f"Object {obj} is not a ListBase instance." assert isinstance(obj.count, int), "ObjectList count is not an integer." if count is not None: assert obj.count == count, "ObjectList does not contain the expected number of items."