Skip to content

Commit 9cdc20e

Browse files
feat(customers): Add new routes scoped to cusomters (#338)
* feat(customers): Add invoices index scoped route * feat(customers): Add credit_notes index scoped route
1 parent a6a19c7 commit 9cdc20e

18 files changed

Lines changed: 518 additions & 0 deletions

lago_python_client/client.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
from .coupons.clients import AppliedCouponClient, CouponClient
77
from .credit_notes.clients import CreditNoteClient
88
from .customers.clients import CustomerClient
9+
from .customers.applied_coupons_client import CustomerAppliedCouponsClient
10+
from .customers.credit_notes_client import CustomerCreditNotesClient
11+
from .customers.invoices_client import CustomerInvoicesClient
12+
from .customers.payments_client import CustomerPaymentsClient
13+
from .customers.payment_requests_client import CustomerPaymentRequestsClient
14+
from .customers.subscriptions_client import CustomerSubscriptionsClient
15+
from .customers.wallets_client import CustomerWalletsClient
916
from .events.clients import EventClient
1017
from .fees.clients import FeeClient
1118
from .functools_ext import callable_cached_property
@@ -92,6 +99,34 @@ def credit_notes(self) -> CreditNoteClient:
9299
def customers(self) -> CustomerClient:
93100
return CustomerClient(self.base_api_url, self.api_key)
94101

102+
@callable_cached_property
103+
def customer_applied_coupons(self) -> CustomerAppliedCouponsClient:
104+
return CustomerAppliedCouponsClient(self.base_api_url, self.api_key)
105+
106+
@callable_cached_property
107+
def customer_credit_notes(self) -> CustomerCreditNotesClient:
108+
return CustomerCreditNotesClient(self.base_api_url, self.api_key)
109+
110+
@callable_cached_property
111+
def customer_invoices(self) -> CustomerInvoicesClient:
112+
return CustomerInvoicesClient(self.base_api_url, self.api_key)
113+
114+
@callable_cached_property
115+
def customer_payments(self) -> CustomerPaymentsClient:
116+
return CustomerPaymentsClient(self.base_api_url, self.api_key)
117+
118+
@callable_cached_property
119+
def customer_payment_requests(self) -> CustomerPaymentRequestsClient:
120+
return CustomerPaymentRequestsClient(self.base_api_url, self.api_key)
121+
122+
@callable_cached_property
123+
def customer_subscriptions(self) -> CustomerSubscriptionsClient:
124+
return CustomerSubscriptionsClient(self.base_api_url, self.api_key)
125+
126+
@callable_cached_property
127+
def customer_wallets(self) -> CustomerWalletsClient:
128+
return CustomerWalletsClient(self.base_api_url, self.api_key)
129+
95130
@callable_cached_property
96131
def events(self) -> EventClient:
97132
return EventClient(self.base_api_url, self.api_key, self.base_ingest_api_url)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from typing import ClassVar, Type
2+
3+
from ..base_client import BaseClient
4+
from ..mixins import FindAllChildrenCommandMixin
5+
from ..models.applied_coupon import AppliedCouponResponse
6+
from ..client import CustomerClient
7+
8+
9+
class CustomerAppliedCouponsClient(FindAllChildrenCommandMixin, BaseClient):
10+
PARENT_API_RESOURCE: ClassVar[str] = CustomerClient.API_RESOURCE
11+
API_RESOURCE: ClassVar[str] = "applied_coupons"
12+
RESPONSE_MODEL: ClassVar[Type[AppliedCouponResponse]] = AppliedCouponResponse
13+
ROOT_NAME: ClassVar[str] = "applied_coupon"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from typing import ClassVar, Type
2+
3+
from ..base_client import BaseClient
4+
from ..mixins import FindAllChildrenCommandMixin
5+
from ..models.credit_note import CreditNoteResponse
6+
from ..client import CustomerClient
7+
8+
9+
class CustomerCreditNotesClient(FindAllChildrenCommandMixin, BaseClient):
10+
PARENT_API_RESOURCE: ClassVar[str] = CustomerClient.API_RESOURCE
11+
API_RESOURCE: ClassVar[str] = "credit_notes"
12+
RESPONSE_MODEL: ClassVar[Type[CreditNoteResponse]] = CreditNoteResponse
13+
ROOT_NAME: ClassVar[str] = "credit_note"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from typing import ClassVar, Type
2+
3+
from ..base_client import BaseClient
4+
from ..mixins import FindAllChildrenCommandMixin
5+
from ..models.invoice import InvoiceResponse
6+
from ..client import CustomerClient
7+
8+
9+
class CustomerInvoicesClient(FindAllChildrenCommandMixin, BaseClient):
10+
PARENT_API_RESOURCE: ClassVar[str] = CustomerClient.API_RESOURCE
11+
API_RESOURCE: ClassVar[str] = "invoices"
12+
RESPONSE_MODEL: ClassVar[Type[InvoiceResponse]] = InvoiceResponse
13+
ROOT_NAME: ClassVar[str] = "invoice"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from typing import ClassVar, Type
2+
3+
from ..base_client import BaseClient
4+
from ..mixins import FindAllChildrenCommandMixin
5+
from ..models.payment_request import PaymentRequestResponse
6+
from ..client import CustomerClient
7+
8+
9+
class CustomerPaymentRequestsClient(FindAllChildrenCommandMixin, BaseClient):
10+
PARENT_API_RESOURCE: ClassVar[str] = CustomerClient.API_RESOURCE
11+
API_RESOURCE: ClassVar[str] = "payment_requests"
12+
RESPONSE_MODEL: ClassVar[Type[PaymentRequestResponse]] = PaymentRequestResponse
13+
ROOT_NAME: ClassVar[str] = "payment_request"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from typing import ClassVar, Type
2+
3+
from ..base_client import BaseClient
4+
from ..mixins import FindAllChildrenCommandMixin
5+
from ..models.payment import PaymentResponse
6+
from ..client import CustomerClient
7+
8+
9+
class CustomerPaymentsClient(FindAllChildrenCommandMixin, BaseClient):
10+
PARENT_API_RESOURCE: ClassVar[str] = CustomerClient.API_RESOURCE
11+
API_RESOURCE: ClassVar[str] = "payments"
12+
RESPONSE_MODEL: ClassVar[Type[PaymentResponse]] = PaymentResponse
13+
ROOT_NAME: ClassVar[str] = "payment"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from typing import ClassVar, Type
2+
3+
from ..base_client import BaseClient
4+
from ..mixins import FindAllChildrenCommandMixin
5+
from ..models.subscription import SubscriptionResponse
6+
from ..client import CustomerClient
7+
8+
9+
class CustomerSubscriptionsClient(FindAllChildrenCommandMixin, BaseClient):
10+
PARENT_API_RESOURCE: ClassVar[str] = CustomerClient.API_RESOURCE
11+
API_RESOURCE: ClassVar[str] = "subscriptions"
12+
RESPONSE_MODEL: ClassVar[Type[SubscriptionResponse]] = SubscriptionResponse
13+
ROOT_NAME: ClassVar[str] = "subscription"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from typing import ClassVar, Type
2+
3+
from ..base_client import BaseClient
4+
from ..mixins import FindAllChildrenCommandMixin
5+
from ..models.wallet import WalletResponse
6+
from ..client import CustomerClient
7+
8+
9+
class CustomerWalletsClient(FindAllChildrenCommandMixin, BaseClient):
10+
PARENT_API_RESOURCE: ClassVar[str] = CustomerClient.API_RESOURCE
11+
API_RESOURCE: ClassVar[str] = "wallets"
12+
RESPONSE_MODEL: ClassVar[Type[WalletResponse]] = WalletResponse
13+
ROOT_NAME: ClassVar[str] = "wallet"

lago_python_client/mixins.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737

3838

3939
class _ClientMixin(Protocol[_PM]):
40+
@property
41+
def PARENT_API_RESOURCE(self) -> str: ...
4042
@property
4143
def API_RESOURCE(self) -> str: ...
4244
@property
@@ -140,6 +142,36 @@ def find_all(
140142
)
141143

142144

145+
class FindAllChildrenCommandMixin(Generic[_M]):
146+
"""Client mixin with `find_all` command scoped to a parent resource."""
147+
148+
def find_all(
149+
self: _ClientMixin[_M],
150+
resource_id: str,
151+
options: QueryPairs = {},
152+
timetour: Optional[httpx.Timeout] = None,
153+
) -> Mapping[str, Any]:
154+
"""Execute `find_all` child command."""
155+
# Send request and save response
156+
api_response: Response = send_get_request(
157+
url=make_url(
158+
origin=self.base_url,
159+
path_parts=(
160+
self.PARENT_API_RESOURCE,
161+
resource_id,
162+
self.API_RESOURCE,
163+
),
164+
query_pairs=options,
165+
),
166+
)
167+
168+
return prepare_index_response(
169+
api_resource=self.API_RESOURCE,
170+
response_model=self.RESPONSE_MODEL,
171+
data=get_response_data(response=api_response),
172+
)
173+
174+
143175
class FindCommandMixin(Generic[_M]):
144176
"""Client mixin with `find` command."""
145177

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import pytest
2+
from pytest_httpx import HTTPXMock
3+
4+
from lago_python_client.client import Client
5+
from lago_python_client.exceptions import LagoApiError
6+
7+
from .utils.mixin import mock_response
8+
9+
10+
def test_valid_find_all_customer_applied_coupons_request(httpx_mock: HTTPXMock):
11+
client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d")
12+
13+
httpx_mock.add_response(
14+
method="GET",
15+
url="https://api.getlago.com/api/v1/customers/external_customer_id/applied_coupons",
16+
content=mock_response(mock="applied_coupon_index"),
17+
)
18+
response = client.customer_applied_coupons.find_all(resource_id="external_customer_id")
19+
20+
assert response["applied_coupons"][0].lago_id == "b7ab2926-1de8-4428-9bcd-779314ac129b"
21+
assert response["meta"]["current_page"] == 1
22+
23+
24+
def test_valid_find_all_customer_applied_coupons_request_with_options(httpx_mock: HTTPXMock):
25+
client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d")
26+
27+
httpx_mock.add_response(
28+
method="GET",
29+
url="https://api.getlago.com/api/v1/customers/external_customer_id/applied_coupons?per_page=2&page=1",
30+
content=mock_response(mock="applied_coupon_index"),
31+
)
32+
response = client.customer_applied_coupons.find_all(
33+
resource_id="external_customer_id", options={"per_page": 2, "page": 1}
34+
)
35+
36+
assert response["applied_coupons"][0].lago_id == "b7ab2926-1de8-4428-9bcd-779314ac129b"
37+
assert response["meta"]["current_page"] == 1
38+
39+
40+
def test_invalid_find_all_applied_coupon_request(httpx_mock: HTTPXMock):
41+
client = Client(api_key="invalid")
42+
43+
httpx_mock.add_response(
44+
method="GET",
45+
url="https://api.getlago.com/api/v1/customers/external_customer_id/applied_coupons",
46+
status_code=404,
47+
content=b"",
48+
)
49+
50+
with pytest.raises(LagoApiError):
51+
client.customer_applied_coupons.find_all(resource_id="external_customer_id")

0 commit comments

Comments
 (0)