Skip to content

Commit

Permalink
docs: http client
Browse files Browse the repository at this point in the history
  • Loading branch information
overcat committed Aug 11, 2019
1 parent 9d44a0f commit ffa99d1
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 29 deletions.
35 changes: 35 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,41 @@ TransactionsCallBuilder
:members:
:inherited-members:

Client
^^^^^^

BaseAsyncClient
---------------

.. autoclass:: stellar_sdk.client.base_async_client.BaseAsyncClient
:members:

BaseAsyncClient
---------------

.. autoclass:: stellar_sdk.client.base_sync_client.BaseSyncClient
:members:

AiohttpClient
--------------

.. autoclass:: stellar_sdk.client.aiohttp_client.AiohttpClient
:members:

SimpleRequestsClient
--------------------

.. autoclass:: stellar_sdk.client.simple_requests_client.SimpleRequestsClient
:members:

Response
--------

.. autoclass:: stellar_sdk.client.response.Response
:members:



Exceptions
^^^^^^^^^^

Expand Down
6 changes: 6 additions & 0 deletions stellar_sdk/call_builder/base_call_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ def call(self) -> Union[Dict[str, Any], Coroutine[Any, Any, Dict[str, Any]]]:
:return: If it is called synchronous, the response will be returned. If
it is called asynchronously, it will return Coroutine.
:raises:
:exc:`ConnectionError <stellar_sdk.exceptions.ConnectionError>`
:exc:`NotFoundError <stellar_sdk.exceptions.NotFoundError>`
:exc:`BadRequestError <stellar_sdk.exceptions.BadRequestError>`
:exc:`BadResponseError <stellar_sdk.exceptions.BadResponseError>`
:exc:`UnknownRequestError <stellar_sdk.exceptions.UnknownRequestError>`
"""
if self.__async:
return self.__call_async()
Expand Down
6 changes: 3 additions & 3 deletions stellar_sdk/call_builder/offers_call_builder.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import Union

from stellar_sdk.call_builder import BaseCallBuilder
from stellar_sdk.client.base_async_client import BaseAsyncClient
from stellar_sdk.client.base_sync_client import BaseSyncClient
from ..call_builder import BaseCallBuilder
from ..client.base_async_client import BaseAsyncClient
from ..client.base_sync_client import BaseSyncClient


class OffersCallBuilder(BaseCallBuilder):
Expand Down
46 changes: 38 additions & 8 deletions stellar_sdk/client/aiohttp_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import asyncio
import json
from typing import Optional, Union, AsyncGenerator, Any, Dict
from typing import Optional, AsyncGenerator, Any, Dict

import aiohttp
from aiohttp_sse_client.client import EventSource
Expand All @@ -23,16 +23,23 @@


class AiohttpClient(BaseAsyncClient):
"""The :class:`AiohttpClient` object is a asynchronous http client,
which represents the interface for making requests to a server instance.
:param pool_size: persistent connection to Horizon and connection pool
:param request_timeout: the timeout for all requests
:param backoff_factor: a backoff factor to apply between attempts after the second try
:param user_agent: the server can use it to identify you
"""

def __init__(
self,
pool_size: Optional[int] = None,
num_retries: Optional[int] = DEFAULT_NUM_RETRIES,
request_timeout: float = DEFAULT_REQUEST_TIMEOUT,
backoff_factor: Optional[float] = DEFAULT_BACKOFF_FACTOR,
user_agent: Optional[str] = None,
**kwargs
) -> None:
self.num_retries = num_retries
self.backoff_factor = backoff_factor

# init session
Expand Down Expand Up @@ -61,6 +68,13 @@ def __init__(
self._sse_session = None

async def get(self, url: str, params: Dict[str, str] = None) -> Response:
"""Perform HTTP GET request.
:param url: the request url
:param params: the requested params
:return: the response from server
:raise: :exc:`ConnectionError <stellar_sdk.exceptions.ConnectionError>`
"""
try:
async with self._session.get(url, params=params) as response:
return Response(
Expand All @@ -73,6 +87,13 @@ async def get(self, url: str, params: Dict[str, str] = None) -> Response:
raise ConnectionError(e)

async def post(self, url: str, data: Dict[str, str] = None) -> Response:
"""Perform HTTP POST request.
:param url: the request url
:param data: the data send to server
:return: the response from server
:raise: :exc:`ConnectionError <stellar_sdk.exceptions.ConnectionError>`
"""
try:
async with self._session.post(url, data=data) as response:
return Response(
Expand All @@ -94,11 +115,16 @@ async def _init_sse_session(self) -> None:
async def stream(
self, url: str, params: Dict[str, str] = None
) -> AsyncGenerator[Dict[str, Any], None]:
"""
SSE generator with timeout between events
:param url: URL to send SSE request to
:param params: params
:return: response dict
"""Creates an EventSource that listens for incoming messages from the server.
See `Horizon Response Format <https://www.stellar.org/developers/horizon/reference/responses.html>`_
See `MDN EventSource <https://developer.mozilla.org/en-US/docs/Web/API/EventSource>`_
:param url: the request url
:param params: the request params
:return: a dict Generator for server response
:raise: :exc:`ConnectionError <stellar_sdk.exceptions.ConnectionError>`
"""

async def _sse_generator() -> AsyncGenerator[dict, Any]:
Expand Down Expand Up @@ -160,6 +186,10 @@ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
await self.close()

async def close(self) -> None:
"""Close underlying connector.
Release all acquired resources.
"""
await self._session.__aexit__(None, None, None)
if self._sse_session is not None:
await self._sse_session.__aexit__(None, None, None)
30 changes: 30 additions & 0 deletions stellar_sdk/client/base_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,48 @@


class BaseAsyncClient(metaclass=ABCMeta):
"""This is an abstract class,
and if you want to implement your own asynchronous client, you **must** implement this class.
"""

@abstractmethod
async def get(self, url: str, params: Dict[str, str] = None) -> Response:
"""Perform HTTP GET request.
:param url: the request url
:param params: the request params
:return: the response from server
:raise: :exc:`ConnectionError <stellar_sdk.exceptions.ConnectionError>`
"""
pass

@abstractmethod
async def post(self, url: str, data: Dict[str, str]) -> Response:
"""Perform HTTP POST request.
:param url: the request url
:param data: the data send to server
:return: the response from server
:raise: :exc:`ConnectionError <stellar_sdk.exceptions.ConnectionError>`
"""
pass

@abstractmethod
async def stream(
self, url: str, params: Dict[str, str] = None
) -> AsyncGenerator[Dict[str, Any], None]:
"""Creates an EventSource that listens for incoming messages from the server.
See `Horizon Response Format <https://www.stellar.org/developers/horizon/reference/responses.html>`_
See `MDN EventSource <https://developer.mozilla.org/en-US/docs/Web/API/EventSource>`_
:param url: the request url
:param params: the request params
:return: a dict AsyncGenerator for server response
:raise: :exc:`ConnectionError <stellar_sdk.exceptions.ConnectionError>`
"""
pass

@abstractmethod
Expand Down
30 changes: 30 additions & 0 deletions stellar_sdk/client/base_sync_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,48 @@


class BaseSyncClient(metaclass=ABCMeta):
"""This is an abstract class,
and if you want to implement your own synchronous client, you **must** implement this class.
"""

@abstractmethod
def get(self, url: str, params: Dict[str, str] = None) -> Response:
"""Perform HTTP GET request.
:param url: the request url
:param params: the request params
:return: the response from server
:raise: :exc:`ConnectionError <stellar_sdk.exceptions.ConnectionError>`
"""
pass

@abstractmethod
def post(self, url: str, data: Dict[str, str]) -> Response:
"""Perform HTTP POST request.
:param url: the request url
:param data: the data send to server
:return: the response from server
:raise: :exc:`ConnectionError <stellar_sdk.exceptions.ConnectionError>`
"""
pass

@abstractmethod
def stream(
self, url: str, params: Dict[str, str] = None
) -> Generator[Dict[str, Any], None, None]:
"""Creates an EventSource that listens for incoming messages from the server.
See `Horizon Response Format <https://www.stellar.org/developers/horizon/reference/responses.html>`_
See `MDN EventSource <https://developer.mozilla.org/en-US/docs/Web/API/EventSource>`_
:param url: the request url
:param params: the request params
:return: a dict Generator for server response
:raise: :exc:`ConnectionError <stellar_sdk.exceptions.ConnectionError>`
"""
pass

@abstractmethod
Expand Down
76 changes: 58 additions & 18 deletions stellar_sdk/client/requests_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
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
from ..exceptions import ConnectionError

# two ledgers + 1 sec, let's retry faster and not wait 60 secs.
DEFAULT_REQUEST_TIMEOUT = 11
Expand All @@ -27,14 +27,25 @@


class RequestsClient(BaseSyncClient):
"""The :class:`RequestsClient` object is a synchronous http client,
which represents the interface for making requests to a server instance.
:param pool_size: persistent connection to Horizon and connection pool
:param num_retries: configurable request retry functionality
:param request_timeout: the timeout for all requests
:param backoff_factor: a backoff factor to apply between attempts after the second try
:param session: the request session
:param stream_session: the stream request session
"""

def __init__(
self,
pool_size: int = DEFAULT_POOLSIZE,
num_retries: int = DEFAULT_NUM_RETRIES,
request_timeout: int = DEFAULT_REQUEST_TIMEOUT,
backoff_factor: float = DEFAULT_BACKOFF_FACTOR,
session: Session = None,
stream_session: Session = None,
self,
pool_size: int = DEFAULT_POOLSIZE,
num_retries: int = DEFAULT_NUM_RETRIES,
request_timeout: int = DEFAULT_REQUEST_TIMEOUT,
backoff_factor: float = DEFAULT_BACKOFF_FACTOR,
session: Session = None,
stream_session: Session = None,
):
self.pool_size = pool_size
self.num_retries = num_retries
Expand Down Expand Up @@ -94,6 +105,13 @@ def __init__(
self._stream_session: Session = stream_session

def get(self, url: str, params: Dict[str, str] = None) -> Response:
"""Perform HTTP GET request.
:param url: the request url
:param params: the request params
:return: the response from server
:raise: :exc:`ConnectionError <stellar_sdk.exceptions.ConnectionError>`
"""
try:
resp = self._session.get(url, params=params, timeout=self.request_timeout)
except (RequestException, NewConnectionError) as err:
Expand All @@ -106,6 +124,13 @@ def get(self, url: str, params: Dict[str, str] = None) -> Response:
)

def post(self, url: str, data: Dict[str, str] = None) -> Response:
"""Perform HTTP POST request.
:param url: the request url
:param data: the data send to server
:return: the response from server
:raise: :exc:`ConnectionError <stellar_sdk.exceptions.ConnectionError>`
"""
try:
resp = self._session.post(url, data=data, timeout=self.request_timeout)
except (RequestException, NewConnectionError) as err:
Expand All @@ -118,8 +143,19 @@ def post(self, url: str, data: Dict[str, str] = None) -> Response:
)

def stream(
self, url: str, params: Dict[str, str] = None
self, url: str, params: Dict[str, str] = None
) -> Generator[Dict[str, Any], None, None]:
"""Creates an EventSource that listens for incoming messages from the server.
See `Horizon Response Format <https://www.stellar.org/developers/horizon/reference/responses.html>`_
See `MDN EventSource <https://developer.mozilla.org/en-US/docs/Web/API/EventSource>`_
:param url: the request url
:param params: the request params
:return: a Generator for server response
:raise: :exc:`ConnectionError <stellar_sdk.exceptions.ConnectionError>`
"""
query_params: Dict[str, Union[int, float, str]] = {**IDENTIFICATION_HEADERS}
if params:
query_params = {**params, **query_params}
Expand All @@ -134,6 +170,10 @@ def stream(
yield message

def close(self) -> None:
"""Close underlying connector.
Release all acquired resources.
"""
self._session.close()
self._stream_session.close()

Expand All @@ -146,14 +186,14 @@ def __exit__(self, exc_type, exc_val, exc_tb):

class _SSEClient:
def __init__(
self,
url,
last_id=None,
retry=3000,
session=None,
chunk_size=1024,
connect_retry=0,
**kwargs
self,
url: str,
last_id: Union[str, int] = None,
retry: int = 3000,
session: Session = None,
chunk_size: int = 1024,
connect_retry: int = 0,
**kwargs
):
if SSEClient is None:
raise ImportError(
Expand All @@ -167,7 +207,7 @@ def __init__(
def __iter__(self):
return self

def __next__(self):
def __next__(self) -> Dict[str, Any]:
while True:
msg = next(self.client)
data = msg.data
Expand Down

0 comments on commit ffa99d1

Please sign in to comment.