diff --git a/.travis.yml b/.travis.yml index e21444eea..3446d53c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ cache: env: global: # If changing this number, please also change it in `tests/conftest.py`. - - STRIPE_MOCK_VERSION=0.30.0 + - STRIPE_MOCK_VERSION=0.32.0 before_install: # Unpack and start stripe-mock so that the test suite can talk to it diff --git a/stripe/__init__.py b/stripe/__init__.py index e8824af59..24798808b 100644 --- a/stripe/__init__.py +++ b/stripe/__init__.py @@ -13,7 +13,7 @@ client_id = None api_base = 'https://api.stripe.com' connect_api_base = 'https://connect.stripe.com' -upload_api_base = 'https://uploads.stripe.com' +upload_api_base = 'https://files.stripe.com' api_version = None verify_ssl_certs = True proxy = None diff --git a/stripe/api_resources/__init__.py b/stripe/api_resources/__init__.py index fc39e42e3..e63c75a9c 100644 --- a/stripe/api_resources/__init__.py +++ b/stripe/api_resources/__init__.py @@ -23,8 +23,9 @@ from stripe.api_resources.ephemeral_key import EphemeralKey from stripe.api_resources.event import Event from stripe.api_resources.exchange_rate import ExchangeRate +from stripe.api_resources.file import File +from stripe.api_resources.file import FileUpload from stripe.api_resources.file_link import FileLink -from stripe.api_resources.file_upload import FileUpload from stripe.api_resources.invoice import Invoice from stripe.api_resources.invoice_item import InvoiceItem from stripe.api_resources.invoice_line_item import InvoiceLineItem diff --git a/stripe/api_resources/file_upload.py b/stripe/api_resources/file.py similarity index 60% rename from stripe/api_resources/file_upload.py rename to stripe/api_resources/file.py index c596873c8..f51d2c531 100644 --- a/stripe/api_resources/file_upload.py +++ b/stripe/api_resources/file.py @@ -5,12 +5,13 @@ from stripe.api_resources.abstract import ListableAPIResource -class FileUpload(ListableAPIResource): - OBJECT_NAME = 'file_upload' - - @classmethod - def api_base(cls): - return stripe.upload_api_base +class File(ListableAPIResource): + # This resource can have two different object names. In latter API + # versions, only `file` is used, but since stripe-python may be used with + # any API version, we need to support deserializing the older + # `file_upload` object into the same class. + OBJECT_NAME = 'file' + OBJECT_NAME_ALT = 'file_upload' @classmethod def class_url(cls): @@ -20,7 +21,7 @@ def class_url(cls): def create(cls, api_key=None, api_version=None, stripe_account=None, **params): requestor = api_requestor.APIRequestor( - api_key, api_base=cls.api_base(), api_version=api_version, + api_key, api_base=stripe.upload_api_base, api_version=api_version, account=stripe_account) url = cls.class_url() supplied_headers = { @@ -30,3 +31,7 @@ def create(cls, api_key=None, api_version=None, stripe_account=None, 'post', url, params=params, headers=supplied_headers) return util.convert_to_stripe_object(response, api_key, api_version, stripe_account) + + +# For backwards compatibility, the `File` class is aliased to `FileUpload`. +FileUpload = File diff --git a/stripe/util.py b/stripe/util.py index 6d9f3bd3f..2b83c3f80 100644 --- a/stripe/util.py +++ b/stripe/util.py @@ -162,8 +162,9 @@ def load_object_classes(): api_resources.EphemeralKey.OBJECT_NAME: api_resources.EphemeralKey, api_resources.Event.OBJECT_NAME: api_resources.Event, api_resources.ExchangeRate.OBJECT_NAME: api_resources.ExchangeRate, + api_resources.File.OBJECT_NAME: api_resources.File, + api_resources.File.OBJECT_NAME_ALT: api_resources.File, api_resources.FileLink.OBJECT_NAME: api_resources.FileLink, - api_resources.FileUpload.OBJECT_NAME: api_resources.FileUpload, api_resources.Invoice.OBJECT_NAME: api_resources.Invoice, api_resources.InvoiceItem.OBJECT_NAME: api_resources.InvoiceItem, api_resources.InvoiceLineItem.OBJECT_NAME: diff --git a/tests/api_resources/issuing/test_dispute.py b/tests/api_resources/issuing/test_dispute.py index 9f410506a..940d01602 100644 --- a/tests/api_resources/issuing/test_dispute.py +++ b/tests/api_resources/issuing/test_dispute.py @@ -10,7 +10,7 @@ class TestDispute(object): def test_is_creatable(self, request_mock): resource = stripe.issuing.Dispute.create( reason='fraudulent', - transaction='ipi_123' + disputed_transaction='ipi_123' ) request_mock.assert_requested( 'post', diff --git a/tests/api_resources/test_file.py b/tests/api_resources/test_file.py new file mode 100644 index 000000000..7f601b3df --- /dev/null +++ b/tests/api_resources/test_file.py @@ -0,0 +1,67 @@ +from __future__ import absolute_import, division, print_function + +import tempfile + +import pytest + +import stripe + + +TEST_RESOURCE_ID = 'file_123' + + +class TestFile(object): + @pytest.fixture(scope='function') + def setup_upload_api_base(self): + stripe.upload_api_base = stripe.api_base + stripe.api_base = None + yield + stripe.api_base = stripe.upload_api_base + stripe.upload_api_base = 'https://files.stripe.com' + + def test_is_listable(self, request_mock): + resources = stripe.File.list() + request_mock.assert_requested( + 'get', + '/v1/files' + ) + assert isinstance(resources.data, list) + assert isinstance(resources.data[0], stripe.File) + + def test_is_retrievable(self, request_mock): + resource = stripe.File.retrieve(TEST_RESOURCE_ID) + request_mock.assert_requested( + 'get', + '/v1/files/%s' % TEST_RESOURCE_ID + ) + assert isinstance(resource, stripe.File) + + def test_is_creatable(self, setup_upload_api_base, request_mock): + stripe.multipart_data_generator.MultipartDataGenerator\ + ._initialize_boundary = lambda self: 1234567890 + test_file = tempfile.TemporaryFile() + resource = stripe.File.create( + purpose='dispute_evidence', + file=test_file + ) + request_mock.assert_api_base(stripe.upload_api_base) + request_mock.assert_requested( + 'post', + '/v1/files', + headers={ + 'Content-Type': 'multipart/form-data; boundary=1234567890', + } + ) + assert isinstance(resource, stripe.File) + + def test_deserializes_from_file(self): + obj = stripe.util.convert_to_stripe_object({ + 'object': 'file', + }) + assert isinstance(obj, stripe.File) + + def test_deserializes_from_file_upload(self): + obj = stripe.util.convert_to_stripe_object({ + 'object': 'file_upload', + }) + assert isinstance(obj, stripe.File) diff --git a/tests/api_resources/test_file_upload.py b/tests/api_resources/test_file_upload.py index 31fbf2951..57ad5e16e 100644 --- a/tests/api_resources/test_file_upload.py +++ b/tests/api_resources/test_file_upload.py @@ -2,6 +2,8 @@ import tempfile +import pytest + import stripe @@ -9,6 +11,14 @@ class TestFileUpload(object): + @pytest.fixture(scope='function') + def setup_upload_api_base(self): + stripe.upload_api_base = stripe.api_base + stripe.api_base = None + yield + stripe.api_base = stripe.upload_api_base + stripe.upload_api_base = 'https://files.stripe.com' + def test_is_listable(self, request_mock): resources = stripe.FileUpload.list() request_mock.assert_requested( @@ -26,7 +36,7 @@ def test_is_retrievable(self, request_mock): ) assert isinstance(resource, stripe.FileUpload) - def test_is_creatable(self, request_mock): + def test_is_creatable(self, setup_upload_api_base, request_mock): stripe.multipart_data_generator.MultipartDataGenerator\ ._initialize_boundary = lambda self: 1234567890 test_file = tempfile.TemporaryFile() @@ -34,6 +44,7 @@ def test_is_creatable(self, request_mock): purpose='dispute_evidence', file=test_file ) + request_mock.assert_api_base(stripe.upload_api_base) request_mock.assert_requested( 'post', '/v1/files', @@ -42,3 +53,15 @@ def test_is_creatable(self, request_mock): } ) assert isinstance(resource, stripe.FileUpload) + + def test_deserializes_from_file(self): + obj = stripe.util.convert_to_stripe_object({ + 'object': 'file', + }) + assert isinstance(obj, stripe.FileUpload) + + def test_deserializes_from_file_upload(self): + obj = stripe.util.convert_to_stripe_object({ + 'object': 'file_upload', + }) + assert isinstance(obj, stripe.FileUpload) diff --git a/tests/conftest.py b/tests/conftest.py index c36ad0fba..714b3d5cd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,7 +13,7 @@ from tests.request_mock import RequestMock -MOCK_MINIMUM_VERSION = '0.30.0' +MOCK_MINIMUM_VERSION = '0.32.0' MOCK_PORT = os.environ.get('STRIPE_MOCK_PORT', 12111) @@ -43,21 +43,18 @@ def setup_stripe(): 'api_key': stripe.api_key, 'client_id': stripe.client_id, 'default_http_client': stripe.default_http_client, - 'upload_api_base': stripe.upload_api_base, } http_client = stripe.http_client.new_default_http_client() stripe.api_base = 'http://localhost:%s' % MOCK_PORT stripe.api_key = 'sk_test_123' stripe.client_id = 'ca_123' stripe.default_http_client = http_client - stripe.upload_api_base = 'http://localhost:%s' % MOCK_PORT yield http_client.close() stripe.api_base = orig_attrs['api_base'] stripe.api_key = orig_attrs['api_key'] stripe.client_id = orig_attrs['client_id'] stripe.default_http_client = orig_attrs['default_http_client'] - stripe.upload_api_base = orig_attrs['upload_api_base'] @pytest.fixture diff --git a/tests/request_mock.py b/tests/request_mock.py index f9b426914..4f047555f 100644 --- a/tests/request_mock.py +++ b/tests/request_mock.py @@ -35,6 +35,25 @@ def stub_request(self, method, url, rbody={}, rcode=200, rheaders={}): self._stub_request_handler.register(method, url, rbody, rcode, rheaders) + def assert_api_base(self, expected_api_base): + # Note that this method only checks that an API base was provided + # as a keyword argument in APIRequestor's constructor, not as a + # positional argument. + + if 'api_base' not in self.constructor_patcher.call_args[1]: + msg = ("Expected APIRequestor to have been constructed with " + "api_base='%s'. No API base was provided." % + expected_api_base) + raise AssertionError(msg) + + actual_api_base = \ + self.constructor_patcher.call_args[1]['api_base'] + if actual_api_base != expected_api_base: + msg = ("Expected APIRequestor to have been constructed with " + "api_base='%s'. Constructed with api_base='%s' " + "instead." % (expected_api_base, actual_api_base)) + raise AssertionError(msg) + def assert_api_version(self, expected_api_version): # Note that this method only checks that an API version was provided # as a keyword argument in APIRequestor's constructor, not as a