async def fetch(self, url, method='GET', headers=None, body=None): """Perform a HTTP request and return decoded JSON data""" request_headers = self.prepare_request_headers(headers) url = self.proxy + url if self.verbose: print("\nRequest:", method, url, headers, body) self.logger.debug("%s %s, Request: %s %s", method, url, headers, body) request_body = body encoded_body = body.encode() if body else None self.open() session_method = getattr(self.session, method.lower()) http_response = None http_status_code = None http_status_text = None json_response = None try: async with session_method(yarl.URL(url, encoded=True), data=encoded_body, headers=request_headers, timeout=(self.timeout / 1000), proxy=self.aiohttp_proxy) as response: http_response = await response.text() http_status_code = response.status http_status_text = response.reason json_response = self.parse_json(http_response) headers = response.headers if self.enableLastHttpResponse: self.last_http_response = http_response if self.enableLastResponseHeaders: self.last_response_headers = headers if self.enableLastJsonResponse: self.last_json_response = json_response if self.verbose: print("\nResponse:", method, url, http_status_code, headers, http_response) self.logger.debug("%s %s, Response: %s %s %s", method, url, http_status_code, headers, http_response) except socket.gaierror as e: raise ExchangeNotAvailable(method + ' ' + url) except concurrent.futures._base.TimeoutError as e: raise RequestTimeout(method + ' ' + url) except aiohttp.client_exceptions.ClientConnectionError as e: raise ExchangeNotAvailable(method + ' ' + url) except aiohttp.client_exceptions.ClientError as e: # base exception class raise ExchangeError(method + ' ' + url) self.handle_errors(http_status_code, http_status_text, url, method, headers, http_response, json_response, request_headers, request_body) self.handle_rest_errors(http_status_code, http_status_text, http_response, url, method) self.handle_rest_response(http_response, json_response, url, method) if json_response is not None: return json_response if self.is_text_response(headers): return http_response return response.content
async def fetch(self, url, method='GET', headers=None, body=None): """Perform a HTTP request and return decoded JSON data""" request_headers = self.prepare_request_headers(headers) url = self.proxy + url if self.verbose: self.print("\nRequest:", method, url, headers, body) self.logger.debug("%s %s, Request: %s %s", method, url, headers, body) request_body = body encoded_body = body.encode() if body else None self.open() session_method = getattr(self.session, method.lower()) http_response = None http_status_code = None http_status_text = None json_response = None try: async with session_method(yarl.URL(url, encoded=True), data=encoded_body, headers=request_headers, timeout=(self.timeout / 1000), proxy=self.aiohttp_proxy) as response: http_response = await response.text() # CIMultiDictProxy raw_headers = response.headers headers = {} for header in raw_headers: if header in headers: headers[header] = headers[header] + ', ' + raw_headers[ header] else: headers[header] = raw_headers[header] http_status_code = response.status http_status_text = response.reason http_response = self.on_rest_response( http_status_code, http_status_text, url, method, headers, http_response, request_headers, request_body) json_response = self.parse_json(http_response) if self.enableLastHttpResponse: self.last_http_response = http_response if self.enableLastResponseHeaders: self.last_response_headers = headers if self.enableLastJsonResponse: self.last_json_response = json_response if self.verbose: self.print("\nResponse:", method, url, http_status_code, headers, http_response) self.logger.debug("%s %s, Response: %s %s %s", method, url, http_status_code, headers, http_response) except socket.gaierror as e: details = ' '.join([self.id, method, url]) raise ExchangeNotAvailable(details) from e except (concurrent.futures.TimeoutError, asyncio.TimeoutError) as e: details = ' '.join([self.id, method, url]) raise RequestTimeout(details) from e except aiohttp.ClientConnectionError as e: details = ' '.join([self.id, method, url]) raise ExchangeNotAvailable(details) from e except aiohttp.ClientError as e: # base exception class details = ' '.join([self.id, method, url]) raise ExchangeError(details) from e self.handle_errors(http_status_code, http_status_text, url, method, headers, http_response, json_response, request_headers, request_body) self.handle_http_status_code(http_status_code, http_status_text, url, method, http_response) if json_response is not None: return json_response if self.is_text_response(headers): return http_response return response.content
def handle_errors(self, httpCode, reason, url, method, headers, body): if not isinstance(body, basestring): return # fallback to default error handler if len(body) < 2: return # fallback to default error handler if (body[0] == '{') or (body[0] == '['): response = json.loads(body) if 'success' in response: # # 1 - Liqui only returns the integer 'success' key from their private API # # {"success": 1, ...} httpCode == 200 # {"success": 0, ...} httpCode == 200 # # 2 - However, exchanges derived from Liqui, can return non-integers # # It can be a numeric string # {"sucesss": "1", ...} # {"sucesss": "0", ...}, httpCode >= 200(can be 403, 502, etc) # # Or just a string # {"success": "true", ...} # {"success": "false", ...}, httpCode >= 200 # # Or a boolean # {"success": True, ...} # {"success": False, ...}, httpCode >= 200 # # 3 - Oversimplified, Python PEP8 forbids comparison operator(==) of different types # # 4 - We do not want to copy-paste and duplicate the code of self handler to other exchanges derived from Liqui # # To cover points 1, 2, 3 and 4 combined self handler should work like self: # success = self.safe_value(response, 'success', False) if isinstance(success, basestring): if (success == 'true') or (success == '1'): success = True else: success = False if not success: code = self.safe_string(response, 'code') message = self.safe_string(response, 'error') feedback = self.id + ' ' + self.json(response) exceptions = self.exceptions if code in exceptions: raise exceptions[code](feedback) # need a second error map for these messages, apparently... # in fact, we can use the same .exceptions with string-keys to save some loc here if message == 'invalid api key': raise AuthenticationError(feedback) elif message == 'invalid sign': raise AuthenticationError(feedback) elif message == 'api key dont have trade permission': raise AuthenticationError(feedback) elif message.find( 'invalid parameter' ) >= 0: # errorCode 0, returned on buy(symbol, 0, 0) raise InvalidOrder(feedback) elif message == 'invalid order': raise InvalidOrder(feedback) elif message == 'Requests too often': raise DDoSProtection(feedback) elif message == 'not available': raise ExchangeNotAvailable(feedback) elif message == 'data unavailable': raise ExchangeNotAvailable(feedback) elif message == 'external service unavailable': raise ExchangeNotAvailable(feedback) else: raise ExchangeError(self.id + ' unknown "error" value: ' + self.json(response))
def handle_errors(self, code, reason, url, method, headers, body): if (code == 418) or (code == 429): raise DDoSProtection(self.id + ' ' + str(code) + ' ' + reason + ' ' + body) # error response in a form: {"code": -1013, "msg": "Invalid quantity."} # following block cointains legacy checks against message patterns in "msg" property # will switch "code" checks eventually, when we know all of them if code >= 400: if body.find('Price * QTY is zero or less') >= 0: raise InvalidOrder( self.id + ' order cost = amount * price is zero or less ' + body) if body.find('LOT_SIZE') >= 0: raise InvalidOrder( self.id + ' order amount should be evenly divisible by lot size ' + body) if body.find('PRICE_FILTER') >= 0: raise InvalidOrder( self.id + ' order price is invalid, i.e. exceeds allowed price precision, exceeds min price or max price limits or is invalid float value in general, use self.price_to_precision(symbol, amount) ' + body) if len(body) > 0: if body[0] == '{': response = json.loads(body) # check success value for wapi endpoints # response in format {'msg': 'The coin does not exist.', 'success': True/false} success = self.safe_value(response, 'success', True) if not success: message = self.safe_string(response, 'msg') parsedMessage = None if message is not None: try: parsedMessage = json.loads(message) except Exception as e: # do nothing parsedMessage = None if parsedMessage is not None: response = parsedMessage # checks against error codes error = self.safe_string(response, 'code') if error is not None: exceptions = self.exceptions if error in exceptions: # a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."} # despite that their message is very confusing, it is raised by Binance # on a temporary ban(the API key is valid, but disabled for a while) if (error == '-2015') and self.options[ 'hasAlreadyAuthenticatedSuccessfully']: raise DDoSProtection(self.id + ' temporary banned: ' + body) message = self.safe_string(response, 'msg') if message == 'Order would trigger immediately.': raise InvalidOrder(self.id + ' ' + body) elif message == 'Account has insufficient balance for requested action.': raise InsufficientFunds(self.id + ' ' + body) elif message == 'Rest API trading is not enabled.': raise ExchangeNotAvailable(self.id + ' ' + body) raise exceptions[error](self.id + ' ' + body) else: raise ExchangeError(self.id + ' ' + body) if not success: raise ExchangeError(self.id + ' ' + body)
def handle_errors(self, code, reason, url, method, headers, body, response=None): if body[0] == '{': response = json.loads(body) # {success: False, message: "message"} success = self.safe_value(response, 'success') if success is None: raise ExchangeError(self.id + ': malformed response: ' + self.json(response)) if isinstance(success, basestring): # bleutrade uses string instead of boolean success = True if (success == 'true') else False if not success: message = self.safe_string(response, 'message') feedback = self.id + ' ' + self.json(response) exceptions = self.exceptions if message == 'APIKEY_INVALID': if self.options['hasAlreadyAuthenticatedSuccessfully']: raise DDoSProtection(feedback) else: raise AuthenticationError(feedback) if message == 'DUST_TRADE_DISALLOWED_MIN_VALUE_50K_SAT': raise InvalidOrder( self.id + ' order cost should be over 50k satoshi ' + self.json(response)) if message == 'INVALID_ORDER': # Bittrex will return an ambiguous INVALID_ORDER message # upon canceling already-canceled and closed orders # therefore self special case for cancelOrder # url = 'https://bittrex.com/api/v1.1/market/cancel?apikey=API_KEY&uuid=ORDER_UUID' cancel = 'cancel' indexOfCancel = url.find(cancel) if indexOfCancel >= 0: parts = url.split('&') orderId = None for i in range(0, len(parts)): part = parts[i] keyValue = part.split('=') if keyValue[0] == 'uuid': orderId = keyValue[1] break if orderId is not None: raise OrderNotFound(self.id + ' cancelOrder ' + orderId + ' ' + self.json(response)) else: raise OrderNotFound(self.id + ' cancelOrder ' + self.json(response)) if message in exceptions: raise exceptions[message](feedback) if message is not None: if message.find('throttled. Try again') >= 0: raise DDoSProtection(feedback) if message.find('problem') >= 0: raise ExchangeNotAvailable( feedback ) # 'There was a problem processing your request. If self problem persists, please contact...') raise ExchangeError(feedback)