From 67ba3fe0cbaf3840eab02b0cf4367a33b764c877 Mon Sep 17 00:00:00 2001 From: Benjamin Ross Date: Sun, 25 Feb 2018 11:49:16 -0800 Subject: [PATCH 01/11] Added error handling. Added tests. Added requirements. Fixed tests --- cbpro/public_client.py | 61 ++++++++ gdax/authenticated_client.py | 290 +++++++++++++++++++++++++++++++++++ gdax/exceptions.py | 75 +++++++++ requirements.txt | 9 ++ tests/test_error_handling.py | 22 +++ 5 files changed, 457 insertions(+) create mode 100644 gdax/authenticated_client.py create mode 100644 gdax/exceptions.py create mode 100644 requirements.txt create mode 100644 tests/test_error_handling.py diff --git a/cbpro/public_client.py b/cbpro/public_client.py index e854a16..fd153a2 100644 --- a/cbpro/public_client.py +++ b/cbpro/public_client.py @@ -5,6 +5,16 @@ # For public requests to the Coinbase exchange import requests +from gdax import exceptions + + +HTTP_200_OK = 200 +HTTP_300_MULTIPLE_CHOICES = 300 +HTTP_400_BAD_REQUEST = 400 +HTTP_401_UNAUTHORIZED = 401 +HTTP_403_FORBIDDEN = 403 +HTTP_404_NOT_FOUND = 404 +HTTP_500_INTERNAL_SERVER_ERROR = 500 class PublicClient(object): @@ -28,6 +38,57 @@ def __init__(self, api_url='https://api.pro.coinbase.com', timeout=30): self.url = api_url.rstrip('/') self.auth = None self.session = requests.Session() + self.timeout = timeout + + def _is_http_success(self, code): + # type: (int) -> bool + return code >= HTTP_200_OK and code < HTTP_300_MULTIPLE_CHOICES + + def _is_http_client_error(self, code): + # type: (int) -> bool + return code >= HTTP_400_BAD_REQUEST and code < HTTP_500_INTERNAL_SERVER_ERROR + + def _is_http_server_error(self, code): + # type: (int) -> bool + return code >= HTTP_500_INTERNAL_SERVER_ERROR + + def _determine_response(self, response): + """ + Determines if GDAX response is success or error + If success, returns response json + If error, raises appropiate GdaxException + """ + if self._is_http_success(response.status_code): + return response.json() + elif self._is_http_client_error(response.status_code): + body = response.json() + message = body.get('message') + if response.status_code == HTTP_400_BAD_REQUEST: + raise exceptions.InvalidGdaxRequest(message, + HTTP_400_BAD_REQUEST) + elif response.status_code == HTTP_401_UNAUTHORIZED: + raise exceptions.UnauthorizedGdaxRequest(message, + HTTP_401_UNAUTHORIZED) + elif response.status_code == HTTP_403_FORBIDDEN: + raise exceptions.ForbiddenGdaxRequest(message, + HTTP_403_FORBIDDEN) + elif response.status_code == HTTP_404_NOT_FOUND: + raise exceptions.NotFoundGdaxRequest(message, + HTTP_404_NOT_FOUND) + else: # Other 4XX response not yet mapped + raise exceptions.UnknownGdaxClientRequest(message, + response.status_code) + + elif self._is_http_server_error(response.status_code): + body = response.json() + raise exceptions.InternalErrorGdaxRequest(body.get('message'), + HTTP_500_INTERNAL_SERVER_ERROR) + + def _get(self, path, params=None): + """Perform get request""" + + r = requests.get(self.url + path, params=params, timeout=self.timeout) + return self._determine_response(r) def get_products(self): """Get a list of available currency pairs for trading. diff --git a/gdax/authenticated_client.py b/gdax/authenticated_client.py new file mode 100644 index 0000000..f550e2b --- /dev/null +++ b/gdax/authenticated_client.py @@ -0,0 +1,290 @@ +# +# gdax/AuthenticatedClient.py +# Daniel Paquin +# +# For authenticated requests to the gdax exchange + +import hmac +import hashlib +import time +import requests +import base64 +import json +from requests.auth import AuthBase +from gdax.public_client import PublicClient +from gdax.gdax_auth import GdaxAuth + + +class AuthenticatedClient(PublicClient): + def __init__(self, key, b64secret, passphrase, api_url="https://api.gdax.com", timeout=30): + super(AuthenticatedClient, self).__init__(api_url) + self.auth = GdaxAuth(key, b64secret, passphrase) + self.timeout = timeout + + def get_account(self, account_id): + r = requests.get(self.url + '/accounts/' + account_id, auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def get_accounts(self): + return self.get_account('') + + def get_account_history(self, account_id): + result = [] + r = requests.get(self.url + '/accounts/{}/ledger'.format(account_id), auth=self.auth, timeout=self.timeout) + # r.raise_for_status() + r_json = self._determine_response(r) + result.append(r_json) + if "cb-after" in r.headers: + self.history_pagination(account_id, result, r.headers["cb-after"]) + return result + + def history_pagination(self, account_id, result, after): + r = requests.get(self.url + '/accounts/{}/ledger?after={}'.format(account_id, str(after)), auth=self.auth, timeout=self.timeout) + r_json = self._determine_response(r) + if r_json: + result.append(r_json) + if "cb-after" in r.headers: + self.history_pagination(account_id, result, r.headers["cb-after"]) + return result + + def get_account_holds(self, account_id): + result = [] + r = requests.get(self.url + '/accounts/{}/holds'.format(account_id), auth=self.auth, timeout=self.timeout) + r_json = self._determine_response(r) + result.append(r_json) + if "cb-after" in r.headers: + self.holds_pagination(account_id, result, r.headers["cb-after"]) + return result + + def holds_pagination(self, account_id, result, after): + r = requests.get(self.url + '/accounts/{}/holds?after={}'.format(account_id, str(after)), auth=self.auth, timeout=self.timeout) + r_json = self._determine_response(r) + if r_json: + result.append(r_json) + if "cb-after" in r.headers: + self.holds_pagination(account_id, result, r.headers["cb-after"]) + return result + + def buy(self, **kwargs): + kwargs["side"] = "buy" + if "product_id" not in kwargs: + kwargs["product_id"] = self.product_id + r = requests.post(self.url + '/orders', + data=json.dumps(kwargs), + auth=self.auth, + timeout=self.timeout) + return self._determine_response(r) + + def sell(self, **kwargs): + kwargs["side"] = "sell" + r = requests.post(self.url + '/orders', + data=json.dumps(kwargs), + auth=self.auth, + timeout=self.timeout) + return self._determine_response(r) + + def cancel_order(self, order_id): + r = requests.delete(self.url + '/orders/' + order_id, auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def cancel_all(self, product_id=''): + url = self.url + '/orders/' + params = {} + if product_id: + params["product_id"] = product_id + r = requests.delete(url, auth=self.auth, params=params, timeout=self.timeout) + return self._determine_response(r) + + def get_order(self, order_id): + r = requests.get(self.url + '/orders/' + order_id, auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def get_orders(self, product_id='', status=[]): + result = [] + url = self.url + '/orders/' + params = {} + if product_id: + params["product_id"] = product_id + if status: + params["status"] = status + r = requests.get(url, auth=self.auth, params=params, timeout=self.timeout) + r_json = self._determine_response(r) + result.append(r_json) + if 'cb-after' in r.headers: + self.paginate_orders(product_id, status, result, r.headers['cb-after']) + return result + + def paginate_orders(self, product_id, status, result, after): + url = self.url + '/orders' + + params = { + "after": str(after), + } + if product_id: + params["product_id"] = product_id + if status: + params["status"] = status + r = requests.get(url, auth=self.auth, params=params, timeout=self.timeout) + r_json = self._determine_response(r) + if r_json: + result.append(r_json) + if 'cb-after' in r.headers: + self.paginate_orders(product_id, status, result, r.headers['cb-after']) + return result + + def get_fills(self, order_id='', product_id='', before='', after='', limit=''): + result = [] + url = self.url + '/fills?' + if order_id: + url += "order_id={}&".format(str(order_id)) + if product_id: + url += "product_id={}&".format(product_id) + if before: + url += "before={}&".format(str(before)) + if after: + url += "after={}&".format(str(after)) + if limit: + url += "limit={}&".format(str(limit)) + r = requests.get(url, auth=self.auth, timeout=self.timeout) + r_json = self._determine_response(r) + result.append(r_json) + if 'cb-after' in r.headers and limit is not len(r_json): + return self.paginate_fills(result, r.headers['cb-after'], order_id=order_id, product_id=product_id) + return result + + def paginate_fills(self, result, after, order_id='', product_id=''): + url = self.url + '/fills?after={}&'.format(str(after)) + if order_id: + url += "order_id={}&".format(str(order_id)) + if product_id: + url += "product_id={}&".format(product_id) + r = requests.get(url, auth=self.auth, timeout=self.timeout) + r_json = self._determine_response(r) + if r_json: + result.append(r_json) + if 'cb-after' in r.headers: + return self.paginate_fills(result, r.headers['cb-after'], order_id=order_id, product_id=product_id) + return result + + def get_fundings(self, result='', status='', after=''): + if not result: + result = [] + url = self.url + '/funding?' + if status: + url += "status={}&".format(str(status)) + if after: + url += 'after={}&'.format(str(after)) + r = requests.get(url, auth=self.auth, timeout=self.timeout) + r_json = self._determine_response(r) + result.append(r_json) + if 'cb-after' in r.headers: + return self.get_fundings(result, status=status, after=r.headers['cb-after']) + return result + + def repay_funding(self, amount='', currency=''): + payload = { + "amount": amount, + "currency": currency # example: USD + } + r = requests.post(self.url + "/funding/repay", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def margin_transfer(self, margin_profile_id="", transfer_type="", currency="", amount=""): + payload = { + "margin_profile_id": margin_profile_id, + "type": transfer_type, + "currency": currency, # example: USD + "amount": amount + } + r = requests.post(self.url + "/profiles/margin-transfer", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def get_position(self): + r = requests.get(self.url + "/position", auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def close_position(self, repay_only=""): + payload = { + "repay_only": repay_only or False + } + r = requests.post(self.url + "/position/close", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def deposit(self, amount="", currency="", payment_method_id=""): + payload = { + "amount": amount, + "currency": currency, + "payment_method_id": payment_method_id + } + r = requests.post(self.url + "/deposits/payment-method", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def coinbase_deposit(self, amount="", currency="", coinbase_account_id=""): + payload = { + "amount": amount, + "currency": currency, + "coinbase_account_id": coinbase_account_id + } + r = requests.post(self.url + "/deposits/coinbase-account", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def withdraw(self, amount="", currency="", payment_method_id=""): + payload = { + "amount": amount, + "currency": currency, + "payment_method_id": payment_method_id + } + r = requests.post(self.url + "/withdrawals/payment-method", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def coinbase_withdraw(self, amount="", currency="", coinbase_account_id=""): + payload = { + "amount": amount, + "currency": currency, + "coinbase_account_id": coinbase_account_id + } + r = requests.post(self.url + "/withdrawals/coinbase-account", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def crypto_withdraw(self, amount="", currency="", crypto_address=""): + payload = { + "amount": amount, + "currency": currency, + "crypto_address": crypto_address + } + r = requests.post(self.url + "/withdrawals/crypto", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def get_payment_methods(self): + r = requests.get(self.url + "/payment-methods", auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def get_coinbase_accounts(self): + r = requests.get(self.url + "/coinbase-accounts", auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def create_report(self, report_type="", start_date="", end_date="", product_id="", account_id="", report_format="", + email=""): + payload = { + "type": report_type, + "start_date": start_date, + "end_date": end_date, + "product_id": product_id, + "account_id": account_id, + "format": report_format, + "email": email + } + r = requests.post(self.url + "/reports", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def get_report(self, report_id=""): + r = requests.get(self.url + "/reports/" + report_id, auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def get_trailing_volume(self): + r = requests.get(self.url + "/users/self/trailing-volume", auth=self.auth, timeout=self.timeout) + return self._determine_response(r) + + def get_deposit_address(self, account_id): + r = requests.post(self.url + '/coinbase-accounts/{}/addresses'.format(account_id), auth=self.auth, timeout=self.timeout) + return self._determine_response(r) diff --git a/gdax/exceptions.py b/gdax/exceptions.py new file mode 100644 index 0000000..5536895 --- /dev/null +++ b/gdax/exceptions.py @@ -0,0 +1,75 @@ +class GdaxException(Exception): + """ + Base GDAX Exception + Raised when Bad Response returned from GDAX + See: https://docs.gdax.com/?python#errors + """ + + def __init__(self, message, code): + """ + :param message: Message from GDAX response + :type message: str + :param code: HTTP Code + :type code: int + """ + self._message = message + self._code = code + + @property + def message(self): + return self._message + + @message.setter + def message(self, message): + self._message = message + + + @property + def code(self): + return self._code + + @message.setter + def message(self, code): + self._code = code + + +class InvalidGdaxRequest(GdaxException): + """ + Raised on 400 response from GDAX + """ + pass + + +class UnauthorizedGdaxRequest(GdaxException): + """ + Raised on 401 response from GDAX + """ + pass + + +class ForbiddenGdaxRequest(GdaxException): + """ + Raised on 403 response from GDAX + """ + pass + + +class NotFoundGdaxRequest(GdaxException): + """ + Raised on 404 response from GDAX + """ + pass + + +class UnknownGdaxClientRequest(GdaxException): + """ + Raised on 4XX responses not tracked + """ + pass + + +class InternalErrorGdaxRequest(GdaxException): + """ + Raised on 500 response from GDAX + """ + pass diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5705bac --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +sortedcontainers>=1.5.9 +bintrees==2.0.7 +python-dateutil==2.6.1 +requests==2.13.0 +six==1.10.0 +websocket-client==0.40.0 +pymongo==3.5.1 +pytest>=3.3.0 +pytest-cov>=2.5.0 diff --git a/tests/test_error_handling.py b/tests/test_error_handling.py new file mode 100644 index 0000000..ccbe841 --- /dev/null +++ b/tests/test_error_handling.py @@ -0,0 +1,22 @@ +from unittest.mock import patch +import pytest +import gdax + + +@pytest.fixture +def client(): + return gdax.PublicClient() + + +@pytest.mark.parametrize("code, exception",[ + (400, gdax.exceptions.InvalidGdaxRequest), + (401, gdax.exceptions.UnauthorizedGdaxRequest), + (403, gdax.exceptions.ForbiddenGdaxRequest), + (404, gdax.exceptions.NotFoundGdaxRequest), + (422, gdax.exceptions.UnknownGdaxClientRequest), + (500, gdax.exceptions.InternalErrorGdaxRequest)]) +@patch('requests.get') +def test_gdax_exceptions(mock_request, client, code, exception): + mock_request.return_value.status_code = code + with pytest.raises(exception): + client.get_products() From aec89f3ec8533343f0bdbadfd9aa611850e46d86 Mon Sep 17 00:00:00 2001 From: Benjamin Ross Date: Sun, 25 Feb 2018 12:00:09 -0800 Subject: [PATCH 02/11] Added README info, added self to contributors, bumped version --- README.md | 25 +++++++++++++++++++++++++ contributors.txt | 3 ++- setup.py | 5 +++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7531641..c8e0219 100644 --- a/README.md +++ b/README.md @@ -354,6 +354,7 @@ time.sleep(10) order_book.close() ``` +<<<<<<< HEAD ### Testing Unit tests are under development using the pytest framework. Contributions are welcome! @@ -370,6 +371,30 @@ python -m pytest - Major overhaul on how pagination is handled *1.0* +======= +### Error Handling +Error handling has been added in version 2.0. Currently, the only HTTP error codes that +are handled are the ones documented on the GDAX API error section: [here](https://docs.gdax.com/?python#errors) + +HTTP STATUS CODE 400: Raises InvalidGdaxRequest +HTTP STATUS CODE 401: Raises UnauthorizedGdaxRequest +HTTP STATUS CODE 403: Raises ForbiddenGdaxRequest +HTTP STATUS CODE 404: Raises NotFoundGdaxRequest +HTTP STATUS CODE 4XX: Raises UnknownGDAXClientRequest +HTTP STATUS CODE 5XX: Raises InternalErrorGdaxRequest + +All HTTP requests from both the public client and authenticated client run through `_determine_response`, which +either returns the JSON body or raises the appropriate error. + +Please consider creating new Exception classes and mapping as Gdax error states are discovered. + + +## Change Log +*2.0* +- Added error handling to all HTTP requests [Error Handling Docs](#Error Handling) + +*1.0* **Current PyPI release** +>>>>>>> Added README info, added self to contributors, bumped version - The first release that is not backwards compatible - Refactored to follow PEP 8 Standards - Improved Documentation diff --git a/contributors.txt b/contributors.txt index b1e5c49..3bab740 100644 --- a/contributors.txt +++ b/contributors.txt @@ -3,4 +3,5 @@ Leonard Lin Jeff Gibson David Caseria Paul Mestemaker -Drew Rice \ No newline at end of file +Drew Rice +Benjamin Ross diff --git a/setup.py b/setup.py index ea04785..c78c97f 100644 --- a/setup.py +++ b/setup.py @@ -19,8 +19,13 @@ long_description = fh.read() setup( +<<<<<<< HEAD name='cbpro', version='1.1.4', +======= + name='gdax', + version='2.0.0', +>>>>>>> Added README info, added self to contributors, bumped version author='Daniel Paquin', author_email='dpaq34@gmail.com', license='MIT', From bc3044d4cb9a4a7174d98a2fc29a6fcf1e251138 Mon Sep 17 00:00:00 2001 From: Benjamin Ross Date: Sun, 25 Feb 2018 12:02:18 -0800 Subject: [PATCH 03/11] Fixed README formatting --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c8e0219..a847390 100644 --- a/README.md +++ b/README.md @@ -376,12 +376,12 @@ python -m pytest Error handling has been added in version 2.0. Currently, the only HTTP error codes that are handled are the ones documented on the GDAX API error section: [here](https://docs.gdax.com/?python#errors) -HTTP STATUS CODE 400: Raises InvalidGdaxRequest -HTTP STATUS CODE 401: Raises UnauthorizedGdaxRequest -HTTP STATUS CODE 403: Raises ForbiddenGdaxRequest -HTTP STATUS CODE 404: Raises NotFoundGdaxRequest -HTTP STATUS CODE 4XX: Raises UnknownGDAXClientRequest -HTTP STATUS CODE 5XX: Raises InternalErrorGdaxRequest +- HTTP STATUS CODE 400: Raises InvalidGdaxRequest +- HTTP STATUS CODE 401: Raises UnauthorizedGdaxRequest +- HTTP STATUS CODE 403: Raises ForbiddenGdaxRequest +- HTTP STATUS CODE 404: Raises NotFoundGdaxRequest +- HTTP STATUS CODE 4XX: Raises UnknownGDAXClientRequest +- HTTP STATUS CODE 5XX: Raises InternalErrorGdaxRequest All HTTP requests from both the public client and authenticated client run through `_determine_response`, which either returns the JSON body or raises the appropriate error. @@ -391,7 +391,7 @@ Please consider creating new Exception classes and mapping as Gdax error states ## Change Log *2.0* -- Added error handling to all HTTP requests [Error Handling Docs](#Error Handling) +- Added error handling to all HTTP requests [Error Handling Docs](#error-handling) *1.0* **Current PyPI release** >>>>>>> Added README info, added self to contributors, bumped version From 8690c15dfde0fb470063eee291fca5bc8618a979 Mon Sep 17 00:00:00 2001 From: Benjamin Ross Date: Sun, 25 Feb 2018 12:03:00 -0800 Subject: [PATCH 04/11] Fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a847390..b8e82dd 100644 --- a/README.md +++ b/README.md @@ -380,7 +380,7 @@ are handled are the ones documented on the GDAX API error section: [here](https: - HTTP STATUS CODE 401: Raises UnauthorizedGdaxRequest - HTTP STATUS CODE 403: Raises ForbiddenGdaxRequest - HTTP STATUS CODE 404: Raises NotFoundGdaxRequest -- HTTP STATUS CODE 4XX: Raises UnknownGDAXClientRequest +- HTTP STATUS CODE 4XX: Raises UnknownGdaxClientRequest - HTTP STATUS CODE 5XX: Raises InternalErrorGdaxRequest All HTTP requests from both the public client and authenticated client run through `_determine_response`, which From f328a6f56562964cdd649603ef889ab92cd27de8 Mon Sep 17 00:00:00 2001 From: Benjamin Ross Date: Sun, 25 Feb 2018 12:10:48 -0800 Subject: [PATCH 05/11] Fixed small things found in PR --- cbpro/public_client.py | 2 +- gdax/exceptions.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cbpro/public_client.py b/cbpro/public_client.py index fd153a2..f2249d1 100644 --- a/cbpro/public_client.py +++ b/cbpro/public_client.py @@ -56,7 +56,7 @@ def _determine_response(self, response): """ Determines if GDAX response is success or error If success, returns response json - If error, raises appropiate GdaxException + If error, raises appropriate GdaxException """ if self._is_http_success(response.status_code): return response.json() diff --git a/gdax/exceptions.py b/gdax/exceptions.py index 5536895..8320f4e 100644 --- a/gdax/exceptions.py +++ b/gdax/exceptions.py @@ -23,7 +23,6 @@ def message(self): def message(self, message): self._message = message - @property def code(self): return self._code From 932c613f06464f7a099eb77e70db4ba566ff05d9 Mon Sep 17 00:00:00 2001 From: Benjamin Ross Date: Sun, 25 Feb 2018 14:42:08 -0800 Subject: [PATCH 06/11] Use PSL defined HTTP codes --- cbpro/public_client.py | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/cbpro/public_client.py b/cbpro/public_client.py index f2249d1..2706a07 100644 --- a/cbpro/public_client.py +++ b/cbpro/public_client.py @@ -4,18 +4,11 @@ # # For public requests to the Coinbase exchange +from http import HTTPStatus import requests from gdax import exceptions -HTTP_200_OK = 200 -HTTP_300_MULTIPLE_CHOICES = 300 -HTTP_400_BAD_REQUEST = 400 -HTTP_401_UNAUTHORIZED = 401 -HTTP_403_FORBIDDEN = 403 -HTTP_404_NOT_FOUND = 404 -HTTP_500_INTERNAL_SERVER_ERROR = 500 - class PublicClient(object): """cbpro public client API. @@ -42,15 +35,15 @@ def __init__(self, api_url='https://api.pro.coinbase.com', timeout=30): def _is_http_success(self, code): # type: (int) -> bool - return code >= HTTP_200_OK and code < HTTP_300_MULTIPLE_CHOICES + return code >= HTTPStatus.OK and code < HTTPStatus.MULTIPLE_CHOICES def _is_http_client_error(self, code): # type: (int) -> bool - return code >= HTTP_400_BAD_REQUEST and code < HTTP_500_INTERNAL_SERVER_ERROR + return code >= HTTPStatus.BAD_REQUEST and code < HTTPStatus.INTERNAL_SERVER_ERROR def _is_http_server_error(self, code): # type: (int) -> bool - return code >= HTTP_500_INTERNAL_SERVER_ERROR + return code >= HTTPStatus.INTERNAL_SERVER_ERROR def _determine_response(self, response): """ @@ -63,18 +56,18 @@ def _determine_response(self, response): elif self._is_http_client_error(response.status_code): body = response.json() message = body.get('message') - if response.status_code == HTTP_400_BAD_REQUEST: + if response.status_code == HTTPStatus.BAD_REQUEST: raise exceptions.InvalidGdaxRequest(message, - HTTP_400_BAD_REQUEST) - elif response.status_code == HTTP_401_UNAUTHORIZED: + HTTPStatus.BAD_REQUEST) + elif response.status_code == HTTPStatus.UNAUTHORIZED: raise exceptions.UnauthorizedGdaxRequest(message, - HTTP_401_UNAUTHORIZED) - elif response.status_code == HTTP_403_FORBIDDEN: + HTTPStatus.UNAUTHORIZED) + elif response.status_code == HTTPStatus.FORBIDDEN: raise exceptions.ForbiddenGdaxRequest(message, - HTTP_403_FORBIDDEN) - elif response.status_code == HTTP_404_NOT_FOUND: + HTTPStatus.FORBIDDEN) + elif response.status_code == HTTPStatus.NOT_FOUND: raise exceptions.NotFoundGdaxRequest(message, - HTTP_404_NOT_FOUND) + HTTPStatus.NOT_FOUND) else: # Other 4XX response not yet mapped raise exceptions.UnknownGdaxClientRequest(message, response.status_code) @@ -82,7 +75,7 @@ def _determine_response(self, response): elif self._is_http_server_error(response.status_code): body = response.json() raise exceptions.InternalErrorGdaxRequest(body.get('message'), - HTTP_500_INTERNAL_SERVER_ERROR) + HTTPStatus.INTERNAL_SERVER_ERROR) def _get(self, path, params=None): """Perform get request""" From ce036cebe7bf0eebd22d91226c2c56a2c8453403 Mon Sep 17 00:00:00 2001 From: Benjamin Ross Date: Sun, 25 Mar 2018 17:09:55 -0700 Subject: [PATCH 07/11] Fixed issue with message -> code. added rate limit exception and tset --- cbpro/public_client.py | 3 +++ gdax/exceptions.py | 9 ++++++++- tests/test_error_handling.py | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cbpro/public_client.py b/cbpro/public_client.py index 2706a07..b7cb00b 100644 --- a/cbpro/public_client.py +++ b/cbpro/public_client.py @@ -68,6 +68,9 @@ def _determine_response(self, response): elif response.status_code == HTTPStatus.NOT_FOUND: raise exceptions.NotFoundGdaxRequest(message, HTTPStatus.NOT_FOUND) + elif response.status_code == HTTPStatus.TOO_MANY_REQUESTS: + raise exceptions.GdaxRateLimitRequest(message, + HTTPStatus.TOO_MANY_REQUESTS) else: # Other 4XX response not yet mapped raise exceptions.UnknownGdaxClientRequest(message, response.status_code) diff --git a/gdax/exceptions.py b/gdax/exceptions.py index 8320f4e..ab167ad 100644 --- a/gdax/exceptions.py +++ b/gdax/exceptions.py @@ -28,7 +28,7 @@ def code(self): return self._code @message.setter - def message(self, code): + def code(self, code): self._code = code @@ -60,6 +60,13 @@ class NotFoundGdaxRequest(GdaxException): pass +class GdaxRateLimitRequest(GdaxException): + """ + Raised on 429 response from GDAX + """ + pass + + class UnknownGdaxClientRequest(GdaxException): """ Raised on 4XX responses not tracked diff --git a/tests/test_error_handling.py b/tests/test_error_handling.py index ccbe841..7a8fce5 100644 --- a/tests/test_error_handling.py +++ b/tests/test_error_handling.py @@ -14,6 +14,7 @@ def client(): (403, gdax.exceptions.ForbiddenGdaxRequest), (404, gdax.exceptions.NotFoundGdaxRequest), (422, gdax.exceptions.UnknownGdaxClientRequest), + (429, gdax.exceptions.GdaxRateLimitRequest), (500, gdax.exceptions.InternalErrorGdaxRequest)]) @patch('requests.get') def test_gdax_exceptions(mock_request, client, code, exception): From 1025c3d817f1a7f0fd70a0fe87e8a97723e634b7 Mon Sep 17 00:00:00 2001 From: Benjamin Ross Date: Sun, 25 Mar 2018 17:10:18 -0700 Subject: [PATCH 08/11] Remove raise for status --- gdax/authenticated_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gdax/authenticated_client.py b/gdax/authenticated_client.py index f550e2b..2fc222b 100644 --- a/gdax/authenticated_client.py +++ b/gdax/authenticated_client.py @@ -31,7 +31,6 @@ def get_accounts(self): def get_account_history(self, account_id): result = [] r = requests.get(self.url + '/accounts/{}/ledger'.format(account_id), auth=self.auth, timeout=self.timeout) - # r.raise_for_status() r_json = self._determine_response(r) result.append(r_json) if "cb-after" in r.headers: From 304f10612aaf8382923b9a368261a388226cc82f Mon Sep 17 00:00:00 2001 From: Benjamin Ross Date: Sun, 25 Mar 2018 17:11:13 -0700 Subject: [PATCH 09/11] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b8e82dd..6ff824c 100644 --- a/README.md +++ b/README.md @@ -380,6 +380,7 @@ are handled are the ones documented on the GDAX API error section: [here](https: - HTTP STATUS CODE 401: Raises UnauthorizedGdaxRequest - HTTP STATUS CODE 403: Raises ForbiddenGdaxRequest - HTTP STATUS CODE 404: Raises NotFoundGdaxRequest +- HTTP STATUS CODE 429: Raises GdaxRateLimitRequest - HTTP STATUS CODE 4XX: Raises UnknownGdaxClientRequest - HTTP STATUS CODE 5XX: Raises InternalErrorGdaxRequest From e0467b110830cd70c3648b0cbe8739fbe3d7ca9d Mon Sep 17 00:00:00 2001 From: Benjamin Ross Date: Sun, 25 Mar 2018 17:13:53 -0700 Subject: [PATCH 10/11] Remove third blank line --- cbpro/public_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cbpro/public_client.py b/cbpro/public_client.py index b7cb00b..ffdda42 100644 --- a/cbpro/public_client.py +++ b/cbpro/public_client.py @@ -9,7 +9,6 @@ from gdax import exceptions - class PublicClient(object): """cbpro public client API. From 6d020e7535b3145d96962b78b1d7d0818e8332ac Mon Sep 17 00:00:00 2001 From: Benjamin Ross Date: Thu, 29 Nov 2018 19:50:57 -0700 Subject: [PATCH 11/11] Rebased with master and updated everything to be CBPRO instead of GDAX. Updated to check errors in send_message instead of _get --- README.md | 32 ++-- cbpro/__init__.py | 1 + cbpro/exceptions.py | 81 ++++++++++ cbpro/public_client.py | 30 ++-- gdax/authenticated_client.py | 289 ----------------------------------- gdax/exceptions.py | 81 ---------- requirements.txt | 9 -- setup.py | 5 - tests/test_error_handling.py | 24 +-- 9 files changed, 119 insertions(+), 433 deletions(-) create mode 100644 cbpro/exceptions.py delete mode 100644 gdax/authenticated_client.py delete mode 100644 gdax/exceptions.py delete mode 100644 requirements.txt diff --git a/README.md b/README.md index 6ff824c..3885f0c 100644 --- a/README.md +++ b/README.md @@ -354,7 +354,6 @@ time.sleep(10) order_book.close() ``` -<<<<<<< HEAD ### Testing Unit tests are under development using the pytest framework. Contributions are welcome! @@ -365,37 +364,32 @@ directory run: python -m pytest ``` -## Change Log -*1.1.2* **Current PyPI release** -- Refactor project for Coinbase Pro -- Major overhaul on how pagination is handled - -*1.0* -======= ### Error Handling Error handling has been added in version 2.0. Currently, the only HTTP error codes that are handled are the ones documented on the GDAX API error section: [here](https://docs.gdax.com/?python#errors) -- HTTP STATUS CODE 400: Raises InvalidGdaxRequest -- HTTP STATUS CODE 401: Raises UnauthorizedGdaxRequest -- HTTP STATUS CODE 403: Raises ForbiddenGdaxRequest -- HTTP STATUS CODE 404: Raises NotFoundGdaxRequest -- HTTP STATUS CODE 429: Raises GdaxRateLimitRequest -- HTTP STATUS CODE 4XX: Raises UnknownGdaxClientRequest -- HTTP STATUS CODE 5XX: Raises InternalErrorGdaxRequest +- HTTP STATUS CODE 400: Raises InvalidCbproRequest +- HTTP STATUS CODE 401: Raises UnauthorizedCbproRequest +- HTTP STATUS CODE 403: Raises ForbiddenCbproRequest +- HTTP STATUS CODE 404: Raises NotFoundCbproRequest +- HTTP STATUS CODE 429: Raises CbproRateLimitRequest +- HTTP STATUS CODE 4XX: Raises UnknownCbproClientRequest +- HTTP STATUS CODE 5XX: Raises InternalErrorCbproRequest All HTTP requests from both the public client and authenticated client run through `_determine_response`, which either returns the JSON body or raises the appropriate error. -Please consider creating new Exception classes and mapping as Gdax error states are discovered. - +Please consider creating new Exception classes and mapping as Cbpro error states are discovered. ## Change Log *2.0* - Added error handling to all HTTP requests [Error Handling Docs](#error-handling) -*1.0* **Current PyPI release** ->>>>>>> Added README info, added self to contributors, bumped version +*1.1.2* **Current PyPI release** +- Refactor project for Coinbase Pro +- Major overhaul on how pagination is handled + +*1.0* - The first release that is not backwards compatible - Refactored to follow PEP 8 Standards - Improved Documentation diff --git a/cbpro/__init__.py b/cbpro/__init__.py index 00937f5..a29f2e3 100644 --- a/cbpro/__init__.py +++ b/cbpro/__init__.py @@ -3,3 +3,4 @@ from cbpro.websocket_client import WebsocketClient from cbpro.order_book import OrderBook from cbpro.cbpro_auth import CBProAuth +from cbpro.exceptions import InvalidCbproRequest, UnauthorizedCbproRequest, ForbiddenCbproRequest, NotFoundCbproRequest, CbproRateLimitRequest, UnknownCbproClientRequest, InternalErrorCbproRequest diff --git a/cbpro/exceptions.py b/cbpro/exceptions.py new file mode 100644 index 0000000..70ec0c0 --- /dev/null +++ b/cbpro/exceptions.py @@ -0,0 +1,81 @@ +class CbproException(Exception): + """ + Base Coinbase Pro Exception + Raised when Bad Response returned from Coinbase Pro + See: https://docs.pro.coinbase.com/?r=1#errors + """ + + def __init__(self, message, code): + """ + :param message: Message from Coinbase Pro response + :type message: str + :param code: HTTP Code + :type code: int + """ + self._message = message + self._code = code + + @property + def message(self): + return self._message + + @message.setter + def message(self, message): + self._message = message + + @property + def code(self): + return self._code + + @message.setter + def code(self, code): + self._code = code + + +class InvalidCbproRequest(CbproException): + """ + Raised on 400 response from Coinbase Pro + """ + pass + + +class UnauthorizedCbproRequest(CbproException): + """ + Raised on 401 response from Coinbase Pro + """ + pass + + +class ForbiddenCbproRequest(CbproException): + """ + Raised on 403 response from Coinbase Pro + """ + pass + + +class NotFoundCbproRequest(CbproException): + """ + Raised on 404 response from Coinbase Pro + """ + pass + + +class CbproRateLimitRequest(CbproException): + """ + Raised on 429 response from Coinbase Pro + """ + pass + + +class UnknownCbproClientRequest(CbproException): + """ + Raised on 4XX responses not tracked + """ + pass + + +class InternalErrorCbproRequest(CbproException): + """ + Raised on 500 response from Coinbase Pro + """ + pass diff --git a/cbpro/public_client.py b/cbpro/public_client.py index ffdda42..8b311b6 100644 --- a/cbpro/public_client.py +++ b/cbpro/public_client.py @@ -6,7 +6,7 @@ from http import HTTPStatus import requests -from gdax import exceptions +from cbpro import exceptions class PublicClient(object): @@ -46,9 +46,9 @@ def _is_http_server_error(self, code): def _determine_response(self, response): """ - Determines if GDAX response is success or error + Determines if CBPRO response is success or error If success, returns response json - If error, raises appropriate GdaxException + If error, raises appropriate CbproException """ if self._is_http_success(response.status_code): return response.json() @@ -56,35 +56,29 @@ def _determine_response(self, response): body = response.json() message = body.get('message') if response.status_code == HTTPStatus.BAD_REQUEST: - raise exceptions.InvalidGdaxRequest(message, + raise exceptions.InvalidCbproRequest(message, HTTPStatus.BAD_REQUEST) elif response.status_code == HTTPStatus.UNAUTHORIZED: - raise exceptions.UnauthorizedGdaxRequest(message, + raise exceptions.UnauthorizedCbproRequest(message, HTTPStatus.UNAUTHORIZED) elif response.status_code == HTTPStatus.FORBIDDEN: - raise exceptions.ForbiddenGdaxRequest(message, + raise exceptions.ForbiddenCbproRequest(message, HTTPStatus.FORBIDDEN) elif response.status_code == HTTPStatus.NOT_FOUND: - raise exceptions.NotFoundGdaxRequest(message, + raise exceptions.NotFoundCbproRequest(message, HTTPStatus.NOT_FOUND) elif response.status_code == HTTPStatus.TOO_MANY_REQUESTS: - raise exceptions.GdaxRateLimitRequest(message, + raise exceptions.CbproRateLimitRequest(message, HTTPStatus.TOO_MANY_REQUESTS) else: # Other 4XX response not yet mapped - raise exceptions.UnknownGdaxClientRequest(message, + raise exceptions.UnknownCbproClientRequest(message, response.status_code) elif self._is_http_server_error(response.status_code): body = response.json() - raise exceptions.InternalErrorGdaxRequest(body.get('message'), + raise exceptions.InternalErrorCbproRequest(body.get('message'), HTTPStatus.INTERNAL_SERVER_ERROR) - def _get(self, path, params=None): - """Perform get request""" - - r = requests.get(self.url + path, params=params, timeout=self.timeout) - return self._determine_response(r) - def get_products(self): """Get a list of available currency pairs for trading. @@ -323,7 +317,7 @@ def _send_message(self, method, endpoint, params=None, data=None): url = self.url + endpoint r = self.session.request(method, url, params=params, data=data, auth=self.auth, timeout=30) - return r.json() + return self._determine_response(r) def _send_paginated_message(self, endpoint, params=None): """ Send API message that results in a paginated response. @@ -353,7 +347,7 @@ def _send_paginated_message(self, endpoint, params=None): url = self.url + endpoint while True: r = self.session.get(url, params=params, auth=self.auth, timeout=30) - results = r.json() + results = self._determine_response(r) for result in results: yield result # If there are no more pages, we're done. Otherwise update `after` diff --git a/gdax/authenticated_client.py b/gdax/authenticated_client.py deleted file mode 100644 index 2fc222b..0000000 --- a/gdax/authenticated_client.py +++ /dev/null @@ -1,289 +0,0 @@ -# -# gdax/AuthenticatedClient.py -# Daniel Paquin -# -# For authenticated requests to the gdax exchange - -import hmac -import hashlib -import time -import requests -import base64 -import json -from requests.auth import AuthBase -from gdax.public_client import PublicClient -from gdax.gdax_auth import GdaxAuth - - -class AuthenticatedClient(PublicClient): - def __init__(self, key, b64secret, passphrase, api_url="https://api.gdax.com", timeout=30): - super(AuthenticatedClient, self).__init__(api_url) - self.auth = GdaxAuth(key, b64secret, passphrase) - self.timeout = timeout - - def get_account(self, account_id): - r = requests.get(self.url + '/accounts/' + account_id, auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def get_accounts(self): - return self.get_account('') - - def get_account_history(self, account_id): - result = [] - r = requests.get(self.url + '/accounts/{}/ledger'.format(account_id), auth=self.auth, timeout=self.timeout) - r_json = self._determine_response(r) - result.append(r_json) - if "cb-after" in r.headers: - self.history_pagination(account_id, result, r.headers["cb-after"]) - return result - - def history_pagination(self, account_id, result, after): - r = requests.get(self.url + '/accounts/{}/ledger?after={}'.format(account_id, str(after)), auth=self.auth, timeout=self.timeout) - r_json = self._determine_response(r) - if r_json: - result.append(r_json) - if "cb-after" in r.headers: - self.history_pagination(account_id, result, r.headers["cb-after"]) - return result - - def get_account_holds(self, account_id): - result = [] - r = requests.get(self.url + '/accounts/{}/holds'.format(account_id), auth=self.auth, timeout=self.timeout) - r_json = self._determine_response(r) - result.append(r_json) - if "cb-after" in r.headers: - self.holds_pagination(account_id, result, r.headers["cb-after"]) - return result - - def holds_pagination(self, account_id, result, after): - r = requests.get(self.url + '/accounts/{}/holds?after={}'.format(account_id, str(after)), auth=self.auth, timeout=self.timeout) - r_json = self._determine_response(r) - if r_json: - result.append(r_json) - if "cb-after" in r.headers: - self.holds_pagination(account_id, result, r.headers["cb-after"]) - return result - - def buy(self, **kwargs): - kwargs["side"] = "buy" - if "product_id" not in kwargs: - kwargs["product_id"] = self.product_id - r = requests.post(self.url + '/orders', - data=json.dumps(kwargs), - auth=self.auth, - timeout=self.timeout) - return self._determine_response(r) - - def sell(self, **kwargs): - kwargs["side"] = "sell" - r = requests.post(self.url + '/orders', - data=json.dumps(kwargs), - auth=self.auth, - timeout=self.timeout) - return self._determine_response(r) - - def cancel_order(self, order_id): - r = requests.delete(self.url + '/orders/' + order_id, auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def cancel_all(self, product_id=''): - url = self.url + '/orders/' - params = {} - if product_id: - params["product_id"] = product_id - r = requests.delete(url, auth=self.auth, params=params, timeout=self.timeout) - return self._determine_response(r) - - def get_order(self, order_id): - r = requests.get(self.url + '/orders/' + order_id, auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def get_orders(self, product_id='', status=[]): - result = [] - url = self.url + '/orders/' - params = {} - if product_id: - params["product_id"] = product_id - if status: - params["status"] = status - r = requests.get(url, auth=self.auth, params=params, timeout=self.timeout) - r_json = self._determine_response(r) - result.append(r_json) - if 'cb-after' in r.headers: - self.paginate_orders(product_id, status, result, r.headers['cb-after']) - return result - - def paginate_orders(self, product_id, status, result, after): - url = self.url + '/orders' - - params = { - "after": str(after), - } - if product_id: - params["product_id"] = product_id - if status: - params["status"] = status - r = requests.get(url, auth=self.auth, params=params, timeout=self.timeout) - r_json = self._determine_response(r) - if r_json: - result.append(r_json) - if 'cb-after' in r.headers: - self.paginate_orders(product_id, status, result, r.headers['cb-after']) - return result - - def get_fills(self, order_id='', product_id='', before='', after='', limit=''): - result = [] - url = self.url + '/fills?' - if order_id: - url += "order_id={}&".format(str(order_id)) - if product_id: - url += "product_id={}&".format(product_id) - if before: - url += "before={}&".format(str(before)) - if after: - url += "after={}&".format(str(after)) - if limit: - url += "limit={}&".format(str(limit)) - r = requests.get(url, auth=self.auth, timeout=self.timeout) - r_json = self._determine_response(r) - result.append(r_json) - if 'cb-after' in r.headers and limit is not len(r_json): - return self.paginate_fills(result, r.headers['cb-after'], order_id=order_id, product_id=product_id) - return result - - def paginate_fills(self, result, after, order_id='', product_id=''): - url = self.url + '/fills?after={}&'.format(str(after)) - if order_id: - url += "order_id={}&".format(str(order_id)) - if product_id: - url += "product_id={}&".format(product_id) - r = requests.get(url, auth=self.auth, timeout=self.timeout) - r_json = self._determine_response(r) - if r_json: - result.append(r_json) - if 'cb-after' in r.headers: - return self.paginate_fills(result, r.headers['cb-after'], order_id=order_id, product_id=product_id) - return result - - def get_fundings(self, result='', status='', after=''): - if not result: - result = [] - url = self.url + '/funding?' - if status: - url += "status={}&".format(str(status)) - if after: - url += 'after={}&'.format(str(after)) - r = requests.get(url, auth=self.auth, timeout=self.timeout) - r_json = self._determine_response(r) - result.append(r_json) - if 'cb-after' in r.headers: - return self.get_fundings(result, status=status, after=r.headers['cb-after']) - return result - - def repay_funding(self, amount='', currency=''): - payload = { - "amount": amount, - "currency": currency # example: USD - } - r = requests.post(self.url + "/funding/repay", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def margin_transfer(self, margin_profile_id="", transfer_type="", currency="", amount=""): - payload = { - "margin_profile_id": margin_profile_id, - "type": transfer_type, - "currency": currency, # example: USD - "amount": amount - } - r = requests.post(self.url + "/profiles/margin-transfer", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def get_position(self): - r = requests.get(self.url + "/position", auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def close_position(self, repay_only=""): - payload = { - "repay_only": repay_only or False - } - r = requests.post(self.url + "/position/close", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def deposit(self, amount="", currency="", payment_method_id=""): - payload = { - "amount": amount, - "currency": currency, - "payment_method_id": payment_method_id - } - r = requests.post(self.url + "/deposits/payment-method", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def coinbase_deposit(self, amount="", currency="", coinbase_account_id=""): - payload = { - "amount": amount, - "currency": currency, - "coinbase_account_id": coinbase_account_id - } - r = requests.post(self.url + "/deposits/coinbase-account", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def withdraw(self, amount="", currency="", payment_method_id=""): - payload = { - "amount": amount, - "currency": currency, - "payment_method_id": payment_method_id - } - r = requests.post(self.url + "/withdrawals/payment-method", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def coinbase_withdraw(self, amount="", currency="", coinbase_account_id=""): - payload = { - "amount": amount, - "currency": currency, - "coinbase_account_id": coinbase_account_id - } - r = requests.post(self.url + "/withdrawals/coinbase-account", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def crypto_withdraw(self, amount="", currency="", crypto_address=""): - payload = { - "amount": amount, - "currency": currency, - "crypto_address": crypto_address - } - r = requests.post(self.url + "/withdrawals/crypto", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def get_payment_methods(self): - r = requests.get(self.url + "/payment-methods", auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def get_coinbase_accounts(self): - r = requests.get(self.url + "/coinbase-accounts", auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def create_report(self, report_type="", start_date="", end_date="", product_id="", account_id="", report_format="", - email=""): - payload = { - "type": report_type, - "start_date": start_date, - "end_date": end_date, - "product_id": product_id, - "account_id": account_id, - "format": report_format, - "email": email - } - r = requests.post(self.url + "/reports", data=json.dumps(payload), auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def get_report(self, report_id=""): - r = requests.get(self.url + "/reports/" + report_id, auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def get_trailing_volume(self): - r = requests.get(self.url + "/users/self/trailing-volume", auth=self.auth, timeout=self.timeout) - return self._determine_response(r) - - def get_deposit_address(self, account_id): - r = requests.post(self.url + '/coinbase-accounts/{}/addresses'.format(account_id), auth=self.auth, timeout=self.timeout) - return self._determine_response(r) diff --git a/gdax/exceptions.py b/gdax/exceptions.py deleted file mode 100644 index ab167ad..0000000 --- a/gdax/exceptions.py +++ /dev/null @@ -1,81 +0,0 @@ -class GdaxException(Exception): - """ - Base GDAX Exception - Raised when Bad Response returned from GDAX - See: https://docs.gdax.com/?python#errors - """ - - def __init__(self, message, code): - """ - :param message: Message from GDAX response - :type message: str - :param code: HTTP Code - :type code: int - """ - self._message = message - self._code = code - - @property - def message(self): - return self._message - - @message.setter - def message(self, message): - self._message = message - - @property - def code(self): - return self._code - - @message.setter - def code(self, code): - self._code = code - - -class InvalidGdaxRequest(GdaxException): - """ - Raised on 400 response from GDAX - """ - pass - - -class UnauthorizedGdaxRequest(GdaxException): - """ - Raised on 401 response from GDAX - """ - pass - - -class ForbiddenGdaxRequest(GdaxException): - """ - Raised on 403 response from GDAX - """ - pass - - -class NotFoundGdaxRequest(GdaxException): - """ - Raised on 404 response from GDAX - """ - pass - - -class GdaxRateLimitRequest(GdaxException): - """ - Raised on 429 response from GDAX - """ - pass - - -class UnknownGdaxClientRequest(GdaxException): - """ - Raised on 4XX responses not tracked - """ - pass - - -class InternalErrorGdaxRequest(GdaxException): - """ - Raised on 500 response from GDAX - """ - pass diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 5705bac..0000000 --- a/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -sortedcontainers>=1.5.9 -bintrees==2.0.7 -python-dateutil==2.6.1 -requests==2.13.0 -six==1.10.0 -websocket-client==0.40.0 -pymongo==3.5.1 -pytest>=3.3.0 -pytest-cov>=2.5.0 diff --git a/setup.py b/setup.py index c78c97f..53610c2 100644 --- a/setup.py +++ b/setup.py @@ -19,13 +19,8 @@ long_description = fh.read() setup( -<<<<<<< HEAD name='cbpro', - version='1.1.4', -======= - name='gdax', version='2.0.0', ->>>>>>> Added README info, added self to contributors, bumped version author='Daniel Paquin', author_email='dpaq34@gmail.com', license='MIT', diff --git a/tests/test_error_handling.py b/tests/test_error_handling.py index 7a8fce5..a4aec3c 100644 --- a/tests/test_error_handling.py +++ b/tests/test_error_handling.py @@ -1,23 +1,23 @@ from unittest.mock import patch import pytest -import gdax +import cbpro @pytest.fixture def client(): - return gdax.PublicClient() + return cbpro.PublicClient() @pytest.mark.parametrize("code, exception",[ - (400, gdax.exceptions.InvalidGdaxRequest), - (401, gdax.exceptions.UnauthorizedGdaxRequest), - (403, gdax.exceptions.ForbiddenGdaxRequest), - (404, gdax.exceptions.NotFoundGdaxRequest), - (422, gdax.exceptions.UnknownGdaxClientRequest), - (429, gdax.exceptions.GdaxRateLimitRequest), - (500, gdax.exceptions.InternalErrorGdaxRequest)]) -@patch('requests.get') -def test_gdax_exceptions(mock_request, client, code, exception): + (400, cbpro.exceptions.InvalidCbproRequest), + (401, cbpro.exceptions.UnauthorizedCbproRequest), + (403, cbpro.exceptions.ForbiddenCbproRequest), + (404, cbpro.exceptions.NotFoundCbproRequest), + (422, cbpro.exceptions.UnknownCbproClientRequest), + (429, cbpro.exceptions.CbproRateLimitRequest), + (500, cbpro.exceptions.InternalErrorCbproRequest)]) +@patch('requests.Session.request') +def test_cbpro_exceptions(mock_request, client, code, exception): mock_request.return_value.status_code = code with pytest.raises(exception): - client.get_products() + response = client.get_products()