Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

custom_method decorator for defining static methods for custom API requests #547

Merged
merged 2 commits into from
Apr 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions stripe/api_resources/abstract/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
)
from stripe.api_resources.abstract.verify_mixin import VerifyMixin

from stripe.api_resources.abstract.custom_method import custom_method

from stripe.api_resources.abstract.nested_resource_class_methods import (
nested_resource_class_methods,
)
43 changes: 43 additions & 0 deletions stripe/api_resources/abstract/custom_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from __future__ import absolute_import, division, print_function

from stripe import util
from stripe.six.moves.urllib.parse import quote_plus


def custom_method(name, http_verb, http_path=None):
if http_verb not in ["get", "post", "delete"]:
raise ValueError(
"Invalid http_verb: %s. Must be one of 'get', 'post' or 'delete'"
% http_verb
)
if http_path is None:
http_path = name

def wrapper(cls):
def custom_method_request(cls, sid, **params):
url = "%s/%s/%s" % (
cls.class_url(),
quote_plus(util.utf8(sid)),
http_path,
)
return cls._static_request(http_verb, url, **params)

existing_method = getattr(cls, name, None)
if existing_method is None:
setattr(cls, name, classmethod(custom_method_request))
else:
# If a method with the same name we want to use already exists on
# the class, we assume it's an instance method. In this case, the
# new class method is prefixed with `_cls_`, and the original
# instance method is decorated with `util.class_method_variant` so
# that the new class method is called when the original method is
# called as a class method.
setattr(cls, "_cls_" + name, classmethod(custom_method_request))
instance_method = util.class_method_variant("_cls_" + name)(
existing_method
)
setattr(cls, name, instance_method)

return cls

return wrapper
3 changes: 2 additions & 1 deletion stripe/api_resources/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
from stripe.api_resources.abstract import UpdateableAPIResource
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import nested_resource_class_methods

from stripe.api_resources.abstract import custom_method
from stripe.six.moves.urllib.parse import quote_plus


@custom_method("reject", http_verb="post")
@nested_resource_class_methods(
"external_account",
operations=["create", "retrieve", "update", "delete", "list"],
Expand Down
2 changes: 2 additions & 0 deletions stripe/api_resources/charge.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
from stripe.api_resources.abstract import CreateableAPIResource
from stripe.api_resources.abstract import UpdateableAPIResource
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import custom_method


@custom_method("capture", http_verb="post")
class Charge(
CreateableAPIResource, ListableAPIResource, UpdateableAPIResource
):
Expand Down
2 changes: 2 additions & 0 deletions stripe/api_resources/customer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
from stripe.api_resources.abstract import DeletableAPIResource
from stripe.api_resources.abstract import UpdateableAPIResource
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import custom_method
from stripe.api_resources.abstract import nested_resource_class_methods


@custom_method("delete_discount", http_verb="delete", http_path="discount")
@nested_resource_class_methods(
"source", operations=["create", "retrieve", "update", "delete", "list"]
)
Expand Down
2 changes: 2 additions & 0 deletions stripe/api_resources/dispute.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
from stripe import util
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import UpdateableAPIResource
from stripe.api_resources.abstract import custom_method


@custom_method("close", http_verb="post")
class Dispute(ListableAPIResource, UpdateableAPIResource):
OBJECT_NAME = "dispute"

Expand Down
6 changes: 6 additions & 0 deletions stripe/api_resources/invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@
from stripe.api_resources.abstract import DeletableAPIResource
from stripe.api_resources.abstract import UpdateableAPIResource
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import custom_method


@custom_method("finalize_invoice", http_verb="post", http_path="finalize")
@custom_method("mark_uncollectible", http_verb="post")
@custom_method("pay", http_verb="post")
@custom_method("send_invoice", http_verb="post", http_path="send")
@custom_method("void_invoice", http_verb="post", http_path="void")
class Invoice(
CreateableAPIResource,
UpdateableAPIResource,
Expand Down
3 changes: 3 additions & 0 deletions stripe/api_resources/issuing/authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
from stripe import util
from stripe.api_resources.abstract import UpdateableAPIResource
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import custom_method


@custom_method("approve", http_verb="post")
@custom_method("decline", http_verb="post")
class Authorization(ListableAPIResource, UpdateableAPIResource):
OBJECT_NAME = "issuing.authorization"

Expand Down
2 changes: 2 additions & 0 deletions stripe/api_resources/issuing/card.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
from stripe.api_resources.abstract import CreateableAPIResource
from stripe.api_resources.abstract import UpdateableAPIResource
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import custom_method


@custom_method("details", http_verb="get")
class Card(CreateableAPIResource, ListableAPIResource, UpdateableAPIResource):
OBJECT_NAME = "issuing.card"

Expand Down
3 changes: 3 additions & 0 deletions stripe/api_resources/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
from stripe.api_resources.abstract import CreateableAPIResource
from stripe.api_resources.abstract import UpdateableAPIResource
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import custom_method


@custom_method("pay", http_verb="post")
@custom_method("return_order", http_verb="post", http_path="returns")
class Order(CreateableAPIResource, UpdateableAPIResource, ListableAPIResource):
OBJECT_NAME = "order"

Expand Down
4 changes: 4 additions & 0 deletions stripe/api_resources/payment_intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
from stripe.api_resources.abstract import CreateableAPIResource
from stripe.api_resources.abstract import UpdateableAPIResource
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import custom_method


@custom_method("cancel", http_verb="post")
@custom_method("capture", http_verb="post")
@custom_method("confirm", http_verb="post")
class PaymentIntent(
CreateableAPIResource, UpdateableAPIResource, ListableAPIResource
):
Expand Down
3 changes: 3 additions & 0 deletions stripe/api_resources/payment_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
from stripe.api_resources.abstract import CreateableAPIResource
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import UpdateableAPIResource
from stripe.api_resources.abstract import custom_method


@custom_method("attach", http_verb="post")
@custom_method("detach", http_verb="post")
class PaymentMethod(
CreateableAPIResource, ListableAPIResource, UpdateableAPIResource
):
Expand Down
2 changes: 2 additions & 0 deletions stripe/api_resources/payout.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
from stripe.api_resources.abstract import CreateableAPIResource
from stripe.api_resources.abstract import UpdateableAPIResource
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import custom_method


@custom_method("cancel", http_verb="post")
class Payout(
CreateableAPIResource, UpdateableAPIResource, ListableAPIResource
):
Expand Down
2 changes: 2 additions & 0 deletions stripe/api_resources/review.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

from stripe import util
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import custom_method


@custom_method("approve", http_verb="post")
class Review(ListableAPIResource):
OBJECT_NAME = "review"

Expand Down
2 changes: 2 additions & 0 deletions stripe/api_resources/subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
from stripe.api_resources.abstract import DeletableAPIResource
from stripe.api_resources.abstract import UpdateableAPIResource
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import custom_method


@custom_method("delete_discount", http_verb="delete", http_path="discount")
class Subscription(
CreateableAPIResource,
DeletableAPIResource,
Expand Down
3 changes: 3 additions & 0 deletions stripe/api_resources/subscription_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
from stripe.api_resources.abstract import UpdateableAPIResource
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import nested_resource_class_methods
from stripe.api_resources.abstract import custom_method


@custom_method("cancel", http_verb="post")
@custom_method("release", http_verb="post")
@nested_resource_class_methods("revision", operations=["retrieve", "list"])
class SubscriptionSchedule(
CreateableAPIResource, UpdateableAPIResource, ListableAPIResource
Expand Down
2 changes: 2 additions & 0 deletions stripe/api_resources/topup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
from stripe.api_resources.abstract import CreateableAPIResource
from stripe.api_resources.abstract import UpdateableAPIResource
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import custom_method


@custom_method("cancel", http_verb="post")
class Topup(CreateableAPIResource, ListableAPIResource, UpdateableAPIResource):
OBJECT_NAME = "topup"

Expand Down
2 changes: 2 additions & 0 deletions stripe/api_resources/transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
from stripe.api_resources.abstract import UpdateableAPIResource
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import nested_resource_class_methods
from stripe.api_resources.abstract import custom_method


@custom_method("cancel", http_verb="post")
@nested_resource_class_methods(
"reversal", operations=["create", "retrieve", "update", "list"]
)
Expand Down
18 changes: 16 additions & 2 deletions tests/api_resources/issuing/test_authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def test_is_saveable(self, request_mock):
assert isinstance(resource, stripe.issuing.Authorization)
assert resource is authorization

def test_is_approveable(self, request_mock):
def test_can_approve(self, request_mock):
resource = stripe.issuing.Authorization.retrieve(TEST_RESOURCE_ID)
authorization = resource.approve()
request_mock.assert_requested(
Expand All @@ -48,11 +48,25 @@ def test_is_approveable(self, request_mock):
assert isinstance(resource, stripe.issuing.Authorization)
assert resource is authorization

def test_is_declineable(self, request_mock):
def test_can_approve_classmethod(self, request_mock):
resource = stripe.issuing.Authorization.approve(TEST_RESOURCE_ID)
request_mock.assert_requested(
"post", "/v1/issuing/authorizations/%s/approve" % TEST_RESOURCE_ID
)
assert isinstance(resource, stripe.issuing.Authorization)

def test_can_decline(self, request_mock):
resource = stripe.issuing.Authorization.retrieve(TEST_RESOURCE_ID)
authorization = resource.decline()
request_mock.assert_requested(
"post", "/v1/issuing/authorizations/%s/decline" % TEST_RESOURCE_ID
)
assert isinstance(resource, stripe.issuing.Authorization)
assert resource is authorization

def test_can_decline_classmethod(self, request_mock):
resource = stripe.issuing.Authorization.decline(TEST_RESOURCE_ID)
request_mock.assert_requested(
"post", "/v1/issuing/authorizations/%s/decline" % TEST_RESOURCE_ID
)
assert isinstance(resource, stripe.issuing.Authorization)
7 changes: 7 additions & 0 deletions tests/api_resources/issuing/test_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,10 @@ def test_can_retrieve_details(self, request_mock):
"get", "/v1/issuing/cards/%s/details" % TEST_RESOURCE_ID
)
assert isinstance(card_details, stripe.issuing.CardDetails)

def test_can_retrieve_details_classmethod(self, request_mock):
card_details = stripe.issuing.Card.details(TEST_RESOURCE_ID)
request_mock.assert_requested(
"get", "/v1/issuing/cards/%s/details" % TEST_RESOURCE_ID
)
assert isinstance(card_details, stripe.issuing.CardDetails)
13 changes: 12 additions & 1 deletion tests/api_resources/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,22 @@ def test_can_reject(self, request_mock):
account = stripe.Account.retrieve(TEST_RESOURCE_ID)
resource = account.reject(reason="fraud")
request_mock.assert_requested(
"post", "/v1/accounts/%s/reject" % TEST_RESOURCE_ID
"post",
"/v1/accounts/%s/reject" % TEST_RESOURCE_ID,
{"reason": "fraud"},
)
assert isinstance(resource, stripe.Account)
assert resource is account

def test_can_reject_classmethod(self, request_mock):
resource = stripe.Account.reject(TEST_RESOURCE_ID, reason="fraud")
request_mock.assert_requested(
"post",
"/v1/accounts/%s/reject" % TEST_RESOURCE_ID,
{"reason": "fraud"},
)
assert isinstance(resource, stripe.Account)

def test_is_deauthorizable(self, request_mock):
account = stripe.Account.retrieve(TEST_RESOURCE_ID)
request_mock.stub_request(
Expand Down
9 changes: 8 additions & 1 deletion tests/api_resources/test_charge.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,21 @@ def test_is_refundable(self, request_mock):
)
assert isinstance(resource, stripe.Charge)

def test_is_capturable(self, request_mock):
def test_can_capture(self, request_mock):
charge = stripe.Charge.retrieve(TEST_RESOURCE_ID)
resource = charge.capture()
request_mock.assert_requested(
"post", "/v1/charges/%s/capture" % TEST_RESOURCE_ID
)
assert isinstance(resource, stripe.Charge)

def test_can_capture_classmethod(self, request_mock):
resource = stripe.Charge.capture(TEST_RESOURCE_ID)
request_mock.assert_requested(
"post", "/v1/charges/%s/capture" % TEST_RESOURCE_ID
)
assert isinstance(resource, stripe.Charge)

def test_can_update_dispute(self, request_mock):
charge = stripe.Charge.retrieve(TEST_RESOURCE_ID)
resource = charge.update_dispute()
Expand Down
6 changes: 6 additions & 0 deletions tests/api_resources/test_customer.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ def test_can_delete_discount(self, request_mock):
"delete", "/v1/customers/%s/discount" % TEST_RESOURCE_ID
)

def test_can_delete_discount_class_method(self, request_mock):
stripe.Customer.delete_discount(TEST_RESOURCE_ID)
request_mock.assert_requested(
"delete", "/v1/customers/%s/discount" % TEST_RESOURCE_ID
)


class TestCustomerSources(object):
def test_is_creatable(self, request_mock):
Expand Down
10 changes: 9 additions & 1 deletion tests/api_resources/test_dispute.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,17 @@ def test_is_modifiable(self, request_mock):
)
assert isinstance(resource, stripe.Dispute)

def test_is_closeable(self, request_mock):
def test_can_close(self, request_mock):
resource = stripe.Dispute.retrieve(TEST_RESOURCE_ID)
resource.close()
request_mock.assert_requested(
"post", "/v1/disputes/%s/close" % TEST_RESOURCE_ID
)
assert isinstance(resource, stripe.Dispute)

def test_can_close_classmethod(self, request_mock):
resource = stripe.Dispute.close(TEST_RESOURCE_ID)
request_mock.assert_requested(
"post", "/v1/disputes/%s/close" % TEST_RESOURCE_ID
)
assert isinstance(resource, stripe.Dispute)
Loading