Skip to content
This repository has been archived by the owner on Jan 13, 2023. It is now read-only.

Commit

Permalink
Merge pull request #301 from lzpap/async
Browse files Browse the repository at this point in the history
Asynchronous API, Networking and Asyncio Support
  • Loading branch information
lzpap committed Feb 18, 2020
2 parents 1423951 + a2630fd commit d7d7285
Show file tree
Hide file tree
Showing 85 changed files with 3,924 additions and 939 deletions.
4 changes: 1 addition & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
language: python
python:
- '2.7'
- '3.5'
- '3.6'
- '3.7'
install:
- pip install .[docs-builder]
- pip install .[docs-builder,test-runner]
- pip install docutils pygments # Used to check package metadata.
script:
- python setup.py check --strict --metadata --restructuredtext
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Please report any issues in our [issue tracker](https://github.com/iotaledger/io

## Prerequisites

To install the IOTA Python client library and its dependencies, you need Python version 3.7, 3.6, 3.5, or 2.7 installed on your device.
To install the IOTA Python client library and its dependencies, you need Python version 3.7 or 3.6 installed on your device.

## Installation

Expand Down
2 changes: 1 addition & 1 deletion docs/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ If you encounter any issues while using PyOTA, please report them using the
============
Dependencies
============
PyOTA is compatible with Python 3.7, 3.6, 3.5 and 2.7
PyOTA is compatible with Python 3.7 and 3.6.

=============
Install PyOTA
Expand Down
2 changes: 1 addition & 1 deletion docs/getting_started.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Installation
============
PyOTA is compatible with Python 3.7, 3.6, 3.5 and 2.7.
PyOTA is compatible with Python 3.7 and 3.6.

Install PyOTA using `pip`:

Expand Down
5 changes: 2 additions & 3 deletions examples/hello_world.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
from pprint import pprint
from sys import argv
from typing import Text

from requests.exceptions import ConnectionError
from httpx.exceptions import NetworkError
from six import text_type

from iota import BadApiResponse, StrictIota, __version__
Expand All @@ -24,7 +23,7 @@ def main(uri):

try:
node_info = api.get_node_info()
except ConnectionError as e:
except NetworkError as e:
print(
"Hm. {uri} isn't responding; is the node running?".format(uri=uri)
)
Expand Down
1 change: 1 addition & 0 deletions iota/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from .types import *
from .transaction import *
from .adapter import *
from .api_async import *
from .api import *
from .trits import *

Expand Down
36 changes: 24 additions & 12 deletions iota/adapter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
from logging import DEBUG, Logger
from socket import getdefaulttimeout as get_default_timeout
from typing import Container, Dict, List, Optional, Text, Tuple, Union

from requests import Response, auth, codes, request
from httpx import AsyncClient, Response, codes, auth
import asyncio
from six import PY2, binary_type, iteritems, moves as compat, text_type, \
add_metaclass

Expand Down Expand Up @@ -59,6 +59,15 @@
# noinspection PyCompatibility,PyUnresolvedReferences
from urllib.parse import SplitResult

def async_return(result):
"""
Turns 'result' into a `Future` object with 'result' value.
Important for mocking, as we can await the mock's return value.
"""
f = asyncio.Future()
f.set_result(result)
return f

class BadApiResponse(ValueError):
"""
Expand Down Expand Up @@ -271,6 +280,7 @@ def __init__(self, uri, timeout=None, authentication=None):
# type: (Union[Text, SplitResult], Optional[int]) -> None
super(HttpAdapter, self).__init__()

self.client = AsyncClient()
self.timeout = timeout
self.authentication = authentication

Expand Down Expand Up @@ -331,13 +341,13 @@ def get_uri(self):
# type: () -> Text
return self.uri.geturl()

def send_request(self, payload, **kwargs):
async def send_request(self, payload, **kwargs):
# type: (dict, dict) -> dict
kwargs.setdefault('headers', {})
for key, value in iteritems(self.DEFAULT_HEADERS):
kwargs['headers'].setdefault(key, value)

response = self._send_http_request(
response = await self._send_http_request(
# Use a custom JSON encoder that knows how to convert Tryte
# values.
payload=JsonEncoder().encode(payload),
Expand All @@ -346,9 +356,9 @@ def send_request(self, payload, **kwargs):
**kwargs
)

return self._interpret_response(response, payload, {codes['ok']})
return self._interpret_response(response, payload, {codes['OK']})

def _send_http_request(self, url, payload, method='post', **kwargs):
async def _send_http_request(self, url, payload, method='post', **kwargs):
# type: (Text, Optional[Text], Text, dict) -> Response
"""
Sends the actual HTTP request.
Expand Down Expand Up @@ -380,8 +390,7 @@ def _send_http_request(self, url, payload, method='post', **kwargs):
'request_url': url,
},
)

response = request(method=method, url=url, data=payload, **kwargs)
response = await self.client.request(method=method, url=url, data=payload, **kwargs)

self._log(
level=DEBUG,
Expand Down Expand Up @@ -474,9 +483,9 @@ def _interpret_response(self, response, payload, expected_status):

error = None
try:
if response.status_code == codes['bad_request']:
if response.status_code == codes['BAD_REQUEST']:
error = decoded['error']
elif response.status_code == codes['internal_server_error']:
elif response.status_code == codes['INTERNAL_SERVER_ERROR']:
error = decoded['exception']
except KeyError:
pass
Expand Down Expand Up @@ -585,7 +594,10 @@ def seed_response(self, command, response):
self.responses[command].append(response)
return self

def send_request(self, payload, **kwargs):
async def send_request(self, payload, **kwargs):
"""
Mimic asynchronous behavior of `HttpAdapter.send_request`.
"""
# type: (dict, dict) -> dict
# Store a snapshot so that we can inspect the request later.
self.requests.append(dict(payload))
Expand Down Expand Up @@ -627,4 +639,4 @@ def send_request(self, payload, **kwargs):
raise with_context(BadApiResponse(error),
context={'request': payload})

return response
return await async_return(response)
4 changes: 2 additions & 2 deletions iota/adapter/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ def get_adapter(self, command):
"""
return self.routes.get(command, self.adapter)

def send_request(self, payload, **kwargs):
async def send_request(self, payload, **kwargs):
# type: (dict, dict) -> dict
command = payload.get('command')

return self.get_adapter(command).send_request(payload, **kwargs)
return await self.get_adapter(command).send_request(payload, **kwargs)

0 comments on commit d7d7285

Please sign in to comment.