Skip to content

Commit

Permalink
feat: Throws an exception when there is a problem with the horizon re…
Browse files Browse the repository at this point in the history
…quest.
  • Loading branch information
overcat committed Aug 10, 2019
1 parent c5f3de7 commit 2fc5d0f
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 41 deletions.
6 changes: 4 additions & 2 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
[run]
source = stellar_sdk
source =
stellar_sdk/
tests/

[report]
omit =
stellar_sdk/xdr/*
stellar_sdk/vendor/*
stellar_sdk/base58.py
tests/conftest.py

exclude_lines =
pragma: no cover
Expand Down
36 changes: 36 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,42 @@ SignatureExistError
.. autoclass:: stellar_sdk.exceptions.SignatureExistError
:members:

BaseRequestError
----------------

.. autoclass:: stellar_sdk.exceptions.BaseRequestError
:members:

ConnectionError
---------------

.. autoclass:: stellar_sdk.exceptions.ConnectionError
:members:

BaseHorizonError
----------------

.. autoclass:: stellar_sdk.exceptions.BaseHorizonError
:members:

NotFoundError
-------------

.. autoclass:: stellar_sdk.exceptions.NotFoundError
:members:

BadRequestError
---------------

.. autoclass:: stellar_sdk.exceptions.BadRequestError
:members:

BadResponseError
----------------

.. autoclass:: stellar_sdk.exceptions.BadResponseError
:members:

Keypair
^^^^^^^

Expand Down
33 changes: 17 additions & 16 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,15 @@
copyright = '2019, Stellar Community'
author = 'Stellar Community'

import stellar_sdk
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '2.0.0'
version = stellar_sdk.__version__.split("-")[0]
# The full version, including alpha/beta/rc tags.
release = '2.0.0'
release = stellar_sdk.__version__

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down Expand Up @@ -110,20 +111,20 @@
#
# This is required for the alabaster theme
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
html_sidebars = {
'index': [
'about.html',
'navigation.html',
'relations.html', # needs 'show_related': True theme option to display
'searchbox.html',
],
'**': [
'about.html',
'localtoc.html',
'relations.html', # needs 'show_related': True theme option to display
'searchbox.html',
]
}
# html_sidebars = {
# 'index': [
# 'about.html',
# 'navigation.html',
# 'relations.html', # needs 'show_related': True theme option to display
# 'searchbox.html',
# ],
# '**': [
# 'about.html',
# 'localtoc.html',
# 'relations.html', # needs 'show_related': True theme option to display
# 'searchbox.html',
# ]
# }

# -- Options for HTMLHelp output ------------------------------------------

Expand Down
29 changes: 26 additions & 3 deletions stellar_sdk/call_builder/base_call_builder.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from typing import Union, Coroutine, Any, AsyncGenerator
from typing import Union, Coroutine, Any
from urllib.parse import urljoin

from ..exceptions import (
NotFoundError,
BadResponseError,
BadRequestError,
UnknownRequestError,
)
from ..client.base_async_client import BaseAsyncClient
from ..client.base_sync_client import BaseSyncClient
from ..client.response import Response
Expand Down Expand Up @@ -41,11 +47,15 @@ def call(self) -> Union[Response, Coroutine[Any, Any, Response]]:

def __call_sync(self) -> Response:
url = urljoin(self.horizon_url, self.endpoint)
return self.client.get(url, self.params)
resp = self.client.get(url, self.params)
self._raise_request_exception(resp)
return resp

async def __call_async(self) -> Response:
url = urljoin(self.horizon_url, self.endpoint)
return await self.client.get(url, self.params)
resp = await self.client.get(url, self.params)
self._raise_request_exception(resp)
return resp

def stream(self):
"""Creates an EventSource that listens for incoming messages from the server.
Expand Down Expand Up @@ -118,6 +128,19 @@ def _add_query_params(self, params: dict):
for k, v in params.items():
self._add_query_param(k, v)

def _raise_request_exception(self, response):
status_code = response.status_code
if status_code == 200:
pass
elif status_code == 400:
raise BadRequestError(response)
elif status_code == 404:
raise NotFoundError(response)
elif 500 <= status_code < 600:
raise BadResponseError(response)
else:
raise UnknownRequestError(response)

def __eq__(self, other: "BaseCallBuilder"):
if not isinstance(other, self.__class__):
return False
Expand Down
34 changes: 20 additions & 14 deletions stellar_sdk/client/aiohttp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,22 +59,28 @@ def __init__(
self._sse_session = None

async def get(self, url: str, params=None) -> Response:
async with self._session.get(url, params=params) as response:
return Response(
status_code=response.status,
text=await response.text(),
headers=dict(response.headers),
url=str(response.url),
)
try:
async with self._session.get(url, params=params) as response:
return Response(
status_code=response.status,
text=await response.text(),
headers=dict(response.headers),
url=str(response.url),
)
except aiohttp.ClientConnectionError as e: # TODO: need more research
raise ConnectionError(e)

async def post(self, url: str, data=None) -> Response:
async with self._session.post(url, data=data) as response:
return Response(
status_code=response.status,
text=await response.text(),
headers=dict(response.headers),
url=str(response.url),
)
try:
async with self._session.post(url, data=data) as response:
return Response(
status_code=response.status,
text=await response.text(),
headers=dict(response.headers),
url=str(response.url),
)
except aiohttp.ClientConnectionError as e:
raise ConnectionError(e)

async def _init_sse_session(self) -> None:
"""Init the sse session """
Expand Down
14 changes: 11 additions & 3 deletions stellar_sdk/client/requests_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
from typing import Generator

import requests
from requests import Session
from requests import Session, RequestException
from requests.adapters import HTTPAdapter, DEFAULT_POOLSIZE
from sseclient import SSEClient
from urllib3.exceptions import NewConnectionError
from urllib3.util import Retry

from stellar_sdk.exceptions import ConnectionError
from ..__version__ import __version__
from ..client.base_sync_client import BaseSyncClient
from ..client.response import Response
Expand Down Expand Up @@ -90,7 +92,10 @@ def __init__(
self._stream_session = sse_session

def get(self, url, params=None) -> Response:
resp = self._session.get(url, params=params, timeout=self.request_timeout)
try:
resp = self._session.get(url, params=params, timeout=self.request_timeout)
except (RequestException, NewConnectionError) as err:
raise ConnectionError(err)
return Response(
status_code=resp.status_code,
text=resp.text,
Expand All @@ -99,7 +104,10 @@ def get(self, url, params=None) -> Response:
)

def post(self, url, data=None) -> Response:
resp = self._session.post(url, data=data, timeout=self.request_timeout)
try:
resp = self._session.post(url, data=data, timeout=self.request_timeout)
except (RequestException, NewConnectionError) as err:
raise ConnectionError(err)
return Response(
status_code=resp.status_code,
text=resp.text,
Expand Down
73 changes: 73 additions & 0 deletions stellar_sdk/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,76 @@ class SignatureExistError(ValueError):
"""A keypair can only sign a transaction once.
"""


class BaseRequestError(SdkError):
"""Base class for requests errors.
"""


class ConnectionError(BaseRequestError):
"""Base class for client connection errors.
"""


class BaseHorizonError(BaseRequestError):
"""Base class for horizon request errors.
"""

def __init__(self, response):
super().__init__(response)
message = response.json()
self.type = message.get("type")
self.title = message.get("title")
self.status = message.get("status")
self.detail = message.get("detail")
self.extras = message.get("extras")

def __str__(self):
return """\n\ttype: {type}
title: {title}
status: {status}
detail: {detail}
extras: {extras}
""".format(
type=self.type,
title=self.title,
status=self.status,
detail=self.detail,
extras=self.extras,
)


class NotFoundError(BaseHorizonError):
"""This exception is thrown when the requested resource does not exist.
"""

def __init__(self, response):
super().__init__(response)


class BadRequestError(BaseHorizonError):
"""The request from the client has an error.
"""

def __init__(self, response):
super().__init__(response)


class BadResponseError(BaseHorizonError):
"""The response from the server has an error.
"""

def __init__(self, response):
super().__init__(response)


class UnknownRequestError(BaseHorizonError):
"""Unknown request exception, please create an issue feedback for this issue.
"""
3 changes: 2 additions & 1 deletion stellar_sdk/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Transaction:
represented as one of the subclasses of the
:class:`Memo <stellar_sdk.memo.Memo>` object.
"""

def __init__(
self,
source: Keypair,
Expand Down Expand Up @@ -84,7 +85,7 @@ def to_xdr_object(self) -> Xdr.types.Transaction:
)

@classmethod
def from_xdr_object(cls, tx_xdr_object) -> 'Transaction':
def from_xdr_object(cls, tx_xdr_object) -> "Transaction":
"""Create a new :class:`Transaction` from an XDR object.
:param tx_xdr_object: The XDR object that represents a transaction.
Expand Down

0 comments on commit 2fc5d0f

Please sign in to comment.