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

Asynchronous API, Networking and Asyncio Support #301

Merged
merged 33 commits into from
Feb 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
3857f05
Replace 'requests' with 'httpx' for networking
lzpap Feb 5, 2020
2186fde
Rewrite adapter tests for async networking
lzpap Feb 6, 2020
7ab91b3
Make `RoutingWrapper` work with async
lzpap Feb 6, 2020
61f8f96
Refactor API classes for async
lzpap Feb 6, 2020
dabcd3e
Make adapter and api tests async
lzpap Feb 7, 2020
9347e94
Make `BroadcastAndStoreCommand` async
lzpap Feb 7, 2020
ee2361b
Make `bundle` commands async
lzpap Feb 7, 2020
e5e339e
Make `FindTransactionObjectsCommand` async
lzpap Feb 7, 2020
703694a
Make `GetAccountDataCommand` async
lzpap Feb 7, 2020
6f33904
Make `GetInputsCommand` async
lzpap Feb 7, 2020
59fe3e9
Make `GetLatestInclusionCommand` async
lzpap Feb 10, 2020
61e4911
Make `GetNewAddressesCommand` async
lzpap Feb 10, 2020
1793754
Make `GetTransactionObjectsCommand` async
lzpap Feb 10, 2020
93ef6ff
Make `GetTransfersCommand` async
lzpap Feb 10, 2020
8640b2f
Remove `Helpers` class from api
lzpap Feb 10, 2020
d696b96
Make `IsPromotableCommand` async
lzpap Feb 10, 2020
c0bbcad
Make `IsReattachableCommand` async
lzpap Feb 10, 2020
e900067
Make `PrepareTransferCommand` async
lzpap Feb 10, 2020
049c0e6
Make `PromoteTransactionCommand` async
lzpap Feb 10, 2020
f238471
Make `ReplayBundleCommand` async
lzpap Feb 10, 2020
6dd62fc
Make `SendTransferCommand` async
lzpap Feb 10, 2020
40f5a30
Make `SendTrytesCommand` async
lzpap Feb 10, 2020
cfcfb2d
Update core command tests for async
lzpap Feb 11, 2020
271cfcf
Update Multisig API to handle async
lzpap Feb 11, 2020
5a71f8c
Remove support for Python 2 and 3.5
lzpap Feb 11, 2020
920fb67
Install [test-runner] in Travis CI
lzpap Feb 11, 2020
321fa47
Use py3-style super() calls in api classes
lzpap Feb 12, 2020
e91a59f
Reorder deps and imports in alphabetical order
lzpap Feb 14, 2020
ee7faa5
Update docs: no more PY2 and PY3.5 support
lzpap Feb 14, 2020
2c59955
Separate sync and async api into two src files
lzpap Feb 17, 2020
0207cbc
Remove ``requests`` dependency
lzpap Feb 17, 2020
ce55c6b
code formatting, type hints
lzpap Feb 17, 2020
a2630fd
Add connection pooling to HttpAdapter
lzpap Feb 18, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Loading