(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody)
| 11635 | return {} |
| 11636 | |
| 11637 | def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody): |
| 11638 | if (code == 418) or (code == 429): |
| 11639 | raise DDoSProtection(self.id + ' ' + str(code) + ' ' + reason + ' ' + body) |
| 11640 | # error response in a form: {"code": -1013, "msg": "Invalid quantity."} |
| 11641 | # following block cointains legacy checks against message patterns in "msg" property |
| 11642 | # will switch "code" checks eventually, when we know all of them |
| 11643 | if code >= 400: |
| 11644 | if body.find('Price * QTY is zero or less') >= 0: |
| 11645 | raise InvalidOrder(self.id + ' order cost = amount * price is zero or less ' + body) |
| 11646 | if body.find('LOT_SIZE') >= 0: |
| 11647 | raise InvalidOrder(self.id + ' order amount should be evenly divisible by lot size ' + body) |
| 11648 | if body.find('PRICE_FILTER') >= 0: |
| 11649 | raise InvalidOrder(self.id + ' order price is invalid, i.e. exceeds allowed price precision, exceeds min price or max price limits or is invalid value in general, use self.price_to_precision(symbol, amount) ' + body) |
| 11650 | if response is None: |
| 11651 | return None # fallback to default error handler |
| 11652 | # response in format {'msg': 'The coin does not exist.', 'success': True/false} |
| 11653 | success = self.safe_bool(response, 'success', True) |
| 11654 | if not success: |
| 11655 | messageNew = self.safe_string(response, 'msg') |
| 11656 | parsedMessage = None |
| 11657 | if messageNew is not None: |
| 11658 | try: |
| 11659 | parsedMessage = json.loads(messageNew) |
| 11660 | except Exception as e: |
| 11661 | # do nothing |
| 11662 | parsedMessage = None |
| 11663 | if parsedMessage is not None: |
| 11664 | response = parsedMessage |
| 11665 | message = self.safe_string(response, 'msg') |
| 11666 | if message is not None: |
| 11667 | self.throw_exactly_matched_exception(self.get_exceptions_by_url(url, 'exact'), message, self.id + ' ' + message) |
| 11668 | self.throw_exactly_matched_exception(self.exceptions['exact'], message, self.id + ' ' + message) |
| 11669 | self.throw_broadly_matched_exception(self.get_exceptions_by_url(url, 'broad'), message, self.id + ' ' + message) |
| 11670 | self.throw_broadly_matched_exception(self.exceptions['broad'], message, self.id + ' ' + message) |
| 11671 | # checks against error codes |
| 11672 | error = self.safe_string(response, 'code') |
| 11673 | if error is not None: |
| 11674 | # https://github.com/ccxt/ccxt/issues/6501 |
| 11675 | # https://github.com/ccxt/ccxt/issues/7742 |
| 11676 | if (error == '200') or Precise.string_equals(error, '0'): |
| 11677 | return None |
| 11678 | # a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."} |
| 11679 | # despite that their message is very confusing, it is raised by Binance |
| 11680 | # on a temporary ban, the API key is valid, but disabled for a while |
| 11681 | if (error == '-2015') and self.options['hasAlreadyAuthenticatedSuccessfully']: |
| 11682 | raise DDoSProtection(self.id + ' ' + body) |
| 11683 | feedback = self.id + ' ' + body |
| 11684 | if message == 'No need to change margin type.': |
| 11685 | # not an error |
| 11686 | # https://github.com/ccxt/ccxt/issues/11268 |
| 11687 | # https://github.com/ccxt/ccxt/pull/11624 |
| 11688 | # POST https://fapi.binance.com/fapi/v1/marginType 400 Bad Request |
| 11689 | # binanceusdm {"code":-4046,"msg":"No need to change margin type."} |
| 11690 | raise MarginModeAlreadySet(feedback) |
| 11691 | self.throw_exactly_matched_exception(self.get_exceptions_by_url(url, 'exact'), error, feedback) |
| 11692 | self.throw_exactly_matched_exception(self.exceptions['exact'], error, feedback) |
| 11693 | raise ExchangeError(feedback) |
| 11694 | if not success: |
nothing calls this directly
no test coverage detected