From dd190e131fc52aaaee2d0a7a184e9575ccac063e Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Wed, 29 Jan 2020 12:34:09 +0100 Subject: [PATCH 01/69] add tests for `iter_used_addresses` --- test/commands/extended/utils_test.py | 243 +++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 test/commands/extended/utils_test.py diff --git a/test/commands/extended/utils_test.py b/test/commands/extended/utils_test.py new file mode 100644 index 0000000..3e66130 --- /dev/null +++ b/test/commands/extended/utils_test.py @@ -0,0 +1,243 @@ +# coding=utf-8 +from __future__ import absolute_import, division, print_function, \ + unicode_literals + +from unittest import TestCase + +from iota.commands.extended.utils import iter_used_addresses + +from iota import MockAdapter +from iota.crypto.types import Seed +from test import mock + + +class IterUsedAddressesTestCase(TestCase): + def setUp(self): + super(IterUsedAddressesTestCase, self).setUp() + + self.adapter = MockAdapter() + self.seed = Seed(trytes='S' * 81) + self.address0 = 'A' * 81 + self.address1 = 'B' * 81 + self.address2 = 'C' * 81 + self.address3 = 'D' * 81 + + # To speed up the tests, we will mock the address generator. + def address_generator(ag, start, step=1): + for addy in [self.address0, self.address1, self.address2, + self.address3][start::step]: + yield addy + self.mock_address_generator = address_generator + + def seed_unused_address(self): + self.adapter.seed_response('findTransactions', { + 'hashes': [], + }) + self.adapter.seed_response('wereAddressesSpentFrom', { + 'states': [False], + }) + + def get_all_used_addresses(self, start=0): + return [address for address, _ + in iter_used_addresses(self.adapter, self.seed, start)] + + def test_fist_address_is_not_used(self): + """ + The very fist address is not used. No address is returned. + """ + # Address 0 + self.seed_unused_address() + + with mock.patch( + 'iota.crypto.addresses.AddressGenerator.create_iterator', + self.mock_address_generator, + ): + self.assertEqual([], self.get_all_used_addresses()) + + self.assertListEqual( + self.adapter.requests, + [ + { + 'command': 'findTransactions', + 'addresses': [self.address0], + }, + { + 'command': 'wereAddressesSpentFrom', + 'addresses': [self.address0], + }, + ] + ) + + def test_transactions_are_considered_used(self): + """ + An address with a transaction is considered used. + """ + # Address 0 + self.adapter.seed_response('findTransactions', { + 'hashes': ['T' * 81], + }) + + # Address 1 + self.seed_unused_address() + + with mock.patch( + 'iota.crypto.addresses.AddressGenerator.create_iterator', + self.mock_address_generator, + ): + self.assertEqual([self.address0], self.get_all_used_addresses()) + + self.assertListEqual( + self.adapter.requests, + [ + { + 'command': 'findTransactions', + 'addresses': [self.address0], + }, + { + 'command': 'findTransactions', + 'addresses': [self.address1], + }, + { + 'command': 'wereAddressesSpentFrom', + 'addresses': [self.address1], + }, + ] + ) + + def test_spent_from_is_considered_used(self): + """ + An address that was spent from is considered used. + """ + # Address 0 + self.adapter.seed_response('findTransactions', { + 'hashes': [], + }) + self.adapter.seed_response('wereAddressesSpentFrom', { + 'states': [True], + }) + + # Address 1 + self.seed_unused_address() + + with mock.patch( + 'iota.crypto.addresses.AddressGenerator.create_iterator', + self.mock_address_generator, + ): + self.assertEqual([self.address0], self.get_all_used_addresses()) + + self.assertListEqual( + self.adapter.requests, + [ + { + 'command': 'findTransactions', + 'addresses': [self.address0], + }, + { + 'command': 'wereAddressesSpentFrom', + 'addresses': [self.address0], + }, + { + 'command': 'findTransactions', + 'addresses': [self.address1], + }, + { + 'command': 'wereAddressesSpentFrom', + 'addresses': [self.address1], + }, + ] + ) + + def test_start_parameter_is_given(self): + """ + The correct address is returned if a start parameter is given + """ + # Address 1 + self.adapter.seed_response('findTransactions', { + 'hashes': ['T' * 81], + }) + + # Address 2 + self.seed_unused_address() + + with mock.patch( + 'iota.crypto.addresses.AddressGenerator.create_iterator', + self.mock_address_generator, + ): + self.assertEqual([self.address1], + self.get_all_used_addresses(start=1)) + + self.assertListEqual( + self.adapter.requests, + [ + { + 'command': 'findTransactions', + 'addresses': [self.address1], + }, + { + 'command': 'findTransactions', + 'addresses': [self.address2], + }, + { + 'command': 'wereAddressesSpentFrom', + 'addresses': [self.address2], + }, + ] + ) + + def test_multiple_addresses_return(self): + """ + A larger test that combines multiple cases and more than one address + should be returned. + Address 0: Was spent from + Address 1: Has a transaction + Address 2: Is not used. Should not be returned + """ + + # Address 0 + self.adapter.seed_response('findTransactions', { + 'hashes': [], + }) + self.adapter.seed_response('wereAddressesSpentFrom', { + 'states': [True], + }) + + # Address 1 + self.adapter.seed_response('findTransactions', { + 'hashes': ['T' * 81], + }) + + # Address 2 + self.seed_unused_address() + + with mock.patch( + 'iota.crypto.addresses.AddressGenerator.create_iterator', + self.mock_address_generator, + ): + self.assertEqual([self.address0, self.address1], + self.get_all_used_addresses()) + + self.assertListEqual( + self.adapter.requests, + [ + { + 'command': 'findTransactions', + 'addresses': [self.address0], + }, + { + 'command': 'wereAddressesSpentFrom', + 'addresses': [self.address0], + }, + { + 'command': 'findTransactions', + 'addresses': [self.address1], + }, + { + 'command': 'findTransactions', + 'addresses': [self.address2], + }, + { + 'command': 'wereAddressesSpentFrom', + 'addresses': [self.address2], + }, + ] + ) From feef769f5a0dfe393554f48f382c8253999705d0 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Thu, 30 Jan 2020 12:53:16 +0100 Subject: [PATCH 02/69] fix typos in `util_test.py` --- test/commands/extended/utils_test.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/commands/extended/utils_test.py b/test/commands/extended/utils_test.py index 3e66130..4ba6bfe 100644 --- a/test/commands/extended/utils_test.py +++ b/test/commands/extended/utils_test.py @@ -3,9 +3,7 @@ unicode_literals from unittest import TestCase - from iota.commands.extended.utils import iter_used_addresses - from iota import MockAdapter from iota.crypto.types import Seed from test import mock @@ -41,9 +39,9 @@ def get_all_used_addresses(self, start=0): return [address for address, _ in iter_used_addresses(self.adapter, self.seed, start)] - def test_fist_address_is_not_used(self): + def test_first_address_is_not_used(self): """ - The very fist address is not used. No address is returned. + The very first address is not used. No address is returned. """ # Address 0 self.seed_unused_address() @@ -149,7 +147,7 @@ def test_spent_from_is_considered_used(self): def test_start_parameter_is_given(self): """ - The correct address is returned if a start parameter is given + The correct address is returned if a start parameter is given. """ # Address 1 self.adapter.seed_response('findTransactions', { From 870fb9c5ae4dcd775c9d244530f4c8917a95b078 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Wed, 29 Jan 2020 12:49:46 +0100 Subject: [PATCH 03/69] Fixes #263 Rename `testnet` to `devnet` --- README.md | 2 +- docs/tutorials.rst | 6 ++-- examples/tutorials/02_send_data.py | 2 +- examples/tutorials/03_fetch_data.py | 2 +- examples/tutorials/04a_gen_address.py | 2 +- examples/tutorials/04b_check_balance.py | 2 +- examples/tutorials/04c_get_acc_data.py | 2 +- examples/tutorials/05_send_tokens.py | 2 +- examples/tutorials/06_store_encrypted.py | 2 +- examples/tutorials/07_fetch_encrypted.py | 2 +- iota/api.py | 30 +++++++++---------- iota/bin/__init__.py | 6 ++-- iota/bin/repl.py | 4 +-- iota/commands/core/attach_to_tangle.py | 2 +- iota/commands/extended/promote_transaction.py | 2 +- iota/commands/extended/replay_bundle.py | 2 +- iota/commands/extended/send_transfer.py | 2 +- iota/commands/extended/send_trytes.py | 2 +- 18 files changed, 37 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 6d19663..c13c6b7 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ Here's how you could send a zero-value transaction, using the library. For the g ```python # You don't need a seed to send zero-value transactions -api = Iota('https://nodes.devnet.iota.org:443', testnet=True) +api = Iota('https://nodes.devnet.iota.org:443', devnet=True) # Define a message to send. # This message must include only ASCII characters. diff --git a/docs/tutorials.rst b/docs/tutorials.rst index b46f00e..0e43dfe 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -100,9 +100,9 @@ Discussion We have seen this part before. Note, that now we import more objects which we will use to construct our transaction. -Notice ``testnet=True`` in the argument list of the API instantiation. We -tell the API directly that we will use the devnet/testnet. By default, the API -is configured for the mainnet. +Notice ``devnet=True`` in the argument list of the API instantiation. We +tell the API directly that we will use IOTA's testnet, known as the devnet. +By default, the API is configured for the mainnet. .. literalinclude:: ../examples/tutorials/02_send_data.py :lines: 7-8 diff --git a/examples/tutorials/02_send_data.py b/examples/tutorials/02_send_data.py index 5732a5a..d8d3122 100644 --- a/examples/tutorials/02_send_data.py +++ b/examples/tutorials/02_send_data.py @@ -2,7 +2,7 @@ from pprint import pprint # Declare an API object -api = Iota('https://nodes.devnet.iota.org:443', testnet=True) +api = Iota('https://nodes.devnet.iota.org:443', devnet=True) # Prepare custom data my_data = TryteString.from_unicode('Hello from the Tangle!') diff --git a/examples/tutorials/03_fetch_data.py b/examples/tutorials/03_fetch_data.py index 66ab768..aa0298f 100644 --- a/examples/tutorials/03_fetch_data.py +++ b/examples/tutorials/03_fetch_data.py @@ -2,7 +2,7 @@ from iota.codecs import TrytesDecodeError # Declare an API object -api = Iota('https://nodes.devnet.iota.org:443', testnet=True) +api = Iota('https://nodes.devnet.iota.org:443', devnet=True) # Address to fetch data from # Replace with your random generated address from Tutorial 2. to fetch the data diff --git a/examples/tutorials/04a_gen_address.py b/examples/tutorials/04a_gen_address.py index 0559c0c..68e3fb8 100644 --- a/examples/tutorials/04a_gen_address.py +++ b/examples/tutorials/04a_gen_address.py @@ -10,7 +10,7 @@ api = Iota( adapter='https://nodes.devnet.iota.org:443', seed=my_seed, - testnet=True, + devnet=True, ) print('Generating the first unused address...') diff --git a/examples/tutorials/04b_check_balance.py b/examples/tutorials/04b_check_balance.py index 9f7353e..af4fb47 100644 --- a/examples/tutorials/04b_check_balance.py +++ b/examples/tutorials/04b_check_balance.py @@ -5,7 +5,7 @@ my_address = Address(b'YOURADDRESSFROMTHEPREVIOUSTUTORIAL') # Declare an API object -api = Iota(adapter='https://nodes.devnet.iota.org:443', testnet=True) +api = Iota(adapter='https://nodes.devnet.iota.org:443', devnet=True) # Script actually runs until you load up your address success = False diff --git a/examples/tutorials/04c_get_acc_data.py b/examples/tutorials/04c_get_acc_data.py index 4061d2e..5ead820 100644 --- a/examples/tutorials/04c_get_acc_data.py +++ b/examples/tutorials/04c_get_acc_data.py @@ -9,7 +9,7 @@ api = Iota( adapter='https://nodes.devnet.iota.org:443', seed=my_seed, - testnet=True + devnet=True ) # Script actually runs until it finds balance diff --git a/examples/tutorials/05_send_tokens.py b/examples/tutorials/05_send_tokens.py index bec20d8..35656f4 100644 --- a/examples/tutorials/05_send_tokens.py +++ b/examples/tutorials/05_send_tokens.py @@ -7,7 +7,7 @@ api = Iota( adapter='https://nodes.devnet.iota.org:443', seed=my_seed, - testnet=True, + devnet=True, ) # Addres to receive 1i diff --git a/examples/tutorials/06_store_encrypted.py b/examples/tutorials/06_store_encrypted.py index f4e5d61..6c70645 100644 --- a/examples/tutorials/06_store_encrypted.py +++ b/examples/tutorials/06_store_encrypted.py @@ -14,7 +14,7 @@ api = Iota( adapter='https://nodes.devnet.iota.org:443', seed=b'YOURSEEDFROMTHEPREVIOUSTUTORIAL', - testnet=True, + devnet=True, ) # Some confidential information diff --git a/examples/tutorials/07_fetch_encrypted.py b/examples/tutorials/07_fetch_encrypted.py index 9bdad45..ebffc6d 100644 --- a/examples/tutorials/07_fetch_encrypted.py +++ b/examples/tutorials/07_fetch_encrypted.py @@ -11,7 +11,7 @@ import json # Declare an API object -api = Iota('https://nodes.devnet.iota.org:443', testnet=True) +api = Iota('https://nodes.devnet.iota.org:443', devnet=True) # Prompt user for tail tx hash of the bundle tail_hash = input('Tail transaction hash of the bundle: ') diff --git a/iota/api.py b/iota/api.py index 19481c5..b420890 100644 --- a/iota/api.py +++ b/iota/api.py @@ -39,9 +39,9 @@ class StrictIota(object): :param AdapterSpec adapter: URI string or BaseAdapter instance. - :param Optional[bool] testnet: - Whether to use testnet settings for this instance. - On the testnet, minimum weight magnitude is set to 9, on mainnet + :param Optional[bool] devnet: + Whether to use devnet settings for this instance. + On the devnet, minimum weight magnitude is set to 9, on mainnet it is 1 by default. :param Optional[bool] local_pow: @@ -54,15 +54,15 @@ class StrictIota(object): """ - def __init__(self, adapter, testnet=False, local_pow=False): + def __init__(self, adapter, devnet=False, local_pow=False): # type: (AdapterSpec, bool, bool) -> None """ :param AdapterSpec adapter: URI string or BaseAdapter instance. - :param bool testnet: - Whether to use testnet settings for this instance. - On the testnet, minimum weight magnitude is set to 9, on mainnet + :param bool devnet: + Whether to use devnet settings for this instance. + On the devnet, minimum weight magnitude is set to 9, on mainnet it is 1 by default. :param Optional[bool] local_pow: @@ -91,7 +91,7 @@ def __init__(self, adapter, testnet=False, local_pow=False): # via pyota-pow extension, or sends the request to a node. # But technically, the parameter belongs to the adapter. self.adapter.set_local_pow(local_pow) - self.testnet = testnet + self.devnet = devnet def create_command(self, command): # type: (Text) -> CustomCommand @@ -134,7 +134,7 @@ def default_min_weight_magnitude(self): Returns the default ``min_weight_magnitude`` value to use for API requests. """ - return 9 if self.testnet else 14 + return 9 if self.devnet else 14 def add_neighbors(self, uris): # type: (Iterable[Text]) -> dict @@ -197,7 +197,7 @@ def attach_to_tangle( :param Optional[int] min_weight_magnitude: Minimum weight magnitude to be used for attaching trytes. - 14 by default on mainnet, 9 on testnet/devnet. + 14 by default on mainnet, 9 on devnet/devnet. :return: ``dict`` with the following structure:: @@ -769,9 +769,9 @@ class Iota(StrictIota): .. note:: This value is never transferred to the node/network. - :param Optional[bool] testnet: - Whether to use testnet settings for this instance. - On the testnet, minimum weight magnitude is decreased, on mainnet + :param Optional[bool] devnet: + Whether to use devnet settings for this instance. + On the devnet, minimum weight magnitude is decreased, on mainnet it is 14 by default. For more info on the Mainnet and the Devnet, visit @@ -791,7 +791,7 @@ class Iota(StrictIota): - https://github.com/iotaledger/wiki/blob/master/api-proposal.md """ - def __init__(self, adapter, seed=None, testnet=False, local_pow=False): + def __init__(self, adapter, seed=None, devnet=False, local_pow=False): # type: (AdapterSpec, Optional[TrytesCompatible], bool, bool) -> None """ :param seed: @@ -801,7 +801,7 @@ def __init__(self, adapter, seed=None, testnet=False, local_pow=False): .. note:: This value is never transferred to the node/network. """ - super(Iota, self).__init__(adapter, testnet, local_pow) + super(Iota, self).__init__(adapter, devnet, local_pow) self.seed = Seed(seed) if seed else Seed.random() self.helpers = Helpers(self) diff --git a/iota/bin/__init__.py b/iota/bin/__init__.py index eb97e20..a505e79 100644 --- a/iota/bin/__init__.py +++ b/iota/bin/__init__.py @@ -102,7 +102,7 @@ def parse_argv(self, argv=None): arguments['api'] = Iota( adapter=arguments.pop('uri'), seed=seed, - testnet=arguments.pop('testnet'), + devnet=arguments.pop('devnet'), ) return arguments @@ -143,10 +143,10 @@ def create_argument_parser(self): ) parser.add_argument( - '--testnet', + '--devnet', action='store_true', default=False, - help='If set, use testnet settings (e.g., for PoW).', + help='If set, use devnet settings (e.g., for PoW).', ) return parser diff --git a/iota/bin/repl.py b/iota/bin/repl.py index bfae94b..3104125 100755 --- a/iota/bin/repl.py +++ b/iota/bin/repl.py @@ -89,10 +89,10 @@ def _start_repl(api): Starts the REPL. """ banner = ( - 'IOTA API client for {uri} ({testnet}) ' + 'IOTA API client for {uri} ({devnet}) ' 'initialized as variable `api`.\n' 'Type `help(api)` for list of API commands.'.format( - testnet='testnet' if api.testnet else 'mainnet', + devnet='devnet' if api.devnet else 'mainnet', uri=api.adapter.get_uri(), ) ) diff --git a/iota/commands/core/attach_to_tangle.py b/iota/commands/core/attach_to_tangle.py index 8c2644f..33250bc 100644 --- a/iota/commands/core/attach_to_tangle.py +++ b/iota/commands/core/attach_to_tangle.py @@ -53,7 +53,7 @@ def __init__(self): f.Required | Trytes(result_type=TransactionTrytes), ), - # Loosely-validated; testnet nodes require a different value + # Loosely-validated; devnet nodes require a different value # than mainnet. 'minWeightMagnitude': f.Required | f.Type(int) | f.Min(1), }) diff --git a/iota/commands/extended/promote_transaction.py b/iota/commands/extended/promote_transaction.py index 3bcbd1a..b7c03d2 100644 --- a/iota/commands/extended/promote_transaction.py +++ b/iota/commands/extended/promote_transaction.py @@ -62,7 +62,7 @@ def __init__(self): 'depth': f.Required | f.Type(int) | f.Min(1), 'transaction': f.Required | Trytes(TransactionHash), - # Loosely-validated; testnet nodes require a different value + # Loosely-validated; devnet nodes require a different value # than mainnet. 'minWeightMagnitude': f.Required | f.Type(int) | f.Min(1), }) diff --git a/iota/commands/extended/replay_bundle.py b/iota/commands/extended/replay_bundle.py index b2c2e68..3d5e313 100644 --- a/iota/commands/extended/replay_bundle.py +++ b/iota/commands/extended/replay_bundle.py @@ -53,7 +53,7 @@ def __init__(self): 'depth': f.Required | f.Type(int) | f.Min(1), 'transaction': f.Required | Trytes(TransactionHash), - # Loosely-validated; testnet nodes require a different value + # Loosely-validated; devnet nodes require a different value # than mainnet. 'minWeightMagnitude': f.Required | f.Type(int) | f.Min(1), }) diff --git a/iota/commands/extended/send_transfer.py b/iota/commands/extended/send_transfer.py index ada6607..ad3def0 100644 --- a/iota/commands/extended/send_transfer.py +++ b/iota/commands/extended/send_transfer.py @@ -70,7 +70,7 @@ def __init__(self): 'depth': f.Required | f.Type(int) | f.Min(1), 'seed': f.Required | Trytes(result_type=Seed), - # Loosely-validated; testnet nodes require a different + # Loosely-validated; devnet nodes require a different # value than mainnet. 'minWeightMagnitude': f.Required | f.Type(int) | f.Min(1), diff --git a/iota/commands/extended/send_trytes.py b/iota/commands/extended/send_trytes.py index 7e54d38..16dac7c 100644 --- a/iota/commands/extended/send_trytes.py +++ b/iota/commands/extended/send_trytes.py @@ -74,7 +74,7 @@ def __init__(self): f.Required | Trytes(TransactionTrytes), ), - # Loosely-validated; testnet nodes require a different value + # Loosely-validated; devnet nodes require a different value # than mainnet. 'minWeightMagnitude': f.Required | f.Type(int) | f.Min(1), From 3857f05c3ccd8ca8e1daf339c9203694bec4d667 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Wed, 5 Feb 2020 12:54:19 +0100 Subject: [PATCH 04/69] Replace 'requests' with 'httpx' for networking - httpx supports both sync and async --- iota/adapter/__init__.py | 14 ++++++-------- setup.py | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/iota/adapter/__init__.py b/iota/adapter/__init__.py index 4586200..ffbbe36 100644 --- a/iota/adapter/__init__.py +++ b/iota/adapter/__init__.py @@ -9,8 +9,7 @@ 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 from six import PY2, binary_type, iteritems, moves as compat, text_type, \ add_metaclass @@ -331,13 +330,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), @@ -346,9 +345,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. @@ -380,8 +379,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 AsyncClient().request(method=method, url=url, data=payload, **kwargs) self._log( level=DEBUG, diff --git a/setup.py b/setup.py index fca22f6..abb5e14 100644 --- a/setup.py +++ b/setup.py @@ -68,7 +68,7 @@ # ``security`` extra wasn't introduced until 2.4.1 # http://docs.python-requests.org/en/latest/community/updates/#id35 'requests[security] >= 2.4.1', - + 'httpx' 'six', 'typing; python_version < "3.0"', ], From 2186fdee6b0dec41301c69acdb1b88fc59d49136 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Thu, 6 Feb 2020 11:12:35 +0100 Subject: [PATCH 05/69] Rewrite adapter tests for async networking --- iota/adapter/__init__.py | 4 +- setup.py | 1 + test/adapter_test.py | 165 ++++++++++++++++++++++++--------------- 3 files changed, 104 insertions(+), 66 deletions(-) diff --git a/iota/adapter/__init__.py b/iota/adapter/__init__.py index ffbbe36..08dca92 100644 --- a/iota/adapter/__init__.py +++ b/iota/adapter/__init__.py @@ -472,9 +472,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 diff --git a/setup.py b/setup.py index abb5e14..3ca919f 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ tests_require = [ 'mock; python_version < "3.0"', 'nose', + 'aiounittest', ] ## diff --git a/test/adapter_test.py b/test/adapter_test.py index 7ea8ca4..658a558 100644 --- a/test/adapter_test.py +++ b/test/adapter_test.py @@ -7,12 +7,24 @@ from typing import Text from unittest import TestCase -import requests +import httpx from iota import BadApiResponse, InvalidUri, TryteString from iota.adapter import API_VERSION, HttpAdapter, MockAdapter, resolve_adapter from six import BytesIO, text_type from test import mock +import asyncio +# Executes async test case within a loop +from aiounittest import async_test +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 ResolveAdapterTestCase(TestCase): """ @@ -55,20 +67,15 @@ def test_unknown_protocol(self): def create_http_response(content, status=200): - # type: (Text, int) -> requests.Response + # type: (Text, int) -> httpx.Response """ Creates an HTTP Response object for a test. - - References: - - :py:meth:`requests.adapters.HTTPAdapter.build_response` """ - response = requests.Response() - - response.encoding = 'utf-8' - response.status_code = status - response.raw = BytesIO(content.encode('utf-8')) - - return response + return httpx.Response( + status, + request=httpx.Request('post','https://localhost:14265/'), + content=content + ) class HttpAdapterTestCase(TestCase): @@ -134,7 +141,8 @@ def test_configure_error_udp(self): with self.assertRaises(InvalidUri): HttpAdapter.configure('udp://localhost:14265') - def test_success_response(self): + @async_test + async def test_success_response(self): """ Simulates sending a command to the node and getting a success response. @@ -145,11 +153,11 @@ def test_success_response(self): expected_result = {'message': 'Hello, IOTA!'} mocked_response = create_http_response(json.dumps(expected_result)) - mocked_sender = mock.Mock(return_value=mocked_response) + mocked_sender = mock.Mock(return_value=async_return(mocked_response)) # noinspection PyUnresolvedReferences with mock.patch.object(adapter, '_send_http_request', mocked_sender): - result = adapter.send_request(payload) + result = await adapter.send_request(payload) self.assertEqual(result, expected_result) @@ -164,7 +172,8 @@ def test_success_response(self): url = adapter.node_url, ) - def test_error_response(self): + @async_test + async def test_error_response(self): """ Simulates sending a command to the node and getting an error response. @@ -182,19 +191,20 @@ def test_error_response(self): }), ) - mocked_sender = mock.Mock(return_value=mocked_response) + mocked_sender = mock.Mock(return_value=async_return(mocked_response)) # noinspection PyUnresolvedReferences with mock.patch.object(adapter, '_send_http_request', mocked_sender): with self.assertRaises(BadApiResponse) as context: - adapter.send_request({'command': 'helloWorld'}) + await adapter.send_request({'command': 'helloWorld'}) self.assertEqual( - text_type(context.exception), + str(context.exception), '400 response from node: {error}'.format(error=error_message), ) - def test_exception_response(self): + @async_test + async def test_exception_response(self): """ Simulates sending a command to the node and getting an exception response. @@ -212,19 +222,20 @@ def test_exception_response(self): }), ) - mocked_sender = mock.Mock(return_value=mocked_response) + mocked_sender = mock.Mock(return_value=async_return(mocked_response)) # noinspection PyUnresolvedReferences with mock.patch.object(adapter, '_send_http_request', mocked_sender): with self.assertRaises(BadApiResponse) as context: - adapter.send_request({'command': 'helloWorld'}) + await adapter.send_request({'command': 'helloWorld'}) self.assertEqual( text_type(context.exception), '500 response from node: {error}'.format(error=error_message), ) - def test_non_200_status(self): + @async_test + async def test_non_200_status(self): """ The node sends back a non-200 response that we don't know how to handle. @@ -238,19 +249,20 @@ def test_non_200_status(self): content = json.dumps(decoded_response), ) - mocked_sender = mock.Mock(return_value=mocked_response) + mocked_sender = mock.Mock(return_value=async_return(mocked_response)) # noinspection PyUnresolvedReferences with mock.patch.object(adapter, '_send_http_request', mocked_sender): with self.assertRaises(BadApiResponse) as context: - adapter.send_request({'command': 'helloWorld'}) + await adapter.send_request({'command': 'helloWorld'}) self.assertEqual( text_type(context.exception), '429 response from node: {decoded}'.format(decoded=decoded_response), ) - def test_empty_response(self): + @async_test + async def test_empty_response(self): """ The response is empty. """ @@ -258,19 +270,20 @@ def test_empty_response(self): mocked_response = create_http_response('') - mocked_sender = mock.Mock(return_value=mocked_response) + mocked_sender = mock.Mock(return_value=async_return(mocked_response)) # noinspection PyUnresolvedReferences with mock.patch.object(adapter, '_send_http_request', mocked_sender): with self.assertRaises(BadApiResponse) as context: - adapter.send_request({'command': 'helloWorld'}) + await adapter.send_request({'command': 'helloWorld'}) self.assertEqual( text_type(context.exception), 'Empty 200 response from node.', ) - def test_non_json_response(self): + @async_test + async def test_non_json_response(self): """ The response is not JSON. """ @@ -279,19 +292,20 @@ def test_non_json_response(self): invalid_response = 'EHLO iotatoken.com' # Erm... mocked_response = create_http_response(invalid_response) - mocked_sender = mock.Mock(return_value=mocked_response) + mocked_sender = mock.Mock(return_value=async_return(mocked_response)) # noinspection PyUnresolvedReferences with mock.patch.object(adapter, '_send_http_request', mocked_sender): with self.assertRaises(BadApiResponse) as context: - adapter.send_request({'command': 'helloWorld'}) + await adapter.send_request({'command': 'helloWorld'}) self.assertEqual( text_type(context.exception), 'Non-JSON 200 response from node: ' + invalid_response, ) - def test_non_object_response(self): + @async_test + async def test_non_object_response(self): """ The response is valid JSON, but it's not an object. """ @@ -300,12 +314,12 @@ def test_non_object_response(self): invalid_response = ['message', 'Hello, IOTA!'] mocked_response = create_http_response(json.dumps(invalid_response)) - mocked_sender = mock.Mock(return_value=mocked_response) + mocked_sender = mock.Mock(return_value=async_return(mocked_response)) # noinspection PyUnresolvedReferences with mock.patch.object(adapter, '_send_http_request', mocked_sender): with self.assertRaises(BadApiResponse) as context: - adapter.send_request({'command': 'helloWorld'}) + await adapter.send_request({'command': 'helloWorld'}) self.assertEqual( text_type(context.exception), @@ -315,24 +329,36 @@ def test_non_object_response(self): ), ) - @mock.patch('iota.adapter.request') - def test_default_timeout(self, request_mock): - # create dummy response - request_mock.return_value = mock.Mock(text='{ "dummy": "payload"}', status_code=200) - + @async_test + async def test_default_timeout(self): # create adapter mock_payload = {'dummy': 'payload'} adapter = HttpAdapter('http://localhost:14265') - # test with default timeout - adapter.send_request(payload=mock_payload) - _, kwargs = request_mock.call_args + # mock for returning dummy response + mocked_request = mock.Mock( + return_value=async_return( + mock.Mock(text='{ "dummy": "payload"}', status_code=200) + ) + ) + + # noinspection PyUnresolvedReferences + with mock.patch('iota.adapter.AsyncClient.request', mocked_request): + # test with default timeout + await adapter.send_request(payload=mock_payload) + + # Was the default timeout correctly injected into the request? + _, kwargs = mocked_request.call_args self.assertEqual(kwargs['timeout'], socket.getdefaulttimeout()) - @mock.patch('iota.adapter.request') - def test_instance_attribute_timeout(self, request_mock): - # create dummy response - request_mock.return_value = mock.Mock(text='{ "dummy": "payload"}', status_code=200) + @async_test + async def test_instance_attribute_timeout(self): + # mock for returning dummy response + mocked_request = mock.Mock( + return_value=async_return( + mock.Mock(text='{ "dummy": "payload"}', status_code=200) + ) + ) # create adapter mock_payload = {'dummy': 'payload'} @@ -340,14 +366,19 @@ def test_instance_attribute_timeout(self, request_mock): # test with explicit attribute adapter.timeout = 77 - adapter.send_request(payload=mock_payload) - _, kwargs = request_mock.call_args + with mock.patch('iota.adapter.AsyncClient.request', mocked_request): + await adapter.send_request(payload=mock_payload) + _, kwargs = mocked_request.call_args self.assertEqual(kwargs['timeout'], 77) - @mock.patch('iota.adapter.request') - def test_argument_overriding_attribute_timeout(self, request_mock): - # create dummy response - request_mock.return_value = mock.Mock(text='{ "dummy": "payload"}', status_code=200) + @async_test + async def test_argument_overriding_attribute_timeout(self): + # mock for returning dummy response + mocked_request = mock.Mock( + return_value=async_return( + mock.Mock(text='{ "dummy": "payload"}', status_code=200) + ) + ) # create adapter mock_payload = {'dummy': 'payload'} @@ -355,14 +386,19 @@ def test_argument_overriding_attribute_timeout(self, request_mock): # test with timeout in kwargs adapter.timeout = 77 - adapter.send_request(payload=mock_payload, timeout=88) - _, kwargs = request_mock.call_args + with mock.patch('iota.adapter.AsyncClient.request', mocked_request): + await adapter.send_request(payload=mock_payload, timeout=88) + _, kwargs = mocked_request.call_args self.assertEqual(kwargs['timeout'], 88) - @mock.patch('iota.adapter.request') - def test_argument_overriding_init_timeout(self, request_mock): - # create dummy response - request_mock.return_value = mock.Mock(text='{ "dummy": "payload"}', status_code=200) + @async_test + async def test_argument_overriding_init_timeout(self): + # mock for returning dummy response + mocked_request = mock.Mock( + return_value=async_return( + mock.Mock(text='{ "dummy": "payload"}', status_code=200) + ) + ) # create adapter mock_payload = {'dummy': 'payload'} @@ -370,13 +406,14 @@ def test_argument_overriding_init_timeout(self, request_mock): # test with timeout at adapter creation adapter = HttpAdapter('http://localhost:14265', timeout=99) - adapter.send_request(payload=mock_payload) - _, kwargs = request_mock.call_args + with mock.patch('iota.adapter.AsyncClient.request', mocked_request): + await adapter.send_request(payload=mock_payload) + _, kwargs = mocked_request.call_args self.assertEqual(kwargs['timeout'], 99) # noinspection SpellCheckingInspection - @staticmethod - def test_trytes_in_request(): + @async_test + async def test_trytes_in_request(self): """ Sending a request that includes trytes. """ @@ -384,11 +421,11 @@ def test_trytes_in_request(): # Response is not important for this test; we just need to make # sure that the request is converted correctly. - mocked_sender = mock.Mock(return_value=create_http_response('{}')) + mocked_sender = mock.Mock(return_value=async_return(create_http_response('{}'))) # noinspection PyUnresolvedReferences with mock.patch.object(adapter, '_send_http_request', mocked_sender): - adapter.send_request({ + await adapter.send_request({ 'command': 'helloWorld', 'trytes': [ TryteString(b'RBTC9D9DCDQAEASBYBCCKBFA'), From 7ab91b376089c3e2df5cfadb09c03330385a108e Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Thu, 6 Feb 2020 11:43:57 +0100 Subject: [PATCH 06/69] Make `RoutingWrapper` work with async --- iota/adapter/__init__.py | 17 +++++++++++++++-- iota/adapter/wrappers.py | 4 ++-- test/adapter/wrappers_test.py | 12 ++++++++---- test/adapter_test.py | 14 ++------------ 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/iota/adapter/__init__.py b/iota/adapter/__init__.py index 08dca92..5eb1b56 100644 --- a/iota/adapter/__init__.py +++ b/iota/adapter/__init__.py @@ -10,6 +10,7 @@ from socket import getdefaulttimeout as get_default_timeout from typing import Container, Dict, List, Optional, Text, Tuple, Union from httpx import AsyncClient, Response, codes, auth +import asyncio from six import PY2, binary_type, iteritems, moves as compat, text_type, \ add_metaclass @@ -58,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): """ @@ -583,7 +593,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)) @@ -625,4 +638,4 @@ def send_request(self, payload, **kwargs): raise with_context(BadApiResponse(error), context={'request': payload}) - return response + return await async_return(response) diff --git a/iota/adapter/wrappers.py b/iota/adapter/wrappers.py index c4f4b6c..9fff672 100644 --- a/iota/adapter/wrappers.py +++ b/iota/adapter/wrappers.py @@ -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) diff --git a/test/adapter/wrappers_test.py b/test/adapter/wrappers_test.py index f8ec7e5..b37aced 100644 --- a/test/adapter/wrappers_test.py +++ b/test/adapter/wrappers_test.py @@ -7,9 +7,13 @@ from iota.adapter import HttpAdapter, MockAdapter from iota.adapter.wrappers import RoutingWrapper +# Executes async test case within a loop +from aiounittest import async_test + class RoutingWrapperTestCase(TestCase): - def test_routing(self): + @async_test + async def test_routing(self): """ Routing commands to different adapters. """ @@ -27,18 +31,18 @@ def test_routing(self): pow_adapter.seed_response('interruptAttachingToTangle', {'id': 'pow2'}) self.assertDictEqual( - wrapper.send_request({'command': 'attachToTangle'}), + await wrapper.send_request({'command': 'attachToTangle'}), {'id': 'pow1'}, ) self.assertDictEqual( - wrapper.send_request({'command': 'interruptAttachingToTangle'}), + await wrapper.send_request({'command': 'interruptAttachingToTangle'}), {'id': 'pow2'}, ) # Any commands that aren't routed go to the default adapter. self.assertDictEqual( - wrapper.send_request({'command': 'getNodeInfo'}), + await wrapper.send_request({'command': 'getNodeInfo'}), {'id': 'default1'}, ) diff --git a/test/adapter_test.py b/test/adapter_test.py index 658a558..31dc54d 100644 --- a/test/adapter_test.py +++ b/test/adapter_test.py @@ -9,23 +9,13 @@ import httpx from iota import BadApiResponse, InvalidUri, TryteString -from iota.adapter import API_VERSION, HttpAdapter, MockAdapter, resolve_adapter +from iota.adapter import API_VERSION, HttpAdapter, MockAdapter, \ + resolve_adapter, async_return from six import BytesIO, text_type from test import mock -import asyncio # Executes async test case within a loop from aiounittest import async_test -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 ResolveAdapterTestCase(TestCase): """ Unit tests for :py:func:`resolve_adapter`. From 61f8f964dc04f09802c889600cac2dc150697403 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Thu, 6 Feb 2020 18:25:29 +0100 Subject: [PATCH 07/69] Refactor API classes for async - Introduce 'AsyncStrictIota' and 'AsyncIota' for async API - Refactor original API classes to rely on their async counterpart - Make 'BaseCommand' async - Make 'AttachToTangleCommand' async --- iota/api.py | 1407 ++++++++++++++++++++++-- iota/commands/__init__.py | 8 +- iota/commands/core/attach_to_tangle.py | 7 +- 3 files changed, 1335 insertions(+), 87 deletions(-) diff --git a/iota/api.py b/iota/api.py index b420890..295804e 100644 --- a/iota/api.py +++ b/iota/api.py @@ -11,11 +11,14 @@ from iota.commands.extended.helpers import Helpers from iota.crypto.addresses import AddressGenerator from iota.crypto.types import Seed +import asyncio __all__ = [ 'InvalidCommand', 'Iota', 'StrictIota', + 'AsyncStrictIota', + 'AsyncIota', ] @@ -25,9 +28,9 @@ class InvalidCommand(ValueError): """ pass -class StrictIota(object): +class AsyncStrictIota(object): """ - API to send HTTP requests for communicating with an IOTA node. + Asynchronous API to send HTTP requests for communicating with an IOTA node. This implementation only exposes the "core" API methods. For a more feature-complete implementation, use :py:class:`Iota` instead. @@ -74,7 +77,7 @@ def __init__(self, adapter, devnet=False, local_pow=False): :ref:`find out` how to use it. """ - super(StrictIota, self).__init__() + super(AsyncStrictIota, self).__init__() if not isinstance(adapter, BaseAdapter): adapter = resolve_adapter(adapter) @@ -136,7 +139,7 @@ def default_min_weight_magnitude(self): """ return 9 if self.devnet else 14 - def add_neighbors(self, uris): + async def add_neighbors(self, uris): # type: (Iterable[Text]) -> dict """ Add one or more neighbors to the node. Lasts until the node is @@ -165,9 +168,9 @@ def add_neighbors(self, uris): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#addneighbors """ - return core.AddNeighborsCommand(self.adapter)(uris=uris) + return await core.AddNeighborsCommand(self.adapter)(uris=uris) - def attach_to_tangle( + async def attach_to_tangle( self, trunk_transaction, # type: TransactionHash branch_transaction, # type: TransactionHash @@ -214,14 +217,14 @@ def attach_to_tangle( if min_weight_magnitude is None: min_weight_magnitude = self.default_min_weight_magnitude - return core.AttachToTangleCommand(self.adapter)( + return await core.AttachToTangleCommand(self.adapter)( trunkTransaction=trunk_transaction, branchTransaction=branch_transaction, minWeightMagnitude=min_weight_magnitude, trytes=trytes, ) - def broadcast_transactions(self, trytes): + async def broadcast_transactions(self, trytes): # type: (Iterable[TryteString]) -> dict """ Broadcast a list of transactions to all neighbors. @@ -244,9 +247,9 @@ def broadcast_transactions(self, trytes): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#broadcasttransactions """ - return core.BroadcastTransactionsCommand(self.adapter)(trytes=trytes) + return await core.BroadcastTransactionsCommand(self.adapter)(trytes=trytes) - def check_consistency(self, tails): + async def check_consistency(self, tails): # type: (Iterable[TransactionHash]) -> dict """ Used to ensure tail resolves to a consistent ledger which is @@ -276,11 +279,11 @@ def check_consistency(self, tails): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#checkconsistency """ - return core.CheckConsistencyCommand(self.adapter)( + return await core.CheckConsistencyCommand(self.adapter)( tails=tails, ) - def find_transactions( + async def find_transactions( self, bundles=None, # type: Optional[Iterable[BundleHash]] addresses=None, # type: Optional[Iterable[Address]] @@ -323,14 +326,14 @@ def find_transactions( - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#findtransactions """ - return core.FindTransactionsCommand(self.adapter)( + return await core.FindTransactionsCommand(self.adapter)( bundles=bundles, addresses=addresses, tags=tags, approvees=approvees, ) - def get_balances( + async def get_balances( self, addresses, # type: Iterable[Address] threshold=100, # type: int @@ -378,13 +381,13 @@ def get_balances( - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getbalances """ - return core.GetBalancesCommand(self.adapter)( + return await core.GetBalancesCommand(self.adapter)( addresses=addresses, threshold=threshold, tips=tips, ) - def get_inclusion_states(self, transactions, tips): + async def get_inclusion_states(self, transactions, tips): # type: (Iterable[TransactionHash], Iterable[TransactionHash]) -> dict """ Get the inclusion states of a set of transactions. This is for @@ -416,12 +419,12 @@ def get_inclusion_states(self, transactions, tips): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getinclusionstates """ - return core.GetInclusionStatesCommand(self.adapter)( + return await core.GetInclusionStatesCommand(self.adapter)( transactions=transactions, tips=tips, ) - def get_missing_transactions(self): + async def get_missing_transactions(self): # type: () -> dict """ Returns all transaction hashes that a node is currently requesting @@ -441,9 +444,9 @@ def get_missing_transactions(self): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getmissingtransactions """ - return core.GetMissingTransactionsCommand(self.adapter)() + return await core.GetMissingTransactionsCommand(self.adapter)() - def get_neighbors(self): + async def get_neighbors(self): # type: () -> dict """ Returns the set of neighbors the node is connected with, as well @@ -474,9 +477,9 @@ def get_neighbors(self): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getneighbors """ - return core.GetNeighborsCommand(self.adapter)() + return await core.GetNeighborsCommand(self.adapter)() - def get_node_api_configuration(self): + async def get_node_api_configuration(self): # type: () -> dict """ Returns a node's API configuration settings. @@ -498,9 +501,9 @@ def get_node_api_configuration(self): - https://docs.iota.org/docs/node-software/0.1/iri/references/iri-configuration-options - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getnodeapiconfiguration """ - return core.GetNodeAPIConfigurationCommand(self.adapter)() + return await core.GetNodeAPIConfigurationCommand(self.adapter)() - def get_node_info(self): + async def get_node_info(self): # type: () -> dict """ Returns information about the node. @@ -557,9 +560,9 @@ def get_node_info(self): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getnodeinfo """ - return core.GetNodeInfoCommand(self.adapter)() + return await core.GetNodeInfoCommand(self.adapter)() - def get_tips(self): + async def get_tips(self): # type: () -> dict """ Returns the list of tips (transactions which have no other @@ -580,9 +583,9 @@ def get_tips(self): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#gettips - https://docs.iota.org/docs/dev-essentials/0.1/references/glossary """ - return core.GetTipsCommand(self.adapter)() + return await core.GetTipsCommand(self.adapter)() - def get_transactions_to_approve(self, depth, reference=None): + async def get_transactions_to_approve(self, depth, reference=None): # type: (int, Optional[TransactionHash]) -> dict """ Tip selection which returns ``trunkTransaction`` and @@ -616,12 +619,12 @@ def get_transactions_to_approve(self, depth, reference=None): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#gettransactionstoapprove """ - return core.GetTransactionsToApproveCommand(self.adapter)( + return await core.GetTransactionsToApproveCommand(self.adapter)( depth=depth, reference=reference, ) - def get_trytes(self, hashes): + async def get_trytes(self, hashes): # type: (Iterable[TransactionHash]) -> dict """ Returns the raw transaction data (trytes) of one or more @@ -647,9 +650,9 @@ def get_trytes(self, hashes): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#gettrytes """ - return core.GetTrytesCommand(self.adapter)(hashes=hashes) + return await core.GetTrytesCommand(self.adapter)(hashes=hashes) - def interrupt_attaching_to_tangle(self): + async def interrupt_attaching_to_tangle(self): # type: () -> dict """ Interrupts and completely aborts the :py:meth:`attach_to_tangle` @@ -667,9 +670,9 @@ def interrupt_attaching_to_tangle(self): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#interruptattachingtotangle """ - return core.InterruptAttachingToTangleCommand(self.adapter)() + return await core.InterruptAttachingToTangleCommand(self.adapter)() - def remove_neighbors(self, uris): + async def remove_neighbors(self, uris): # type: (Iterable[Text]) -> dict """ Removes one or more neighbors from the node. Lasts until the @@ -693,9 +696,9 @@ def remove_neighbors(self, uris): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#removeneighbors """ - return core.RemoveNeighborsCommand(self.adapter)(uris=uris) + return await core.RemoveNeighborsCommand(self.adapter)(uris=uris) - def store_transactions(self, trytes): + async def store_transactions(self, trytes): # type: (Iterable[TryteString]) -> dict """ Store transactions into local storage of the node. @@ -720,9 +723,9 @@ def store_transactions(self, trytes): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#storetransactions """ - return core.StoreTransactionsCommand(self.adapter)(trytes=trytes) + return await core.StoreTransactionsCommand(self.adapter)(trytes=trytes) - def were_addresses_spent_from(self, addresses): + async def were_addresses_spent_from(self, addresses): # type: (Iterable[Address]) -> dict """ Check if a list of addresses was ever spent from, in the current @@ -749,15 +752,293 @@ def were_addresses_spent_from(self, addresses): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#wereaddressesspentfrom """ - return core.WereAddressesSpentFromCommand(self.adapter)( + return await core.WereAddressesSpentFromCommand(self.adapter)( addresses=addresses, ) +class StrictIota(AsyncStrictIota): + """ + Synchronous API to send HTTP requests for communicating with an IOTA node. + + This implementation only exposes the "core" API methods. For a more + feature-complete implementation, use :py:class:`Iota` instead. + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference + + :param AdapterSpec adapter: + URI string or BaseAdapter instance. + + :param Optional[bool] devnet: + Whether to use devnet settings for this instance. + On the devnet, minimum weight magnitude is set to 9, on mainnet + it is 1 by default. + + :param Optional[bool] local_pow: + Whether to perform proof-of-work locally by redirecting all calls + to :py:meth:`attach_to_tangle` to + `ccurl pow interface `_. + + See :ref:`Optional Local Pow` for more info and + :ref:`find out` how to use it. + + """ + + def __init__(self, adapter, devnet=False, local_pow=False): + # type: (AdapterSpec, bool, bool) -> None + # Copy parent's doctrsing + __doc__ = super(StrictIota, self).__init__.__doc__ + super(StrictIota, self).__init__(adapter, devnet, local_pow) + + + def add_neighbors(self, uris): + # type: (Iterable[Text]) -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).add_neighbors.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).add_neighbors(uris) + ) + + def attach_to_tangle( + self, + trunk_transaction, # type: TransactionHash + branch_transaction, # type: TransactionHash + trytes, # type: Iterable[TryteString] + min_weight_magnitude=None, # type: Optional[int] + ): + # type: (...) -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).attach_to_tangle.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).attach_to_tangle( + trunk_transaction, + branch_transaction, + trytes, + min_weight_magnitude, + ) + ) + + def broadcast_transactions(self, trytes): + # type: (Iterable[TryteString]) -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).broadcast_transactions.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).broadcast_transactions( + trytes, + ) + ) + + + def check_consistency(self, tails): + # type: (Iterable[TransactionHash]) -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).check_consistency.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).check_consistency( + tails, + ) + ) + + def find_transactions( + self, + bundles=None, # type: Optional[Iterable[BundleHash]] + addresses=None, # type: Optional[Iterable[Address]] + tags=None, # type: Optional[Iterable[Tag]] + approvees=None, # type: Optional[Iterable[TransactionHash]] + ): + # type: (...) -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).find_transactions.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).find_transactions( + bundles, + addresses, + tags, + approvees, + ) + ) + + def get_balances( + self, + addresses, # type: Iterable[Address] + threshold=100, # type: int + tips=None, # type: Optional[Iterable[TransactionHash]] + ): + # type: (...) -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).get_balances.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).get_balances( + addresses, + threshold, + tips, + ) + ) + + def get_inclusion_states(self, transactions, tips): + # type: (Iterable[TransactionHash], Iterable[TransactionHash]) -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).get_inclusion_states.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).get_inclusion_states( + transactions, + tips, + ) + ) + + def get_missing_transactions(self): + # type: () -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).get_missing_transactions.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).get_missing_transactions() + ) + + def get_neighbors(self): + # type: () -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).get_neighbors.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).get_neighbors() + ) + + def get_node_api_configuration(self): + # type: () -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).get_node_api_configuration.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).get_node_api_configuration() + ) + + def get_node_info(self): + # type: () -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).get_node_info.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).get_node_info() + ) + + def get_tips(self): + # type: () -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).get_tips.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).get_tips() + ) + + def get_transactions_to_approve(self, depth, reference=None): + # type: (int, Optional[TransactionHash]) -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).get_transactions_to_approve.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).get_transactions_to_approve( + depth, + reference, + ) + ) + + def get_trytes(self, hashes): + # type: (Iterable[TransactionHash]) -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).get_trytes.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).get_trytes( + hashes, + ) + ) + + def interrupt_attaching_to_tangle(self): + # type: () -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).interrupt_attaching_to_tangle.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).interrupt_attaching_to_tangle() + ) + + def remove_neighbors(self, uris): + # type: (Iterable[Text]) -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).remove_neighbors.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).remove_neighbors(uris) + ) + + def store_transactions(self, trytes): + # type: (Iterable[TryteString]) -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).store_transactions.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).store_transactions(trytes) + ) + + def were_addresses_spent_from(self, addresses): + # type: (Iterable[Address]) -> dict + # Copy parent's docstring + __doc__ = super(StrictIota, self).were_addresses_spent_from.__doc__ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(StrictIota, self).were_addresses_spent_from(addresses) + ) -class Iota(StrictIota): + +class AsyncIota(AsyncStrictIota): """ - Implements the core API, plus additional wrapper methods for common - operations. + Implements the async core API, plus additional async wrapper methods for + common operations. :param AdapterSpec adapter: URI string or BaseAdapter instance. @@ -801,12 +1082,12 @@ def __init__(self, adapter, seed=None, devnet=False, local_pow=False): .. note:: This value is never transferred to the node/network. """ - super(Iota, self).__init__(adapter, devnet, local_pow) + super(AsyncIota, self).__init__(adapter, devnet, local_pow) self.seed = Seed(seed) if seed else Seed.random() self.helpers = Helpers(self) - def broadcast_and_store(self, trytes): + async def broadcast_and_store(self, trytes): # type: (Iterable[TransactionTrytes]) -> dict """ Broadcasts and stores a set of transaction trytes. @@ -827,9 +1108,11 @@ def broadcast_and_store(self, trytes): - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#broadcastandstore """ - return extended.BroadcastAndStoreCommand(self.adapter)(trytes=trytes) + return await extended.BroadcastAndStoreCommand(self.adapter)( + trytes=trytes, + ) - def broadcast_bundle(self, tail_transaction_hash): + async def broadcast_bundle(self, tail_transaction_hash): # type (TransactionHash) -> dict """ Re-broadcasts all transactions in a bundle given the tail transaction hash. @@ -852,9 +1135,11 @@ def broadcast_bundle(self, tail_transaction_hash): - https://github.com/iotaledger/iota.js/blob/next/api_reference.md#module_core.broadcastBundle """ - return extended.BroadcastBundleCommand(self.adapter)(tail_hash=tail_transaction_hash) + return await extended.BroadcastBundleCommand(self.adapter)( + tail_hash=tail_transaction_hash, + ) - def find_transaction_objects( + async def find_transaction_objects( self, bundles=None, # type: Optional[Iterable[BundleHash]] addresses=None, # type: Optional[Iterable[Address]] @@ -866,7 +1151,7 @@ def find_transaction_objects( A more extensive version of :py:meth:`find_transactions` that returns transaction objects instead of hashes. - Effectively, this is :py:meth:`find_transactions` + + Effectively, this is :py:meth:`find_transactions` + :py:meth:`get_trytes` + converting the trytes into transaction objects. @@ -899,14 +1184,14 @@ def find_transaction_objects( } """ - return extended.FindTransactionObjectsCommand(self.adapter)( + return await extended.FindTransactionObjectsCommand(self.adapter)( bundles=bundles, addresses=addresses, tags=tags, approvees=approvees, ) - def get_account_data(self, start=0, stop=None, inclusion_states=False, security_level=None): + async def get_account_data(self, start=0, stop=None, inclusion_states=False, security_level=None): # type: (int, Optional[int], bool, Optional[int]) -> dict """ More comprehensive version of :py:meth:`get_transfers` that @@ -975,7 +1260,7 @@ def get_account_data(self, start=0, stop=None, inclusion_states=False, security_ } """ - return extended.GetAccountDataCommand(self.adapter)( + return await extended.GetAccountDataCommand(self.adapter)( seed=self.seed, start=start, stop=stop, @@ -983,7 +1268,7 @@ def get_account_data(self, start=0, stop=None, inclusion_states=False, security_ security_level=security_level ) - def get_bundles(self, transactions): + async def get_bundles(self, transactions): # type: (Iterable[TransactionHash]) -> dict """ Returns the bundle(s) associated with the specified transaction @@ -1009,9 +1294,11 @@ def get_bundles(self, transactions): - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getbundle """ - return extended.GetBundlesCommand(self.adapter)(transactions=transactions) + return await extended.GetBundlesCommand(self.adapter)( + transactions=transactions, + ) - def get_inputs( + async def get_inputs( self, start=0, stop=None, @@ -1115,7 +1402,7 @@ def get_inputs( - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getinputs """ - return extended.GetInputsCommand(self.adapter)( + return await extended.GetInputsCommand(self.adapter)( seed=self.seed, start=start, stop=stop, @@ -1123,7 +1410,7 @@ def get_inputs( securityLevel=security_level ) - def get_latest_inclusion(self, hashes): + async def get_latest_inclusion(self, hashes): # type: (Iterable[TransactionHash]) -> Dict[TransactionHash, bool] """ Fetches the inclusion state for the specified transaction @@ -1145,9 +1432,9 @@ def get_latest_inclusion(self, hashes): } """ - return extended.GetLatestInclusionCommand(self.adapter)(hashes=hashes) + return await extended.GetLatestInclusionCommand(self.adapter)(hashes=hashes) - def get_new_addresses( + async def get_new_addresses( self, index=0, count=1, @@ -1208,7 +1495,7 @@ def get_new_addresses( - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getnewaddress """ - return extended.GetNewAddressesCommand(self.adapter)( + return await extended.GetNewAddressesCommand(self.adapter)( count=count, index=index, securityLevel=security_level, @@ -1216,7 +1503,7 @@ def get_new_addresses( seed=self.seed, ) - def get_transaction_objects( + async def get_transaction_objects( self, hashes, # type: [Iterable[TransactionHash]] ): @@ -1242,11 +1529,11 @@ def get_transaction_objects( List of Transaction objects that match the input. } """ - return extended.GetTransactionObjectsCommand(self.adapter)( + return await extended.GetTransactionObjectsCommand(self.adapter)( hashes=hashes, ) - def get_transfers(self, start=0, stop=None, inclusion_states=False): + async def get_transfers(self, start=0, stop=None, inclusion_states=False): # type: (int, Optional[int], bool) -> dict """ Returns all transfers associated with the seed. @@ -1299,14 +1586,14 @@ def get_transfers(self, start=0, stop=None, inclusion_states=False): - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#gettransfers """ - return extended.GetTransfersCommand(self.adapter)( + return await extended.GetTransfersCommand(self.adapter)( seed=self.seed, start=start, stop=stop, inclusionStates=inclusion_states, ) - def is_promotable( + async def is_promotable( self, tails, # type: Iterable[TransactionHash] ): @@ -1341,11 +1628,11 @@ def is_promotable( References: - https://github.com/iotaledger/iota.js/blob/next/api_reference.md#module_core.isPromotable """ - return extended.IsPromotableCommand(self.adapter)( + return await extended.IsPromotableCommand(self.adapter)( tails=tails, ) - def prepare_transfer( + async def prepare_transfer( self, transfers, # type: Iterable[ProposedTransaction] inputs=None, # type: Optional[Iterable[Address]] @@ -1399,7 +1686,7 @@ def prepare_transfer( - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#preparetransfers """ - return extended.PrepareTransferCommand(self.adapter)( + return await extended.PrepareTransferCommand(self.adapter)( seed=self.seed, transfers=transfers, inputs=inputs, @@ -1407,7 +1694,7 @@ def prepare_transfer( securityLevel=security_level, ) - def promote_transaction( + async def promote_transaction( self, transaction, depth=3, @@ -1441,13 +1728,13 @@ def promote_transaction( if min_weight_magnitude is None: min_weight_magnitude = self.default_min_weight_magnitude - return extended.PromoteTransactionCommand(self.adapter)( + return await extended.PromoteTransactionCommand(self.adapter)( transaction=transaction, depth=depth, minWeightMagnitude=min_weight_magnitude, ) - def replay_bundle( + async def replay_bundle( self, transaction, depth=3, @@ -1487,13 +1774,13 @@ def replay_bundle( if min_weight_magnitude is None: min_weight_magnitude = self.default_min_weight_magnitude - return extended.ReplayBundleCommand(self.adapter)( + return await extended.ReplayBundleCommand(self.adapter)( transaction=transaction, depth=depth, minWeightMagnitude=min_weight_magnitude, ) - def send_transfer( + async def send_transfer( self, transfers, # type: Iterable[ProposedTransaction] depth=3, # type: int @@ -1556,7 +1843,7 @@ def send_transfer( if min_weight_magnitude is None: min_weight_magnitude = self.default_min_weight_magnitude - return extended.SendTransferCommand(self.adapter)( + return await extended.SendTransferCommand(self.adapter)( seed=self.seed, depth=depth, transfers=transfers, @@ -1566,7 +1853,7 @@ def send_transfer( securityLevel=security_level, ) - def send_trytes(self, trytes, depth=3, min_weight_magnitude=None): + async def send_trytes(self, trytes, depth=3, min_weight_magnitude=None): # type: (Iterable[TransactionTrytes], int, Optional[int]) -> dict """ Attaches transaction trytes to the Tangle, then broadcasts and @@ -1600,13 +1887,13 @@ def send_trytes(self, trytes, depth=3, min_weight_magnitude=None): if min_weight_magnitude is None: min_weight_magnitude = self.default_min_weight_magnitude - return extended.SendTrytesCommand(self.adapter)( + return await extended.SendTrytesCommand(self.adapter)( trytes=trytes, depth=depth, minWeightMagnitude=min_weight_magnitude, ) - def is_reattachable(self, addresses): + async def is_reattachable(self, addresses): # type: (Iterable[Address]) -> dict """ This API function helps you to determine whether you should @@ -1633,11 +1920,11 @@ def is_reattachable(self, addresses): } """ - return extended.IsReattachableCommand(self.adapter)( + return await extended.IsReattachableCommand(self.adapter)( addresses=addresses ) - def traverse_bundle(self, tail_hash): + async def traverse_bundle(self, tail_hash): # type: (TransactionHash) -> dict """ Fetches and traverses a bundle from the Tangle given a tail transaction @@ -1661,6 +1948,966 @@ def traverse_bundle(self, tail_hash): } """ - return extended.TraverseBundleCommand(self.adapter)( + return await extended.TraverseBundleCommand(self.adapter)( transaction=tail_hash ) + +class Iota(StrictIota, AsyncIota): + """ + Implements the synchronous core API, plus additional synchronous wrapper + methods for common operations. + + :param AdapterSpec adapter: + URI string or BaseAdapter instance. + + :param Optional[Seed] seed: + Seed used to generate new addresses. + If not provided, a random one will be generated. + + .. note:: + This value is never transferred to the node/network. + + :param Optional[bool] devnet: + Whether to use devnet settings for this instance. + On the devnet, minimum weight magnitude is decreased, on mainnet + it is 14 by default. + + For more info on the Mainnet and the Devnet, visit + `the official docs site`. + + :param Optional[bool] local_pow: + Whether to perform proof-of-work locally by redirecting all calls + to :py:meth:`attach_to_tangle` to + `ccurl pow interface `_. + + See :ref:`Optional Local Pow` for more info and + :ref:`find out` how to use it. + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md + """ + + def __init__(self, adapter, seed=None, devnet=False, local_pow=False): + # type: (AdapterSpec, Optional[TrytesCompatible], bool, bool) -> None + """ + :param seed: + Seed used to generate new addresses. + If not provided, a random one will be generated. + + .. note:: + This value is never transferred to the node/network. + """ + # Exolicitly call AsyncIota's init, as we need the seed + AsyncIota.__init__(self, adapter, seed, devnet, local_pow) + + def broadcast_and_store(self, trytes): + # type: (Iterable[TransactionTrytes]) -> dict + """ + Broadcasts and stores a set of transaction trytes. + + :param Iterable[TransactionTrytes] trytes: + Transaction trytes to broadcast and store. + + :return: + ``dict`` with the following structure:: + + { + 'trytes': List[TransactionTrytes], + List of TransactionTrytes that were broadcast. + Same as the input ``trytes``. + } + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#broadcastandstore + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).broadcast_and_store(trytes) + ) + + def broadcast_bundle(self, tail_transaction_hash): + # type (TransactionHash) -> dict + """ + Re-broadcasts all transactions in a bundle given the tail transaction hash. + It might be useful when transactions did not properly propagate, + particularly in the case of large bundles. + + :param TransactionHash tail_transaction_hash: + Tail transaction hash of the bundle. + + :return: + ``dict`` with the following structure:: + + { + 'trytes': List[TransactionTrytes], + List of TransactionTrytes that were broadcast. + } + + References: + + - https://github.com/iotaledger/iota.js/blob/next/api_reference.md#module_core.broadcastBundle + """ + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).broadcast_bundle(tail_transaction_hash) + ) + + def find_transaction_objects( + self, + bundles=None, # type: Optional[Iterable[BundleHash]] + addresses=None, # type: Optional[Iterable[Address]] + tags=None, # type: Optional[Iterable[Tag]] + approvees=None, # type: Optional[Iterable[TransactionHash]] + ): + # type: (...) -> dict + """ + A more extensive version of :py:meth:`find_transactions` that + returns transaction objects instead of hashes. + + Effectively, this is :py:meth:`find_transactions` + + :py:meth:`get_trytes` + converting the trytes into + transaction objects. + + It accepts the same parameters as :py:meth:`find_transactions`. + + Find the transactions which match the specified input. + All input values are lists, for which a list of return values + (transaction hashes), in the same order, is returned for all + individual elements. Using multiple of these input fields returns the + intersection of the values. + + :param Optional[Iterable[BundleHash]] bundles: + List of bundle IDs. + + :param Optional[Iterable[Address]] addresses: + List of addresses. + + :param Optional[Iterable[Tag]] tags: + List of tags. + + :param Optional[Iterable[TransactionHash]] approvees: + List of approvee transaction IDs. + + :return: + ``dict`` with the following structure:: + + { + 'transactions': List[Transaction], + List of Transaction objects that match the input. + } + + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).find_transaction_objects( + bundles, + addresses, + tags, + approvees, + ) + ) + + def get_account_data(self, start=0, stop=None, inclusion_states=False, security_level=None): + # type: (int, Optional[int], bool, Optional[int]) -> dict + """ + More comprehensive version of :py:meth:`get_transfers` that + returns addresses and account balance in addition to bundles. + + This function is useful in getting all the relevant information + of your account. + + :param int start: + Starting key index. + + :param Optional[int] stop: + Stop before this index. + + Note that this parameter behaves like the ``stop`` attribute + in a :py:class:`slice` object; the stop index is *not* + included in the result. + + If ``None`` (default), then this method will check every + address until it finds one that is unused. + + .. note:: + An unused address is an address that **has not been spent from** + and **has no transactions** referencing it on the Tangle. + + A snapshot removes transactions from the Tangle. As a + consequence, after a snapshot, it may happen that this API does + not return the correct account data with ``stop`` being ``None``. + + As a workaround, you can save your used addresses and their + ``key_index`` attribute in a local database. Use the + ``start`` and ``stop`` parameters to tell the API from where to + start checking and where to stop. + + :param bool inclusion_states: + Whether to also fetch the inclusion states of the transfers. + + This requires an additional API call to the node, so it is + disabled by default. + + :param Optional[int] security_level: + Number of iterations to use when generating new addresses + (see :py:meth:`get_new_addresses`). + + This value must be between 1 and 3, inclusive. + + If not set, defaults to + :py:attr:`AddressGenerator.DEFAULT_SECURITY_LEVEL`. + + :return: + ``dict`` with the following structure:: + + { + 'addresses': List[Address], + List of generated addresses. + + Note that this list may include unused + addresses. + + 'balance': int, + Total account balance. Might be 0. + + 'bundles': List[Bundle], + List of bundles with transactions to/from this + account. + } + + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).get_account_data( + start, + stop, + inclusion_states, + security_level, + ) + ) + + def get_bundles(self, transactions): + # type: (Iterable[TransactionHash]) -> dict + """ + Returns the bundle(s) associated with the specified transaction + hashes. + + :param Iterable[TransactionHash] transactions: + Transaction hashes. Must be a tail transaction. + + :return: + ``dict`` with the following structure:: + + { + 'bundles': List[Bundle], + List of matching bundles. Note that this value is + always a list, even if only one bundle was found. + } + + :raise :py:class:`iota.adapter.BadApiResponse`: + - if any of the bundles fails validation. + - if any of the bundles is not visible on the Tangle. + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getbundle + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).get_bundles(transactions) + ) + + def get_inputs( + self, + start=0, + stop=None, + threshold=None, + security_level=None, + ): + # type: (int, Optional[int], Optional[int], Optional[int]) -> dict + """ + Gets all possible inputs of a seed and returns them, along with + the total balance. + + This is either done deterministically (by generating all + addresses until :py:meth:`find_transactions` returns an empty + result), or by providing a key range to search. + + :param int start: + Starting key index. + Defaults to 0. + + :param Optional[int] stop: + Stop before this index. + + Note that this parameter behaves like the ``stop`` attribute + in a :py:class:`slice` object; the stop index is *not* + included in the result. + + If ``None`` (default), then this method will not stop until + it finds an unused address. + + .. note:: + An unused address is an address that **has not been spent from** + and **has no transactions** referencing it on the Tangle. + + A snapshot removes transactions from the Tangle. As a + consequence, after a snapshot, it may happen that this API does + not return the correct inputs with ``stop`` being ``None``. + + As a workaround, you can save your used addresses and their + ``key_index`` attribute in a local database. Use the + ``start`` and ``stop`` parameters to tell the API from where to + start checking for inputs and where to stop. + + :param Optional[int] threshold: + If set, determines the minimum threshold for a successful + result: + + - As soon as this threshold is reached, iteration will stop. + - If the command runs out of addresses before the threshold + is reached, an exception is raised. + + .. note:: + This method does not attempt to "optimize" the result + (e.g., smallest number of inputs, get as close to + ``threshold`` as possible, etc.); it simply accumulates + inputs in order until the threshold is met. + + If ``threshold`` is 0, the first address in the key range + with a non-zero balance will be returned (if it exists). + + If ``threshold`` is ``None`` (default), this method will + return **all** inputs in the specified key range. + + :param Optional[int] security_level: + Number of iterations to use when generating new addresses + (see :py:meth:`get_new_addresses`). + + This value must be between 1 and 3, inclusive. + + If not set, defaults to + :py:attr:`AddressGenerator.DEFAULT_SECURITY_LEVEL`. + + :return: + ``dict`` with the following structure:: + + { + 'inputs': List[Address], + Addresses with nonzero balances that can be used + as inputs. + + 'totalBalance': int, + Aggregate balance from all matching addresses. + } + + Note that each :py:class:`Address` in the result has its + :py:attr:`Address.balance` attribute set. + + Example: + + .. code-block:: python + + response = iota.get_inputs(...) + + input0 = response['inputs'][0] # type: Address + input0.balance # 42 + + :raise: + - :py:class:`iota.adapter.BadApiResponse` if ``threshold`` + is not met. Not applicable if ``threshold`` is ``None``. + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getinputs + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).get_inputs( + start, + stop, + threshold, + security_level, + ) + ) + + def get_latest_inclusion(self, hashes): + # type: (Iterable[TransactionHash]) -> Dict[TransactionHash, bool] + """ + Fetches the inclusion state for the specified transaction + hashes, as of the latest milestone that the node has processed. + + Effectively, this is :py:meth:`get_node_info` + + :py:meth:`get_inclusion_states`. + + :param Iterable[TransactionHash] hashes: + List of transaction hashes. + + :return: + ``dict`` with the following structure:: + + { + "states": Dict[TransactionHash, bool] + ``dict`` with one boolean per transaction hash in + ``hashes``. + } + + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).get_latest_inclusion(hashes) + ) + + def get_new_addresses( + self, + index=0, + count=1, + security_level=AddressGenerator.DEFAULT_SECURITY_LEVEL, + checksum=False, + ): + # type: (int, int, int, bool) -> dict + """ + Generates one or more new addresses from the seed. + + :param int index: + The key index of the first new address to generate (must be + >= 0). + + :param int count: + Number of addresses to generate (must be >= 1). + + .. tip:: + This is more efficient than calling :py:meth:`get_new_addresses` + inside a loop. + + If ``None``, this method will progressively generate + addresses and scan the Tangle until it finds one that has no + transactions referencing it and was never spent from. + + .. note:: + A snapshot removes transactions from the Tangle. As a + consequence, after a snapshot, it may happen that when ``count`` + is ``None``, this API call returns a "new" address that used to + have transactions before the snapshot. + As a workaround, you can save your used addresses and their + ``key_index`` attribute in a local database. Use the + ``index`` parameter to tell the API from where to start + generating and checking new addresses. + + :param int security_level: + Number of iterations to use when generating new addresses. + + Larger values take longer, but the resulting signatures are + more secure. + + This value must be between 1 and 3, inclusive. + + :param bool checksum: + Specify whether to return the address with the checksum. + Defaults to ``False``. + + :return: + ``dict`` with the following structure:: + + { + 'addresses': List[Address], + Always a list, even if only one address was + generated. + } + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getnewaddress + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).get_new_addresses( + count=count, + index=index, + securityLevel=security_level, + checksum=checksum, + seed=self.seed, + ) + ) + + def get_transaction_objects( + self, + hashes, # type: [Iterable[TransactionHash]] + ): + # type: (...) -> dict + """ + Fetches transaction objects from the Tangle given their + transaction IDs (hashes). + + Effectively, this is :py:meth:`get_trytes` + + converting the trytes into transaction objects. + + Similar to :py:meth:`find_transaction_objects`, but accepts + list of transaction hashes as input. + + :param Iterable[TransactionHash] hashes: + List of transaction IDs (transaction hashes). + + :return: + ``dict`` with the following structure:: + + { + 'transactions': List[Transaction], + List of Transaction objects that match the input. + } + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).get_transaction_objects(hashes) + ) + + def get_transfers(self, start=0, stop=None, inclusion_states=False): + # type: (int, Optional[int], bool) -> dict + """ + Returns all transfers associated with the seed. + + :param int start: + Starting key index. + + :param Optional[int] stop: + Stop before this index. + + Note that this parameter behaves like the ``stop`` attribute + in a :py:class:`slice` object; the stop index is *not* + included in the result. + + If ``None`` (default), then this method will check every + address until it finds one that is unused. + + .. note:: + An unused address is an address that **has not been spent from** + and **has no transactions** referencing it on the Tangle. + + A snapshot removes transactions from the Tangle. As a + consequence, after a snapshot, it may happen that this API does + not return the expected transfers with ``stop`` being ``None``. + + As a workaround, you can save your used addresses and their + ``key_index`` attribute in a local database. Use the + ``start`` and ``stop`` parameters to tell the API from where to + start checking for transfers and where to stop. + + :param bool inclusion_states: + Whether to also fetch the inclusion states of the transfers. + + This requires an additional API call to the node, so it is + disabled by default. + + :return: + ``dict`` with the following structure:: + + { + 'bundles': List[Bundle], + Matching bundles, sorted by tail transaction + timestamp. + + This value is always a list, even if only one + bundle was found. + } + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#gettransfers + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).get_transfers( + start, + stop, + inclusion_states, + ) + ) + + def is_promotable( + self, + tails, # type: Iterable[TransactionHash] + ): + # type: (Iterable(TransactionHash)] -> dict + """ + Checks if tail transaction(s) is promotable by calling + :py:meth:`check_consistency` and verifying that ``attachmentTimestamp`` + is above a lower bound. + Lower bound is calculated based on number of milestones issued + since transaction attachment. + + :param Iterable(TransactionHash) tails: + List of tail transaction hashes. + + :return: + The return type mimics that of :py:meth:`check_consistency`. + ``dict`` with the following structure:: + + { + 'promotable': bool, + If ``True``, all tails are promotable. If ``False``, see + `info` field. + + 'info': Optional(List[Text]) + If `promotable` is ``False``, this contains info about what + went wrong. + Note that when 'promotable' is ``True``, 'info' does not + exist. + + } + + References: + - https://github.com/iotaledger/iota.js/blob/next/api_reference.md#module_core.isPromotable + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).is_promotable(tails) + ) + + def prepare_transfer( + self, + transfers, # type: Iterable[ProposedTransaction] + inputs=None, # type: Optional[Iterable[Address]] + change_address=None, # type: Optional[Address] + security_level=None, # type: Optional[int] + ): + # type: (...) -> dict + """ + Prepares transactions to be broadcast to the Tangle, by + generating the correct bundle, as well as choosing and signing + the inputs (for value transfers). + + :param Iterable[ProposedTransaction] transfers: + Transaction objects to prepare. + + :param Optional[Iterable[Address]] inputs: + List of addresses used to fund the transfer. + Ignored for zero-value transfers. + + If not provided, addresses will be selected automatically by + scanning the Tangle for unspent inputs. Depending on how + many transfers you've already sent with your seed, this + process could take awhile. + + :param Optional[Address] change_address: + If inputs are provided, any unspent amount will be sent to + this address. + + If not specified, a change address will be generated + automatically. + + :param Optional[int] security_level: + Number of iterations to use when generating new addresses + (see :py:meth:`get_new_addresses`). + + This value must be between 1 and 3, inclusive. + + If not set, defaults to + :py:attr:`AddressGenerator.DEFAULT_SECURITY_LEVEL`. + + :return: + ``dict`` with the following structure:: + + { + 'trytes': List[TransactionTrytes], + Raw trytes for the transactions in the bundle, + ready to be provided to :py:meth:`send_trytes`. + } + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#preparetransfers + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).prepare_transfer( + transfers, + inputs, + change_address, + security_level, + ) + ) + + def promote_transaction( + self, + transaction, + depth=3, + min_weight_magnitude=None, + ): + # type: (TransactionHash, int, Optional[int]) -> dict + """ + Promotes a transaction by adding spam on top of it. + + :param TransactionHash transaction: + Transaction hash. Must be a tail transaction. + + :param int depth: + Depth at which to attach the bundle. + Defaults to 3. + + :param Optional[int] min_weight_magnitude: + Min weight magnitude, used by the node to calibrate Proof of + Work. + + If not provided, a default value will be used. + + :return: + ``dict`` with the following structure:: + + { + 'bundle': Bundle, + The newly-published bundle. + } + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).promote_transaction( + transaction, + dept, + min_weight_magnitude, + ) + ) + + def replay_bundle( + self, + transaction, + depth=3, + min_weight_magnitude=None, + ): + # type: (TransactionHash, int, Optional[int]) -> dict + """ + Takes a tail transaction hash as input, gets the bundle + associated with the transaction and then replays the bundle by + attaching it to the Tangle. + + :param TransactionHash transaction: + Transaction hash. Must be a tail. + + :param int depth: + Depth at which to attach the bundle. + Defaults to 3. + + :param Optional[int] min_weight_magnitude: + Min weight magnitude, used by the node to calibrate Proof of + Work. + + If not provided, a default value will be used. + + :return: + ``dict`` with the following structure:: + + { + 'trytes': List[TransactionTrytes], + Raw trytes that were published to the Tangle. + } + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#replaytransfer + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).replay_bundle( + transaction, + dept, + min_weight_magnitude, + ) + ) + + def send_transfer( + self, + transfers, # type: Iterable[ProposedTransaction] + depth=3, # type: int + inputs=None, # type: Optional[Iterable[Address]] + change_address=None, # type: Optional[Address] + min_weight_magnitude=None, # type: Optional[int] + security_level=None, # type: Optional[int] + ): + # type: (...) -> dict + """ + Prepares a set of transfers and creates the bundle, then + attaches the bundle to the Tangle, and broadcasts and stores the + transactions. + + :param Iterable[ProposedTransaction] transfers: + Transfers to include in the bundle. + + :param int depth: + Depth at which to attach the bundle. + Defaults to 3. + + :param Optional[Iterable[Address]] inputs: + List of inputs used to fund the transfer. + Not needed for zero-value transfers. + + :param Optional[Address] change_address: + If inputs are provided, any unspent amount will be sent to + this address. + + If not specified, a change address will be generated + automatically. + + :param Optional[int] min_weight_magnitude: + Min weight magnitude, used by the node to calibrate Proof of + Work. + + If not provided, a default value will be used. + + :param Optional[int] security_level: + Number of iterations to use when generating new addresses + (see :py:meth:`get_new_addresses`). + + This value must be between 1 and 3, inclusive. + + If not set, defaults to + :py:attr:`AddressGenerator.DEFAULT_SECURITY_LEVEL`. + + :return: + ``dict`` with the following structure:: + + { + 'bundle': Bundle, + The newly-published bundle. + } + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#sendtransfer + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).send_transfer( + transfers, + depth, + inputs, + change_address, + min_weight_magnitude, + security_level, + ) + ) + + def send_trytes(self, trytes, depth=3, min_weight_magnitude=None): + # type: (Iterable[TransactionTrytes], int, Optional[int]) -> dict + """ + Attaches transaction trytes to the Tangle, then broadcasts and + stores them. + + :param Iterable[TransactionTrytes] trytes: + Transaction encoded as a tryte sequence. + + :param int depth: + Depth at which to attach the bundle. + Defaults to 3. + + :param Optional[int] min_weight_magnitude: + Min weight magnitude, used by the node to calibrate Proof of + Work. + + If not provided, a default value will be used. + + :return: + ``dict`` with the following structure:: + + { + 'trytes': List[TransactionTrytes], + Raw trytes that were published to the Tangle. + } + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#sendtrytes + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).send_trytes( + trytes, + depth, + min_weight_magnitude, + ) + ) + + def is_reattachable(self, addresses): + # type: (Iterable[Address]) -> dict + """ + This API function helps you to determine whether you should + replay a transaction or make a new one (either with the same + input, or a different one). + + This method takes one or more input addresses (i.e. from spent + transactions) as input and then checks whether any transactions + with a value transferred are confirmed. + + If yes, it means that this input address has already been + successfully used in a different transaction, and as such you + should no longer replay the transaction. + + :param Iterable[Address] addresses: + List of addresses. + + :return: + ``dict`` with the following structure:: + + { + 'reattachable': List[bool], + Always a list, even if only one address was queried. + } + + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).is_reattachable( + addresses, + ) + ) + + def traverse_bundle(self, tail_hash): + # type: (TransactionHash) -> dict + """ + Fetches and traverses a bundle from the Tangle given a tail transaction + hash. + Recursively traverse the Tangle, collecting transactions until + we hit a new bundle. + + This method is (usually) faster than :py:meth:`find_transactions`, and + it ensures we don't collect transactions from replayed bundles. + + :param TransactionHash tail_hash: + Tail transaction hash of the bundle. + + :return: + ``dict`` with the following structure:: + + { + 'bundle': List[Bundle], + List of matching bundles. Note that this value is + always a list, even if only one bundle was found. + } + + """ + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super(Iota, self).traverse_bundle( + tail_hash, + ) + ) \ No newline at end of file diff --git a/iota/commands/__init__.py b/iota/commands/__init__.py index 39695bc..d78eb1d 100644 --- a/iota/commands/__init__.py +++ b/iota/commands/__init__.py @@ -43,7 +43,7 @@ def __init__(self, adapter): self.request = None # type: dict self.response = None # type: dict - def __call__(self, **kwargs): + async def __call__(self, **kwargs): # type: (**Any) -> dict """ Sends the command to the node. @@ -64,7 +64,7 @@ def __call__(self, **kwargs): if replacement is not None: self.request = replacement - self.response = self._execute(self.request) + self.response = await self._execute(self.request) replacement = self._prepare_response(self.response) if replacement is not None: @@ -83,7 +83,7 @@ def reset(self): self.request = None # type: dict self.response = None # type: dict - def _execute(self, request): + async def _execute(self, request): # type: (dict) -> dict """ Sends the request object to the adapter and returns the response. @@ -92,7 +92,7 @@ def _execute(self, request): before it is sent (note: this will modify the request object). """ request['command'] = self.command - return self.adapter.send_request(request) + return await self.adapter.send_request(request) @abstract_method def _prepare_request(self, request): diff --git a/iota/commands/core/attach_to_tangle.py b/iota/commands/core/attach_to_tangle.py index 33250bc..7fed061 100644 --- a/iota/commands/core/attach_to_tangle.py +++ b/iota/commands/core/attach_to_tangle.py @@ -7,6 +7,7 @@ from iota import TransactionHash, TransactionTrytes from iota.commands import FilterCommand, RequestFilter, ResponseFilter from iota.filters import Trytes +from iota.adapter import async_return __all__ = [ 'AttachToTangleCommand', @@ -27,7 +28,7 @@ def get_request_filter(self): def get_response_filter(self): return AttachToTangleResponseFilter() - def _execute(self, request): + async def _execute(self, request): if self.adapter.local_pow is True: from pow import ccurl_interface powed_trytes = ccurl_interface.attach_to_tangle( @@ -36,9 +37,9 @@ def _execute(self, request): request['trunkTransaction'], request['minWeightMagnitude'] ) - return {'trytes': powed_trytes} + return await async_return({'trytes': powed_trytes}) else: - return super(FilterCommand, self)._execute(request) + return await super(FilterCommand, self)._execute(request) class AttachToTangleRequestFilter(RequestFilter): def __init__(self): From dabcd3eefd02449e4ae47c51799ef91b8d11ac05 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Fri, 7 Feb 2020 11:46:56 +0100 Subject: [PATCH 08/69] Make adapter and api tests async --- iota/api.py | 2 +- test/__init__.py | 3 +++ test/adapter/wrappers_test.py | 4 +--- test/adapter_test.py | 4 +--- test/api_test.py | 25 +++++++++++++++---------- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/iota/api.py b/iota/api.py index 295804e..69b6e62 100644 --- a/iota/api.py +++ b/iota/api.py @@ -1999,7 +1999,7 @@ def __init__(self, adapter, seed=None, devnet=False, local_pow=False): .. note:: This value is never transferred to the node/network. """ - # Exolicitly call AsyncIota's init, as we need the seed + # Explicitly call AsyncIota's init, as we need the seed AsyncIota.__init__(self, adapter, seed, devnet, local_pow) def broadcast_and_store(self, trytes): diff --git a/test/__init__.py b/test/__init__.py index 0b9065d..5d3d136 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -14,3 +14,6 @@ # noinspection PyUnresolvedReferences import mock from mock import MagicMock, patch + +# Executes async test case within a loop +from aiounittest import async_test diff --git a/test/adapter/wrappers_test.py b/test/adapter/wrappers_test.py index b37aced..e58cc11 100644 --- a/test/adapter/wrappers_test.py +++ b/test/adapter/wrappers_test.py @@ -6,9 +6,7 @@ from iota.adapter import HttpAdapter, MockAdapter from iota.adapter.wrappers import RoutingWrapper - -# Executes async test case within a loop -from aiounittest import async_test +from test import async_test class RoutingWrapperTestCase(TestCase): diff --git a/test/adapter_test.py b/test/adapter_test.py index 31dc54d..4b4c569 100644 --- a/test/adapter_test.py +++ b/test/adapter_test.py @@ -12,9 +12,7 @@ from iota.adapter import API_VERSION, HttpAdapter, MockAdapter, \ resolve_adapter, async_return from six import BytesIO, text_type -from test import mock -# Executes async test case within a loop -from aiounittest import async_test +from test import mock, async_test class ResolveAdapterTestCase(TestCase): """ diff --git a/test/api_test.py b/test/api_test.py index 95084f4..745b4f2 100644 --- a/test/api_test.py +++ b/test/api_test.py @@ -11,6 +11,7 @@ from iota.adapter import MockAdapter from iota.commands import CustomCommand from iota.commands.core.get_node_info import GetNodeInfoCommand +from test import async_test class CustomCommandTestCase(TestCase): @@ -21,7 +22,8 @@ def setUp(self): self.adapter = MockAdapter() self.command = CustomCommand(self.adapter, self.name) - def test_call(self): + @async_test + async def test_call(self): """ Sending a custom command. """ @@ -29,7 +31,7 @@ def test_call(self): self.adapter.seed_response('helloWorld', expected_response) - response = self.command() + response = await self.command() self.assertEqual(response, expected_response) self.assertTrue(self.command.called) @@ -39,7 +41,8 @@ def test_call(self): [{'command': 'helloWorld'}], ) - def test_call_with_parameters(self): + @async_test + async def test_call_with_parameters(self): """ Sending a custom command with parameters. """ @@ -47,7 +50,7 @@ def test_call_with_parameters(self): self.adapter.seed_response('helloWorld', expected_response) - response = self.command(foo='bar', baz='luhrmann') + response = await self.command(foo='bar', baz='luhrmann') self.assertEqual(response, expected_response) self.assertTrue(self.command.called) @@ -57,24 +60,26 @@ def test_call_with_parameters(self): [{'command': 'helloWorld', 'foo': 'bar', 'baz': 'luhrmann'}], ) - def test_call_error_already_called(self): + @async_test + async def test_call_error_already_called(self): """ A command can only be called once. """ self.adapter.seed_response('helloWorld', {}) - self.command() + await self.command() with self.assertRaises(RuntimeError): - self.command(extra='params') + await self.command(extra='params') self.assertDictEqual(self.command.request, {'command': 'helloWorld'}) - def test_call_reset(self): + @async_test + async def test_call_reset(self): """ Resetting a command allows it to be called more than once. """ self.adapter.seed_response('helloWorld', {'message': 'Hello, IOTA!'}) - self.command() + await self.command() self.command.reset() @@ -84,7 +89,7 @@ def test_call_reset(self): expected_response = {'message': 'Welcome back!'} self.adapter.seed_response('helloWorld', expected_response) - response = self.command(foo='bar') + response = await self.command(foo='bar') self.assertDictEqual(response, expected_response) self.assertDictEqual(self.command.response, expected_response) From 9347e948399dff96738c6eee62114c0b0b8cf378 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Fri, 7 Feb 2020 12:06:16 +0100 Subject: [PATCH 09/69] Make `BroadcastAndStoreCommand` async - update tests for async --- iota/api.py | 3 +- iota/commands/extended/broadcast_and_store.py | 11 ++++-- .../extended/broadcast_and_store_test.py | 39 +++++++++++++++---- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/iota/api.py b/iota/api.py index 69b6e62..ee2ff6f 100644 --- a/iota/api.py +++ b/iota/api.py @@ -2436,9 +2436,8 @@ def get_new_addresses( super(Iota, self).get_new_addresses( count=count, index=index, - securityLevel=security_level, + security_level=security_level, checksum=checksum, - seed=self.seed, ) ) diff --git a/iota/commands/extended/broadcast_and_store.py b/iota/commands/extended/broadcast_and_store.py index c28526b..50840b5 100644 --- a/iota/commands/extended/broadcast_and_store.py +++ b/iota/commands/extended/broadcast_and_store.py @@ -6,6 +6,7 @@ from iota.commands.core.broadcast_transactions import \ BroadcastTransactionsCommand from iota.commands.core.store_transactions import StoreTransactionsCommand +import asyncio __all__ = [ 'BroadcastAndStoreCommand', @@ -26,9 +27,13 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): - BroadcastTransactionsCommand(self.adapter)(**request) - StoreTransactionsCommand(self.adapter)(**request) + async def _execute(self, request): + # Submit the two coroutines to the already running event loop + await asyncio.gather( + BroadcastTransactionsCommand(self.adapter)(**request), + StoreTransactionsCommand(self.adapter)(**request), + ) + return { 'trytes': request['trytes'], } diff --git a/test/commands/extended/broadcast_and_store_test.py b/test/commands/extended/broadcast_and_store_test.py index 9354784..6cd19f1 100644 --- a/test/commands/extended/broadcast_and_store_test.py +++ b/test/commands/extended/broadcast_and_store_test.py @@ -6,11 +6,10 @@ from six import text_type -from iota import Iota, TransactionTrytes -from iota.adapter import MockAdapter +from iota import Iota, AsyncIota, TransactionTrytes +from iota.adapter import MockAdapter, async_return from iota.commands.extended.broadcast_and_store import BroadcastAndStoreCommand -from test import patch, MagicMock - +from test import patch, MagicMock, async_test class BroadcastAndStoreCommandTestCase(TestCase): # noinspection SpellCheckingInspection @@ -27,12 +26,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.broadcast_and_store.BroadcastAndStoreCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -47,7 +46,31 @@ def test_wireup(self): 'You found me!' ) - def test_happy_path(self): + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.broadcast_and_store.BroadcastAndStoreCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.broadcast_and_store('trytes') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_happy_path(self): """ Successful invocation of ``broadcastAndStore``. """ @@ -65,6 +88,6 @@ def test_happy_path(self): TransactionTrytes(self.trytes2), ] - response = self.command(trytes=trytes) + response = await self.command(trytes=trytes) self.assertDictEqual(response, {'trytes': trytes}) From ee2361b5f7c2de4ffe4ea61d351726d4c37dd54c Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Fri, 7 Feb 2020 12:54:57 +0100 Subject: [PATCH 10/69] Make `bundle` commands async - Refactor `BroadCastBundleCommand` and update its tests - Refactor `GetBundleCommand` and its update tests - Refactor `TraverseBundleCommand` and update its tests `get_bundles` fetches bundles concurrently, which speeds it up if it is called with more than one argument. --- iota/commands/extended/broadcast_bundle.py | 6 +- iota/commands/extended/get_bundles.py | 19 +++--- iota/commands/extended/traverse_bundle.py | 14 ++--- .../extended/broadcast_bundle_test.py | 51 +++++++++++----- test/commands/extended/get_bundles_test.py | 48 +++++++++++---- .../commands/extended/traverse_bundle_test.py | 58 ++++++++++++++----- 6 files changed, 139 insertions(+), 57 deletions(-) diff --git a/iota/commands/extended/broadcast_bundle.py b/iota/commands/extended/broadcast_bundle.py index b7d84b3..76deceb 100644 --- a/iota/commands/extended/broadcast_bundle.py +++ b/iota/commands/extended/broadcast_bundle.py @@ -31,13 +31,13 @@ def get_response_filter(self): # Return value is filtered before hitting us. pass - def _execute(self, request): + async def _execute(self, request): # Given tail hash, fetches the bundle from the tangle # and validates it. # Returns List[List[TransactionTrytes]] # (outer list has one item in current implementation) - bundle = GetBundlesCommand(self.adapter)(transactions=[request['tail_hash']]) - BroadcastTransactionsCommand(self.adapter)(trytes=bundle[0]) + bundle = await GetBundlesCommand(self.adapter)(transactions=[request['tail_hash']]) + await BroadcastTransactionsCommand(self.adapter)(trytes=bundle[0]) return { 'trytes': bundle[0], } diff --git a/iota/commands/extended/get_bundles.py b/iota/commands/extended/get_bundles.py index cb1acb3..5697014 100644 --- a/iota/commands/extended/get_bundles.py +++ b/iota/commands/extended/get_bundles.py @@ -10,6 +10,7 @@ from iota.exceptions import with_context from iota.transaction.validator import BundleValidator from iota.filters import Trytes +import asyncio __all__ = [ 'GetBundlesCommand', @@ -30,16 +31,13 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + async def _execute(self, request): transaction_hashes = request['transactions'] # type: Iterable[TransactionHash] - bundles = [] - - # Fetch bundles one-by-one - for tx_hash in transaction_hashes: - bundle = TraverseBundleCommand(self.adapter)( + async def fetch_and_validate(tx_hash): + bundle = (await TraverseBundleCommand(self.adapter)( transaction=tx_hash - )['bundles'][0] # Currently 1 bundle only + ))['bundles'][0] # Currently 1 bundle only validator = BundleValidator(bundle) @@ -55,7 +53,12 @@ def _execute(self, request): }, ) - bundles.append(bundle) + return bundle + + # Fetch bundles asynchronously + bundles = await asyncio.gather( + *[fetch_and_validate(tx_hash) for tx_hash in transaction_hashes] + ) return { 'bundles': bundles, diff --git a/iota/commands/extended/traverse_bundle.py b/iota/commands/extended/traverse_bundle.py index d1176db..a4ddc94 100644 --- a/iota/commands/extended/traverse_bundle.py +++ b/iota/commands/extended/traverse_bundle.py @@ -32,10 +32,10 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + async def _execute(self, request): txn_hash = request['transaction'] # type: TransactionHash - bundle = Bundle(self._traverse_bundle(txn_hash, None)) + bundle = Bundle(await self._traverse_bundle(txn_hash, None)) # No bundle validation @@ -43,7 +43,7 @@ def _execute(self, request): 'bundles' : [bundle] } - def _traverse_bundle(self, txn_hash, target_bundle_hash): + async def _traverse_bundle(self, txn_hash, target_bundle_hash): """ Recursively traverse the Tangle, collecting transactions until we hit a new bundle. @@ -51,9 +51,9 @@ def _traverse_bundle(self, txn_hash, target_bundle_hash): This method is (usually) faster than ``findTransactions``, and it ensures we don't collect transactions from replayed bundles. """ - trytes = ( - GetTrytesCommand(self.adapter)(hashes=[txn_hash])['trytes'] - ) # type: List[TryteString] + trytes =(await GetTrytesCommand(self.adapter)( + hashes=[txn_hash]) + )['trytes'] # type: List[TryteString] # If no tx was found by the node for txn_hash, it returns 9s, # so we check here if it returned all 9s trytes. @@ -99,7 +99,7 @@ def _traverse_bundle(self, txn_hash, target_bundle_hash): # Recursively follow the trunk transaction, to fetch the next # transaction in the bundle. - return [transaction] + self._traverse_bundle( + return [transaction] + await self._traverse_bundle( transaction.trunk_transaction_hash, target_bundle_hash ) diff --git a/test/commands/extended/broadcast_bundle_test.py b/test/commands/extended/broadcast_bundle_test.py index 3d04780..a4af43e 100644 --- a/test/commands/extended/broadcast_bundle_test.py +++ b/test/commands/extended/broadcast_bundle_test.py @@ -8,11 +8,11 @@ from filters.test import BaseFilterTestCase from iota import Address, BadApiResponse, Bundle, BundleHash, Fragment, Hash, \ - Iota, Tag, Transaction, TransactionHash, TransactionTrytes, Nonce -from iota.adapter import MockAdapter + Iota, AsyncIota, Tag, Transaction, TransactionHash, TransactionTrytes, Nonce +from iota.adapter import MockAdapter, async_return from iota.commands.extended.broadcast_bundle import BroadcastBundleCommand from iota.filters import Trytes -from test import patch, MagicMock +from test import patch, MagicMock, async_test # RequestFilterTestCase code reused from get_bundles_test.py @@ -142,12 +142,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.broadcast_bundle.BroadcastBundleCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -161,8 +161,32 @@ def test_wireup(self): response, 'You found me!' ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.broadcast_bundle.BroadcastBundleCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.broadcast_bundle('trytes') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) - def test_happy_path(self): + @async_test + async def test_happy_path(self): """ Test command flow executes as expected. """ @@ -174,22 +198,23 @@ def test_happy_path(self): # whole command, so no filter is applied. It is safe because it is tested # elsewhere. with patch('iota.commands.extended.get_bundles.GetBundlesCommand.__call__', - MagicMock(return_value=[self.trytes])) as mocked_get_bundles: + MagicMock(return_value=async_return([self.trytes]))) as mocked_get_bundles: # We could seed a reponse to our MockAdapter, but then the returned value # from `GetBundlesCommand` shall be valid to pass # BroadcastTransactionRequestFilter. # Anyway, nature loves symmetry and so do we. with patch('iota.commands.core.BroadcastTransactionsCommand.__call__', - MagicMock(return_value=[])) as mocked_broadcast: + MagicMock(return_value=async_return([]))) as mocked_broadcast: - response = self.command(tail_hash=self.tail) + response = await self.command(tail_hash=self.tail) self.assertEqual( response['trytes'], self.trytes ) - def test_happy_path_multiple_bundle(self): + @async_test + async def test_happy_path_multiple_bundle(self): """ Test if command returns the correct bundle if underlying `get_bundles` returns multiple bundles. @@ -199,12 +224,12 @@ def test_happy_path_multiple_bundle(self): # BroadcastTransactionsCommand either. # Note that GetBundlesCommand returns multiple bundles! with patch('iota.commands.extended.get_bundles.GetBundlesCommand.__call__', - MagicMock(return_value=[self.trytes, self.trytes_dummy]) + MagicMock(return_value=async_return([self.trytes, self.trytes_dummy])) ) as mocked_get_bundles: with patch('iota.commands.core.BroadcastTransactionsCommand.__call__', - MagicMock(return_value=[])) as mocked_broadcast: + MagicMock(return_value=async_return([]))) as mocked_broadcast: - response = self.command(tail_hash=self.tail) + response = await self.command(tail_hash=self.tail) # Expect only the first bundle self.assertEqual( diff --git a/test/commands/extended/get_bundles_test.py b/test/commands/extended/get_bundles_test.py index 4c91e85..f079bec 100644 --- a/test/commands/extended/get_bundles_test.py +++ b/test/commands/extended/get_bundles_test.py @@ -8,11 +8,11 @@ from filters.test import BaseFilterTestCase from iota import Address, BadApiResponse, Bundle, \ - Iota, TransactionHash, TransactionTrytes -from iota.adapter import MockAdapter + Iota, AsyncIota, TransactionHash, TransactionTrytes +from iota.adapter import MockAdapter, async_return from iota.commands.extended.get_bundles import GetBundlesCommand from iota.filters import Trytes -from test import patch, MagicMock +from test import patch, MagicMock, async_test class GetBundlesRequestFilterTestCase(BaseFilterTestCase): @@ -330,12 +330,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.get_bundles.GetBundlesCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -350,7 +350,31 @@ def test_wireup(self): 'You found me!' ) - def test_happy_path(self): + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.get_bundles.GetBundlesCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.get_bundles('transactions') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_happy_path(self): """ Get a bundle with multiple transactions. """ @@ -363,7 +387,7 @@ def test_happy_path(self): 'trytes': [self.spam_trytes], }) - response = self.command(transactions = [self.tx_hash]) + response = await self.command(transactions = [self.tx_hash]) self.maxDiff = None original_bundle = Bundle.from_tryte_strings(self.bundle_trytes) @@ -372,7 +396,8 @@ def test_happy_path(self): original_bundle.as_json_compatible(), ) - def test_happy_path_multiple_bundles(self): + @async_test + async def test_happy_path_multiple_bundles(self): """ Get two bundles with multiple transactions. """ @@ -387,7 +412,7 @@ def test_happy_path_multiple_bundles(self): 'trytes': [self.spam_trytes], }) - response = self.command(transactions = [self.tx_hash, self.tx_hash]) + response = await self.command(transactions = [self.tx_hash, self.tx_hash]) self.maxDiff = None original_bundle = Bundle.from_tryte_strings(self.bundle_trytes) @@ -402,7 +427,8 @@ def test_happy_path_multiple_bundles(self): original_bundle.as_json_compatible(), ) - def test_validator_error(self): + @async_test + async def test_validator_error(self): """ TraverseBundleCommand returns bundle but it is invalid. """ @@ -420,4 +446,4 @@ def test_validator_error(self): }) with self.assertRaises(BadApiResponse): - response = self.command(transactions = [self.tx_hash]) \ No newline at end of file + response = await self.command(transactions = [self.tx_hash]) \ No newline at end of file diff --git a/test/commands/extended/traverse_bundle_test.py b/test/commands/extended/traverse_bundle_test.py index b869b36..383b225 100644 --- a/test/commands/extended/traverse_bundle_test.py +++ b/test/commands/extended/traverse_bundle_test.py @@ -8,11 +8,11 @@ from filters.test import BaseFilterTestCase from iota import Address, BadApiResponse, Bundle, BundleHash, Fragment, Hash, \ - Iota, Tag, Transaction, TransactionHash, TransactionTrytes, Nonce -from iota.adapter import MockAdapter + Iota, AsyncIota, Tag, Transaction, TransactionHash, TransactionTrytes, Nonce +from iota.adapter import MockAdapter, async_return from iota.commands.extended.traverse_bundle import TraverseBundleCommand from iota.filters import Trytes -from test import patch, MagicMock +from test import patch, MagicMock, async_test # Same tests as for GetBundlesRequestFilter (it is the same filter) class TraverseBundleRequestFilterTestCase(BaseFilterTestCase): @@ -129,12 +129,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.traverse_bundle.TraverseBundleCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -149,7 +149,31 @@ def test_wireup(self): 'You found me!' ) - def test_single_transaction(self): + @async_test + async def test_wireup(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.traverse_bundle.TraverseBundleCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.traverse_bundle('tail') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_single_transaction(self): """ Getting a bundle that contains a single transaction. """ @@ -204,7 +228,7 @@ def test_single_transaction(self): 'trytes': [transaction.as_tryte_string()], }) - response = self.command(transaction=transaction.hash) + response = await self.command(transaction=transaction.hash) bundle = response['bundles'][0] # type: Bundle self.assertEqual(len(bundle), 1) @@ -215,7 +239,8 @@ def test_single_transaction(self): transaction.as_json_compatible(), ) - def test_multiple_transactions(self): + @async_test + async def test_multiple_transactions(self): """ Getting a bundle that contains multiple transactions. """ @@ -363,7 +388,7 @@ def test_multiple_transactions(self): ], }) - response = self.command( + response = await self.command( transaction = TransactionHash( b'TOYJPHKMLQNDVLDHDILARUJCCIUMQBLUSWPCTIVA' @@ -376,7 +401,8 @@ def test_multiple_transactions(self): bundle.as_json_compatible(), ) - def test_non_tail_transaction(self): + @async_test + async def test_non_tail_transaction(self): """ Trying to get a bundle for a non-tail transaction. @@ -429,7 +455,7 @@ def test_non_tail_transaction(self): }) with self.assertRaises(BadApiResponse): - self.command( + await self.command( transaction = TransactionHash( b'FSEWUNJOEGNUI9QOCRFMYSIFAZLJHKZBPQZZYFG9' @@ -437,14 +463,15 @@ def test_non_tail_transaction(self): ), ) - def test_missing_transaction(self): + @async_test + async def test_missing_transaction(self): """ Unable to find the requested transaction. """ self.adapter.seed_response('getTrytes', {'trytes': []}) with self.assertRaises(BadApiResponse): - self.command( + await self.command( transaction = TransactionHash( b'FSEWUNJOEGNUI9QOCRFMYSIFAZLJHKZBPQZZYFG9' @@ -452,7 +479,8 @@ def test_missing_transaction(self): ), ) - def test_missing_transaction_zero_trytes(self): + @async_test + async def test_missing_transaction_zero_trytes(self): """ Unable to find the requested transaction. getTrytes returned only zeros, no tx was found. @@ -461,7 +489,7 @@ def test_missing_transaction_zero_trytes(self): self.adapter.seed_response('getTrytes', {'trytes': [zero_trytes]}) with self.assertRaises(BadApiResponse): - self.command( + await self.command( transaction = TransactionHash( b'FSEWUNJOEGNUI9QOCRFMYSIFAZLJHKZBPQZZYFG9' From e5e339ee66fac2a441ad028cc6c101c6de5f5f95 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Fri, 7 Feb 2020 14:26:09 +0100 Subject: [PATCH 11/69] Make `FindTransactionObjectsCommand` async - Update tests for async --- .../extended/find_transaction_objects.py | 6 +- ...ts.py => find_transaction_objects_test.py} | 70 ++++++++++++++++--- 2 files changed, 62 insertions(+), 14 deletions(-) rename test/commands/extended/{find_transaction_objects.py => find_transaction_objects_test.py} (72%) diff --git a/iota/commands/extended/find_transaction_objects.py b/iota/commands/extended/find_transaction_objects.py index e0dd096..5bd4d44 100644 --- a/iota/commands/extended/find_transaction_objects.py +++ b/iota/commands/extended/find_transaction_objects.py @@ -23,7 +23,7 @@ class FindTransactionObjectsCommand(FindTransactionsCommand): def get_response_filter(self): pass - def _execute(self, request): + async def _execute(self, request): bundles = request\ .get('bundles') # type: Optional[Iterable[BundleHash]] addresses = request\ @@ -33,7 +33,7 @@ def _execute(self, request): approvees = request\ .get('approvees') # type: Optional[Iterable[TransactionHash]] - ft_response = FindTransactionsCommand(adapter=self.adapter)( + ft_response = await FindTransactionsCommand(adapter=self.adapter)( bundles=bundles, addresses=addresses, tags=tags, @@ -43,7 +43,7 @@ def _execute(self, request): hashes = ft_response['hashes'] transactions = [] if hashes: - gt_response = GetTrytesCommand(adapter=self.adapter)(hashes=hashes) + gt_response = await GetTrytesCommand(adapter=self.adapter)(hashes=hashes) transactions = list(map( Transaction.from_tryte_string, diff --git a/test/commands/extended/find_transaction_objects.py b/test/commands/extended/find_transaction_objects_test.py similarity index 72% rename from test/commands/extended/find_transaction_objects.py rename to test/commands/extended/find_transaction_objects_test.py index e87b380..ddc15e0 100644 --- a/test/commands/extended/find_transaction_objects.py +++ b/test/commands/extended/find_transaction_objects_test.py @@ -4,9 +4,10 @@ from unittest import TestCase -from iota import Iota, MockAdapter, Transaction +from iota import Iota, AsyncIota, MockAdapter, Transaction from iota.commands.extended import FindTransactionObjectsCommand -from test import patch, MagicMock, mock +from iota.adapter import async_return +from test import patch, MagicMock, mock, async_test class FindTransactionObjectsCommandTestCase(TestCase): @@ -70,12 +71,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.find_transaction_objects.FindTransactionObjectsCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -90,7 +91,53 @@ def test_wireup(self): 'You found me!' ) - def test_transaction_found(self): + def test_wireup(self): + """ + Verify that the command is wired up correctly. (sync) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.find_transaction_objects.FindTransactionObjectsCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = Iota(self.adapter) + + # Don't need to call with proper args here. + response = api.find_transaction_objects('bundle') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.find_transaction_objects.FindTransactionObjectsCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.find_transaction_objects('bundle') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_transaction_found(self): """ A transaction is found with the inputs. A transaction object is returned @@ -98,29 +145,30 @@ def test_transaction_found(self): with mock.patch( 'iota.commands.core.find_transactions.FindTransactionsCommand.' '_execute', - mock.Mock(return_value={'hashes': [self.transaction_hash, ]}), + mock.Mock(return_value=async_return({'hashes': [self.transaction_hash, ]})), ): with mock.patch( 'iota.commands.core.get_trytes.GetTrytesCommand._execute', - mock.Mock(return_value={'trytes': [self.trytes, ]}), + mock.Mock(return_value=async_return({'trytes': [self.trytes, ]})), ): - response = self.command(addresses=[self.address]) + response = await self.command(addresses=[self.address]) self.assertEqual(len(response['transactions']), 1) transaction = response['transactions'][0] self.assertIsInstance(transaction, Transaction) self.assertEqual(transaction.address, self.address) - def test_no_transactions_fround(self): + @async_test + async def test_no_transactions_fround(self): """ No transaction is found with the inputs. An empty list is returned """ with mock.patch( 'iota.commands.core.find_transactions.FindTransactionsCommand.' '_execute', - mock.Mock(return_value={'hashes': []}), + mock.Mock(return_value=async_return({'hashes': []})), ): - response = self.command(addresses=[self.address]) + response = await self.command(addresses=[self.address]) self.assertDictEqual( response, From 703694af5ea9cdadc1a9b46ad938fa776fedc944 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Fri, 7 Feb 2020 15:33:17 +0100 Subject: [PATCH 12/69] Make `GetAccountDataCommand` async - Updated tests for async - `iter_used_addresses` becomes an async generator - `get_bundles_from_transaction_hashes` ready for async - Update `utils_test.py` --- iota/commands/extended/get_account_data.py | 10 +-- iota/commands/extended/utils.py | 35 +++++----- .../extended/get_account_data_test.py | 70 ++++++++++++++----- test/commands/extended/utils_test.py | 34 +++++---- 4 files changed, 96 insertions(+), 53 deletions(-) diff --git a/iota/commands/extended/get_account_data.py b/iota/commands/extended/get_account_data.py index 46dd8d1..4e1fcd2 100644 --- a/iota/commands/extended/get_account_data.py +++ b/iota/commands/extended/get_account_data.py @@ -36,7 +36,7 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + async def _execute(self, request): inclusion_states = request['inclusionStates'] # type: bool seed = request['seed'] # type: Seed start = request['start'] # type: int @@ -47,7 +47,7 @@ def _execute(self, request): my_addresses = [] # type: List[Address] my_hashes = [] # type: List[TransactionHash] - for addy, hashes in iter_used_addresses(self.adapter, seed, start, security_level): + async for addy, hashes in iter_used_addresses(self.adapter, seed, start, security_level): my_addresses.append(addy) my_hashes.extend(hashes) else: @@ -56,13 +56,13 @@ def _execute(self, request): my_addresses = ( AddressGenerator(seed, security_level).get_addresses(start, stop - start) ) - my_hashes = ft_command(addresses=my_addresses).get('hashes') or [] + my_hashes = (await ft_command(addresses=my_addresses)).get('hashes') or [] account_balance = 0 if my_addresses: # Load balances for the addresses that we generated. gb_response = ( - GetBalancesCommand(self.adapter)(addresses=my_addresses) + await GetBalancesCommand(self.adapter)(addresses=my_addresses) ) for i, balance in enumerate(gb_response['balances']): @@ -76,7 +76,7 @@ def _execute(self, request): 'balance': account_balance, 'bundles': - get_bundles_from_transaction_hashes( + await get_bundles_from_transaction_hashes( adapter=self.adapter, transaction_hashes=my_hashes, inclusion_states=inclusion_states, diff --git a/iota/commands/extended/utils.py b/iota/commands/extended/utils.py index 3292a16..aebe215 100644 --- a/iota/commands/extended/utils.py +++ b/iota/commands/extended/utils.py @@ -19,7 +19,8 @@ from iota.crypto.types import Seed -def iter_used_addresses( +# This is an async generator! +async def iter_used_addresses( adapter, # type: BaseAdapter seed, # type: Seed start, # type: int @@ -40,12 +41,12 @@ def iter_used_addresses( wasf_command = WereAddressesSpentFromCommand(adapter) for addy in AddressGenerator(seed, security_level).create_iterator(start): - ft_response = ft_command(addresses=[addy]) + ft_response = await ft_command(addresses=[addy]) if ft_response['hashes']: yield addy, ft_response['hashes'] else: - wasf_response = wasf_command(addresses=[addy]) + wasf_response = await wasf_command(addresses=[addy]) if wasf_response['states'][0]: yield addy, [] else: @@ -56,7 +57,7 @@ def iter_used_addresses( wasf_command.reset() -def get_bundles_from_transaction_hashes( +async def get_bundles_from_transaction_hashes( adapter, transaction_hashes, inclusion_states, @@ -70,13 +71,11 @@ def get_bundles_from_transaction_hashes( if not transaction_hashes: return [] - my_bundles = [] # type: List[Bundle] - # Sort transactions into tail and non-tail. tail_transaction_hashes = set() non_tail_bundle_hashes = set() - gt_response = GetTrytesCommand(adapter)(hashes=transaction_hashes) + gt_response = await GetTrytesCommand(adapter)(hashes=transaction_hashes) all_transactions = list(map( Transaction.from_tryte_string, gt_response['trytes'], @@ -92,9 +91,9 @@ def get_bundles_from_transaction_hashes( non_tail_bundle_hashes.add(txn.bundle_hash) if non_tail_bundle_hashes: - for txn in FindTransactionObjectsCommand(adapter=adapter)( + for txn in (await FindTransactionObjectsCommand(adapter=adapter)( bundles=list(non_tail_bundle_hashes), - )['transactions']: + ))['transactions']: if txn.is_tail: if txn.hash not in tail_transaction_hashes: all_transactions.append(txn) @@ -109,7 +108,7 @@ def get_bundles_from_transaction_hashes( # Attach inclusion states, if requested. if inclusion_states: - gli_response = GetLatestInclusionCommand(adapter)( + gli_response = await GetLatestInclusionCommand(adapter)( hashes=list(tail_transaction_hashes), ) @@ -117,17 +116,15 @@ def get_bundles_from_transaction_hashes( txn.is_confirmed = gli_response['states'].get(txn.hash) # Find the bundles for each transaction. - for txn in tail_transactions: - gb_response = GetBundlesCommand(adapter)(transactions=[txn.hash]) - txn_bundles = gb_response['bundles'] # type: List[Bundle] - - if inclusion_states: - for bundle in txn_bundles: - bundle.is_confirmed = txn.is_confirmed + txn_bundles = (await GetBundlesCommand(adapter)( + transactions=[txn.hash for txn in tail_transactions] + ))['bundles'] # type: List[Bundle] - my_bundles.extend(txn_bundles) + if inclusion_states: + for bundle, txn in zip(txn_bundles, tail_transactions): + bundle.is_confirmed = txn.is_confirmed return list(sorted( - my_bundles, + txn_bundles, key=lambda bundle_: bundle_.tail_transaction.timestamp, )) diff --git a/test/commands/extended/get_account_data_test.py b/test/commands/extended/get_account_data_test.py index 1a0e524..6b62372 100644 --- a/test/commands/extended/get_account_data_test.py +++ b/test/commands/extended/get_account_data_test.py @@ -8,14 +8,14 @@ from filters.test import BaseFilterTestCase from six import binary_type -from iota import Address, Bundle, Iota, TransactionHash -from iota.adapter import MockAdapter +from iota import Address, Bundle, Iota, AsyncIota, TransactionHash +from iota.adapter import MockAdapter, async_return from iota.commands.extended.get_account_data import GetAccountDataCommand, \ GetAccountDataRequestFilter from iota.crypto.types import Seed from iota.filters import Trytes from test import mock -from test import patch, MagicMock +from test import patch, MagicMock, async_test class GetAccountDataRequestFilterTestCase(BaseFilterTestCase): @@ -322,6 +322,18 @@ def test_fail_inclusion_states_wrong_type(self): }, ) +class AsyncIter: + """ + Class for mocking async generators. + + Used here to mock the return values of `iter_used_addresses`. + """ + def __init__(self, items): + self.items = items + + async def __aiter__(self): + for item in self.items: + yield item class GetAccountDataCommandTestCase(TestCase): # noinspection SpellCheckingInspection @@ -362,12 +374,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.get_account_data.GetAccountDataCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -382,12 +394,36 @@ def test_wireup(self): 'You found me!' ) - def test_happy_path(self): + @async_test + async def test_wireup(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.get_account_data.GetAccountDataCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.get_account_data() + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_happy_path(self): """ Loading account data for an account. """ # noinspection PyUnusedLocal - def mock_iter_used_addresses(adapter, seed, start, security_level): + async def mock_iter_used_addresses(adapter, seed, start, security_level): """ Mocks the ``iter_used_addresses`` function, so that we can simulate its functionality without actually connecting to the @@ -399,12 +435,12 @@ def mock_iter_used_addresses(adapter, seed, start, security_level): yield self.addy1, [self.hash1] yield self.addy2, [self.hash2] - mock_get_balances = mock.Mock(return_value={'balances': [42, 0]}) + mock_get_balances = mock.Mock(return_value=async_return({'balances': [42, 0]})) # Not particularly realistic, but good enough to prove that the # mocked function was invoked correctly. bundles = [Bundle(), Bundle()] - mock_get_bundles_from_transaction_hashes = mock.Mock(return_value=bundles) + mock_get_bundles_from_transaction_hashes = mock.Mock(return_value=async_return(bundles)) with mock.patch( 'iota.commands.extended.get_account_data.iter_used_addresses', @@ -418,7 +454,7 @@ def mock_iter_used_addresses(adapter, seed, start, security_level): 'iota.commands.core.get_balances.GetBalancesCommand._execute', mock_get_balances, ): - response = self.command(seed=Seed.random()) + response = await self.command(seed=Seed.random()) self.assertDictEqual( response, @@ -430,15 +466,16 @@ def mock_iter_used_addresses(adapter, seed, start, security_level): }, ) - def test_no_transactions(self): + @async_test + async def test_no_transactions(self): """ Loading account data for a seed that hasn't been used yet. """ with mock.patch( 'iota.commands.extended.get_account_data.iter_used_addresses', - mock.Mock(return_value=[]), + mock.Mock(return_value=AsyncIter([])), ): - response = self.command(seed=Seed.random()) + response = await self.command(seed=Seed.random()) self.assertDictEqual( response, @@ -450,20 +487,21 @@ def test_no_transactions(self): }, ) - def test_balance_is_found_for_address_without_transaction(self): + @async_test + async def test_balance_is_found_for_address_without_transaction(self): """ If an address has a balance, no transactions and was spent from, the balance should still be found and returned. """ with mock.patch( 'iota.commands.extended.get_account_data.iter_used_addresses', - mock.Mock(return_value=[(self.addy1, [])]), + mock.Mock(return_value=AsyncIter([(self.addy1, [])])), ): self.adapter.seed_response('getBalances', { 'balances': [42], }) - response = self.command(seed=Seed.random()) + response = await self.command(seed=Seed.random()) self.assertDictEqual( response, diff --git a/test/commands/extended/utils_test.py b/test/commands/extended/utils_test.py index 4ba6bfe..480c924 100644 --- a/test/commands/extended/utils_test.py +++ b/test/commands/extended/utils_test.py @@ -6,7 +6,7 @@ from iota.commands.extended.utils import iter_used_addresses from iota import MockAdapter from iota.crypto.types import Seed -from test import mock +from test import mock, async_test class IterUsedAddressesTestCase(TestCase): @@ -35,11 +35,13 @@ def seed_unused_address(self): 'states': [False], }) - def get_all_used_addresses(self, start=0): - return [address for address, _ + async def get_all_used_addresses(self, start=0): + # `iter_used_addresses` is an async generator, so we have to use `async for` + return [address async for address, _ in iter_used_addresses(self.adapter, self.seed, start)] - def test_first_address_is_not_used(self): + @async_test + async def test_first_address_is_not_used(self): """ The very first address is not used. No address is returned. """ @@ -50,7 +52,7 @@ def test_first_address_is_not_used(self): 'iota.crypto.addresses.AddressGenerator.create_iterator', self.mock_address_generator, ): - self.assertEqual([], self.get_all_used_addresses()) + self.assertEqual([], await self.get_all_used_addresses()) self.assertListEqual( self.adapter.requests, @@ -66,7 +68,8 @@ def test_first_address_is_not_used(self): ] ) - def test_transactions_are_considered_used(self): + @async_test + async def test_transactions_are_considered_used(self): """ An address with a transaction is considered used. """ @@ -82,7 +85,7 @@ def test_transactions_are_considered_used(self): 'iota.crypto.addresses.AddressGenerator.create_iterator', self.mock_address_generator, ): - self.assertEqual([self.address0], self.get_all_used_addresses()) + self.assertEqual([self.address0], await self.get_all_used_addresses()) self.assertListEqual( self.adapter.requests, @@ -102,7 +105,8 @@ def test_transactions_are_considered_used(self): ] ) - def test_spent_from_is_considered_used(self): + @async_test + async def test_spent_from_is_considered_used(self): """ An address that was spent from is considered used. """ @@ -121,7 +125,7 @@ def test_spent_from_is_considered_used(self): 'iota.crypto.addresses.AddressGenerator.create_iterator', self.mock_address_generator, ): - self.assertEqual([self.address0], self.get_all_used_addresses()) + self.assertEqual([self.address0], await self.get_all_used_addresses()) self.assertListEqual( self.adapter.requests, @@ -145,7 +149,8 @@ def test_spent_from_is_considered_used(self): ] ) - def test_start_parameter_is_given(self): + @async_test + async def test_start_parameter_is_given(self): """ The correct address is returned if a start parameter is given. """ @@ -162,7 +167,7 @@ def test_start_parameter_is_given(self): self.mock_address_generator, ): self.assertEqual([self.address1], - self.get_all_used_addresses(start=1)) + await self.get_all_used_addresses(start=1)) self.assertListEqual( self.adapter.requests, @@ -182,7 +187,8 @@ def test_start_parameter_is_given(self): ] ) - def test_multiple_addresses_return(self): + @async_test + async def test_multiple_addresses_return(self): """ A larger test that combines multiple cases and more than one address should be returned. @@ -212,7 +218,7 @@ def test_multiple_addresses_return(self): self.mock_address_generator, ): self.assertEqual([self.address0, self.address1], - self.get_all_used_addresses()) + await self.get_all_used_addresses()) self.assertListEqual( self.adapter.requests, @@ -239,3 +245,5 @@ def test_multiple_addresses_return(self): }, ] ) + +# TODO: add tests for `get_bundles_from_transaction_hashes` \ No newline at end of file From 6f3390453acff71d1e45fe0e276c1de10994d182 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Fri, 7 Feb 2020 15:51:00 +0100 Subject: [PATCH 13/69] Make `GetInputsCommand` async - Update tests for async --- iota/commands/extended/get_inputs.py | 6 +- test/commands/extended/get_inputs_test.py | 93 ++++++++++++++++------- 2 files changed, 67 insertions(+), 32 deletions(-) diff --git a/iota/commands/extended/get_inputs.py b/iota/commands/extended/get_inputs.py index 0349c0c..f21a46f 100644 --- a/iota/commands/extended/get_inputs.py +++ b/iota/commands/extended/get_inputs.py @@ -34,7 +34,7 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + async def _execute(self, request): stop = request['stop'] # type: Optional[int] seed = request['seed'] # type: Seed start = request['start'] # type: int @@ -43,7 +43,7 @@ def _execute(self, request): # Determine the addresses we will be scanning. if stop is None: - addresses = [addy for addy, _ in iter_used_addresses( + addresses = [addy async for addy, _ in iter_used_addresses( adapter=self.adapter, seed=seed, start=start, @@ -59,7 +59,7 @@ def _execute(self, request): if addresses: # Load balances for the addresses that we generated. - gb_response = GetBalancesCommand(self.adapter)(addresses=addresses) + gb_response = await GetBalancesCommand(self.adapter)(addresses=addresses) else: gb_response = {'balances': []} diff --git a/test/commands/extended/get_inputs_test.py b/test/commands/extended/get_inputs_test.py index 439a687..6ac6697 100644 --- a/test/commands/extended/get_inputs_test.py +++ b/test/commands/extended/get_inputs_test.py @@ -7,14 +7,14 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Address, BadApiResponse, Iota, TransactionHash -from iota.adapter import MockAdapter +from iota import Address, BadApiResponse, Iota, AsyncIota, TransactionHash +from iota.adapter import MockAdapter, async_return from iota.commands.extended.get_inputs import GetInputsCommand, GetInputsRequestFilter from iota.crypto.addresses import AddressGenerator from iota.crypto.types import Seed from iota.filters import Trytes from test import mock -from test import patch, MagicMock +from test import patch, MagicMock, async_test class GetInputsRequestFilterTestCase(BaseFilterTestCase): @@ -441,12 +441,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.get_inputs.GetInputsCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -461,7 +461,31 @@ def test_wireup(self): 'You found me!' ) - def test_stop_threshold_met(self): + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.get_inputs.GetInputsCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.get_inputs() + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_stop_threshold_met(self): """ ``stop`` provided, balance meets ``threshold``. """ @@ -478,7 +502,7 @@ def test_stop_threshold_met(self): 'iota.crypto.addresses.AddressGenerator.get_addresses', mock_address_generator, ): - response = self.command( + response = await self.command( seed = Seed.random(), stop = 2, threshold = 71, @@ -501,7 +525,8 @@ def test_stop_threshold_met(self): self.assertEqual(input1.balance, 29) self.assertEqual(input1.key_index, 1) - def test_stop_threshold_not_met(self): + @async_test + async def test_stop_threshold_not_met(self): """ ``stop`` provided, balance does not meet ``threshold``. """ @@ -519,13 +544,14 @@ def test_stop_threshold_not_met(self): mock_address_generator, ): with self.assertRaises(BadApiResponse): - self.command( + await self.command( seed = Seed.random(), stop = 2, threshold = 72, ) - def test_stop_threshold_zero(self): + @async_test + async def test_stop_threshold_zero(self): """ ``stop`` provided, ``threshold`` is 0. """ @@ -543,7 +569,7 @@ def test_stop_threshold_zero(self): 'iota.crypto.addresses.AddressGenerator.get_addresses', mock_address_generator, ): - response = self.command( + response = await self.command( seed = Seed.random(), stop = 2, threshold = 0, @@ -560,7 +586,8 @@ def test_stop_threshold_zero(self): self.assertEqual(input0.balance, 1) self.assertEqual(input0.key_index, 1) - def test_stop_no_threshold(self): + @async_test + async def test_stop_no_threshold(self): """ ``stop`` provided, no ``threshold``. """ @@ -577,7 +604,7 @@ def test_stop_no_threshold(self): 'iota.crypto.addresses.AddressGenerator.get_addresses', mock_address_generator, ): - response = self.command( + response = await self.command( seed = Seed.random(), start = 0, stop = 2, @@ -600,7 +627,8 @@ def test_stop_no_threshold(self): self.assertEqual(input1.balance, 29) self.assertEqual(input1.key_index, 1) - def test_no_stop_threshold_met(self): + @async_test + async def test_no_stop_threshold_met(self): """ No ``stop`` provided, balance meets ``threshold``. """ @@ -652,7 +680,7 @@ def mock_address_generator(ag, start, step=1): 'iota.crypto.addresses.AddressGenerator.create_iterator', mock_address_generator, ): - response = self.command( + response = await self.command( seed = Seed.random(), threshold = 71, ) @@ -674,7 +702,8 @@ def mock_address_generator(ag, start, step=1): self.assertEqual(input1.balance, 29) self.assertEqual(input1.key_index, 1) - def test_no_stop_threshold_not_met(self): + @async_test + async def test_no_stop_threshold_not_met(self): """ No ``stop`` provided, balance does not meet ``threshold``. """ @@ -696,12 +725,13 @@ def mock_address_generator(ag, start, step=1): mock_address_generator, ): with self.assertRaises(BadApiResponse): - self.command( + await self.command( seed = Seed.random(), threshold = 72, ) - def test_no_stop_threshold_zero(self): + @async_test + async def test_no_stop_threshold_zero(self): """ No ``stop`` provided, ``threshold`` is 0. """ @@ -755,7 +785,7 @@ def mock_address_generator(ag, start, step=1): 'iota.crypto.addresses.AddressGenerator.create_iterator', mock_address_generator, ): - response = self.command( + response = await self.command( seed = Seed.random(), threshold = 0, ) @@ -771,7 +801,8 @@ def mock_address_generator(ag, start, step=1): self.assertEqual(input0.balance, 1) self.assertEqual(input0.key_index, 1) - def test_no_stop_no_threshold(self): + @async_test + async def test_no_stop_no_threshold(self): """ No ``stop`` provided, no ``threshold``. """ @@ -823,7 +854,7 @@ def mock_address_generator(ag, start, step=1): 'iota.crypto.addresses.AddressGenerator.create_iterator', mock_address_generator, ): - response = self.command( + response = await self.command( seed = Seed.random(), ) @@ -844,7 +875,8 @@ def mock_address_generator(ag, start, step=1): self.assertEqual(input1.balance, 29) self.assertEqual(input1.key_index, 1) - def test_start(self): + @async_test + async def test_start(self): """ Using ``start`` to offset the key range. """ @@ -888,7 +920,7 @@ def mock_address_generator(ag, start, step=1): 'iota.crypto.addresses.AddressGenerator.create_iterator', mock_address_generator, ): - response = self.command( + response = await self.command( seed = Seed.random(), start = 1, ) @@ -903,7 +935,8 @@ def mock_address_generator(ag, start, step=1): self.assertEqual(input0.balance, 86) self.assertEqual(input0.key_index, 1) - def test_start_stop(self): + @async_test + async def test_start_stop(self): """ Using ``start`` and ``stop`` at once. Checking if correct number of addresses is returned. Must be stop - start @@ -927,7 +960,7 @@ def mock_address_generator(ag, start, step=1): 'iota.crypto.addresses.AddressGenerator.create_iterator', mock_address_generator, ): - response = self.command( + response = await self.command( seed = Seed.random(), start = 1, stop = 3, @@ -949,7 +982,8 @@ def mock_address_generator(ag, start, step=1): self.assertEqual(input1.key_index, 2) - def test_security_level_1_no_stop(self): + @async_test + async def test_security_level_1_no_stop(self): """ Testing GetInputsCoommand: - with security_level = 1 (non default) @@ -984,7 +1018,7 @@ def test_security_level_1_no_stop(self): 'balances': [86], }) - response = GetInputsCommand(self.adapter)( + response = await GetInputsCommand(self.adapter)( seed=seed, securityLevel=1, ) @@ -998,7 +1032,8 @@ def test_security_level_1_no_stop(self): self.assertEqual(input0.balance, 86) self.assertEqual(input0.key_index, 0) - def test_security_level_1_with_stop(self): + @async_test + async def test_security_level_1_with_stop(self): """ Testing GetInputsCoommand: - with security_level = 1 (non default) @@ -1028,7 +1063,7 @@ def test_security_level_1_with_stop(self): 'hashes': [], }) - response = GetInputsCommand(self.adapter)( + response = await GetInputsCommand(self.adapter)( seed=seed, securityLevel=1, stop=1, # <<<<< here From 59fe3e9ad00b224c27ca186fa8e2040740249664 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 10 Feb 2020 10:17:08 +0100 Subject: [PATCH 14/69] Make `GetLatestInclusionCommand` async - Update tests for async --- .../commands/extended/get_latest_inclusion.py | 6 +-- .../extended/get_latest_inclusion_test.py | 38 +++++++++++++++---- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/iota/commands/extended/get_latest_inclusion.py b/iota/commands/extended/get_latest_inclusion.py index cf96bbf..eaf7c17 100644 --- a/iota/commands/extended/get_latest_inclusion.py +++ b/iota/commands/extended/get_latest_inclusion.py @@ -31,12 +31,12 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + async def _execute(self, request): hashes = request['hashes'] # type: List[TransactionHash] - gni_response = GetNodeInfoCommand(self.adapter)() + gni_response = await GetNodeInfoCommand(self.adapter)() - gis_response = GetInclusionStatesCommand(self.adapter)( + gis_response = await GetInclusionStatesCommand(self.adapter)( transactions=hashes, tips=[gni_response['latestSolidSubtangleMilestone']], ) diff --git a/test/commands/extended/get_latest_inclusion_test.py b/test/commands/extended/get_latest_inclusion_test.py index 4680693..843d6e2 100644 --- a/test/commands/extended/get_latest_inclusion_test.py +++ b/test/commands/extended/get_latest_inclusion_test.py @@ -7,12 +7,12 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota, TransactionHash, TryteString -from iota.adapter import MockAdapter +from iota import Iota, AsyncIota, TransactionHash, TryteString +from iota.adapter import MockAdapter, async_return from iota.commands.extended.get_latest_inclusion import \ GetLatestInclusionCommand from iota.filters import Trytes -from test import patch, MagicMock +from test import patch, MagicMock, async_test class GetLatestInclusionRequestFilterTestCase(BaseFilterTestCase): @@ -204,12 +204,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -224,7 +224,31 @@ def test_wireup(self): 'You found me!' ) - def test_happy_path(self): + @async_test + async def test_wireup(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.get_latest_inclusion('hashes') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_happy_path(self): """ Successfully requesting latest inclusion state. """ @@ -239,7 +263,7 @@ def test_happy_path(self): 'states': [True, False], }) - response = self.command(hashes=[self.hash1, self.hash2]) + response = await self.command(hashes=[self.hash1, self.hash2]) self.assertDictEqual( response, From 61e4911a1af04fef82b68e0a8961f362b825ad1f Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 10 Feb 2020 11:23:30 +0100 Subject: [PATCH 15/69] Make `GetNewAddressesCommand` async - Update tests for async --- iota/commands/extended/get_new_addresses.py | 27 ++++--- .../extended/get_new_addresses_test.py | 81 ++++++++++++++----- 2 files changed, 74 insertions(+), 34 deletions(-) diff --git a/iota/commands/extended/get_new_addresses.py b/iota/commands/extended/get_new_addresses.py index 65f3c57..49141b2 100644 --- a/iota/commands/extended/get_new_addresses.py +++ b/iota/commands/extended/get_new_addresses.py @@ -14,6 +14,7 @@ from iota.crypto.addresses import AddressGenerator from iota.crypto.types import Seed from iota.filters import SecurityLevel, Trytes +import asyncio __all__ = [ 'GetNewAddressesCommand', @@ -34,7 +35,7 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + async def _execute(self, request): checksum = request['checksum'] # type: bool count = request['count'] # type: Optional[int] index = request['index'] # type: int @@ -43,7 +44,7 @@ def _execute(self, request): return { 'addresses': - self._find_addresses( + await self._find_addresses( seed, index, count, @@ -52,7 +53,7 @@ def _execute(self, request): ), } - def _find_addresses(self, seed, index, count, security_level, checksum): + async def _find_addresses(self, seed, index, count, security_level, checksum): # type: (Seed, int, Optional[int], int, bool) -> List[Address] """ Find addresses matching the command parameters. @@ -64,16 +65,18 @@ def _find_addresses(self, seed, index, count, security_level, checksum): for addy in generator.create_iterator(start=index): # We use addy.address here because the commands do # not work on an address with a checksum - response = WereAddressesSpentFromCommand(self.adapter)( - addresses=[addy.address], + # Execute two checks concurrently + responses = await asyncio.gather( + WereAddressesSpentFromCommand(self.adapter)( + addresses=[addy.address], + ), + FindTransactionsCommand(self.adapter)( + addresses=[addy.address], + ), ) - if response['states'][0]: - continue - - response = FindTransactionsCommand(self.adapter)( - addresses=[addy.address], - ) - if response.get('hashes'): + # responses[0] -> was it spent from? + # responses[1] -> any transaction found? + if responses[0]['states'][0] or responses[1].get('hashes'): continue return [addy] diff --git a/test/commands/extended/get_new_addresses_test.py b/test/commands/extended/get_new_addresses_test.py index bf14b0a..ed6c81a 100644 --- a/test/commands/extended/get_new_addresses_test.py +++ b/test/commands/extended/get_new_addresses_test.py @@ -7,13 +7,13 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Address, Iota -from iota.adapter import MockAdapter +from iota import Address, Iota, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.extended.get_new_addresses import GetNewAddressesCommand from iota.crypto.addresses import AddressGenerator from iota.crypto.types import Seed from iota.filters import Trytes -from test import patch, MagicMock +from test import patch, MagicMock, async_test class GetNewAddressesRequestFilterTestCase(BaseFilterTestCase): @@ -365,12 +365,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.get_new_addresses.GetNewAddressesCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -385,13 +385,37 @@ def test_wireup(self): 'You found me!' ) - def test_get_addresses_offline(self): + @async_test + async def test_wireup(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.get_new_addresses.GetNewAddressesCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.get_new_addresses('hashes') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_get_addresses_offline(self): """ Generate addresses in offline mode (without filtering used addresses). """ response =\ - self.command( + await self.command( count = 2, index = 0, seed = self.seed, @@ -405,12 +429,13 @@ def test_get_addresses_offline(self): # No API requests were made. self.assertListEqual(self.adapter.requests, []) - def test_security_level(self): + @async_test + async def test_security_level(self): """ Generating addresses with a different security level. """ response =\ - self.command( + await self.command( count = 2, index = 0, securityLevel = 1, @@ -437,7 +462,8 @@ def test_security_level(self): }, ) - def test_get_addresses_online_already_spent_from(self): + @async_test + async def test_get_addresses_online_already_spent_from(self): """ Generate address in online mode (filtering used addresses). Test if an address that was already spent from will not be returned. @@ -449,6 +475,11 @@ def test_get_addresses_online_already_spent_from(self): 'states': [True], }) + self.adapter.seed_response('findTransactions', { + 'duration': 1, + 'hashes': [], + }) + self.adapter.seed_response('wereAddressesSpentFrom', { 'states': [False], }) @@ -459,7 +490,7 @@ def test_get_addresses_online_already_spent_from(self): }) response =\ - self.command( + await self.command( # If ``count`` is missing or ``None``, the command will operate # in online mode. # count = None, @@ -472,18 +503,22 @@ def test_get_addresses_online_already_spent_from(self): # it skipped that one. self.assertDictEqual(response, {'addresses': [self.addy_2]}) - self.assertListEqual( + # Due to running WereAddressesSpentFromCommand and FindTransactionsCommand + # with asyncio.gather, we can't infer their execution order. Therefore, + # we need to assert if the contents of the two lists match by value, + # regardless of their order: + # https://docs.python.org/3.5/library/unittest.html#unittest.TestCase.assertCountEqual + self.assertCountEqual( self.adapter.requests, - - # The command issued a `wereAddressesSpentFrom` API request to - # check if the first address was used. Then it called `wereAddressesSpentFrom` - # and `findTransactions` to verify that the second address was - # indeed not used. [ { 'command': 'wereAddressesSpentFrom', 'addresses': [self.addy_1], }, + { + 'command': 'findTransactions', + 'addresses': [self.addy_1], + }, { 'command': 'wereAddressesSpentFrom', 'addresses': [self.addy_2], @@ -495,7 +530,8 @@ def test_get_addresses_online_already_spent_from(self): ], ) - def test_get_addresses_online_has_transaction(self): + @async_test + async def test_get_addresses_online_has_transaction(self): """ Generate address in online mode (filtering used addresses). Test if an address that has a transaction will not be returned. @@ -521,13 +557,13 @@ def test_get_addresses_online_has_transaction(self): 'hashes': [], }) - response = self.command(index=0, seed=self.seed) + response = await self.command(index=0, seed=self.seed) # The command determined that ``self.addy1`` was already used, so # it skipped that one. self.assertDictEqual(response, {'addresses': [self.addy_2]}) - self.assertListEqual( + self.assertCountEqual( self.adapter.requests, [ { @@ -549,12 +585,13 @@ def test_get_addresses_online_has_transaction(self): ], ) - def test_new_address_checksum(self): + @async_test + async def test_new_address_checksum(self): """ Generate address with a checksum. """ response =\ - self.command( + await self.command( checksum = True, count = 1, index = 0, From 1793754de447f279f10204fe72f8dc9fedd5e812 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 10 Feb 2020 11:30:36 +0100 Subject: [PATCH 16/69] Make `GetTransactionObjectsCommand` async - Update tests for async --- .../extended/get_transaction_objects.py | 4 +- .../extended/get_account_data_test.py | 2 +- .../extended/get_latest_inclusion_test.py | 2 +- .../extended/get_new_addresses_test.py | 2 +- .../extended/get_transaction_objects_test.py | 46 +++++++++++++++---- 5 files changed, 41 insertions(+), 15 deletions(-) diff --git a/iota/commands/extended/get_transaction_objects.py b/iota/commands/extended/get_transaction_objects.py index 4f47564..cc4070c 100644 --- a/iota/commands/extended/get_transaction_objects.py +++ b/iota/commands/extended/get_transaction_objects.py @@ -30,13 +30,13 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + async def _execute(self, request): hashes = request\ .get('hashes') # type: Iterable[TransactionHash] transactions = [] if hashes: - gt_response = GetTrytesCommand(adapter=self.adapter)(hashes=hashes) + gt_response = await GetTrytesCommand(adapter=self.adapter)(hashes=hashes) transactions = list(map( Transaction.from_tryte_string, diff --git a/test/commands/extended/get_account_data_test.py b/test/commands/extended/get_account_data_test.py index 6b62372..0d53adf 100644 --- a/test/commands/extended/get_account_data_test.py +++ b/test/commands/extended/get_account_data_test.py @@ -395,7 +395,7 @@ def test_wireup(self): ) @async_test - async def test_wireup(self): + async def test_wireup_async(self): """ Verify that the command is wired up correctly. (async) diff --git a/test/commands/extended/get_latest_inclusion_test.py b/test/commands/extended/get_latest_inclusion_test.py index 843d6e2..611f79f 100644 --- a/test/commands/extended/get_latest_inclusion_test.py +++ b/test/commands/extended/get_latest_inclusion_test.py @@ -225,7 +225,7 @@ def test_wireup(self): ) @async_test - async def test_wireup(self): + async def test_wireup_async(self): """ Verify that the command is wired up correctly. (async) diff --git a/test/commands/extended/get_new_addresses_test.py b/test/commands/extended/get_new_addresses_test.py index ed6c81a..51bc514 100644 --- a/test/commands/extended/get_new_addresses_test.py +++ b/test/commands/extended/get_new_addresses_test.py @@ -386,7 +386,7 @@ def test_wireup(self): ) @async_test - async def test_wireup(self): + async def test_wireup_async(self): """ Verify that the command is wired up correctly. (async) diff --git a/test/commands/extended/get_transaction_objects_test.py b/test/commands/extended/get_transaction_objects_test.py index 552889a..f283957 100644 --- a/test/commands/extended/get_transaction_objects_test.py +++ b/test/commands/extended/get_transaction_objects_test.py @@ -4,9 +4,10 @@ from unittest import TestCase -from iota import Iota, MockAdapter, Transaction +from iota import Iota, AsyncIota, MockAdapter, Transaction from iota.commands.extended import GetTransactionObjectsCommand -from test import patch, MagicMock, mock +from iota.adapter import async_return +from test import patch, MagicMock, mock, async_test class GetTransactionObjectsCommandTestCase(TestCase): @@ -69,12 +70,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.get_transaction_objects.GetTransactionObjectsCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -89,31 +90,56 @@ def test_wireup(self): 'You found me!' ) - def test_transaction_found(self): + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.get_transaction_objects.GetTransactionObjectsCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.get_transaction_objects('hashes') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_transaction_found(self): """ A transaction is found with the inputs. A transaction object is returned """ with mock.patch( 'iota.commands.core.get_trytes.GetTrytesCommand._execute', - mock.Mock(return_value={'trytes': [self.trytes, ]}), + mock.Mock(return_value=async_return({'trytes': [self.trytes, ]})), ): - response = self.command(hashes=[self.transaction_hash]) + response = await self.command(hashes=[self.transaction_hash]) self.assertEqual(len(response['transactions']), 1) transaction = response['transactions'][0] self.assertIsInstance(transaction, Transaction) self.assertEqual(transaction.hash, self.transaction_hash) - def test_no_transactions_fround(self): + @async_test + async def test_no_transactions_fround(self): """ No transaction is found with the inputs. An empty list is returned """ with mock.patch( 'iota.commands.core.get_trytes.GetTrytesCommand._execute', - mock.Mock(return_value={'trytes': []}), + mock.Mock(return_value=async_return({'trytes': []})), ): - response = self.command(hashes=[self.transaction_hash]) + response = await self.command(hashes=[self.transaction_hash]) self.assertDictEqual( response, From 93ef6ff1b9846e32aa7aa53c81f1da891242371b Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 10 Feb 2020 12:19:46 +0100 Subject: [PATCH 17/69] Make `GetTransfersCommand` async - Update tests for async --- iota/commands/extended/get_transfers.py | 12 +-- test/commands/extended/get_transfers_test.py | 79 +++++++++++++------- 2 files changed, 61 insertions(+), 30 deletions(-) diff --git a/iota/commands/extended/get_transfers.py b/iota/commands/extended/get_transfers.py index a8e5e9a..d7b1d70 100644 --- a/iota/commands/extended/get_transfers.py +++ b/iota/commands/extended/get_transfers.py @@ -34,7 +34,7 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + async def _execute(self, request): inclusion_states = request['inclusionStates'] # type: bool seed = request['seed'] # type: Seed start = request['start'] # type: int @@ -44,12 +44,14 @@ def _execute(self, request): # transaction hashes. if stop is None: my_hashes = list(chain(*( - hashes - for _, hashes in iter_used_addresses(self.adapter, seed, start) + [ + hashes async for _, hashes in + iter_used_addresses(self.adapter, seed, start) + ] ))) else: ft_response = \ - FindTransactionsCommand(self.adapter)( + await FindTransactionsCommand(self.adapter)( addresses= AddressGenerator(seed).get_addresses(start, stop - start), ) @@ -58,7 +60,7 @@ def _execute(self, request): return { 'bundles': - get_bundles_from_transaction_hashes( + await get_bundles_from_transaction_hashes( adapter=self.adapter, transaction_hashes=my_hashes, inclusion_states=inclusion_states, diff --git a/test/commands/extended/get_transfers_test.py b/test/commands/extended/get_transfers_test.py index 7373daf..896ec0f 100644 --- a/test/commands/extended/get_transfers_test.py +++ b/test/commands/extended/get_transfers_test.py @@ -8,14 +8,14 @@ from filters.test import BaseFilterTestCase from six import binary_type -from iota import Address, Bundle, Iota, Tag, Transaction, TryteString -from iota.adapter import MockAdapter +from iota import Address, Bundle, Iota, AsyncIota, Tag, Transaction, TryteString +from iota.adapter import MockAdapter, async_return from iota.commands.extended.get_transfers import GetTransfersCommand, \ GetTransfersRequestFilter from iota.crypto.types import Seed from iota.filters import Trytes from test import mock -from test import patch, MagicMock +from test import patch, MagicMock, async_test class GetTransfersRequestFilterTestCase(BaseFilterTestCase): @@ -339,12 +339,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.get_transfers.GetTransfersCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -358,7 +358,32 @@ def test_wireup(self): response, 'You found me!' ) - def test_full_scan(self): + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.get_transfers.GetTransfersCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.get_transfers() + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_full_scan(self): """ Scanning the Tangle for all transfers. """ @@ -437,9 +462,9 @@ def create_generator(ag, start, step=1): ]) mock_get_bundles =\ - mock.Mock(return_value={ + mock.Mock(return_value=async_return({ 'bundles': [bundle], - }) + })) with mock.patch( 'iota.crypto.addresses.AddressGenerator.create_iterator', @@ -449,7 +474,7 @@ def create_generator(ag, start, step=1): 'iota.commands.extended.get_bundles.GetBundlesCommand._execute', mock_get_bundles, ): - response = self.command(seed=Seed.random()) + response = await self.command(seed=Seed.random()) self.assertDictEqual( response, @@ -459,7 +484,8 @@ def create_generator(ag, start, step=1): }, ) - def test_no_transactions(self): + @async_test + async def test_no_transactions(self): """ There are no transactions for the specified seed. """ @@ -491,11 +517,12 @@ def create_generator(ag, start, step=1): 'iota.crypto.addresses.AddressGenerator.create_iterator', create_generator, ): - response = self.command(seed=Seed.random()) + response = await self.command(seed=Seed.random()) self.assertDictEqual(response, {'bundles': []}) - def test_start(self): + @async_test + async def test_start(self): """ Scanning the Tangle for all transfers, with start index. """ @@ -568,9 +595,9 @@ def create_generator(ag, start, step=1): ) ]) - mock_get_bundles = mock.Mock(return_value={ + mock_get_bundles = mock.Mock(return_value=async_return({ 'bundles': [bundle], - }) + })) with mock.patch( 'iota.crypto.addresses.AddressGenerator.create_iterator', @@ -580,7 +607,7 @@ def create_generator(ag, start, step=1): 'iota.commands.extended.get_bundles.GetBundlesCommand._execute', mock_get_bundles, ): - response = self.command(seed=Seed.random(), start=1) + response = await self.command(seed=Seed.random(), start=1) self.assertDictEqual( response, @@ -590,7 +617,8 @@ def create_generator(ag, start, step=1): }, ) - def test_stop(self): + @async_test + async def test_stop(self): """ Scanning the Tangle for all transfers, with stop index. """ @@ -646,9 +674,9 @@ def create_generator(ag, start, step=1): ) ]) - mock_get_bundles = mock.Mock(return_value={ + mock_get_bundles = mock.Mock(return_value=async_return({ 'bundles': [bundle], - }) + })) with mock.patch( 'iota.crypto.addresses.AddressGenerator.create_iterator', @@ -658,7 +686,7 @@ def create_generator(ag, start, step=1): 'iota.commands.extended.get_bundles.GetBundlesCommand._execute', mock_get_bundles, ): - response = self.command(seed=Seed.random(), stop=1) + response = await self.command(seed=Seed.random(), stop=1) self.assertDictEqual( response, @@ -668,7 +696,8 @@ def create_generator(ag, start, step=1): }, ) - def test_get_inclusion_states(self): + @async_test + async def test_get_inclusion_states(self): """ Fetching inclusion states with transactions. """ @@ -748,15 +777,15 @@ def create_generator(ag, start, step=1): transaction = Transaction.from_tryte_string(transaction_trytes) - mock_get_bundles = mock.Mock(return_value={ + mock_get_bundles = mock.Mock(return_value=async_return({ 'bundles': [Bundle([transaction])], - }) + })) - mock_get_latest_inclusion = mock.Mock(return_value={ + mock_get_latest_inclusion = mock.Mock(return_value=async_return({ 'states': { transaction.hash: True, }, - }) + })) with mock.patch( 'iota.crypto.addresses.AddressGenerator.create_iterator', @@ -770,7 +799,7 @@ def create_generator(ag, start, step=1): 'iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand._execute', mock_get_latest_inclusion, ): - response = self.command( + response = await self.command( seed = Seed.random(), inclusionStates = True, From 8640b2fa7abc91849ebdf69ba58614ec93f861e0 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 10 Feb 2020 13:07:24 +0100 Subject: [PATCH 18/69] Remove `Helpers` class from api `Helpers` added `is_promotable` method (wrapper for checkConsistency) to the api class, which is now implemented as a standalone API command, see `IsPromotableCommand`. --- iota/api.py | 2 -- iota/commands/extended/helpers.py | 28 ---------------- test/commands/extended/helpers_test.py | 45 -------------------------- 3 files changed, 75 deletions(-) delete mode 100644 iota/commands/extended/helpers.py delete mode 100644 test/commands/extended/helpers_test.py diff --git a/iota/api.py b/iota/api.py index ee2ff6f..808e62d 100644 --- a/iota/api.py +++ b/iota/api.py @@ -8,7 +8,6 @@ TransactionHash, TransactionTrytes, TryteString, TrytesCompatible from iota.adapter import BaseAdapter, resolve_adapter from iota.commands import BaseCommand, CustomCommand, core, extended -from iota.commands.extended.helpers import Helpers from iota.crypto.addresses import AddressGenerator from iota.crypto.types import Seed import asyncio @@ -1085,7 +1084,6 @@ def __init__(self, adapter, seed=None, devnet=False, local_pow=False): super(AsyncIota, self).__init__(adapter, devnet, local_pow) self.seed = Seed(seed) if seed else Seed.random() - self.helpers = Helpers(self) async def broadcast_and_store(self, trytes): # type: (Iterable[TransactionTrytes]) -> dict diff --git a/iota/commands/extended/helpers.py b/iota/commands/extended/helpers.py deleted file mode 100644 index 6f9a5a7..0000000 --- a/iota/commands/extended/helpers.py +++ /dev/null @@ -1,28 +0,0 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - -from iota.transaction.types import TransactionHash - - -class Helpers(object): - """ - Adds additional helper functions that aren't part of the core or - extended API. - - See https://github.com/iotaledger/iota.py/pull/124 for more - context. - """ - - def __init__(self, api): - self.api = api - - def is_promotable(self, tail): - # type: (TransactionHash) -> bool - """ - Determines if a tail transaction is promotable. - - :param tail: - Transaction hash. Must be a tail transaction. - """ - return self.api.check_consistency(tails=[tail])['state'] diff --git a/test/commands/extended/helpers_test.py b/test/commands/extended/helpers_test.py deleted file mode 100644 index 65059ab..0000000 --- a/test/commands/extended/helpers_test.py +++ /dev/null @@ -1,45 +0,0 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - -from unittest import TestCase - -from iota import Iota, TransactionHash -from iota.adapter import MockAdapter - - -class HelpersTestCase(TestCase): - def setUp(self): - super(HelpersTestCase, self).setUp() - - self.api = api = Iota('mock://') - self.api.adapter = MockAdapter() - - # noinspection SpellCheckingInspection - self.transaction = ( - 'TESTVALUE9DONTUSEINPRODUCTION99999KPZOTR' - 'VDB9GZDJGZSSDCBIX9QOK9PAV9RMDBGDXLDTIZTWQ' - ) - - def test_positive_is_promotable(self): - """ - Transaction is promotable - """ - - self.api.adapter.seed_response('checkConsistency', { - 'state': True, - }) - - self.assertTrue(self.api.helpers.is_promotable(tail=self.transaction)) - - def test_negative_is_promotable(self): - """ - Transaction is not promotable - """ - - self.api.adapter.seed_response('checkConsistency', { - 'state': False, - 'info': 'Inconsistent state', - }) - - self.assertFalse(self.api.helpers.is_promotable(tail=self.transaction)) From d696b96947a0a635ea92d2f59663f00a22b374f4 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 10 Feb 2020 13:19:29 +0100 Subject: [PATCH 19/69] Make `IsPromotableCommand` async - Update tests for async --- iota/commands/extended/is_promotable.py | 6 +-- test/commands/extended/is_promotable_test.py | 48 +++++++++++++++----- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/iota/commands/extended/is_promotable.py b/iota/commands/extended/is_promotable.py index cda9769..4a623d2 100644 --- a/iota/commands/extended/is_promotable.py +++ b/iota/commands/extended/is_promotable.py @@ -51,7 +51,7 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + async def _execute(self, request): tails = request['tails'] # First, check consistency @@ -59,7 +59,7 @@ def _execute(self, request): # - The node isn't missing the transaction's branch or trunk transactions # - The transaction's bundle is valid # - The transaction's branch and trunk transactions are valid - cc_response = CheckConsistencyCommand(self.adapter)( + cc_response = await CheckConsistencyCommand(self.adapter)( tails=tails, ) @@ -72,7 +72,7 @@ def _execute(self, request): transactions = [ Transaction.from_tryte_string(x) for x in - GetTrytesCommand(self.adapter)(hashes=tails)['trytes'] + (await GetTrytesCommand(self.adapter)(hashes=tails))['trytes'] ] response = { diff --git a/test/commands/extended/is_promotable_test.py b/test/commands/extended/is_promotable_test.py index cc1506a..f0c7095 100644 --- a/test/commands/extended/is_promotable_test.py +++ b/test/commands/extended/is_promotable_test.py @@ -8,13 +8,13 @@ from filters.test import BaseFilterTestCase from iota import Iota, TransactionHash, TryteString, TransactionTrytes, \ - Transaction -from iota.adapter import MockAdapter + Transaction, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.extended.is_promotable import IsPromotableCommand, \ get_current_ms, is_within_depth, MILESTONE_INTERVAL, ONE_WAY_DELAY from iota.filters import Trytes from test import mock -from test import patch, MagicMock +from test import patch, MagicMock, async_test class IsPromotableRequestFilterTestCase(BaseFilterTestCase): filter_type = IsPromotableCommand(MockAdapter()).get_request_filter @@ -290,12 +290,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.is_promotable.IsPromotableCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -310,7 +310,31 @@ def test_wireup(self): 'You found me!' ) - def test_happy_path(self): + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.is_promotable.IsPromotableCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.is_promotable('tails') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_happy_path(self): """ Successfully checking promotability. """ @@ -324,7 +348,7 @@ def test_happy_path(self): with mock.patch('iota.commands.extended.is_promotable.get_current_ms', mock.MagicMock(return_value=self.valid_now)): - response = self.command(tails=[self.hash1, self.hash2]) + response = await self.command(tails=[self.hash1, self.hash2]) self.assertDictEqual( response, @@ -334,7 +358,8 @@ def test_happy_path(self): } ) - def test_not_consistent(self): + @async_test + async def test_not_consistent(self): """ One of the tails is not consistent. """ @@ -347,7 +372,7 @@ def test_not_consistent(self): # No need for mokcing `getTrytes` becasue we should not # reach that part - response = self.command(tails=[self.hash1, self.hash2]) + response = await self.command(tails=[self.hash1, self.hash2]) self.assertDictEqual( response, @@ -358,7 +383,8 @@ def test_not_consistent(self): } ) - def test_one_timestamp_invalid(self): + @async_test + async def test_one_timestamp_invalid(self): """ Test invalid timestamp in one of the transactions. """ @@ -378,7 +404,7 @@ def test_one_timestamp_invalid(self): # Here we don`t mock get_current_ms. # Tx 1 will have updated, passing timestamp. # Tx 2 has the old one, so should fail. - response = self.command(tails=[self.hash1, self.hash2]) + response = await self.command(tails=[self.hash1, self.hash2]) self.assertDictEqual( response, From c0bbcad0d1729604556dbdc07755d860114ae05b Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 10 Feb 2020 13:36:09 +0100 Subject: [PATCH 20/69] Make `IsReattachableCommand` async - Update tests for async --- iota/commands/extended/is_reattachable.py | 8 ++--- .../commands/extended/is_reattachable_test.py | 33 ++++++++++++++++--- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/iota/commands/extended/is_reattachable.py b/iota/commands/extended/is_reattachable.py index 3b5e83a..13aeeb9 100644 --- a/iota/commands/extended/is_reattachable.py +++ b/iota/commands/extended/is_reattachable.py @@ -29,13 +29,13 @@ def get_request_filter(self): def get_response_filter(self): return IsReattachableResponseFilter() - def _execute(self, request): + async def _execute(self, request): addresses = request['addresses'] # type: List[Address] # fetch full transaction objects - transactions = FindTransactionObjectsCommand(adapter=self.adapter)( + transactions = (await FindTransactionObjectsCommand(adapter=self.adapter)( addresses=addresses, - )['transactions'] + ))['transactions'] # Map and filter transactions which have zero value. # If multiple transactions for the same address are returned, @@ -52,7 +52,7 @@ def _execute(self, request): } # Fetch inclusion states. - inclusion_states = GetLatestInclusionCommand(adapter=self.adapter)( + inclusion_states = await GetLatestInclusionCommand(adapter=self.adapter)( hashes=list(transaction_map.values()), ) inclusion_states = inclusion_states['states'] diff --git a/test/commands/extended/is_reattachable_test.py b/test/commands/extended/is_reattachable_test.py index 2800b06..641f83f 100644 --- a/test/commands/extended/is_reattachable_test.py +++ b/test/commands/extended/is_reattachable_test.py @@ -8,10 +8,10 @@ from filters.test import BaseFilterTestCase from six import text_type -from iota import Address, Iota -from iota.adapter import MockAdapter +from iota import Address, Iota, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.extended.is_reattachable import IsReattachableCommand -from test import patch, MagicMock +from test import patch, MagicMock, async_test class IsReattachableRequestFilterTestCase(BaseFilterTestCase): @@ -199,12 +199,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.is_reattachable.IsReattachableCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -218,3 +218,26 @@ def test_wireup(self): response, 'You found me!' ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.is_reattachable.IsReattachableCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.is_reattachable('addresses') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) \ No newline at end of file From e900067d12931cdc503d3b0e8c917fe44f288760 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 10 Feb 2020 17:17:45 +0100 Subject: [PATCH 21/69] Make `PrepareTransferCommand` async - Update tests for async --- iota/commands/extended/prepare_transfer.py | 10 +- .../extended/prepare_transfer_test.py | 109 ++++++++++++------ 2 files changed, 77 insertions(+), 42 deletions(-) diff --git a/iota/commands/extended/prepare_transfer.py b/iota/commands/extended/prepare_transfer.py index dd725b8..ed77525 100644 --- a/iota/commands/extended/prepare_transfer.py +++ b/iota/commands/extended/prepare_transfer.py @@ -36,7 +36,7 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + async def _execute(self, request): # Required parameters. seed = request['seed'] # type: Seed bundle = ProposedBundle(request['transfers']) @@ -53,7 +53,7 @@ def _execute(self, request): if proposed_inputs is None: # No inputs provided. Scan addresses for unspent # inputs. - gi_response = GetInputsCommand(self.adapter)( + gi_response = await GetInputsCommand(self.adapter)( seed=seed, threshold=want_to_spend, securityLevel=security_level, @@ -66,7 +66,7 @@ def _execute(self, request): available_to_spend = 0 confirmed_inputs = [] # type: List[Address] - gb_response = GetBalancesCommand(self.adapter)( + gb_response = await GetBalancesCommand(self.adapter)( addresses=[i.address for i in proposed_inputs], ) @@ -105,10 +105,10 @@ def _execute(self, request): if bundle.balance < 0: if not change_address: change_address = \ - GetNewAddressesCommand(self.adapter)( + (await GetNewAddressesCommand(self.adapter)( seed=seed, securityLevel=security_level, - )['addresses'][0] + ))['addresses'][0] bundle.send_unspent_inputs_to(change_address) diff --git a/test/commands/extended/prepare_transfer_test.py b/test/commands/extended/prepare_transfer_test.py index 0a6c46b..e25c73c 100644 --- a/test/commands/extended/prepare_transfer_test.py +++ b/test/commands/extended/prepare_transfer_test.py @@ -9,14 +9,14 @@ from six import binary_type, iterkeys from iota import Address, BadApiResponse, Iota, ProposedTransaction, Tag, \ - TryteString, Transaction, TransactionHash -from iota.adapter import MockAdapter + TryteString, Transaction, TransactionHash, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.extended.prepare_transfer import PrepareTransferCommand from iota.crypto.addresses import AddressGenerator from iota.crypto.types import Seed from iota.filters import GeneratedAddress, Trytes from test import mock -from test import patch, MagicMock +from test import patch, MagicMock, async_test class PrepareTransferRequestFilterTestCase(BaseFilterTestCase): @@ -576,12 +576,12 @@ def get_current_timestamp(): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.prepare_transfer.PrepareTransferCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -596,12 +596,36 @@ def test_wireup(self): 'You found me!' ) - def test_pass_inputs_not_needed(self): + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.prepare_transfer.PrepareTransferCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.prepare_transfer('transfers') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_pass_inputs_not_needed(self): """ Preparing a bundle that does not transfer any IOTAs. """ response =\ - self.command( + await self.command( seed = Seed( 'TESTVALUE9DONTUSEINPRODUCTION99999HORPYY' @@ -647,7 +671,8 @@ def test_pass_inputs_not_needed(self): ) - def test_pass_inputs_explicit_no_change(self): + @async_test + async def test_pass_inputs_explicit_no_change(self): """ Preparing a bundle with specified inputs, no change address needed. """ @@ -660,7 +685,7 @@ def test_pass_inputs_explicit_no_change(self): 'FIVFBBYQHFYZYIEEWZL9VPMMKIIYTEZRRHXJXKIKF', }) - response = self.command( + response = await self.command( seed = Seed( 'TESTVALUEONE9DONTUSEINPRODUCTION99999C9V' @@ -741,7 +766,8 @@ def test_pass_inputs_explicit_no_change(self): TryteString('999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999TESTVALUETWO9DONTUSEINPRODUCTION99999XYYNXZLKBYNFPXA9RUGZVEGVPLLFJEM9ZZOUINE9ONOWOB9999999999999999999999999OJ9999999999999999999999999NYBKIVD99999999999D99999999WNQNUFDDEVEKCLVLUJCFRRWBHSHXQQKSCWACHBLWXPEBWWEBJWJXQQBFJ9HSSDATPLVLL9SLSRFAVRE9Z999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999'), ) - def test_pass_inputs_explicit_with_change(self): + @async_test + async def test_pass_inputs_explicit_with_change(self): """ Preparing a bundle with specified inputs, change address needed. """ @@ -754,7 +780,7 @@ def test_pass_inputs_explicit_with_change(self): 'FIVFBBYQHFYZYIEEWZL9VPMMKIIYTEZRRHXJXKIKF', }) - response = self.command( + response = await self.command( seed = Seed( 'TESTVALUEONE9DONTUSEINPRODUCTION99999C9V' @@ -820,7 +846,8 @@ def test_pass_inputs_explicit_with_change(self): TryteString('999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999TESTVALUETWO9DONTUSEINPRODUCTION99999XYYNXZLKBYNFPXA9RUGZVEGVPLLFJEM9ZZOUINE9ONOWOB9999999999999999999999999UA9999999999999999999999999NYBKIVD99999999999C99999999GZTLUWOGA9QLYBHUHB9GVUABQHPIJRWUIUOXFIBGYEJWUA9QUZVAKCFLDVUUZEFIDZIUOWUHSFOQIWJFD999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999'), ) - def test_fail_inputs_explicit_insufficient(self): + @async_test + async def test_fail_inputs_explicit_insufficient(self): """ Specified inputs are not sufficient to cover spend amount. """ @@ -834,7 +861,7 @@ def test_fail_inputs_explicit_insufficient(self): }) with self.assertRaises(BadApiResponse): - self.command( + await self.command( seed = Seed( b'TESTVALUEONE9DONTUSEINPRODUCTION99999C9V' b'C9RHFCQAIGSFICL9HIY9ZEUATFVHFGAEUHSECGQAK' @@ -862,7 +889,8 @@ def test_fail_inputs_explicit_insufficient(self): ], ) - def test_pass_inputs_implicit_no_change(self): + @async_test + async def test_pass_inputs_implicit_no_change(self): """ Preparing a bundle that finds inputs to use automatically, no change address needed. @@ -875,7 +903,7 @@ def test_pass_inputs_implicit_no_change(self): # - :py:class:`iota.commands.extended.get_inputs.GetInputsCommand` mock_get_inputs =\ mock.Mock( - return_value = { + return_value = async_return({ 'inputs': [ Address( trytes = @@ -899,7 +927,7 @@ def test_pass_inputs_implicit_no_change(self): ], 'totalBalance': 42, - }, + }), ) with mock.patch( @@ -907,7 +935,7 @@ def test_pass_inputs_implicit_no_change(self): mock_get_inputs, ): response =\ - self.command( + await self.command( seed = Seed( 'TESTVALUEONE9DONTUSEINPRODUCTION99999C9V' @@ -962,7 +990,8 @@ def test_pass_inputs_implicit_no_change(self): TryteString('999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999TESTVALUETWO9DONTUSEINPRODUCTION99999XYYNXZLKBYNFPXA9RUGZVEGVPLLFJEM9ZZOUINE9ONOWOB9999999999999999999999999OJ9999999999999999999999999NYBKIVD99999999999D99999999WNQNUFDDEVEKCLVLUJCFRRWBHSHXQQKSCWACHBLWXPEBWWEBJWJXQQBFJ9HSSDATPLVLL9SLSRFAVRE9Z999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999'), ) - def test_pass_inputs_implicit_with_change(self): + @async_test + async def test_pass_inputs_implicit_with_change(self): """ Preparing a bundle that finds inputs to use automatically, change address needed. @@ -975,7 +1004,7 @@ def test_pass_inputs_implicit_with_change(self): # - :py:class:`iota.commands.extended.get_inputs.GetInputsCommand` mock_get_inputs =\ mock.Mock( - return_value = { + return_value = async_return({ 'inputs': [ Address( trytes = @@ -989,14 +1018,14 @@ def test_pass_inputs_implicit_with_change(self): ], 'totalBalance': 86, - }, + }), ) with mock.patch( 'iota.commands.extended.get_inputs.GetInputsCommand._execute', mock_get_inputs, ): - response = self.command( + response = await self.command( seed = Seed( 'TESTVALUEONE9DONTUSEINPRODUCTION99999C9V' @@ -1051,7 +1080,8 @@ def test_pass_inputs_implicit_with_change(self): TryteString('999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999TESTVALUETWO9DONTUSEINPRODUCTION99999XYYNXZLKBYNFPXA9RUGZVEGVPLLFJEM9ZZOUINE9ONOWOB9999999999999999999999999BI9999999999999999999999999NYBKIVD99999999999C99999999FWVD9JAZQGWBOFXANTLCCUHZTKWDDTBRICCOXGWGDDZSXJXKYAJJSCRWSVWVLXKNGOBUJLASABZRJXKVX999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999'), ) - def test_fail_inputs_implicit_insufficient(self): + @async_test + async def test_fail_inputs_implicit_insufficient(self): """ Account's total balance is not enough to cover spend amount. """ @@ -1068,7 +1098,7 @@ def test_fail_inputs_implicit_insufficient(self): mock_get_inputs, ): with self.assertRaises(BadApiResponse): - self.command( + await self.command( seed = Seed( b'TESTVALUEONE9DONTUSEINPRODUCTION99999C9V' b'C9RHFCQAIGSFICL9HIY9ZEUATFVHFGAEUHSECGQAK' @@ -1085,7 +1115,8 @@ def test_fail_inputs_implicit_insufficient(self): ], ) - def test_pass_change_address_auto_generated(self): + @async_test + async def test_pass_change_address_auto_generated(self): """ Preparing a bundle with an auto-generated change address. """ @@ -1097,7 +1128,7 @@ def test_pass_change_address_auto_generated(self): # - :py:class:`iota.commands.extended.get_new_addresses.GetNewAddressesCommand` mock_get_new_addresses_command =\ mock.Mock( - return_value = { + return_value = async_return({ 'addresses': [ Address( trytes = @@ -1108,7 +1139,7 @@ def test_pass_change_address_auto_generated(self): security_level = 2, ), ], - }, + }), ) self.adapter.seed_response('getBalances', { @@ -1125,7 +1156,7 @@ def test_pass_change_address_auto_generated(self): mock_get_new_addresses_command, ): response = \ - self.command( + await self.command( seed = Seed( b'TESTVALUEONE9DONTUSEINPRODUCTION99999C9V' @@ -1185,11 +1216,12 @@ def test_pass_change_address_auto_generated(self): TryteString(b'999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999TESTVALUETWO9DONTUSEINPRODUCTION99999XYYNXZLKBYNFPXA9RUGZVEGVPLLFJEM9ZZOUINE9ONOWOB9999999999999999999999999999999999999999999999999999NYBKIVD99999999999C99999999IXYSIGLOJQGEKAIDXIRITVQTDKZ9RYRXVHUJJOCTDJEEPQVLLLPWCZOQBOVZEFFFGZVI9AXQTLIZIRZGB999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999'), ) - def test_pass_message_short(self): + @async_test + async def test_pass_message_short(self): """ Adding a message to a transaction. """ - response = self.command( + response = await self.command( seed = Seed( 'TESTVALUE9DONTUSEINPRODUCTION99999HORPYY' @@ -1219,12 +1251,13 @@ def test_pass_message_short(self): TryteString('HHVFHFHHVFEFHHVFOFHHVFHFHHVFMEHHVFSFHHVFCEHHVFPFHHVFEFHHWFVDHHVFCFHHVFUDFA9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999TESTVALUE9DONTUSEINPRODUCTION99999YMSWGXVNDMLXPT9HMVAOWUUZMLSJZFWGKDVGXPSQAWAEBJN999999999999999999999999999YAOTA9UNIT9TESTS99999999999NYBKIVD999999999999999999999DRTUWFTZIDCUOAWLSDGIAWAIOGDYWFZNJHCEXDGAHCPKOIUIICUWGPXTGCRJWZBV9AXBCBRAKLDPRKQW999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999PYOTA9UNIT9TESTS99999999999999999999999999999999999999999999999999999999999999999'), ) - def test_pass_message_long(self): + @async_test + async def test_pass_message_long(self): """ The message is too long to fit into a single transaction. """ response =\ - self.command( + await self.command( seed = Seed( 'TESTVALUE9DONTUSEINPRODUCTION99999HORPYY' @@ -1289,7 +1322,8 @@ def test_pass_message_long(self): TryteString('SGKETGDEEASG9GSGSFEASGZFSGAGSGTFSGSFTGVDSGSFEATGUDSGBGTGTDSGNFSGPFSGVFTGVDTGEETGUDTGHEEASGBGTGTDSGNFSGPFSGRFTGWDFAEASGZETGDESG9GQAEASGZFTGDEEASGTFSGVFSGPFSGSFSGZFEASGPFEASGZFSGVFTGTDSGSFQAEASGXFSGAGTGVDSGAGTGTDTGDESGWFEASGVFSGZFSGSFSGSFTGVDEATGUDTGVDSGSFSG9GTGDESAEASGQEEATGFETGVDSGVFEATGUDTGVDSGSFSG9GTGDEEASGRFSGAGSGYFSGTFSG9GTGDEEASGOFTGDETGVDTGEEEASGAGTGYDTGTDSGNFSG9GTGHETGGETGVDEASGYFTGGESGRFSGVFEATGUDEASGAGTGTDTGWDSGTFSGVFSGSFSGZFSAEASGSETGVDSGAGEASGOFTGWDSGRFSGSFTGVDEATGFETGVDSGAGEASGRFSGSFSGYFSGNFTGVDTGEEIBEASGKETGDEIBEASGKETGDEQAEASGYFSGSFSGWFTGVDSGSFSG9GSGNFSG9GTGVDEAFCTCXCBDQCTCFDVCIBEASGAFEASGZFSGSFSG9GTGHEEASGSFTGUDTGVDTGEEEASGOFSGAGSGYFTGEETGAESGNFTGHEEASGAGTGVDSGPFSGSFTGVDTGUDTGVDSGPFSGSFSG9GSG9GSGAGTGUDTGVDTGEEQAEATG9ESGSFSGZFEASGPFTGDEEASGZFSGAGSGTFSGSFTGVDSGSFEASGBGSGAGSG9GTGHETGVDTGEESAEASG9FTGDEEASGBGSGYFSGNFTG9ESGSFTGAETGEEEASGZESGNFSG9GTGVDTGEETGHESGQFSGAGEASGVFEASGBGTGTDSGAGSGXFSGYFTGHESG9GSGVFEASGZFSGAGTGTDTGUDSGXFSGVFTGYDEASGBGSGSFTGYDSGAGTGVDSGVFSG9GTGZDSGSFSGPFSAEASGAFEASGPFSGNFTGUDEASGSFTGUDTGVDTGEEEATGVDSGNFSGXFSGAGSGWFEATGTDSGAGTGUDSGXFSGAGTGAESGVFSAEASGAFEASGPFSGNFTGUDEASGSFTGUDTGVDTGEEEATGTDSGAGTGUDSGXFSGAGTGAETGEEQAEASG9GSGSFEASGUFSG9GSGNFTGHEQAEATG9ETGVDSGAGEATGHEEASGUFSG9GSGNFTGGEDBEATG9ETGVDSGAGEATGUDSGZFSGSFTGTDTGVDTGEEEASGZESGNFSG9GTGVDTGEETGHESGQFSGAGQAEASGPFEATGVDSGAGEASGPFTGTDSGSFSGZFTGHEEASGXFSGNFSGXFEATGVDTGTDSGNFSGQFSGVFTG9ESGSFTGUDSGXFSGVFSGWFQAEASGPFSGSFTGTDSGAGTGHETGVDSG9GSGAGQAEATGUDSGBGSGNFTGUDEASGTFSGVFSGUFSG9GTGEESAEASGQEEASGZFSGAGSGSFEATGUDTGWDTGBESGSFTGUDTGVDSGPFSGAGSGPFSGNFSG9GSGVFSGSFQAEASGPFEATGVDSGAGEASGPFTGTDSGSFSGZFTGHEEASGXFSGNFSGXFEASGQFTGTDSGAGTGVDSGSFTGUDSGXFEASGVFEASG9GSGSFSGBGSGAGSG9GTGHETGVDSG9GTGDESGZFSGVFEASGRFSGYFTGHEEASGPFSGNFTGUDQAEATGUDSGBGSGNFTGUDSGNFSGSFTGVDEASGTFSGVFSGUFSG9GSGVFEASASASAEASGKETGDEEASG9GSGSFEATGYDSGAGTGVDSGVFTGVDSGSFEASGUFSG9GSGNFTGVDTGEEEASGBGTGTDSGNFSGPFSGRFTGWDSAEASGXESGAGTGVDSGAGSGZFTGWDEATG9ETGVDSGAGEASGPFEASGQFSGYFTGWDSGOFSGVFSG9GSGSFEASGRFTGWDTGAESGVFQAEASGPFEATGVDSGSFTGYDEASGZFSGSFTGUDTGVDSGNFTGYDQAEASGPFTGDEEASG9GSGSFEASGQFSGAGSGPFSGAGTGTDSGVFTGVDSGSFEASGAGEASG9GSGNFEASGPFSGSFTG9ESGSFTGTDSGVFSG9GSGXFSGNFTGYDQAEASGPFTGDEEATGYDSGAGTGVDSGVFTGVDSGSFEASGZFSGSTESTVALUE9DONTUSEINPRODUCTION99999YMSWGXVNDMLXPT9HMVAOWUUZMLSJZFWGKDVGXPSQAWAEBJN999999999999999999999999999GFOTA9UNIT9TESTS99999999999NYBKIVD99999999999B99999999EKHBGESJFZXE9PY9UVFIPRHGGFKDFKQOQFKQAYISJOWCXIVBSGHOZGT9DZEQPPLTYHKTWBQZOFX9BEAID999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999PYOTA9UNIT9TESTS99999999999999999999999999999999999999999999999999999999999999999'), ) - def test_security_level(self): + @async_test + async def test_security_level(self): """ testing use of security_level when inputs are given and change address is not given. """ @@ -1316,7 +1350,7 @@ def mock_get_balances_execute(adapter, request): # returns balances of input addresses equal to SEND_VALUE + security_level * 11 addr = request["addresses"][0] security_level = [l for l, a in mock_addresses.items() if str(a) == addr][0] - return dict(balances=[SEND_VALUE + security_level * 11], milestone=None) + return async_return(dict(balances=[SEND_VALUE + security_level * 11], milestone=None)) # testing for several security levels for security_level in SECURITY_LEVELS_TO_TEST: @@ -1337,7 +1371,7 @@ def mock_get_balances_execute(adapter, request): mock_get_balances_execute, ): response = \ - self.command( + await self.command( seed=seed, transfers=[ ProposedTransaction( @@ -1366,7 +1400,8 @@ def mock_get_balances_execute(adapter, request): self.assertEqual(change_tx.value, EXPECTED_CHANGE_VALUE) - def test_security_level_no_inputs(self): + @async_test + async def test_security_level_no_inputs(self): """ testing use of security_level when neither inputs nor change address is given. """ @@ -1390,7 +1425,7 @@ def mock_get_balances_execute(adapter, request): # returns balances of input addresses equal to SEND_VALUE + security_level * 11 addr = request["addresses"][0] security_level = [l for l, a in addresses.items() if str(a) == addr][0] - return dict(balances=[SEND_VALUE + security_level * 11], milestone=None) + return async_return(dict(balances=[SEND_VALUE + security_level * 11], milestone=None)) # testing several security levels for security_level in SECURITY_LEVELS_TO_TEST: @@ -1429,7 +1464,7 @@ def mock_get_balances_execute(adapter, request): mock_get_balances_execute, ): response = \ - self.command( + await self.command( seed=seed, transfers=[ ProposedTransaction( From 049c0e65596935a295192ef1ef813b442893c613 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 10 Feb 2020 17:28:36 +0100 Subject: [PATCH 22/69] Make `PromoteTransactionCommand` async - Update tests for async --- iota/api.py | 2 +- iota/commands/extended/promote_transaction.py | 6 +-- .../extended/promote_transaction_test.py | 48 ++++++++++++++----- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/iota/api.py b/iota/api.py index 808e62d..f903ea4 100644 --- a/iota/api.py +++ b/iota/api.py @@ -2676,7 +2676,7 @@ def promote_transaction( return asyncio.get_event_loop().run_until_complete( super(Iota, self).promote_transaction( transaction, - dept, + depth, min_weight_magnitude, ) ) diff --git a/iota/commands/extended/promote_transaction.py b/iota/commands/extended/promote_transaction.py index b7c03d2..564d147 100644 --- a/iota/commands/extended/promote_transaction.py +++ b/iota/commands/extended/promote_transaction.py @@ -30,12 +30,12 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + async def _execute(self, request): depth = request['depth'] # type: int min_weight_magnitude = request['minWeightMagnitude'] # type: int transaction = request['transaction'] # type: TransactionHash - cc_response = CheckConsistencyCommand(self.adapter)(tails=[transaction]) + cc_response = await CheckConsistencyCommand(self.adapter)(tails=[transaction]) if cc_response['state'] is False: raise BadApiResponse( 'Transaction {transaction} is not promotable. ' @@ -47,7 +47,7 @@ def _execute(self, request): value=0, ) - return SendTransferCommand(self.adapter)( + return await SendTransferCommand(self.adapter)( seed=spam_transfer.address, depth=depth, transfers=[spam_transfer], diff --git a/test/commands/extended/promote_transaction_test.py b/test/commands/extended/promote_transaction_test.py index e732ae4..b8e9c4e 100644 --- a/test/commands/extended/promote_transaction_test.py +++ b/test/commands/extended/promote_transaction_test.py @@ -8,12 +8,13 @@ from filters.test import BaseFilterTestCase from six import binary_type -from iota import Bundle, Iota, TransactionHash, TransactionTrytes, BadApiResponse -from iota.adapter import MockAdapter +from iota import Bundle, Iota, TransactionHash, TransactionTrytes, \ + BadApiResponse, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.extended.promote_transaction import PromoteTransactionCommand from iota.filters import Trytes from test import mock -from test import patch, MagicMock +from test import patch, MagicMock, async_test class PromoteTransactionRequestFilterTestCase(BaseFilterTestCase): @@ -312,12 +313,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.promote_transaction.PromoteTransactionCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -332,7 +333,31 @@ def test_wireup(self): 'You found me!' ) - def test_happy_path(self): + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.promote_transaction.PromoteTransactionCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.promote_transaction('transaction') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_happy_path(self): """ Successfully promoting a bundle. """ @@ -345,16 +370,16 @@ def test_happy_path(self): TransactionTrytes(self.trytes1), TransactionTrytes(self.trytes2), ]) - mock_send_transfer = mock.Mock(return_value={ + mock_send_transfer = mock.Mock(return_value=async_return({ 'bundle': result_bundle, - }) + })) with mock.patch( 'iota.commands.extended.send_transfer.SendTransferCommand._execute', mock_send_transfer, ): - response = self.command( + response = await self.command( transaction=self.hash1, depth=3, minWeightMagnitude=16, @@ -368,7 +393,8 @@ def test_happy_path(self): } ) - def test_not_promotable(self): + @async_test + async def test_not_promotable(self): """ Bundle isn't promotable. """ @@ -378,7 +404,7 @@ def test_not_promotable(self): }) with self.assertRaises(BadApiResponse): - response = self.command( + response = await self.command( transaction=self.hash1, depth=3, minWeightMagnitude=16, From f23847148500cb1b8a7fd854460b6f26dd2194a7 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 10 Feb 2020 17:41:30 +0100 Subject: [PATCH 23/69] Make `ReplayBundleCommand` async - Update tests for async --- iota/api.py | 2 +- iota/commands/extended/replay_bundle.py | 6 +-- test/commands/extended/replay_bundle_test.py | 44 +++++++++++++++----- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/iota/api.py b/iota/api.py index f903ea4..2a57b14 100644 --- a/iota/api.py +++ b/iota/api.py @@ -2723,7 +2723,7 @@ def replay_bundle( return asyncio.get_event_loop().run_until_complete( super(Iota, self).replay_bundle( transaction, - dept, + depth, min_weight_magnitude, ) ) diff --git a/iota/commands/extended/replay_bundle.py b/iota/commands/extended/replay_bundle.py index 3d5e313..c0eb3e6 100644 --- a/iota/commands/extended/replay_bundle.py +++ b/iota/commands/extended/replay_bundle.py @@ -29,18 +29,18 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + async def _execute(self, request): depth = request['depth'] # type: int min_weight_magnitude = request['minWeightMagnitude'] # type: int transaction = request['transaction'] # type: TransactionHash - gb_response = GetBundlesCommand(self.adapter)(transactions=[transaction]) + gb_response = await GetBundlesCommand(self.adapter)(transactions=[transaction]) # Note that we only replay the first bundle returned by # ``getBundles``. bundle = gb_response['bundles'][0] # type: Bundle - return SendTrytesCommand(self.adapter)( + return await SendTrytesCommand(self.adapter)( depth=depth, minWeightMagnitude=min_weight_magnitude, trytes=bundle.as_tryte_strings(), diff --git a/test/commands/extended/replay_bundle_test.py b/test/commands/extended/replay_bundle_test.py index 858e038..07f5765 100644 --- a/test/commands/extended/replay_bundle_test.py +++ b/test/commands/extended/replay_bundle_test.py @@ -9,12 +9,12 @@ from six import binary_type from iota import Address, Bundle, BundleHash, Fragment, Iota, Nonce, Tag, \ - Transaction, TransactionHash -from iota.adapter import MockAdapter + Transaction, TransactionHash, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.extended.replay_bundle import ReplayBundleCommand from iota.filters import Trytes from test import mock -from test import patch, MagicMock +from test import patch, MagicMock, async_test class ReplayBundleRequestFilterTestCase(BaseFilterTestCase): @@ -304,12 +304,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.replay_bundle.ReplayBundleCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -324,7 +324,31 @@ def test_wireup(self): 'You found me!' ) - def test_happy_path(self): + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.replay_bundle.ReplayBundleCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.replay_bundle('transaction') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_happy_path(self): """ Successfully replaying a bundle. """ @@ -602,9 +626,9 @@ def test_happy_path(self): ]) mock_get_bundles =\ - mock.Mock(return_value={ + mock.Mock(return_value=async_return({ 'bundles': [bundle], - }) + })) send_trytes_response = { 'trytes': bundle.as_tryte_strings(), @@ -619,7 +643,7 @@ def mock_send_trytes(_,request): - https://github.com/iotaledger/iota.py/issues/74 """ self.assertEqual(request['trytes'], send_trytes_response['trytes']) - return send_trytes_response + return async_return(send_trytes_response) with mock.patch( 'iota.commands.extended.get_bundles.GetBundlesCommand._execute', @@ -629,7 +653,7 @@ def mock_send_trytes(_,request): 'iota.commands.extended.send_trytes.SendTrytesCommand._execute', mock_send_trytes, ): - response = self.command( + response = await self.command( depth = 100, minWeightMagnitude = 18, transaction = bundle[0].hash, From 6dd62fcb4900696b5bf5e77affbd88e10bc86d31 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 10 Feb 2020 17:46:33 +0100 Subject: [PATCH 24/69] Make `SendTransferCommand` async - Update tests for async --- iota/commands/extended/send_transfer.py | 6 +-- test/commands/extended/send_transfer_test.py | 46 +++++++++++++++----- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/iota/commands/extended/send_transfer.py b/iota/commands/extended/send_transfer.py index ad3def0..8efa482 100644 --- a/iota/commands/extended/send_transfer.py +++ b/iota/commands/extended/send_transfer.py @@ -32,7 +32,7 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + async def _execute(self, request): change_address = request['changeAddress'] # type: Optional[Address] depth = request['depth'] # type: int inputs = request['inputs'] # type: Optional[List[Address]] @@ -42,7 +42,7 @@ def _execute(self, request): reference = request['reference'] # type: Optional[TransactionHash] security_level = request['securityLevel'] # int - pt_response = PrepareTransferCommand(self.adapter)( + pt_response = await PrepareTransferCommand(self.adapter)( changeAddress=change_address, inputs=inputs, seed=seed, @@ -50,7 +50,7 @@ def _execute(self, request): securityLevel=security_level, ) - st_response = SendTrytesCommand(self.adapter)( + st_response = await SendTrytesCommand(self.adapter)( depth=depth, minWeightMagnitude=min_weight_magnitude, trytes=pt_response['trytes'], diff --git a/test/commands/extended/send_transfer_test.py b/test/commands/extended/send_transfer_test.py index 8a277c0..c9ab2fd 100644 --- a/test/commands/extended/send_transfer_test.py +++ b/test/commands/extended/send_transfer_test.py @@ -9,14 +9,14 @@ from six import binary_type from iota import Address, Bundle, Iota, ProposedTransaction, TransactionHash, \ - TransactionTrytes, TryteString -from iota.adapter import MockAdapter + TransactionTrytes, TryteString, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.extended.send_transfer import SendTransferCommand from iota.crypto.addresses import AddressGenerator from iota.crypto.types import Seed from iota.filters import Trytes from test import mock -from test import patch, MagicMock +from test import patch, MagicMock, async_test class SendTransferRequestFilterTestCase(BaseFilterTestCase): @@ -672,12 +672,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.send_transfer.SendTransferCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -692,7 +692,31 @@ def test_wireup(self): 'You found me!' ) - def test_happy_path(self): + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.send_transfer.SendTransferCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.send_transfer('transfers') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_happy_path(self): """ Sending a transfer successfully. """ @@ -743,14 +767,14 @@ def test_happy_path(self): ) mock_prepare_transfer =\ - mock.Mock(return_value={ + mock.Mock(return_value=async_return({ 'trytes': [transaction1], - }) + })) mock_send_trytes =\ - mock.Mock(return_value={ + mock.Mock(return_value=async_return({ 'trytes': [transaction1], - }) + })) with mock.patch( 'iota.commands.extended.prepare_transfer.PrepareTransferCommand._execute', @@ -760,7 +784,7 @@ def test_happy_path(self): 'iota.commands.extended.send_trytes.SendTrytesCommand._execute', mock_send_trytes, ): - response = self.command( + response = await self.command( depth = 100, minWeightMagnitude = 18, seed = Seed.random(), From 40f5a305bea9cc3f0e5c8a7eccf8ef43db46ad24 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 10 Feb 2020 18:16:08 +0100 Subject: [PATCH 25/69] Make `SendTrytesCommand` async - Update tests for async --- iota/commands/extended/send_trytes.py | 8 ++--- test/commands/extended/send_trytes_test.py | 37 ++++++++++++++++++---- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/iota/commands/extended/send_trytes.py b/iota/commands/extended/send_trytes.py index 16dac7c..9126932 100644 --- a/iota/commands/extended/send_trytes.py +++ b/iota/commands/extended/send_trytes.py @@ -33,7 +33,7 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + async def _execute(self, request): depth = request['depth'] # type: int min_weight_magnitude = request['minWeightMagnitude'] # type: int trytes = request['trytes'] # type: List[TryteString] @@ -41,12 +41,12 @@ def _execute(self, request): # Call ``getTransactionsToApprove`` to locate trunk and branch # transactions so that we can attach the bundle to the Tangle. - gta_response = GetTransactionsToApproveCommand(self.adapter)( + gta_response = await GetTransactionsToApproveCommand(self.adapter)( depth=depth, reference=reference, ) - att_response = AttachToTangleCommand(self.adapter)( + att_response = await AttachToTangleCommand(self.adapter)( branchTransaction=gta_response.get('branchTransaction'), trunkTransaction=gta_response.get('trunkTransaction'), @@ -57,7 +57,7 @@ def _execute(self, request): # ``trytes`` now have POW! trytes = att_response['trytes'] - BroadcastAndStoreCommand(self.adapter)(trytes=trytes) + await BroadcastAndStoreCommand(self.adapter)(trytes=trytes) return { 'trytes': trytes, diff --git a/test/commands/extended/send_trytes_test.py b/test/commands/extended/send_trytes_test.py index 7030180..12989ee 100644 --- a/test/commands/extended/send_trytes_test.py +++ b/test/commands/extended/send_trytes_test.py @@ -8,11 +8,12 @@ from filters.test import BaseFilterTestCase from six import binary_type, text_type -from iota import Iota, TransactionTrytes, TryteString, TransactionHash -from iota.adapter import MockAdapter +from iota import Iota, TransactionTrytes, TryteString, TransactionHash, \ + AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.extended.send_trytes import SendTrytesCommand from iota.filters import Trytes -from test import patch, MagicMock +from test import patch, MagicMock, async_test class SendTrytesRequestFilterTestCase(BaseFilterTestCase): @@ -376,7 +377,7 @@ def test_wireup(self): The API method indeed calls the appropiate command. """ with patch('iota.commands.extended.send_trytes.SendTrytesCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) @@ -391,7 +392,31 @@ def test_wireup(self): 'You found me!' ) - def test_happy_path(self): + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.extended.send_trytes.SendTrytesCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.send_trytes('trytes') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_happy_path(self): """ Successful invocation of ``sendTrytes``. """ @@ -415,7 +440,7 @@ def test_happy_path(self): TransactionTrytes(self.trytes2), ] - response = self.command( + response = await self.command( trytes = trytes, depth = 100, minWeightMagnitude = 18, From cfcfb2dbadc692ef66dd0b28d031928fdb530ad9 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Tue, 11 Feb 2020 11:59:41 +0100 Subject: [PATCH 26/69] Update core command tests for async --- setup.py | 2 +- test/commands/core/add_neighbors_test.py | 34 +++++++++++--- test/commands/core/attach_to_tangle_test.py | 34 +++++++++++--- .../core/broadcast_transactions_test.py | 33 ++++++++++--- test/commands/core/check_consistency_test.py | 47 ++++++++++++++----- test/commands/core/find_transactions_test.py | 38 +++++++++++---- test/commands/core/get_balances_test.py | 35 +++++++++++--- .../core/get_inclusion_states_test.py | 33 ++++++++++--- .../core/get_missing_transactions_test.py | 35 +++++++++++--- test/commands/core/get_neighbors_test.py | 33 ++++++++++--- .../core/get_node_api_configuration_test.py | 35 +++++++++++--- test/commands/core/get_node_info_test.py | 33 ++++++++++--- test/commands/core/get_tips_test.py | 33 ++++++++++--- .../core/get_transactions_to_approve_test.py | 35 +++++++++++--- test/commands/core/get_trytes_test.py | 33 ++++++++++--- .../interrupt_attaching_to_tangle_test.py | 33 ++++++++++--- test/commands/core/remove_neighbors_test.py | 33 ++++++++++--- test/commands/core/store_transactions_test.py | 33 ++++++++++--- .../core/were_addresses_spent_from_test.py | 35 +++++++++++--- 19 files changed, 505 insertions(+), 122 deletions(-) diff --git a/setup.py b/setup.py index 3ca919f..5f6528e 100644 --- a/setup.py +++ b/setup.py @@ -69,7 +69,7 @@ # ``security`` extra wasn't introduced until 2.4.1 # http://docs.python-requests.org/en/latest/community/updates/#id35 'requests[security] >= 2.4.1', - 'httpx' + 'httpx', 'six', 'typing; python_version < "3.0"', ], diff --git a/test/commands/core/add_neighbors_test.py b/test/commands/core/add_neighbors_test.py index 985c3ac..a65a6f3 100644 --- a/test/commands/core/add_neighbors_test.py +++ b/test/commands/core/add_neighbors_test.py @@ -6,11 +6,11 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import StrictIota -from iota.adapter import MockAdapter +from iota import Iota, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.core.add_neighbors import AddNeighborsCommand from iota.filters import NodeUri -from test import patch, MagicMock +from test import patch, MagicMock, async_test class AddNeighborsRequestFilterTestCase(BaseFilterTestCase): @@ -151,20 +151,42 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.add_neighbors.AddNeighborsCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: - api = StrictIota(self.adapter) + api = Iota(self.adapter) response = api.add_neighbors('test_uri') self.assertTrue(mocked_command.called) + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.add_neighbors.AddNeighborsCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.add_neighbors('test_uri') + + self.assertTrue(mocked_command.called) + self.assertEqual( response, 'You found me!' diff --git a/test/commands/core/attach_to_tangle_test.py b/test/commands/core/attach_to_tangle_test.py index 604057e..8321295 100644 --- a/test/commands/core/attach_to_tangle_test.py +++ b/test/commands/core/attach_to_tangle_test.py @@ -6,12 +6,13 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota, TransactionHash, TransactionTrytes, TryteString -from iota.adapter import MockAdapter +from iota import Iota, TransactionHash, TransactionTrytes, TryteString, \ + AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.core.attach_to_tangle import AttachToTangleCommand from iota.filters import Trytes from six import binary_type, text_type -from test import patch, MagicMock +from test import patch, MagicMock, async_test class AttachToTangleRequestFilterTestCase(BaseFilterTestCase): @@ -432,17 +433,16 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.attach_to_tangle.AttachToTangleCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. response = api.attach_to_tangle('trunk', 'branch', 'trytes') self.assertTrue(mocked_command.called) @@ -451,3 +451,25 @@ def test_wireup(self): response, 'You found me!' ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.attach_to_tangle.AttachToTangleCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.attach_to_tangle('trunk', 'branch', 'trytes') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) \ No newline at end of file diff --git a/test/commands/core/broadcast_transactions_test.py b/test/commands/core/broadcast_transactions_test.py index e5c3b73..2d20440 100644 --- a/test/commands/core/broadcast_transactions_test.py +++ b/test/commands/core/broadcast_transactions_test.py @@ -8,12 +8,12 @@ from filters.test import BaseFilterTestCase from six import binary_type, text_type -from iota import Iota, TransactionTrytes, TryteString -from iota.adapter import MockAdapter +from iota import Iota, AsyncIota, TransactionTrytes, TryteString +from iota.adapter import MockAdapter, async_return from iota.commands.core.broadcast_transactions import \ BroadcastTransactionsCommand from iota.filters import Trytes -from test import patch, MagicMock +from test import patch, MagicMock, async_test class BroadcastTransactionsRequestFilterTestCase(BaseFilterTestCase): filter_type = BroadcastTransactionsCommand(MockAdapter()).get_request_filter @@ -186,17 +186,16 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.broadcast_transactions.BroadcastTransactionsCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. response = api.broadcast_transactions('trytes') self.assertTrue(mocked_command.called) @@ -205,3 +204,25 @@ def test_wireup(self): response, 'You found me!' ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.broadcast_transactions.BroadcastTransactionsCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.broadcast_transactions('trytes') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) \ No newline at end of file diff --git a/test/commands/core/check_consistency_test.py b/test/commands/core/check_consistency_test.py index 57517df..b328788 100644 --- a/test/commands/core/check_consistency_test.py +++ b/test/commands/core/check_consistency_test.py @@ -7,11 +7,11 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota, TransactionHash, TryteString -from iota.adapter import MockAdapter +from iota import Iota, AsyncIota, TransactionHash, TryteString +from iota.adapter import MockAdapter, async_return from iota.commands.core.check_consistency import CheckConsistencyCommand from iota.filters import Trytes -from test import patch, MagicMock +from test import patch, MagicMock, async_test class CheckConsistencyRequestFilterTestCase(BaseFilterTestCase): @@ -200,17 +200,16 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.check_consistency.CheckConsistencyCommand.__call__', - MagicMock(return_value='You found me!') - ) as mocked_command: + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. response = api.check_consistency('tails') self.assertTrue(mocked_command.called) @@ -220,7 +219,30 @@ def test_wireup(self): 'You found me!' ) - def test_happy_path(self): + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.check_consistency.CheckConsistencyCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.check_consistency('tails') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_happy_path(self): """ Successfully checking consistency. """ @@ -229,7 +251,7 @@ def test_happy_path(self): 'state': True, }) - response = self.command(tails=[self.hash1, self.hash2]) + response = await self.command(tails=[self.hash1, self.hash2]) self.assertDictEqual( response, @@ -239,7 +261,8 @@ def test_happy_path(self): } ) - def test_info_with_false_state(self): + @async_test + async def test_info_with_false_state(self): """ `info` field exists when `state` is False. """ @@ -249,7 +272,7 @@ def test_info_with_false_state(self): 'info': 'Additional information', }) - response = self.command(tails=[self.hash1, self.hash2]) + response = await self.command(tails=[self.hash1, self.hash2]) self.assertDictEqual( response, @@ -258,4 +281,4 @@ def test_info_with_false_state(self): 'state': False, 'info': 'Additional information', } - ) + ) \ No newline at end of file diff --git a/test/commands/core/find_transactions_test.py b/test/commands/core/find_transactions_test.py index 3d5a72c..cf4c795 100644 --- a/test/commands/core/find_transactions_test.py +++ b/test/commands/core/find_transactions_test.py @@ -8,12 +8,13 @@ from filters.test import BaseFilterTestCase from six import text_type -from iota import Address, Iota, Tag, BundleHash, TransactionHash, TryteString -from iota.adapter import MockAdapter +from iota import Address, Iota, Tag, BundleHash, TransactionHash, TryteString, \ + AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.core.find_transactions import FindTransactionsCommand, \ FindTransactionsRequestFilter from iota.filters import Trytes -from test import patch, MagicMock +from test import patch, MagicMock, async_test class FindTransactionsRequestFilterTestCase(BaseFilterTestCase): @@ -561,18 +562,39 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ - with patch('iota.commands.core.check_consistency.CheckConsistencyCommand.__call__', - MagicMock(return_value='You found me!') + with patch('iota.commands.core.find_transactions.FindTransactionsCommand.__call__', + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. - response = api.check_consistency('tails') + response = api.find_transactions('addresses') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.find_transactions.FindTransactionsCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.find_transactions('addresses') self.assertTrue(mocked_command.called) diff --git a/test/commands/core/get_balances_test.py b/test/commands/core/get_balances_test.py index bfd1450..7bb5c91 100644 --- a/test/commands/core/get_balances_test.py +++ b/test/commands/core/get_balances_test.py @@ -7,11 +7,11 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Address, Iota, TryteString -from iota.adapter import MockAdapter +from iota import Address, Iota, TryteString, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.core.get_balances import GetBalancesCommand from iota.filters import Trytes -from test import patch, MagicMock +from test import patch, MagicMock, async_test class GetBalancesRequestFilterTestCase(BaseFilterTestCase): @@ -351,17 +351,16 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.get_balances.GetBalancesCommand.__call__', - MagicMock(return_value='You found me!') - ) as mocked_command: + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. response = api.get_balances('addresses') self.assertTrue(mocked_command.called) @@ -370,3 +369,25 @@ def test_wireup(self): response, 'You found me!' ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.get_balances.GetBalancesCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.get_balances('addresses') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) \ No newline at end of file diff --git a/test/commands/core/get_inclusion_states_test.py b/test/commands/core/get_inclusion_states_test.py index 873a49a..31fb77f 100644 --- a/test/commands/core/get_inclusion_states_test.py +++ b/test/commands/core/get_inclusion_states_test.py @@ -6,12 +6,12 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota, TransactionHash, TryteString -from iota.adapter import MockAdapter +from iota import Iota, TransactionHash, TryteString, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.core.get_inclusion_states import GetInclusionStatesCommand from iota.filters import Trytes from six import binary_type, text_type -from test import patch, MagicMock +from test import patch, MagicMock, async_test class GetInclusionStatesRequestFilterTestCase(BaseFilterTestCase): @@ -259,17 +259,16 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.get_inclusion_states.GetInclusionStatesCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. response = api.get_inclusion_states('transactions', 'tips') self.assertTrue(mocked_command.called) @@ -278,3 +277,25 @@ def test_wireup(self): response, 'You found me!' ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.get_inclusion_states.GetInclusionStatesCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.get_inclusion_states('transactions', 'tips') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) \ No newline at end of file diff --git a/test/commands/core/get_missing_transactions_test.py b/test/commands/core/get_missing_transactions_test.py index 0c5adb0..859c69b 100644 --- a/test/commands/core/get_missing_transactions_test.py +++ b/test/commands/core/get_missing_transactions_test.py @@ -7,10 +7,10 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota, TransactionHash -from iota.adapter import MockAdapter +from iota import Iota, TransactionHash, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.core import GetMissingTransactionsCommand -from test import patch, MagicMock +from test import patch, MagicMock, async_test class GetMissingTransactionsRequestFilterTestCase(BaseFilterTestCase): @@ -109,17 +109,16 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.get_missing_transactions.GetMissingTransactionsCommand.__call__', - MagicMock(return_value='You found me!') - ) as mocked_command: + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. response = api.get_missing_transactions() self.assertTrue(mocked_command.called) @@ -128,3 +127,25 @@ def test_wireup(self): response, 'You found me!' ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.get_missing_transactions.GetMissingTransactionsCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.get_missing_transactions() + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) \ No newline at end of file diff --git a/test/commands/core/get_neighbors_test.py b/test/commands/core/get_neighbors_test.py index 83e7117..4a890e6 100644 --- a/test/commands/core/get_neighbors_test.py +++ b/test/commands/core/get_neighbors_test.py @@ -6,10 +6,10 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota -from iota.adapter import MockAdapter +from iota import Iota, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.core.get_neighbors import GetNeighborsCommand -from test import patch, MagicMock +from test import patch, MagicMock, async_test class GetNeighborsRequestFilterTestCase(BaseFilterTestCase): @@ -49,17 +49,16 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.get_neighbors.GetNeighborsCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. response = api.get_neighbors() self.assertTrue(mocked_command.called) @@ -68,3 +67,25 @@ def test_wireup(self): response, 'You found me!' ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.get_neighbors.GetNeighborsCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.get_neighbors() + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) \ No newline at end of file diff --git a/test/commands/core/get_node_api_configuration_test.py b/test/commands/core/get_node_api_configuration_test.py index 9f15487..a24794f 100644 --- a/test/commands/core/get_node_api_configuration_test.py +++ b/test/commands/core/get_node_api_configuration_test.py @@ -7,10 +7,10 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota -from iota.adapter import MockAdapter +from iota import Iota, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.core import GetNodeAPIConfigurationCommand -from test import patch, MagicMock +from test import patch, MagicMock, async_test class GetNodeAPIConfigurationRequestFilterTestCase(BaseFilterTestCase): @@ -53,17 +53,16 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.get_node_api_configuration.GetNodeAPIConfigurationCommand.__call__', - MagicMock(return_value='You found me!') - ) as mocked_command: + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. response = api.get_node_api_configuration() self.assertTrue(mocked_command.called) @@ -72,3 +71,25 @@ def test_wireup(self): response, 'You found me!' ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.get_node_api_configuration.GetNodeAPIConfigurationCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.get_node_api_configuration() + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) \ No newline at end of file diff --git a/test/commands/core/get_node_info_test.py b/test/commands/core/get_node_info_test.py index 9927ed7..b04a780 100644 --- a/test/commands/core/get_node_info_test.py +++ b/test/commands/core/get_node_info_test.py @@ -6,10 +6,10 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota, TransactionHash -from iota.adapter import MockAdapter +from iota import Iota, TransactionHash, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.core.get_node_info import GetNodeInfoCommand -from test import patch, MagicMock +from test import patch, MagicMock, async_test class GetNodeInfoRequestFilterTestCase(BaseFilterTestCase): @@ -122,17 +122,16 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.get_node_info.GetNodeInfoCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. response = api.get_node_info() self.assertTrue(mocked_command.called) @@ -141,3 +140,25 @@ def test_wireup(self): response, 'You found me!' ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.get_node_info.GetNodeInfoCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.get_node_info() + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) \ No newline at end of file diff --git a/test/commands/core/get_tips_test.py b/test/commands/core/get_tips_test.py index 5dfb191..11cb03b 100644 --- a/test/commands/core/get_tips_test.py +++ b/test/commands/core/get_tips_test.py @@ -7,11 +7,11 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Address, Iota -from iota.adapter import MockAdapter +from iota import Address, Iota, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.core.get_tips import GetTipsCommand from iota.transaction.types import TransactionHash -from test import patch, MagicMock +from test import patch, MagicMock, async_test class GetTipsRequestFilterTestCase(BaseFilterTestCase): @@ -120,17 +120,16 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.get_tips.GetTipsCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. response = api.get_tips() self.assertTrue(mocked_command.called) @@ -140,6 +139,28 @@ def test_wireup(self): 'You found me!' ) + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.get_tips.GetTipsCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.get_tips() + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + def test_type_coercion(self): """ The result is coerced to the proper type. diff --git a/test/commands/core/get_transactions_to_approve_test.py b/test/commands/core/get_transactions_to_approve_test.py index b515800..652831b 100644 --- a/test/commands/core/get_transactions_to_approve_test.py +++ b/test/commands/core/get_transactions_to_approve_test.py @@ -6,12 +6,12 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota, TransactionHash -from iota.adapter import MockAdapter +from iota import Iota, TransactionHash, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.core.get_transactions_to_approve import \ GetTransactionsToApproveCommand from iota.filters import Trytes -from test import patch, MagicMock +from test import patch, MagicMock, async_test class GetTransactionsToApproveRequestFilterTestCase(BaseFilterTestCase): @@ -227,17 +227,16 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.get_transactions_to_approve.GetTransactionsToApproveCommand.__call__', - MagicMock(return_value='You found me!') - ) as mocked_command: + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. response = api.get_transactions_to_approve('depth') self.assertTrue(mocked_command.called) @@ -246,3 +245,25 @@ def test_wireup(self): response, 'You found me!' ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.get_transactions_to_approve.GetTransactionsToApproveCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.get_transactions_to_approve('depth') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) \ No newline at end of file diff --git a/test/commands/core/get_trytes_test.py b/test/commands/core/get_trytes_test.py index 2cf36bb..99eba59 100644 --- a/test/commands/core/get_trytes_test.py +++ b/test/commands/core/get_trytes_test.py @@ -7,11 +7,11 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota, TransactionHash, TryteString -from iota.adapter import MockAdapter +from iota import Iota, TransactionHash, TryteString, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.core.get_trytes import GetTrytesCommand from iota.filters import Trytes -from test import patch, MagicMock +from test import patch, MagicMock, async_test class GetTrytesRequestFilterTestCase(BaseFilterTestCase): @@ -242,17 +242,16 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.get_trytes.GetTrytesCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. response = api.get_trytes('hashes') self.assertTrue(mocked_command.called) @@ -261,3 +260,25 @@ def test_wireup(self): response, 'You found me!' ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.get_trytes.GetTrytesCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.get_trytes('hashes') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) \ No newline at end of file diff --git a/test/commands/core/interrupt_attaching_to_tangle_test.py b/test/commands/core/interrupt_attaching_to_tangle_test.py index 3bcb8c7..c5e5cde 100644 --- a/test/commands/core/interrupt_attaching_to_tangle_test.py +++ b/test/commands/core/interrupt_attaching_to_tangle_test.py @@ -6,11 +6,11 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota -from iota.adapter import MockAdapter +from iota import Iota, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.core.interrupt_attaching_to_tangle import \ InterruptAttachingToTangleCommand -from test import patch, MagicMock +from test import patch, MagicMock, async_test class InterruptAttachingToTangleRequestFilterTestCase(BaseFilterTestCase): @@ -48,17 +48,16 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.interrupt_attaching_to_tangle.InterruptAttachingToTangleCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. response = api.interrupt_attaching_to_tangle() self.assertTrue(mocked_command.called) @@ -67,3 +66,25 @@ def test_wireup(self): response, 'You found me!' ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.interrupt_attaching_to_tangle.InterruptAttachingToTangleCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.interrupt_attaching_to_tangle() + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) \ No newline at end of file diff --git a/test/commands/core/remove_neighbors_test.py b/test/commands/core/remove_neighbors_test.py index 32ca749..571745b 100644 --- a/test/commands/core/remove_neighbors_test.py +++ b/test/commands/core/remove_neighbors_test.py @@ -6,11 +6,11 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota -from iota.adapter import MockAdapter +from iota import Iota, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.core.remove_neighbors import RemoveNeighborsCommand from iota.filters import NodeUri -from test import patch, MagicMock +from test import patch, MagicMock, async_test class RemoveNeighborsRequestFilterTestCase(BaseFilterTestCase): @@ -153,17 +153,16 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.remove_neighbors.RemoveNeighborsCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. response = api.remove_neighbors('uris') self.assertTrue(mocked_command.called) @@ -172,3 +171,25 @@ def test_wireup(self): response, 'You found me!' ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.remove_neighbors.RemoveNeighborsCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.remove_neighbors('uris') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) \ No newline at end of file diff --git a/test/commands/core/store_transactions_test.py b/test/commands/core/store_transactions_test.py index b1a87d1..a034fce 100644 --- a/test/commands/core/store_transactions_test.py +++ b/test/commands/core/store_transactions_test.py @@ -8,11 +8,11 @@ from filters.test import BaseFilterTestCase from six import text_type -from iota import Iota, TransactionTrytes, TryteString -from iota.adapter import MockAdapter +from iota import Iota, TransactionTrytes, TryteString, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.core.store_transactions import StoreTransactionsCommand from iota.filters import Trytes -from test import patch, MagicMock +from test import patch, MagicMock, async_test class StoreTransactionsRequestFilterTestCase(BaseFilterTestCase): @@ -187,17 +187,16 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.store_transactions.StoreTransactionsCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. response = api.store_transactions('trytes') self.assertTrue(mocked_command.called) @@ -206,3 +205,25 @@ def test_wireup(self): response, 'You found me!' ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.store_transactions.StoreTransactionsCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.store_transactions('trytes') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) \ No newline at end of file diff --git a/test/commands/core/were_addresses_spent_from_test.py b/test/commands/core/were_addresses_spent_from_test.py index 399a197..e7b1c28 100644 --- a/test/commands/core/were_addresses_spent_from_test.py +++ b/test/commands/core/were_addresses_spent_from_test.py @@ -7,11 +7,11 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Address, Iota, TryteString -from iota.adapter import MockAdapter +from iota import Address, Iota, TryteString, AsyncIota +from iota.adapter import MockAdapter, async_return from iota.commands.core import WereAddressesSpentFromCommand from iota.filters import Trytes -from test import patch, MagicMock +from test import patch, MagicMock, async_test class WereAddressesSpentFromRequestFilterTestCase(BaseFilterTestCase): @@ -169,17 +169,16 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.commands.core.were_addresses_spent_from.WereAddressesSpentFromCommand.__call__', - MagicMock(return_value='You found me!') - ) as mocked_command: + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: api = Iota(self.adapter) - # Don't need to call with proper args here. response = api.were_addresses_spent_from('addresses') self.assertTrue(mocked_command.called) @@ -188,3 +187,25 @@ def test_wireup(self): response, 'You found me!' ) + + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.commands.core.were_addresses_spent_from.WereAddressesSpentFromCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncIota(self.adapter) + + response = await api.were_addresses_spent_from('addresses') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) \ No newline at end of file From 271cfcfb11bf41cea51c99de5ad1c91e4c33dcf0 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Tue, 11 Feb 2020 13:18:36 +0100 Subject: [PATCH 27/69] Update Multisig API to handle async - New class `AsyncMultisigIota` - Original `MultisigIota` rebased on `AsyncMultisigIota` - Updated multisig commands to work with async - Updated test for async --- iota/multisig/api.py | 253 +++++++++++++++++- .../commands/create_multisig_address.py | 4 +- iota/multisig/commands/get_digests.py | 6 +- iota/multisig/commands/get_private_keys.py | 4 +- .../commands/prepare_multisig_transfer.py | 4 +- .../commands/create_multisig_address_test.py | 38 ++- test/multisig/commands/get_digests_test.py | 47 +++- .../commands/get_private_keys_test.py | 43 ++- .../prepare_multisig_transfer_test.py | 58 ++-- 9 files changed, 398 insertions(+), 59 deletions(-) diff --git a/iota/multisig/api.py b/iota/multisig/api.py index 0ad14b8..d345138 100644 --- a/iota/multisig/api.py +++ b/iota/multisig/api.py @@ -4,21 +4,23 @@ from typing import Iterable, Optional -from iota import Address, Iota, ProposedTransaction +from iota import Address, Iota, AsyncIota, ProposedTransaction from iota.crypto.addresses import AddressGenerator from iota.crypto.types import Digest from iota.multisig import commands from iota.multisig.types import MultisigAddress +import asyncio __all__ = [ 'MultisigIota', + 'AsyncMultisigIota', ] -class MultisigIota(Iota): +class AsyncMultisigIota(AsyncIota): """ Extends the IOTA API so that it can send multi-signature - transactions. + transactions. Asynchronous API. .. caution:: Make sure you understand how multisig works before attempting to @@ -31,7 +33,7 @@ class MultisigIota(Iota): - https://github.com/iotaledger/wiki/blob/master/multisigs.md """ - def create_multisig_address(self, digests): + async def create_multisig_address(self, digests): # type: (Iterable[Digest]) -> dict """ Generates a multisig address from a collection of digests. @@ -52,11 +54,11 @@ def create_multisig_address(self, digests): The generated multisig address. } """ - return commands.CreateMultisigAddressCommand(self.adapter)( + return await commands.CreateMultisigAddressCommand(self.adapter)( digests=digests, ) - def get_digests( + async def get_digests( self, index=0, count=1, @@ -92,14 +94,14 @@ def get_digests( was generated. } """ - return commands.GetDigestsCommand(self.adapter)( + return await commands.GetDigestsCommand(self.adapter)( seed=self.seed, index=index, count=count, securityLevel=security_level, ) - def get_private_keys( + async def get_private_keys( self, index=0, count=1, @@ -141,14 +143,14 @@ def get_private_keys( - :py:class:`iota.crypto.signing.KeyGenerator` - https://github.com/iotaledger/wiki/blob/master/multisigs.md#how-m-of-n-works """ - return commands.GetPrivateKeysCommand(self.adapter)( + return await commands.GetPrivateKeysCommand(self.adapter)( seed=self.seed, index=index, count=count, securityLevel=security_level, ) - def prepare_multisig_transfer( + async def prepare_multisig_transfer( self, transfers, # type: Iterable[ProposedTransaction] multisig_input, # type: MultisigAddress @@ -234,8 +236,237 @@ def prepare_multisig_transfer( proof of work (``attachToTangle``) and broadcast the bundle using :py:meth:`iota.api.Iota.send_trytes`. """ - return commands.PrepareMultisigTransferCommand(self.adapter)( + return await commands.PrepareMultisigTransferCommand(self.adapter)( changeAddress=change_address, multisigInput=multisig_input, transfers=transfers, ) + +class MultisigIota(Iota, AsyncMultisigIota): + """ + Extends the IOTA API so that it can send multi-signature + transactions. Synchronous API. + + .. caution:: + Make sure you understand how multisig works before attempting to + use it. If you are not careful, you could easily compromise the + security of your private keys, send IOTAs to unspendable + addresses, etc. + + References: + + - https://github.com/iotaledger/wiki/blob/master/multisigs.md + """ + + def create_multisig_address(self, digests): + # type: (Iterable[Digest]) -> dict + """ + Generates a multisig address from a collection of digests. + + :param digests: + Digests to use to create the multisig address. + + .. important:: + In order to spend IOTAs from a multisig address, the + signature must be generated from the corresponding private + keys in the exact same order. + + :return: + Dict with the following items:: + + { + 'address': MultisigAddress, + The generated multisig address. + } + """ + return asyncio.get_event_loop().run_until_complete( + super(MultisigIota, self).create_multisig_address(digests) + ) + + def get_digests( + self, + index=0, + count=1, + security_level=AddressGenerator.DEFAULT_SECURITY_LEVEL, + ): + # type: (int, int, int) -> dict + """ + Generates one or more key digests from the seed. + + Digests are safe to share; use them to generate multisig + addresses. + + :param index: + The starting key index. + + :param count: + Number of digests to generate. + + :param security_level: + Number of iterations to use when generating new addresses. + + Larger values take longer, but the resulting signatures are + more secure. + + This value must be between 1 and 3, inclusive. + + :return: + Dict with the following items:: + + { + 'digests': List[Digest], + Always contains a list, even if only one digest + was generated. + } + """ + return asyncio.get_event_loop().run_until_complete( + super(MultisigIota, self).get_digests( + index, + count, + security_level, + ) + ) + + def get_private_keys( + self, + index=0, + count=1, + security_level=AddressGenerator.DEFAULT_SECURITY_LEVEL, + ): + # type: (int, int, int) -> dict + """ + Generates one or more private keys from the seed. + + As the name implies, private keys should not be shared. + However, in a few cases it may be necessary (e.g., for M-of-N + transactions). + + :param index: + The starting key index. + + :param count: + Number of keys to generate. + + :param security_level: + Number of iterations to use when generating new keys. + + Larger values take longer, but the resulting signatures are + more secure. + + This value must be between 1 and 3, inclusive. + + :return: + Dict with the following items:: + + { + 'keys': List[PrivateKey], + Always contains a list, even if only one key was + generated. + } + + References: + + - :py:class:`iota.crypto.signing.KeyGenerator` + - https://github.com/iotaledger/wiki/blob/master/multisigs.md#how-m-of-n-works + """ + return asyncio.get_event_loop().run_until_complete( + super(MultisigIota, self).get_private_keys( + index, + count, + security_level, + ) + ) + + def prepare_multisig_transfer( + self, + transfers, # type: Iterable[ProposedTransaction] + multisig_input, # type: MultisigAddress + change_address=None, # type: Optional[Address] + ): + # type: (...) -> dict + """ + Prepares a bundle that authorizes the spending of IOTAs from a + multisig address. + + .. note:: + This method is used exclusively to spend IOTAs from a + multisig address. + + If you want to spend IOTAs from non-multisig addresses, or + if you want to create 0-value transfers (i.e., that don't + require inputs), use + :py:meth:`iota.api.Iota.prepare_transfer` instead. + + :param transfers: + Transaction objects to prepare. + + .. important:: + Must include at least one transaction that spends IOTAs + (i.e., has a nonzero ``value``). If you want to prepare + a bundle that does not spend any IOTAs, use + :py:meth:`iota.api.prepare_transfer` instead. + + :param multisig_input: + The multisig address to use as the input for the transfers. + + .. note:: + This method only supports creating a bundle with a + single multisig input. + + If you would like to spend from multiple multisig + addresses in the same bundle, create the + :py:class:`iota.multisig.transaction.ProposedMultisigBundle` + object manually. + + :param change_address: + If inputs are provided, any unspent amount will be sent to + this address. + + If the bundle has no unspent inputs, ``change_address` is + ignored. + + .. important:: + Unlike :py:meth:`iota.api.Iota.prepare_transfer`, this + method will NOT generate a change address automatically. + If there are unspent inputs and ``change_address`` is + empty, an exception will be raised. + + This is because multisig transactions typically involve + multiple individuals, and it would be unfair to the + participants if we generated a change address + automatically using the seed of whoever happened to run + the ``prepare_multisig_transfer`` method! + + .. danger:: + Note that this protective measure is not a + substitute for due diligence! + + Always verify the details of every transaction in a + bundle (including the change transaction) before + signing the input(s)! + + :return: + Dict containing the following values:: + + { + 'trytes': List[TransactionTrytes], + Finalized bundle, as trytes. + The input transactions are not signed. + } + + In order to authorize the spending of IOTAs from the multisig + input, you must generate the correct private keys and invoke + the :py:meth:`iota.crypto.types.PrivateKey.sign_input_at` + method for each key, in the correct order. + + Once the correct signatures are applied, you can then perform + proof of work (``attachToTangle``) and broadcast the bundle + using :py:meth:`iota.api.Iota.send_trytes`. + """ + return asyncio.get_event_loop().run_until_complete( + super(MultisigIota, self).prepare_multisig_transfer( + transfers, + multisig_input, + change_address, + ) + ) \ No newline at end of file diff --git a/iota/multisig/commands/create_multisig_address.py b/iota/multisig/commands/create_multisig_address.py index 06ff70c..9aa81b5 100644 --- a/iota/multisig/commands/create_multisig_address.py +++ b/iota/multisig/commands/create_multisig_address.py @@ -32,7 +32,9 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + # There is no async operation going on here, but the base class is async, + # so from the outside, we have to act like we are doing async. + async def _execute(self, request): digests = request['digests'] # type: List[Digest] builder = MultisigAddressBuilder() diff --git a/iota/multisig/commands/get_digests.py b/iota/multisig/commands/get_digests.py index 91256b9..48cbba6 100644 --- a/iota/multisig/commands/get_digests.py +++ b/iota/multisig/commands/get_digests.py @@ -32,13 +32,15 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + # There is no async operation going on here, but the base class is async, + # so from the outside, we have to act like we are doing async. + async def _execute(self, request): count = request['count'] # type: Optional[int] index = request['index'] # type: int seed = request['seed'] # type: Seed security_level = request['securityLevel'] # type: int - gpk_result = GetPrivateKeysCommand(self.adapter)( + gpk_result = await GetPrivateKeysCommand(self.adapter)( seed=seed, count=count, index=index, diff --git a/iota/multisig/commands/get_private_keys.py b/iota/multisig/commands/get_private_keys.py index 2a58abf..58b9f0f 100644 --- a/iota/multisig/commands/get_private_keys.py +++ b/iota/multisig/commands/get_private_keys.py @@ -33,7 +33,9 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + # There is no async operation going on here, but the base class is async, + # so from the outside, we have to act like we are doing async. + async def _execute(self, request): count = request['count'] # type: Optional[int] index = request['index'] # type: int seed = request['seed'] # type: Seed diff --git a/iota/multisig/commands/prepare_multisig_transfer.py b/iota/multisig/commands/prepare_multisig_transfer.py index 7d43c2c..cc339de 100644 --- a/iota/multisig/commands/prepare_multisig_transfer.py +++ b/iota/multisig/commands/prepare_multisig_transfer.py @@ -35,7 +35,7 @@ def get_request_filter(self): def get_response_filter(self): pass - def _execute(self, request): + async def _execute(self, request): change_address = request['changeAddress'] # type: Optional[Address] multisig_input = request['multisigInput'] # type: MultisigAddress transfers = request['transfers'] # type: List[ProposedTransaction] @@ -44,7 +44,7 @@ def _execute(self, request): want_to_spend = bundle.balance if want_to_spend > 0: - gb_response = GetBalancesCommand(self.adapter)( + gb_response = await GetBalancesCommand(self.adapter)( addresses=[multisig_input], ) diff --git a/test/multisig/commands/create_multisig_address_test.py b/test/multisig/commands/create_multisig_address_test.py index be7f3ad..954dce6 100644 --- a/test/multisig/commands/create_multisig_address_test.py +++ b/test/multisig/commands/create_multisig_address_test.py @@ -9,13 +9,13 @@ from six import binary_type from iota import TryteString -from iota.adapter import MockAdapter +from iota.adapter import MockAdapter, async_return from iota.crypto.types import Digest from iota.filters import Trytes -from iota.multisig import MultisigIota +from iota.multisig import MultisigIota, AsyncMultisigIota from iota.multisig.commands import CreateMultisigAddressCommand from iota.multisig.types import MultisigAddress -from test import patch, MagicMock +from test import patch, MagicMock, async_test class CreateMultisigAddressCommandTestCase(TestCase): @@ -49,12 +49,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.multisig.commands.create_multisig_address.CreateMultisigAddressCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = MultisigIota(self.adapter) @@ -69,11 +69,35 @@ def test_wireup(self): 'You found me!' ) - def test_happy_path(self): + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.multisig.commands.create_multisig_address.CreateMultisigAddressCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncMultisigIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.create_multisig_address('digests') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_happy_path(self): """ Generating a multisig address. """ - result = self.command(digests=[self.digest_1, self.digest_2]) + result = await self.command(digests=[self.digest_1, self.digest_2]) # noinspection SpellCheckingInspection self.assertDictEqual( diff --git a/test/multisig/commands/get_digests_test.py b/test/multisig/commands/get_digests_test.py index daa88c2..f201ef7 100644 --- a/test/multisig/commands/get_digests_test.py +++ b/test/multisig/commands/get_digests_test.py @@ -9,14 +9,14 @@ from six import binary_type from iota import Hash, TryteString -from iota.adapter import MockAdapter +from iota.adapter import MockAdapter, async_return from iota.crypto import FRAGMENT_LENGTH from iota.crypto.addresses import AddressGenerator from iota.crypto.types import Digest, PrivateKey, Seed from iota.filters import Trytes -from iota.multisig import MultisigIota +from iota.multisig import MultisigIota, AsyncMultisigIota from iota.multisig.commands import GetDigestsCommand -from test import mock, patch, MagicMock +from test import mock, patch, MagicMock, async_test class GetDigestsCommandTestCase(TestCase): @@ -36,12 +36,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.multisig.commands.get_digests.GetDigestsCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = MultisigIota(self.adapter) @@ -56,13 +56,37 @@ def test_wireup(self): 'You found me!' ) - def test_generate_single_digest(self): + @async_test + async def test_wireup(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.multisig.commands.get_digests.GetDigestsCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncMultisigIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.get_digests() + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_generate_single_digest(self): """ Generating a single digest. """ seed = Seed.random() - mock_get_private_keys = mock.Mock(return_value={'keys': [self.key1]}) + mock_get_private_keys = mock.Mock(return_value=async_return({'keys': [self.key1]})) with mock.patch( 'iota.multisig.commands.get_private_keys.GetPrivateKeysCommand._execute', @@ -72,7 +96,7 @@ def test_generate_single_digest(self): with mock.patch.object(self.key1, 'get_digest') as mock_get_digest_1: # type: mock.MagicMock mock_get_digest_1.return_value = self.digest1 - result = self.command(seed=seed, index=0, count=1, securityLevel=1) + result = await self.command(seed=seed, index=0, count=1, securityLevel=1) self.assertDictEqual(result, {'digests': [self.digest1]}) @@ -83,14 +107,15 @@ def test_generate_single_digest(self): 'seed': seed, }) - def test_generate_multiple_digests(self): + @async_test + async def test_generate_multiple_digests(self): """ Generating multiple digests. """ seed = Seed.random() mock_get_private_keys =\ - mock.Mock(return_value={'keys': [self.key1, self.key2]}) + mock.Mock(return_value=async_return({'keys': [self.key1, self.key2]})) with mock.patch( 'iota.multisig.commands.get_private_keys.GetPrivateKeysCommand._execute', @@ -104,7 +129,7 @@ def test_generate_multiple_digests(self): with mock.patch.object(self.key2, 'get_digest') as mock_get_digest_2: # type: mock.MagicMock mock_get_digest_2.return_value = self.digest2 - result = self.command(seed=seed, index=0, count=2, securityLevel=1) + result = await self.command(seed=seed, index=0, count=2, securityLevel=1) self.assertDictEqual(result, {'digests': [self.digest1, self.digest2]}) diff --git a/test/multisig/commands/get_private_keys_test.py b/test/multisig/commands/get_private_keys_test.py index a6dc7d5..8c541e2 100644 --- a/test/multisig/commands/get_private_keys_test.py +++ b/test/multisig/commands/get_private_keys_test.py @@ -9,14 +9,14 @@ from six import binary_type from iota import TryteString -from iota.adapter import MockAdapter +from iota.adapter import MockAdapter, async_return from iota.crypto import FRAGMENT_LENGTH from iota.crypto.addresses import AddressGenerator from iota.crypto.types import PrivateKey, Seed from iota.filters import Trytes -from iota.multisig import MultisigIota +from iota.multisig import MultisigIota, AsyncMultisigIota from iota.multisig.commands import GetPrivateKeysCommand -from test import mock, patch, MagicMock +from test import mock, patch, MagicMock, async_test class GetPrivateKeysCommandTestCase(TestCase): @@ -40,12 +40,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.multisig.commands.get_private_keys.GetPrivateKeysCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = MultisigIota(self.adapter) @@ -60,7 +60,31 @@ def test_wireup(self): 'You found me!' ) - def test_generate_single_key(self): + @async_test + async def test_wireup_async(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.multisig.commands.get_private_keys.GetPrivateKeysCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncMultisigIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.get_private_keys() + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_generate_single_key(self): """ Generating a single key. """ @@ -68,7 +92,7 @@ def test_generate_single_key(self): mock_get_keys = mock.Mock(return_value=keys) with mock.patch('iota.crypto.signing.KeyGenerator.get_keys', mock_get_keys): - result = self.command(seed=Seed.random(), securityLevel=2) + result = await self.command(seed=Seed.random(), securityLevel=2) self.assertDictEqual(result, {'keys': keys}) mock_get_keys.assert_called_once_with( @@ -77,7 +101,8 @@ def test_generate_single_key(self): start = 0, ) - def test_generate_multiple_keys(self): + @async_test + async def test_generate_multiple_keys(self): """ Generating multiple keys. """ @@ -86,7 +111,7 @@ def test_generate_multiple_keys(self): mock_get_keys = mock.Mock(return_value=keys) with mock.patch('iota.crypto.signing.KeyGenerator.get_keys', mock_get_keys): result =\ - self.command( + await self.command( count = 2, index = 0, securityLevel = 1, diff --git a/test/multisig/commands/prepare_multisig_transfer_test.py b/test/multisig/commands/prepare_multisig_transfer_test.py index bbf4ac6..46dd373 100644 --- a/test/multisig/commands/prepare_multisig_transfer_test.py +++ b/test/multisig/commands/prepare_multisig_transfer_test.py @@ -8,13 +8,13 @@ from filters.test import BaseFilterTestCase from iota import Address, Bundle, Fragment, ProposedTransaction -from iota.adapter import MockAdapter +from iota.adapter import MockAdapter, async_return from iota.commands.core import GetBalancesCommand from iota.crypto.types import Digest -from iota.multisig import MultisigIota +from iota.multisig import MultisigIota, AsyncMultisigIota from iota.multisig.commands import PrepareMultisigTransferCommand from iota.multisig.types import MultisigAddress -from test import patch, MagicMock +from test import patch, MagicMock, async_test class PrepareMultisigTransferRequestFilterTestCase(BaseFilterTestCase): @@ -527,12 +527,12 @@ def setUp(self): def test_wireup(self): """ - Verify that the command is wired up correctly. + Verify that the command is wired up correctly. (sync) The API method indeed calls the appropiate command. """ with patch('iota.multisig.commands.prepare_multisig_transfer.PrepareMultisigTransferCommand.__call__', - MagicMock(return_value='You found me!') + MagicMock(return_value=async_return('You found me!')) ) as mocked_command: api = MultisigIota(self.adapter) @@ -547,7 +547,31 @@ def test_wireup(self): 'You found me!' ) - def test_happy_path(self): + @async_test + async def test_wireup(self): + """ + Verify that the command is wired up correctly. (async) + + The API method indeed calls the appropiate command. + """ + with patch('iota.multisig.commands.prepare_multisig_transfer.PrepareMultisigTransferCommand.__call__', + MagicMock(return_value=async_return('You found me!')) + ) as mocked_command: + + api = AsyncMultisigIota(self.adapter) + + # Don't need to call with proper args here. + response = await api.prepare_multisig_transfer('transfer', 'multisig_input') + + self.assertTrue(mocked_command.called) + + self.assertEqual( + response, + 'You found me!' + ) + + @async_test + async def test_happy_path(self): """ Preparing a bundle with a multisig input. """ @@ -563,7 +587,7 @@ def test_happy_path(self): ) pmt_result =\ - self.command( + await self.command( transfers = [ ProposedTransaction( address = Address(self.trytes_1), @@ -624,7 +648,8 @@ def test_happy_path(self): self.assertEqual(txn_5.value, 0) self.assertEqual(txn_5.signature_message_fragment, Fragment(b'')) - def test_unspent_inputs_with_change_address(self): + @async_test + async def test_unspent_inputs_with_change_address(self): """ The bundle has unspent inputs, so it uses the provided change address. @@ -639,7 +664,7 @@ def test_unspent_inputs_with_change_address(self): ) pmt_result =\ - self.command( + await self.command( transfers = [ ProposedTransaction( address = Address(self.trytes_1), @@ -694,7 +719,8 @@ def test_unspent_inputs_with_change_address(self): self.assertEqual(txn_6.address, self.trytes_3) self.assertEqual(txn_6.value, 59) - def test_error_zero_iotas_transferred(self): + @async_test + async def test_error_zero_iotas_transferred(self): """ The bundle doesn't spend any IOTAs. @@ -705,7 +731,7 @@ def test_error_zero_iotas_transferred(self): using :py:meth:`iota.api.Iota.prepare_transfer` instead. """ with self.assertRaises(ValueError): - self.command( + await self.command( transfers = [ ProposedTransaction( address = Address(self.trytes_1), @@ -720,7 +746,8 @@ def test_error_zero_iotas_transferred(self): ), ) - def test_error_insufficient_inputs(self): + @async_test + async def test_error_insufficient_inputs(self): """ The multisig input does not contain sufficient IOTAs to cover the spends. @@ -735,7 +762,7 @@ def test_error_insufficient_inputs(self): ) with self.assertRaises(ValueError): - self.command( + await self.command( transfers = [ ProposedTransaction( address = Address(self.trytes_1), @@ -750,7 +777,8 @@ def test_error_insufficient_inputs(self): ), ) - def test_error_unspent_inputs_no_change_address(self): + @async_test + async def test_error_unspent_inputs_no_change_address(self): """ The bundle has unspent inputs, but no change address was specified. @@ -773,7 +801,7 @@ def test_error_unspent_inputs_no_change_address(self): ) with self.assertRaises(ValueError): - self.command( + await self.command( transfers = [ ProposedTransaction( address = Address(self.trytes_1), From 5a71f8c96b528ce18eb0221df43eb93d73407956 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Tue, 11 Feb 2020 13:20:46 +0100 Subject: [PATCH 28/69] Remove support for Python 2 and 3.5 The new async functionalities are incompatible with anything older, than Python 3.6. Future work: - Clean up legacy PY2 -> PY3 code snippets. (future, six, etc..) - Transform codebase to use PY3 style code. --- .travis.yml | 2 -- tox.ini | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 33763b0..63d0025 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: python python: - - '2.7' - - '3.5' - '3.6' - '3.7' install: diff --git a/tox.ini b/tox.ini index 3220f26..9e030e8 100644 --- a/tox.ini +++ b/tox.ini @@ -4,10 +4,11 @@ # and then run "tox" from this directory. [tox] -envlist = py27, py35, py36, py37 +envlist = py36, py37 [testenv] commands = nosetests deps = mock nose + aiounittest From 920fb67f19de258bbe4164ed93994ede89339643 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Tue, 11 Feb 2020 17:07:18 +0100 Subject: [PATCH 29/69] Install [test-runner] in Travis CI --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 63d0025..916012f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ python: - '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 From 455940a5019da1b2fa8e0c15eb904372015fd2b5 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Wed, 12 Feb 2020 11:42:05 +0100 Subject: [PATCH 30/69] fix bug in Tutorial 7 - get_bundles() accepts a list as input --- examples/tutorials/07_fetch_encrypted.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tutorials/07_fetch_encrypted.py b/examples/tutorials/07_fetch_encrypted.py index ebffc6d..3ca2a99 100644 --- a/examples/tutorials/07_fetch_encrypted.py +++ b/examples/tutorials/07_fetch_encrypted.py @@ -18,7 +18,7 @@ print('Looking for bundle on the Tangle...') # Fetch bundle -bundle = api.get_bundles(tail_hash)['bundles'][0] +bundle = api.get_bundles([tail_hash])['bundles'][0] print('Extracting data from bundle...') # Get all messages from the bundle and concatenate them From 321fa4731c1892a9233300352554a2e616f04e23 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Wed, 12 Feb 2020 15:48:05 +0100 Subject: [PATCH 31/69] Use py3-style super() calls in api classes --- iota/api.py | 116 ++++++++++++++++++++++++++-------------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/iota/api.py b/iota/api.py index 2a57b14..ea23e97 100644 --- a/iota/api.py +++ b/iota/api.py @@ -76,7 +76,7 @@ def __init__(self, adapter, devnet=False, local_pow=False): :ref:`find out` how to use it. """ - super(AsyncStrictIota, self).__init__() + super().__init__() if not isinstance(adapter, BaseAdapter): adapter = resolve_adapter(adapter) @@ -787,19 +787,19 @@ class StrictIota(AsyncStrictIota): def __init__(self, adapter, devnet=False, local_pow=False): # type: (AdapterSpec, bool, bool) -> None # Copy parent's doctrsing - __doc__ = super(StrictIota, self).__init__.__doc__ - super(StrictIota, self).__init__(adapter, devnet, local_pow) + __doc__ = super().__init__.__doc__ + super().__init__(adapter, devnet, local_pow) def add_neighbors(self, uris): # type: (Iterable[Text]) -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).add_neighbors.__doc__ + __doc__ = super().add_neighbors.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).add_neighbors(uris) + super().add_neighbors(uris) ) def attach_to_tangle( @@ -811,12 +811,12 @@ def attach_to_tangle( ): # type: (...) -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).attach_to_tangle.__doc__ + __doc__ = super().attach_to_tangle.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).attach_to_tangle( + super().attach_to_tangle( trunk_transaction, branch_transaction, trytes, @@ -827,12 +827,12 @@ def attach_to_tangle( def broadcast_transactions(self, trytes): # type: (Iterable[TryteString]) -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).broadcast_transactions.__doc__ + __doc__ = super().broadcast_transactions.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).broadcast_transactions( + super().broadcast_transactions( trytes, ) ) @@ -841,12 +841,12 @@ def broadcast_transactions(self, trytes): def check_consistency(self, tails): # type: (Iterable[TransactionHash]) -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).check_consistency.__doc__ + __doc__ = super().check_consistency.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).check_consistency( + super().check_consistency( tails, ) ) @@ -860,12 +860,12 @@ def find_transactions( ): # type: (...) -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).find_transactions.__doc__ + __doc__ = super().find_transactions.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).find_transactions( + super().find_transactions( bundles, addresses, tags, @@ -881,12 +881,12 @@ def get_balances( ): # type: (...) -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).get_balances.__doc__ + __doc__ = super().get_balances.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).get_balances( + super().get_balances( addresses, threshold, tips, @@ -896,12 +896,12 @@ def get_balances( def get_inclusion_states(self, transactions, tips): # type: (Iterable[TransactionHash], Iterable[TransactionHash]) -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).get_inclusion_states.__doc__ + __doc__ = super().get_inclusion_states.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).get_inclusion_states( + super().get_inclusion_states( transactions, tips, ) @@ -910,67 +910,67 @@ def get_inclusion_states(self, transactions, tips): def get_missing_transactions(self): # type: () -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).get_missing_transactions.__doc__ + __doc__ = super().get_missing_transactions.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).get_missing_transactions() + super().get_missing_transactions() ) def get_neighbors(self): # type: () -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).get_neighbors.__doc__ + __doc__ = super().get_neighbors.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).get_neighbors() + super().get_neighbors() ) def get_node_api_configuration(self): # type: () -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).get_node_api_configuration.__doc__ + __doc__ = super().get_node_api_configuration.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).get_node_api_configuration() + super().get_node_api_configuration() ) def get_node_info(self): # type: () -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).get_node_info.__doc__ + __doc__ = super().get_node_info.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).get_node_info() + super().get_node_info() ) def get_tips(self): # type: () -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).get_tips.__doc__ + __doc__ = super().get_tips.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).get_tips() + super().get_tips() ) def get_transactions_to_approve(self, depth, reference=None): # type: (int, Optional[TransactionHash]) -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).get_transactions_to_approve.__doc__ + __doc__ = super().get_transactions_to_approve.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).get_transactions_to_approve( + super().get_transactions_to_approve( depth, reference, ) @@ -979,12 +979,12 @@ def get_transactions_to_approve(self, depth, reference=None): def get_trytes(self, hashes): # type: (Iterable[TransactionHash]) -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).get_trytes.__doc__ + __doc__ = super().get_trytes.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).get_trytes( + super().get_trytes( hashes, ) ) @@ -992,45 +992,45 @@ def get_trytes(self, hashes): def interrupt_attaching_to_tangle(self): # type: () -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).interrupt_attaching_to_tangle.__doc__ + __doc__ = super().interrupt_attaching_to_tangle.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).interrupt_attaching_to_tangle() + super().interrupt_attaching_to_tangle() ) def remove_neighbors(self, uris): # type: (Iterable[Text]) -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).remove_neighbors.__doc__ + __doc__ = super().remove_neighbors.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).remove_neighbors(uris) + super().remove_neighbors(uris) ) def store_transactions(self, trytes): # type: (Iterable[TryteString]) -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).store_transactions.__doc__ + __doc__ = super().store_transactions.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).store_transactions(trytes) + super().store_transactions(trytes) ) def were_addresses_spent_from(self, addresses): # type: (Iterable[Address]) -> dict # Copy parent's docstring - __doc__ = super(StrictIota, self).were_addresses_spent_from.__doc__ + __doc__ = super().were_addresses_spent_from.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(StrictIota, self).were_addresses_spent_from(addresses) + super().were_addresses_spent_from(addresses) ) @@ -1081,7 +1081,7 @@ def __init__(self, adapter, seed=None, devnet=False, local_pow=False): .. note:: This value is never transferred to the node/network. """ - super(AsyncIota, self).__init__(adapter, devnet, local_pow) + super().__init__(adapter, devnet, local_pow) self.seed = Seed(seed) if seed else Seed.random() @@ -2024,7 +2024,7 @@ def broadcast_and_store(self, trytes): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).broadcast_and_store(trytes) + super().broadcast_and_store(trytes) ) def broadcast_bundle(self, tail_transaction_hash): @@ -2053,7 +2053,7 @@ def broadcast_bundle(self, tail_transaction_hash): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).broadcast_bundle(tail_transaction_hash) + super().broadcast_bundle(tail_transaction_hash) ) def find_transaction_objects( @@ -2104,7 +2104,7 @@ def find_transaction_objects( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).find_transaction_objects( + super().find_transaction_objects( bundles, addresses, tags, @@ -2184,7 +2184,7 @@ def get_account_data(self, start=0, stop=None, inclusion_states=False, security_ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).get_account_data( + super().get_account_data( start, stop, inclusion_states, @@ -2221,7 +2221,7 @@ def get_bundles(self, transactions): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).get_bundles(transactions) + super().get_bundles(transactions) ) def get_inputs( @@ -2331,7 +2331,7 @@ def get_inputs( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).get_inputs( + super().get_inputs( start, stop, threshold, @@ -2364,7 +2364,7 @@ def get_latest_inclusion(self, hashes): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).get_latest_inclusion(hashes) + super().get_latest_inclusion(hashes) ) def get_new_addresses( @@ -2431,7 +2431,7 @@ def get_new_addresses( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).get_new_addresses( + super().get_new_addresses( count=count, index=index, security_level=security_level, @@ -2468,7 +2468,7 @@ def get_transaction_objects( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).get_transaction_objects(hashes) + super().get_transaction_objects(hashes) ) def get_transfers(self, start=0, stop=None, inclusion_states=False): @@ -2527,7 +2527,7 @@ def get_transfers(self, start=0, stop=None, inclusion_states=False): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).get_transfers( + super().get_transfers( start, stop, inclusion_states, @@ -2572,7 +2572,7 @@ def is_promotable( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).is_promotable(tails) + super().is_promotable(tails) ) def prepare_transfer( @@ -2632,7 +2632,7 @@ def prepare_transfer( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).prepare_transfer( + super().prepare_transfer( transfers, inputs, change_address, @@ -2674,7 +2674,7 @@ def promote_transaction( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).promote_transaction( + super().promote_transaction( transaction, depth, min_weight_magnitude, @@ -2721,7 +2721,7 @@ def replay_bundle( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).replay_bundle( + super().replay_bundle( transaction, depth, min_weight_magnitude, @@ -2791,7 +2791,7 @@ def send_transfer( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).send_transfer( + super().send_transfer( transfers, depth, inputs, @@ -2835,7 +2835,7 @@ def send_trytes(self, trytes, depth=3, min_weight_magnitude=None): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).send_trytes( + super().send_trytes( trytes, depth, min_weight_magnitude, @@ -2872,7 +2872,7 @@ def is_reattachable(self, addresses): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).is_reattachable( + super().is_reattachable( addresses, ) ) @@ -2904,7 +2904,7 @@ def traverse_bundle(self, tail_hash): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super(Iota, self).traverse_bundle( + super().traverse_bundle( tail_hash, ) ) \ No newline at end of file From e91a59ff90c69eb1f077fac0adb57a08d65ea789 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Fri, 14 Feb 2020 14:27:15 +0100 Subject: [PATCH 32/69] Reorder deps and imports in alphabetical order - docstrings update - info in comment for future devs about api classes --- iota/api.py | 28 ++++++++++++++++++++++++++-- iota/commands/extended/utils.py | 5 ++++- setup.py | 6 +++--- tox.ini | 2 +- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/iota/api.py b/iota/api.py index ea23e97..f47e2ab 100644 --- a/iota/api.py +++ b/iota/api.py @@ -13,11 +13,11 @@ import asyncio __all__ = [ + 'AsyncIota', + 'AsyncStrictIota', 'InvalidCommand', 'Iota', 'StrictIota', - 'AsyncStrictIota', - 'AsyncIota', ] @@ -1950,6 +1950,30 @@ async def traverse_bundle(self, tail_hash): transaction=tail_hash ) +# There is a compact and easy way to create the synchronous version of the async +# classes: + +# import inspect +# def make_synchronous(new_name, async_class: type): +# def make_sync(method): +# def sync_version(*args, **kwargs): +# return asyncio.get_event_loop().run_until_complete(method(*args, **kwargs)) +# return sync_version + +# return type(new_name, (async_class,), { +# name: make_sync(method) if inspect.iscoroutinefunction(method) else method +# for name, method in inspect.getmembers(async_class) +# }) + +# # create the sync version of the class +# Iota = make_synchronous('Iota', AsyncIota) + +# While this approach would work, no IDE static analysis would pick up the +# method definitions or docstrings for the new `Iota` class, meaning no +# suggestions, intellisense, code completion, etc. for the user. +# Therefore we keep the manual approach. + + class Iota(StrictIota, AsyncIota): """ Implements the synchronous core API, plus additional synchronous wrapper diff --git a/iota/commands/extended/utils.py b/iota/commands/extended/utils.py index aebe215..0989850 100644 --- a/iota/commands/extended/utils.py +++ b/iota/commands/extended/utils.py @@ -19,7 +19,6 @@ from iota.crypto.types import Seed -# This is an async generator! async def iter_used_addresses( adapter, # type: BaseAdapter seed, # type: Seed @@ -33,6 +32,10 @@ async def iter_used_addresses( This is basically the opposite of invoking ``getNewAddresses`` with ``count=None``. + + .. important:: + This is an async generator! + """ if security_level is None: security_level = AddressGenerator.DEFAULT_SECURITY_LEVEL diff --git a/setup.py b/setup.py index 5f6528e..7066291 100644 --- a/setup.py +++ b/setup.py @@ -28,9 +28,9 @@ # either automatically (``python setup.py test``) or manually # (``pip install -e .[test-runner]``). tests_require = [ + 'aiounittest', 'mock; python_version < "3.0"', 'nose', - 'aiounittest', ] ## @@ -63,21 +63,21 @@ install_requires=[ 'filters; python_version < "3.5"', + 'httpx', 'phx-filters; python_version >= "3.5"', 'pysha3', # ``security`` extra wasn't introduced until 2.4.1 # http://docs.python-requests.org/en/latest/community/updates/#id35 'requests[security] >= 2.4.1', - 'httpx', 'six', 'typing; python_version < "3.0"', ], extras_require={ 'ccurl': ['pyota-ccurl'], - 'pow': ['pyota-pow >= 1.0.2'], 'docs-builder': ['sphinx', 'sphinx_rtd_theme'], + 'pow': ['pyota-pow >= 1.0.2'], # tox is able to run the tests in parallel since version 3.7 'test-runner': ['tox >= 3.7'] + tests_require, }, diff --git a/tox.ini b/tox.ini index 9e030e8..821cac8 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,6 @@ envlist = py36, py37 [testenv] commands = nosetests deps = + aiounittest mock nose - aiounittest From ee7faa514ed4d4a6422e441e3228614c6a5004c6 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Fri, 14 Feb 2020 14:40:35 +0100 Subject: [PATCH 33/69] Update docs: no more PY2 and PY3.5 support --- README.md | 2 +- docs/README.rst | 2 +- docs/getting_started.rst | 2 +- setup.py | 3 --- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c13c6b7..390e9a5 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/README.rst b/docs/README.rst index a3a8988..740ed23 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -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 diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 574ebb5..c8a0187 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -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`: diff --git a/setup.py b/setup.py index 7066291..f6d2079 100644 --- a/setup.py +++ b/setup.py @@ -92,10 +92,7 @@ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Topic :: Software Development :: Libraries :: Python Modules', From 2c59955fbae9766e0cfa87b6b505639b491634a3 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 17 Feb 2020 11:28:06 +0100 Subject: [PATCH 34/69] Separate sync and async api into two src files - Better reflect the diff between them, - For easier code maintenance , - Add explanatory comment. --- iota/__init__.py | 1 + iota/api.py | 1513 +++++------------------------------------ iota/api_async.py | 1656 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1826 insertions(+), 1344 deletions(-) create mode 100644 iota/api_async.py diff --git a/iota/__init__.py b/iota/__init__.py index 8ab602b..65e1da7 100644 --- a/iota/__init__.py +++ b/iota/__init__.py @@ -35,6 +35,7 @@ from .types import * from .transaction import * from .adapter import * +from .api_async import * from .api import * from .trits import * diff --git a/iota/api.py b/iota/api.py index f47e2ab..50110db 100644 --- a/iota/api.py +++ b/iota/api.py @@ -1,20 +1,10 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - -from typing import Dict, Iterable, Optional, Text - from iota import AdapterSpec, Address, BundleHash, ProposedTransaction, Tag, \ - TransactionHash, TransactionTrytes, TryteString, TrytesCompatible -from iota.adapter import BaseAdapter, resolve_adapter -from iota.commands import BaseCommand, CustomCommand, core, extended + TransactionHash, TransactionTrytes, TryteString from iota.crypto.addresses import AddressGenerator -from iota.crypto.types import Seed +from iota.api_async import AsyncStrictIota, AsyncIota import asyncio __all__ = [ - 'AsyncIota', - 'AsyncStrictIota', 'InvalidCommand', 'Iota', 'StrictIota', @@ -27,9 +17,32 @@ class InvalidCommand(ValueError): """ pass -class AsyncStrictIota(object): +# There is a compact and easy way to create the synchronous version of the async +# classes: + +# import inspect +# def make_synchronous(new_name, async_class: type): +# def make_sync(method): +# def sync_version(*args, **kwargs): +# return asyncio.get_event_loop().run_until_complete(method(*args, **kwargs)) +# return sync_version + +# return type(new_name, (async_class,), { +# name: make_sync(method) if inspect.iscoroutinefunction(method) else method +# for name, method in inspect.getmembers(async_class) +# }) + +# # create the sync version of the class +# Iota = make_synchronous('Iota', AsyncIota) + +# While this approach would work, no IDE static analysis would pick up the +# method definitions or docstrings for the new `Iota` class, meaning no +# suggestions, intellisense, code completion, etc. for the user. +# Therefore we keep the manual approach. + +class StrictIota(AsyncStrictIota): """ - Asynchronous API to send HTTP requests for communicating with an IOTA node. + Synchronous API to send HTTP requests for communicating with an IOTA node. This implementation only exposes the "core" API methods. For a more feature-complete implementation, use :py:class:`Iota` instead. @@ -74,71 +87,11 @@ def __init__(self, adapter, devnet=False, local_pow=False): See :ref:`Optional Local Pow` for more info and :ref:`find out` how to use it. - - """ - super().__init__() - - if not isinstance(adapter, BaseAdapter): - adapter = resolve_adapter(adapter) - - self.adapter = adapter # type: BaseAdapter - # Note that the `local_pow` parameter is passed to adapter, - # the api class has no notion about it. The reason being, - # that this parameter is used in `AttachToTangeCommand` calls, - # that is called from various api calls (`attach_to_tangle`, - # `send_trytes` or `send_transfer`). Inside `AttachToTangeCommand`, - # we no longer have access to the attributes of the API class, therefore - # `local_pow` needs to be associated with the adapter. - # Logically, `local_pow` will decide if the api call does pow - # via pyota-pow extension, or sends the request to a node. - # But technically, the parameter belongs to the adapter. - self.adapter.set_local_pow(local_pow) - self.devnet = devnet - - def create_command(self, command): - # type: (Text) -> CustomCommand """ - Creates a pre-configured CustomCommand instance. - - This method is useful for invoking undocumented or experimental - methods, or if you just want to troll your node for awhile. - - :param Text command: - The name of the command to create. - - """ - return CustomCommand(self.adapter, command) - - def set_local_pow(self, local_pow): - # type: (bool) -> None - """ - Sets the :py:attr:`local_pow` attribute of the adapter of the api - instance. If it is ``True``, :py:meth:`attach_to_tangle` command calls - external interface to perform proof of work, instead of sending the - request to a node. - - By default, :py:attr:`local_pow` is set to ``False``. - This particular method is needed if one wants to change - local_pow behavior dynamically. - - :param bool local_pow: - Whether to perform pow locally. - - :returns: None - - """ - self.adapter.set_local_pow(local_pow) + super().__init__(adapter, devnet, local_pow) - @property - def default_min_weight_magnitude(self): - # type: () -> int - """ - Returns the default ``min_weight_magnitude`` value to use for - API requests. - """ - return 9 if self.devnet else 14 - async def add_neighbors(self, uris): + def add_neighbors(self, uris): # type: (Iterable[Text]) -> dict """ Add one or more neighbors to the node. Lasts until the node is @@ -167,9 +120,14 @@ async def add_neighbors(self, uris): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#addneighbors """ - return await core.AddNeighborsCommand(self.adapter)(uris=uris) - async def attach_to_tangle( + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().add_neighbors(uris) + ) + + def attach_to_tangle( self, trunk_transaction, # type: TransactionHash branch_transaction, # type: TransactionHash @@ -213,17 +171,19 @@ async def attach_to_tangle( - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#attachtotangle """ - if min_weight_magnitude is None: - min_weight_magnitude = self.default_min_weight_magnitude - return await core.AttachToTangleCommand(self.adapter)( - trunkTransaction=trunk_transaction, - branchTransaction=branch_transaction, - minWeightMagnitude=min_weight_magnitude, - trytes=trytes, + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().attach_to_tangle( + trunk_transaction, + branch_transaction, + trytes, + min_weight_magnitude, + ) ) - async def broadcast_transactions(self, trytes): + def broadcast_transactions(self, trytes): # type: (Iterable[TryteString]) -> dict """ Broadcast a list of transactions to all neighbors. @@ -246,9 +206,17 @@ async def broadcast_transactions(self, trytes): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#broadcasttransactions """ - return await core.BroadcastTransactionsCommand(self.adapter)(trytes=trytes) - async def check_consistency(self, tails): + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().broadcast_transactions( + trytes, + ) + ) + + + def check_consistency(self, tails): # type: (Iterable[TransactionHash]) -> dict """ Used to ensure tail resolves to a consistent ledger which is @@ -278,11 +246,16 @@ async def check_consistency(self, tails): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#checkconsistency """ - return await core.CheckConsistencyCommand(self.adapter)( - tails=tails, + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().check_consistency( + tails, + ) ) - async def find_transactions( + def find_transactions( self, bundles=None, # type: Optional[Iterable[BundleHash]] addresses=None, # type: Optional[Iterable[Address]] @@ -325,14 +298,19 @@ async def find_transactions( - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#findtransactions """ - return await core.FindTransactionsCommand(self.adapter)( - bundles=bundles, - addresses=addresses, - tags=tags, - approvees=approvees, + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().find_transactions( + bundles, + addresses, + tags, + approvees, + ) ) - async def get_balances( + def get_balances( self, addresses, # type: Iterable[Address] threshold=100, # type: int @@ -380,13 +358,18 @@ async def get_balances( - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getbalances """ - return await core.GetBalancesCommand(self.adapter)( - addresses=addresses, - threshold=threshold, - tips=tips, + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().get_balances( + addresses, + threshold, + tips, + ) ) - async def get_inclusion_states(self, transactions, tips): + def get_inclusion_states(self, transactions, tips): # type: (Iterable[TransactionHash], Iterable[TransactionHash]) -> dict """ Get the inclusion states of a set of transactions. This is for @@ -418,12 +401,17 @@ async def get_inclusion_states(self, transactions, tips): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getinclusionstates """ - return await core.GetInclusionStatesCommand(self.adapter)( - transactions=transactions, - tips=tips, + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().get_inclusion_states( + transactions, + tips, + ) ) - async def get_missing_transactions(self): + def get_missing_transactions(self): # type: () -> dict """ Returns all transaction hashes that a node is currently requesting @@ -443,9 +431,14 @@ async def get_missing_transactions(self): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getmissingtransactions """ - return await core.GetMissingTransactionsCommand(self.adapter)() - async def get_neighbors(self): + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().get_missing_transactions() + ) + + def get_neighbors(self): # type: () -> dict """ Returns the set of neighbors the node is connected with, as well @@ -476,9 +469,14 @@ async def get_neighbors(self): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getneighbors """ - return await core.GetNeighborsCommand(self.adapter)() - async def get_node_api_configuration(self): + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().get_neighbors() + ) + + def get_node_api_configuration(self): # type: () -> dict """ Returns a node's API configuration settings. @@ -500,9 +498,14 @@ async def get_node_api_configuration(self): - https://docs.iota.org/docs/node-software/0.1/iri/references/iri-configuration-options - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getnodeapiconfiguration """ - return await core.GetNodeAPIConfigurationCommand(self.adapter)() - async def get_node_info(self): + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().get_node_api_configuration() + ) + + def get_node_info(self): # type: () -> dict """ Returns information about the node. @@ -559,9 +562,14 @@ async def get_node_info(self): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getnodeinfo """ - return await core.GetNodeInfoCommand(self.adapter)() - async def get_tips(self): + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().get_node_info() + ) + + def get_tips(self): # type: () -> dict """ Returns the list of tips (transactions which have no other @@ -582,9 +590,14 @@ async def get_tips(self): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#gettips - https://docs.iota.org/docs/dev-essentials/0.1/references/glossary """ - return await core.GetTipsCommand(self.adapter)() - async def get_transactions_to_approve(self, depth, reference=None): + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().get_tips() + ) + + def get_transactions_to_approve(self, depth, reference=None): # type: (int, Optional[TransactionHash]) -> dict """ Tip selection which returns ``trunkTransaction`` and @@ -618,12 +631,17 @@ async def get_transactions_to_approve(self, depth, reference=None): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#gettransactionstoapprove """ - return await core.GetTransactionsToApproveCommand(self.adapter)( - depth=depth, - reference=reference, + + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().get_transactions_to_approve( + depth, + reference, + ) ) - async def get_trytes(self, hashes): + def get_trytes(self, hashes): # type: (Iterable[TransactionHash]) -> dict """ Returns the raw transaction data (trytes) of one or more @@ -649,9 +667,16 @@ async def get_trytes(self, hashes): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#gettrytes """ - return await core.GetTrytesCommand(self.adapter)(hashes=hashes) - async def interrupt_attaching_to_tangle(self): + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().get_trytes( + hashes, + ) + ) + + def interrupt_attaching_to_tangle(self): # type: () -> dict """ Interrupts and completely aborts the :py:meth:`attach_to_tangle` @@ -669,9 +694,14 @@ async def interrupt_attaching_to_tangle(self): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#interruptattachingtotangle """ - return await core.InterruptAttachingToTangleCommand(self.adapter)() - async def remove_neighbors(self, uris): + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().interrupt_attaching_to_tangle() + ) + + def remove_neighbors(self, uris): # type: (Iterable[Text]) -> dict """ Removes one or more neighbors from the node. Lasts until the @@ -695,9 +725,14 @@ async def remove_neighbors(self, uris): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#removeneighbors """ - return await core.RemoveNeighborsCommand(self.adapter)(uris=uris) - async def store_transactions(self, trytes): + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().remove_neighbors(uris) + ) + + def store_transactions(self, trytes): # type: (Iterable[TryteString]) -> dict """ Store transactions into local storage of the node. @@ -722,9 +757,14 @@ async def store_transactions(self, trytes): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#storetransactions """ - return await core.StoreTransactionsCommand(self.adapter)(trytes=trytes) - async def were_addresses_spent_from(self, addresses): + # Execute original coroutine inside an event loop to make this method + # synchronous + return asyncio.get_event_loop().run_until_complete( + super().store_transactions(trytes) + ) + + def were_addresses_spent_from(self, addresses): # type: (Iterable[Address]) -> dict """ Check if a list of addresses was ever spent from, in the current @@ -751,1228 +791,13 @@ async def were_addresses_spent_from(self, addresses): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#wereaddressesspentfrom """ - return await core.WereAddressesSpentFromCommand(self.adapter)( - addresses=addresses, - ) - -class StrictIota(AsyncStrictIota): - """ - Synchronous API to send HTTP requests for communicating with an IOTA node. - - This implementation only exposes the "core" API methods. For a more - feature-complete implementation, use :py:class:`Iota` instead. - - References: - - - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference - - :param AdapterSpec adapter: - URI string or BaseAdapter instance. - - :param Optional[bool] devnet: - Whether to use devnet settings for this instance. - On the devnet, minimum weight magnitude is set to 9, on mainnet - it is 1 by default. - - :param Optional[bool] local_pow: - Whether to perform proof-of-work locally by redirecting all calls - to :py:meth:`attach_to_tangle` to - `ccurl pow interface `_. - - See :ref:`Optional Local Pow` for more info and - :ref:`find out` how to use it. - - """ - - def __init__(self, adapter, devnet=False, local_pow=False): - # type: (AdapterSpec, bool, bool) -> None - # Copy parent's doctrsing - __doc__ = super().__init__.__doc__ - super().__init__(adapter, devnet, local_pow) - - - def add_neighbors(self, uris): - # type: (Iterable[Text]) -> dict - # Copy parent's docstring - __doc__ = super().add_neighbors.__doc__ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().add_neighbors(uris) - ) - - def attach_to_tangle( - self, - trunk_transaction, # type: TransactionHash - branch_transaction, # type: TransactionHash - trytes, # type: Iterable[TryteString] - min_weight_magnitude=None, # type: Optional[int] - ): - # type: (...) -> dict - # Copy parent's docstring - __doc__ = super().attach_to_tangle.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().attach_to_tangle( - trunk_transaction, - branch_transaction, - trytes, - min_weight_magnitude, - ) + super().were_addresses_spent_from(addresses) ) - def broadcast_transactions(self, trytes): - # type: (Iterable[TryteString]) -> dict - # Copy parent's docstring - __doc__ = super().broadcast_transactions.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().broadcast_transactions( - trytes, - ) - ) - - - def check_consistency(self, tails): - # type: (Iterable[TransactionHash]) -> dict - # Copy parent's docstring - __doc__ = super().check_consistency.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().check_consistency( - tails, - ) - ) - - def find_transactions( - self, - bundles=None, # type: Optional[Iterable[BundleHash]] - addresses=None, # type: Optional[Iterable[Address]] - tags=None, # type: Optional[Iterable[Tag]] - approvees=None, # type: Optional[Iterable[TransactionHash]] - ): - # type: (...) -> dict - # Copy parent's docstring - __doc__ = super().find_transactions.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().find_transactions( - bundles, - addresses, - tags, - approvees, - ) - ) - - def get_balances( - self, - addresses, # type: Iterable[Address] - threshold=100, # type: int - tips=None, # type: Optional[Iterable[TransactionHash]] - ): - # type: (...) -> dict - # Copy parent's docstring - __doc__ = super().get_balances.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().get_balances( - addresses, - threshold, - tips, - ) - ) - - def get_inclusion_states(self, transactions, tips): - # type: (Iterable[TransactionHash], Iterable[TransactionHash]) -> dict - # Copy parent's docstring - __doc__ = super().get_inclusion_states.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().get_inclusion_states( - transactions, - tips, - ) - ) - - def get_missing_transactions(self): - # type: () -> dict - # Copy parent's docstring - __doc__ = super().get_missing_transactions.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().get_missing_transactions() - ) - - def get_neighbors(self): - # type: () -> dict - # Copy parent's docstring - __doc__ = super().get_neighbors.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().get_neighbors() - ) - - def get_node_api_configuration(self): - # type: () -> dict - # Copy parent's docstring - __doc__ = super().get_node_api_configuration.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().get_node_api_configuration() - ) - - def get_node_info(self): - # type: () -> dict - # Copy parent's docstring - __doc__ = super().get_node_info.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().get_node_info() - ) - - def get_tips(self): - # type: () -> dict - # Copy parent's docstring - __doc__ = super().get_tips.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().get_tips() - ) - - def get_transactions_to_approve(self, depth, reference=None): - # type: (int, Optional[TransactionHash]) -> dict - # Copy parent's docstring - __doc__ = super().get_transactions_to_approve.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().get_transactions_to_approve( - depth, - reference, - ) - ) - - def get_trytes(self, hashes): - # type: (Iterable[TransactionHash]) -> dict - # Copy parent's docstring - __doc__ = super().get_trytes.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().get_trytes( - hashes, - ) - ) - - def interrupt_attaching_to_tangle(self): - # type: () -> dict - # Copy parent's docstring - __doc__ = super().interrupt_attaching_to_tangle.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().interrupt_attaching_to_tangle() - ) - - def remove_neighbors(self, uris): - # type: (Iterable[Text]) -> dict - # Copy parent's docstring - __doc__ = super().remove_neighbors.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().remove_neighbors(uris) - ) - - def store_transactions(self, trytes): - # type: (Iterable[TryteString]) -> dict - # Copy parent's docstring - __doc__ = super().store_transactions.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().store_transactions(trytes) - ) - - def were_addresses_spent_from(self, addresses): - # type: (Iterable[Address]) -> dict - # Copy parent's docstring - __doc__ = super().were_addresses_spent_from.__doc__ - - # Execute original coroutine inside an event loop to make this method - # synchronous - return asyncio.get_event_loop().run_until_complete( - super().were_addresses_spent_from(addresses) - ) - - -class AsyncIota(AsyncStrictIota): - """ - Implements the async core API, plus additional async wrapper methods for - common operations. - - :param AdapterSpec adapter: - URI string or BaseAdapter instance. - - :param Optional[Seed] seed: - Seed used to generate new addresses. - If not provided, a random one will be generated. - - .. note:: - This value is never transferred to the node/network. - - :param Optional[bool] devnet: - Whether to use devnet settings for this instance. - On the devnet, minimum weight magnitude is decreased, on mainnet - it is 14 by default. - - For more info on the Mainnet and the Devnet, visit - `the official docs site`. - - :param Optional[bool] local_pow: - Whether to perform proof-of-work locally by redirecting all calls - to :py:meth:`attach_to_tangle` to - `ccurl pow interface `_. - - See :ref:`Optional Local Pow` for more info and - :ref:`find out` how to use it. - - References: - - - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference - - https://github.com/iotaledger/wiki/blob/master/api-proposal.md - """ - - def __init__(self, adapter, seed=None, devnet=False, local_pow=False): - # type: (AdapterSpec, Optional[TrytesCompatible], bool, bool) -> None - """ - :param seed: - Seed used to generate new addresses. - If not provided, a random one will be generated. - - .. note:: - This value is never transferred to the node/network. - """ - super().__init__(adapter, devnet, local_pow) - - self.seed = Seed(seed) if seed else Seed.random() - - async def broadcast_and_store(self, trytes): - # type: (Iterable[TransactionTrytes]) -> dict - """ - Broadcasts and stores a set of transaction trytes. - - :param Iterable[TransactionTrytes] trytes: - Transaction trytes to broadcast and store. - - :return: - ``dict`` with the following structure:: - - { - 'trytes': List[TransactionTrytes], - List of TransactionTrytes that were broadcast. - Same as the input ``trytes``. - } - - References: - - - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#broadcastandstore - """ - return await extended.BroadcastAndStoreCommand(self.adapter)( - trytes=trytes, - ) - - async def broadcast_bundle(self, tail_transaction_hash): - # type (TransactionHash) -> dict - """ - Re-broadcasts all transactions in a bundle given the tail transaction hash. - It might be useful when transactions did not properly propagate, - particularly in the case of large bundles. - - :param TransactionHash tail_transaction_hash: - Tail transaction hash of the bundle. - - :return: - ``dict`` with the following structure:: - - { - 'trytes': List[TransactionTrytes], - List of TransactionTrytes that were broadcast. - } - - References: - - - https://github.com/iotaledger/iota.js/blob/next/api_reference.md#module_core.broadcastBundle - """ - - return await extended.BroadcastBundleCommand(self.adapter)( - tail_hash=tail_transaction_hash, - ) - - async def find_transaction_objects( - self, - bundles=None, # type: Optional[Iterable[BundleHash]] - addresses=None, # type: Optional[Iterable[Address]] - tags=None, # type: Optional[Iterable[Tag]] - approvees=None, # type: Optional[Iterable[TransactionHash]] - ): - # type: (...) -> dict - """ - A more extensive version of :py:meth:`find_transactions` that - returns transaction objects instead of hashes. - - Effectively, this is :py:meth:`find_transactions` + - :py:meth:`get_trytes` + converting the trytes into - transaction objects. - - It accepts the same parameters as :py:meth:`find_transactions`. - - Find the transactions which match the specified input. - All input values are lists, for which a list of return values - (transaction hashes), in the same order, is returned for all - individual elements. Using multiple of these input fields returns the - intersection of the values. - - :param Optional[Iterable[BundleHash]] bundles: - List of bundle IDs. - - :param Optional[Iterable[Address]] addresses: - List of addresses. - - :param Optional[Iterable[Tag]] tags: - List of tags. - - :param Optional[Iterable[TransactionHash]] approvees: - List of approvee transaction IDs. - - :return: - ``dict`` with the following structure:: - - { - 'transactions': List[Transaction], - List of Transaction objects that match the input. - } - - """ - return await extended.FindTransactionObjectsCommand(self.adapter)( - bundles=bundles, - addresses=addresses, - tags=tags, - approvees=approvees, - ) - - async def get_account_data(self, start=0, stop=None, inclusion_states=False, security_level=None): - # type: (int, Optional[int], bool, Optional[int]) -> dict - """ - More comprehensive version of :py:meth:`get_transfers` that - returns addresses and account balance in addition to bundles. - - This function is useful in getting all the relevant information - of your account. - - :param int start: - Starting key index. - - :param Optional[int] stop: - Stop before this index. - - Note that this parameter behaves like the ``stop`` attribute - in a :py:class:`slice` object; the stop index is *not* - included in the result. - - If ``None`` (default), then this method will check every - address until it finds one that is unused. - - .. note:: - An unused address is an address that **has not been spent from** - and **has no transactions** referencing it on the Tangle. - - A snapshot removes transactions from the Tangle. As a - consequence, after a snapshot, it may happen that this API does - not return the correct account data with ``stop`` being ``None``. - - As a workaround, you can save your used addresses and their - ``key_index`` attribute in a local database. Use the - ``start`` and ``stop`` parameters to tell the API from where to - start checking and where to stop. - - :param bool inclusion_states: - Whether to also fetch the inclusion states of the transfers. - - This requires an additional API call to the node, so it is - disabled by default. - - :param Optional[int] security_level: - Number of iterations to use when generating new addresses - (see :py:meth:`get_new_addresses`). - - This value must be between 1 and 3, inclusive. - - If not set, defaults to - :py:attr:`AddressGenerator.DEFAULT_SECURITY_LEVEL`. - - :return: - ``dict`` with the following structure:: - - { - 'addresses': List[Address], - List of generated addresses. - - Note that this list may include unused - addresses. - - 'balance': int, - Total account balance. Might be 0. - - 'bundles': List[Bundle], - List of bundles with transactions to/from this - account. - } - - """ - return await extended.GetAccountDataCommand(self.adapter)( - seed=self.seed, - start=start, - stop=stop, - inclusionStates=inclusion_states, - security_level=security_level - ) - - async def get_bundles(self, transactions): - # type: (Iterable[TransactionHash]) -> dict - """ - Returns the bundle(s) associated with the specified transaction - hashes. - - :param Iterable[TransactionHash] transactions: - Transaction hashes. Must be a tail transaction. - - :return: - ``dict`` with the following structure:: - - { - 'bundles': List[Bundle], - List of matching bundles. Note that this value is - always a list, even if only one bundle was found. - } - - :raise :py:class:`iota.adapter.BadApiResponse`: - - if any of the bundles fails validation. - - if any of the bundles is not visible on the Tangle. - - References: - - - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getbundle - """ - return await extended.GetBundlesCommand(self.adapter)( - transactions=transactions, - ) - - async def get_inputs( - self, - start=0, - stop=None, - threshold=None, - security_level=None, - ): - # type: (int, Optional[int], Optional[int], Optional[int]) -> dict - """ - Gets all possible inputs of a seed and returns them, along with - the total balance. - - This is either done deterministically (by generating all - addresses until :py:meth:`find_transactions` returns an empty - result), or by providing a key range to search. - - :param int start: - Starting key index. - Defaults to 0. - - :param Optional[int] stop: - Stop before this index. - - Note that this parameter behaves like the ``stop`` attribute - in a :py:class:`slice` object; the stop index is *not* - included in the result. - - If ``None`` (default), then this method will not stop until - it finds an unused address. - - .. note:: - An unused address is an address that **has not been spent from** - and **has no transactions** referencing it on the Tangle. - - A snapshot removes transactions from the Tangle. As a - consequence, after a snapshot, it may happen that this API does - not return the correct inputs with ``stop`` being ``None``. - - As a workaround, you can save your used addresses and their - ``key_index`` attribute in a local database. Use the - ``start`` and ``stop`` parameters to tell the API from where to - start checking for inputs and where to stop. - - :param Optional[int] threshold: - If set, determines the minimum threshold for a successful - result: - - - As soon as this threshold is reached, iteration will stop. - - If the command runs out of addresses before the threshold - is reached, an exception is raised. - - .. note:: - This method does not attempt to "optimize" the result - (e.g., smallest number of inputs, get as close to - ``threshold`` as possible, etc.); it simply accumulates - inputs in order until the threshold is met. - - If ``threshold`` is 0, the first address in the key range - with a non-zero balance will be returned (if it exists). - - If ``threshold`` is ``None`` (default), this method will - return **all** inputs in the specified key range. - - :param Optional[int] security_level: - Number of iterations to use when generating new addresses - (see :py:meth:`get_new_addresses`). - - This value must be between 1 and 3, inclusive. - - If not set, defaults to - :py:attr:`AddressGenerator.DEFAULT_SECURITY_LEVEL`. - - :return: - ``dict`` with the following structure:: - - { - 'inputs': List[Address], - Addresses with nonzero balances that can be used - as inputs. - - 'totalBalance': int, - Aggregate balance from all matching addresses. - } - - Note that each :py:class:`Address` in the result has its - :py:attr:`Address.balance` attribute set. - - Example: - - .. code-block:: python - - response = iota.get_inputs(...) - - input0 = response['inputs'][0] # type: Address - input0.balance # 42 - - :raise: - - :py:class:`iota.adapter.BadApiResponse` if ``threshold`` - is not met. Not applicable if ``threshold`` is ``None``. - - References: - - - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getinputs - """ - return await extended.GetInputsCommand(self.adapter)( - seed=self.seed, - start=start, - stop=stop, - threshold=threshold, - securityLevel=security_level - ) - - async def get_latest_inclusion(self, hashes): - # type: (Iterable[TransactionHash]) -> Dict[TransactionHash, bool] - """ - Fetches the inclusion state for the specified transaction - hashes, as of the latest milestone that the node has processed. - - Effectively, this is :py:meth:`get_node_info` + - :py:meth:`get_inclusion_states`. - - :param Iterable[TransactionHash] hashes: - List of transaction hashes. - - :return: - ``dict`` with the following structure:: - - { - "states": Dict[TransactionHash, bool] - ``dict`` with one boolean per transaction hash in - ``hashes``. - } - - """ - return await extended.GetLatestInclusionCommand(self.adapter)(hashes=hashes) - - async def get_new_addresses( - self, - index=0, - count=1, - security_level=AddressGenerator.DEFAULT_SECURITY_LEVEL, - checksum=False, - ): - # type: (int, int, int, bool) -> dict - """ - Generates one or more new addresses from the seed. - - :param int index: - The key index of the first new address to generate (must be - >= 0). - - :param int count: - Number of addresses to generate (must be >= 1). - - .. tip:: - This is more efficient than calling :py:meth:`get_new_addresses` - inside a loop. - - If ``None``, this method will progressively generate - addresses and scan the Tangle until it finds one that has no - transactions referencing it and was never spent from. - - .. note:: - A snapshot removes transactions from the Tangle. As a - consequence, after a snapshot, it may happen that when ``count`` - is ``None``, this API call returns a "new" address that used to - have transactions before the snapshot. - As a workaround, you can save your used addresses and their - ``key_index`` attribute in a local database. Use the - ``index`` parameter to tell the API from where to start - generating and checking new addresses. - - :param int security_level: - Number of iterations to use when generating new addresses. - - Larger values take longer, but the resulting signatures are - more secure. - - This value must be between 1 and 3, inclusive. - - :param bool checksum: - Specify whether to return the address with the checksum. - Defaults to ``False``. - - :return: - ``dict`` with the following structure:: - - { - 'addresses': List[Address], - Always a list, even if only one address was - generated. - } - - References: - - - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getnewaddress - """ - return await extended.GetNewAddressesCommand(self.adapter)( - count=count, - index=index, - securityLevel=security_level, - checksum=checksum, - seed=self.seed, - ) - - async def get_transaction_objects( - self, - hashes, # type: [Iterable[TransactionHash]] - ): - # type: (...) -> dict - """ - Fetches transaction objects from the Tangle given their - transaction IDs (hashes). - - Effectively, this is :py:meth:`get_trytes` + - converting the trytes into transaction objects. - - Similar to :py:meth:`find_transaction_objects`, but accepts - list of transaction hashes as input. - - :param Iterable[TransactionHash] hashes: - List of transaction IDs (transaction hashes). - - :return: - ``dict`` with the following structure:: - - { - 'transactions': List[Transaction], - List of Transaction objects that match the input. - } - """ - return await extended.GetTransactionObjectsCommand(self.adapter)( - hashes=hashes, - ) - - async def get_transfers(self, start=0, stop=None, inclusion_states=False): - # type: (int, Optional[int], bool) -> dict - """ - Returns all transfers associated with the seed. - - :param int start: - Starting key index. - - :param Optional[int] stop: - Stop before this index. - - Note that this parameter behaves like the ``stop`` attribute - in a :py:class:`slice` object; the stop index is *not* - included in the result. - - If ``None`` (default), then this method will check every - address until it finds one that is unused. - - .. note:: - An unused address is an address that **has not been spent from** - and **has no transactions** referencing it on the Tangle. - - A snapshot removes transactions from the Tangle. As a - consequence, after a snapshot, it may happen that this API does - not return the expected transfers with ``stop`` being ``None``. - - As a workaround, you can save your used addresses and their - ``key_index`` attribute in a local database. Use the - ``start`` and ``stop`` parameters to tell the API from where to - start checking for transfers and where to stop. - - :param bool inclusion_states: - Whether to also fetch the inclusion states of the transfers. - - This requires an additional API call to the node, so it is - disabled by default. - - :return: - ``dict`` with the following structure:: - - { - 'bundles': List[Bundle], - Matching bundles, sorted by tail transaction - timestamp. - - This value is always a list, even if only one - bundle was found. - } - - References: - - - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#gettransfers - """ - return await extended.GetTransfersCommand(self.adapter)( - seed=self.seed, - start=start, - stop=stop, - inclusionStates=inclusion_states, - ) - - async def is_promotable( - self, - tails, # type: Iterable[TransactionHash] - ): - # type: (Iterable(TransactionHash)] -> dict - """ - Checks if tail transaction(s) is promotable by calling - :py:meth:`check_consistency` and verifying that ``attachmentTimestamp`` - is above a lower bound. - Lower bound is calculated based on number of milestones issued - since transaction attachment. - - :param Iterable(TransactionHash) tails: - List of tail transaction hashes. - - :return: - The return type mimics that of :py:meth:`check_consistency`. - ``dict`` with the following structure:: - - { - 'promotable': bool, - If ``True``, all tails are promotable. If ``False``, see - `info` field. - - 'info': Optional(List[Text]) - If `promotable` is ``False``, this contains info about what - went wrong. - Note that when 'promotable' is ``True``, 'info' does not - exist. - - } - - References: - - https://github.com/iotaledger/iota.js/blob/next/api_reference.md#module_core.isPromotable - """ - return await extended.IsPromotableCommand(self.adapter)( - tails=tails, - ) - - async def prepare_transfer( - self, - transfers, # type: Iterable[ProposedTransaction] - inputs=None, # type: Optional[Iterable[Address]] - change_address=None, # type: Optional[Address] - security_level=None, # type: Optional[int] - ): - # type: (...) -> dict - """ - Prepares transactions to be broadcast to the Tangle, by - generating the correct bundle, as well as choosing and signing - the inputs (for value transfers). - - :param Iterable[ProposedTransaction] transfers: - Transaction objects to prepare. - - :param Optional[Iterable[Address]] inputs: - List of addresses used to fund the transfer. - Ignored for zero-value transfers. - - If not provided, addresses will be selected automatically by - scanning the Tangle for unspent inputs. Depending on how - many transfers you've already sent with your seed, this - process could take awhile. - - :param Optional[Address] change_address: - If inputs are provided, any unspent amount will be sent to - this address. - - If not specified, a change address will be generated - automatically. - - :param Optional[int] security_level: - Number of iterations to use when generating new addresses - (see :py:meth:`get_new_addresses`). - - This value must be between 1 and 3, inclusive. - - If not set, defaults to - :py:attr:`AddressGenerator.DEFAULT_SECURITY_LEVEL`. - - :return: - ``dict`` with the following structure:: - - { - 'trytes': List[TransactionTrytes], - Raw trytes for the transactions in the bundle, - ready to be provided to :py:meth:`send_trytes`. - } - - References: - - - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#preparetransfers - """ - return await extended.PrepareTransferCommand(self.adapter)( - seed=self.seed, - transfers=transfers, - inputs=inputs, - changeAddress=change_address, - securityLevel=security_level, - ) - - async def promote_transaction( - self, - transaction, - depth=3, - min_weight_magnitude=None, - ): - # type: (TransactionHash, int, Optional[int]) -> dict - """ - Promotes a transaction by adding spam on top of it. - - :param TransactionHash transaction: - Transaction hash. Must be a tail transaction. - - :param int depth: - Depth at which to attach the bundle. - Defaults to 3. - - :param Optional[int] min_weight_magnitude: - Min weight magnitude, used by the node to calibrate Proof of - Work. - - If not provided, a default value will be used. - - :return: - ``dict`` with the following structure:: - - { - 'bundle': Bundle, - The newly-published bundle. - } - """ - if min_weight_magnitude is None: - min_weight_magnitude = self.default_min_weight_magnitude - - return await extended.PromoteTransactionCommand(self.adapter)( - transaction=transaction, - depth=depth, - minWeightMagnitude=min_weight_magnitude, - ) - - async def replay_bundle( - self, - transaction, - depth=3, - min_weight_magnitude=None, - ): - # type: (TransactionHash, int, Optional[int]) -> dict - """ - Takes a tail transaction hash as input, gets the bundle - associated with the transaction and then replays the bundle by - attaching it to the Tangle. - - :param TransactionHash transaction: - Transaction hash. Must be a tail. - - :param int depth: - Depth at which to attach the bundle. - Defaults to 3. - - :param Optional[int] min_weight_magnitude: - Min weight magnitude, used by the node to calibrate Proof of - Work. - - If not provided, a default value will be used. - - :return: - ``dict`` with the following structure:: - - { - 'trytes': List[TransactionTrytes], - Raw trytes that were published to the Tangle. - } - - References: - - - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#replaytransfer - """ - if min_weight_magnitude is None: - min_weight_magnitude = self.default_min_weight_magnitude - - return await extended.ReplayBundleCommand(self.adapter)( - transaction=transaction, - depth=depth, - minWeightMagnitude=min_weight_magnitude, - ) - - async def send_transfer( - self, - transfers, # type: Iterable[ProposedTransaction] - depth=3, # type: int - inputs=None, # type: Optional[Iterable[Address]] - change_address=None, # type: Optional[Address] - min_weight_magnitude=None, # type: Optional[int] - security_level=None, # type: Optional[int] - ): - # type: (...) -> dict - """ - Prepares a set of transfers and creates the bundle, then - attaches the bundle to the Tangle, and broadcasts and stores the - transactions. - - :param Iterable[ProposedTransaction] transfers: - Transfers to include in the bundle. - - :param int depth: - Depth at which to attach the bundle. - Defaults to 3. - - :param Optional[Iterable[Address]] inputs: - List of inputs used to fund the transfer. - Not needed for zero-value transfers. - - :param Optional[Address] change_address: - If inputs are provided, any unspent amount will be sent to - this address. - - If not specified, a change address will be generated - automatically. - - :param Optional[int] min_weight_magnitude: - Min weight magnitude, used by the node to calibrate Proof of - Work. - - If not provided, a default value will be used. - - :param Optional[int] security_level: - Number of iterations to use when generating new addresses - (see :py:meth:`get_new_addresses`). - - This value must be between 1 and 3, inclusive. - - If not set, defaults to - :py:attr:`AddressGenerator.DEFAULT_SECURITY_LEVEL`. - - :return: - ``dict`` with the following structure:: - - { - 'bundle': Bundle, - The newly-published bundle. - } - - References: - - - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#sendtransfer - """ - if min_weight_magnitude is None: - min_weight_magnitude = self.default_min_weight_magnitude - - return await extended.SendTransferCommand(self.adapter)( - seed=self.seed, - depth=depth, - transfers=transfers, - inputs=inputs, - changeAddress=change_address, - minWeightMagnitude=min_weight_magnitude, - securityLevel=security_level, - ) - - async def send_trytes(self, trytes, depth=3, min_weight_magnitude=None): - # type: (Iterable[TransactionTrytes], int, Optional[int]) -> dict - """ - Attaches transaction trytes to the Tangle, then broadcasts and - stores them. - - :param Iterable[TransactionTrytes] trytes: - Transaction encoded as a tryte sequence. - - :param int depth: - Depth at which to attach the bundle. - Defaults to 3. - - :param Optional[int] min_weight_magnitude: - Min weight magnitude, used by the node to calibrate Proof of - Work. - - If not provided, a default value will be used. - - :return: - ``dict`` with the following structure:: - - { - 'trytes': List[TransactionTrytes], - Raw trytes that were published to the Tangle. - } - - References: - - - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#sendtrytes - """ - if min_weight_magnitude is None: - min_weight_magnitude = self.default_min_weight_magnitude - - return await extended.SendTrytesCommand(self.adapter)( - trytes=trytes, - depth=depth, - minWeightMagnitude=min_weight_magnitude, - ) - - async def is_reattachable(self, addresses): - # type: (Iterable[Address]) -> dict - """ - This API function helps you to determine whether you should - replay a transaction or make a new one (either with the same - input, or a different one). - - This method takes one or more input addresses (i.e. from spent - transactions) as input and then checks whether any transactions - with a value transferred are confirmed. - - If yes, it means that this input address has already been - successfully used in a different transaction, and as such you - should no longer replay the transaction. - - :param Iterable[Address] addresses: - List of addresses. - - :return: - ``dict`` with the following structure:: - - { - 'reattachable': List[bool], - Always a list, even if only one address was queried. - } - - """ - return await extended.IsReattachableCommand(self.adapter)( - addresses=addresses - ) - - async def traverse_bundle(self, tail_hash): - # type: (TransactionHash) -> dict - """ - Fetches and traverses a bundle from the Tangle given a tail transaction - hash. - Recursively traverse the Tangle, collecting transactions until - we hit a new bundle. - - This method is (usually) faster than :py:meth:`find_transactions`, and - it ensures we don't collect transactions from replayed bundles. - - :param TransactionHash tail_hash: - Tail transaction hash of the bundle. - - :return: - ``dict`` with the following structure:: - - { - 'bundle': List[Bundle], - List of matching bundles. Note that this value is - always a list, even if only one bundle was found. - } - - """ - return await extended.TraverseBundleCommand(self.adapter)( - transaction=tail_hash - ) - -# There is a compact and easy way to create the synchronous version of the async -# classes: - -# import inspect -# def make_synchronous(new_name, async_class: type): -# def make_sync(method): -# def sync_version(*args, **kwargs): -# return asyncio.get_event_loop().run_until_complete(method(*args, **kwargs)) -# return sync_version - -# return type(new_name, (async_class,), { -# name: make_sync(method) if inspect.iscoroutinefunction(method) else method -# for name, method in inspect.getmembers(async_class) -# }) - -# # create the sync version of the class -# Iota = make_synchronous('Iota', AsyncIota) - -# While this approach would work, no IDE static analysis would pick up the -# method definitions or docstrings for the new `Iota` class, meaning no -# suggestions, intellisense, code completion, etc. for the user. -# Therefore we keep the manual approach. - class Iota(StrictIota, AsyncIota): """ diff --git a/iota/api_async.py b/iota/api_async.py new file mode 100644 index 0000000..7ec4781 --- /dev/null +++ b/iota/api_async.py @@ -0,0 +1,1656 @@ +from iota import AdapterSpec, Address, BundleHash, ProposedTransaction, Tag, \ + TransactionHash, TransactionTrytes, TryteString, TrytesCompatible +from iota.adapter import BaseAdapter, resolve_adapter +from iota.commands import BaseCommand, CustomCommand, core, extended +from iota.crypto.addresses import AddressGenerator +from iota.crypto.types import Seed +import asyncio + +__all__ = [ + 'AsyncIota', + 'AsyncStrictIota', +] + + +class AsyncStrictIota: + """ + Asynchronous API to send HTTP requests for communicating with an IOTA node. + + This implementation only exposes the "core" API methods. For a more + feature-complete implementation, use :py:class:`Iota` instead. + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference + + :param AdapterSpec adapter: + URI string or BaseAdapter instance. + + :param Optional[bool] devnet: + Whether to use devnet settings for this instance. + On the devnet, minimum weight magnitude is set to 9, on mainnet + it is 1 by default. + + :param Optional[bool] local_pow: + Whether to perform proof-of-work locally by redirecting all calls + to :py:meth:`attach_to_tangle` to + `ccurl pow interface `_. + + See :ref:`Optional Local Pow` for more info and + :ref:`find out` how to use it. + + """ + + def __init__(self, adapter, devnet=False, local_pow=False): + # type: (AdapterSpec, bool, bool) -> None + """ + :param AdapterSpec adapter: + URI string or BaseAdapter instance. + + :param bool devnet: + Whether to use devnet settings for this instance. + On the devnet, minimum weight magnitude is set to 9, on mainnet + it is 1 by default. + + :param Optional[bool] local_pow: + Whether to perform proof-of-work locally by redirecting all calls + to :py:meth:`attach_to_tangle` to + `ccurl pow interface `_. + + See :ref:`Optional Local Pow` for more info and + :ref:`find out` how to use it. + """ + super().__init__() + + if not isinstance(adapter, BaseAdapter): + adapter = resolve_adapter(adapter) + + self.adapter = adapter # type: BaseAdapter + # Note that the `local_pow` parameter is passed to adapter, + # the api class has no notion about it. The reason being, + # that this parameter is used in `AttachToTangeCommand` calls, + # that is called from various api calls (`attach_to_tangle`, + # `send_trytes` or `send_transfer`). Inside `AttachToTangeCommand`, + # we no longer have access to the attributes of the API class, therefore + # `local_pow` needs to be associated with the adapter. + # Logically, `local_pow` will decide if the api call does pow + # via pyota-pow extension, or sends the request to a node. + # But technically, the parameter belongs to the adapter. + self.adapter.set_local_pow(local_pow) + self.devnet = devnet + + def create_command(self, command): + # type: (Text) -> CustomCommand + """ + Creates a pre-configured CustomCommand instance. + + This method is useful for invoking undocumented or experimental + methods, or if you just want to troll your node for awhile. + + :param Text command: + The name of the command to create. + + """ + return CustomCommand(self.adapter, command) + + def set_local_pow(self, local_pow): + # type: (bool) -> None + """ + Sets the :py:attr:`local_pow` attribute of the adapter of the api + instance. If it is ``True``, :py:meth:`attach_to_tangle` command calls + external interface to perform proof of work, instead of sending the + request to a node. + + By default, :py:attr:`local_pow` is set to ``False``. + This particular method is needed if one wants to change + local_pow behavior dynamically. + + :param bool local_pow: + Whether to perform pow locally. + + :returns: None + + """ + self.adapter.set_local_pow(local_pow) + + @property + def default_min_weight_magnitude(self): + # type: () -> int + """ + Returns the default ``min_weight_magnitude`` value to use for + API requests. + """ + return 9 if self.devnet else 14 + + async def add_neighbors(self, uris): + # type: (Iterable[Text]) -> dict + """ + Add one or more neighbors to the node. Lasts until the node is + restarted. + + :param Iterable[Text] uris: + Use format ``://:``. + Example: ``add_neighbors(['udp://example.com:14265'])`` + + .. note:: + These URIs are for node-to-node communication (e.g., + weird things will happen if you specify a node's HTTP + API URI here). + + :return: + ``dict`` with the following structure:: + + { + 'addedNeighbors': int, + Total number of added neighbors. + 'duration': int, + Number of milliseconds it took to complete the request. + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#addneighbors + """ + return await core.AddNeighborsCommand(self.adapter)(uris=uris) + + async def attach_to_tangle( + self, + trunk_transaction, # type: TransactionHash + branch_transaction, # type: TransactionHash + trytes, # type: Iterable[TryteString] + min_weight_magnitude=None, # type: Optional[int] + ): + # type: (...) -> dict + """ + Attaches the specified transactions (trytes) to the Tangle by + doing Proof of Work. You need to supply branchTransaction as + well as trunkTransaction (basically the tips which you're going + to validate and reference with this transaction) - both of which + you'll get through the :py:meth:`get_transactions_to_approve` API call. + + The returned value is a different set of tryte values which you + can input into :py:meth:`broadcast_transactions` and + :py:meth:`store_transactions`. + + :param TransactionHash trunk_transaction: + Trunk transaction hash. + + :param TransactionHash branch_transaction: + Branch transaction hash. + + :param Iterable[TransactionTrytes] trytes: + List of transaction trytes in the bundle to be attached. + + :param Optional[int] min_weight_magnitude: + Minimum weight magnitude to be used for attaching trytes. + 14 by default on mainnet, 9 on devnet/devnet. + + :return: + ``dict`` with the following structure:: + + { + 'trytes': List[TransactionTrytes], + Transaction trytes that include a valid nonce field. + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#attachtotangle + """ + if min_weight_magnitude is None: + min_weight_magnitude = self.default_min_weight_magnitude + + return await core.AttachToTangleCommand(self.adapter)( + trunkTransaction=trunk_transaction, + branchTransaction=branch_transaction, + minWeightMagnitude=min_weight_magnitude, + trytes=trytes, + ) + + async def broadcast_transactions(self, trytes): + # type: (Iterable[TryteString]) -> dict + """ + Broadcast a list of transactions to all neighbors. + + The input trytes for this call are provided by + :py:meth:`attach_to_tangle`. + + :param Iterable[TransactionTrytes] trytes: + List of transaction trytes to be broadcast. + + :return: + ``dict`` with the following structure:: + + { + 'duration': int, + Number of milliseconds it took to complete the request. + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#broadcasttransactions + """ + return await core.BroadcastTransactionsCommand(self.adapter)(trytes=trytes) + + async def check_consistency(self, tails): + # type: (Iterable[TransactionHash]) -> dict + """ + Used to ensure tail resolves to a consistent ledger which is + necessary to validate before attempting promotion. Checks + transaction hashes for promotability. + + This is called with a pending transaction (or more of them) and + it will tell you if it is still possible for this transaction + (or all the transactions simultaneously if you give more than + one) to be confirmed, or not (because it conflicts with another + already confirmed transaction). + + :param Iterable[TransactionHash] tails: + Transaction hashes. Must be tail transactions. + + :return: + ``dict`` with the following structure:: + + { + 'state': bool, + Whether tails resolve to consistent ledger. + 'info': Text, + This field will only exist if 'state' is ``False``. + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#checkconsistency + """ + return await core.CheckConsistencyCommand(self.adapter)( + tails=tails, + ) + + async def find_transactions( + self, + bundles=None, # type: Optional[Iterable[BundleHash]] + addresses=None, # type: Optional[Iterable[Address]] + tags=None, # type: Optional[Iterable[Tag]] + approvees=None, # type: Optional[Iterable[TransactionHash]] + ): + # type: (...) -> dict + """ + Find the transactions which match the specified input and + return. + + All input values are lists, for which a list of return values + (transaction hashes), in the same order, is returned for all + individual elements. + + Using multiple of these input fields returns the intersection of + the values. + + :param Optional[Iterable[BundleHash] bundles: + List of bundle IDs. + + :param Optional[Iterable[Address]] addresses: + List of addresses. + + :param Optional[Iterable[Tag]] tags: + List of tags. + + :param Optional[Iterable[TransactionHash]] approvees: + List of approvee transaction IDs. + + :return: + ``dict`` with the following structure:: + + { + 'hashes': List[TransationHash], + Found transactions. + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#findtransactions + """ + return await core.FindTransactionsCommand(self.adapter)( + bundles=bundles, + addresses=addresses, + tags=tags, + approvees=approvees, + ) + + async def get_balances( + self, + addresses, # type: Iterable[Address] + threshold=100, # type: int + tips=None, # type: Optional[Iterable[TransactionHash]] + ): + # type: (...) -> dict + """ + Similar to :py:meth:`get_inclusion_states`. Returns the + confirmed balance which a list of addresses have at the latest + confirmed milestone. + + In addition to the balances, it also returns the milestone as + well as the index with which the confirmed balance was + determined. The balances are returned as a list in the same + order as the addresses were provided as input. + + :param Iterable[Address] addresses: + List of addresses to get the confirmed balance for. + + :param int threshold: + Confirmation threshold between 0 and 100. + + :param Optional[Iterable[TransactionHash]] tips: + Tips whose history of transactions to traverse to find the balance. + + :return: + ``dict`` with the following structure:: + + { + 'balances': List[int], + List of balances in the same order as the addresses + parameters that were passed to the endpoint. + 'references': List[TransactionHash], + The referencing tips. If no tips parameter was passed + to the endpoint, this field contains the hash of the + latest milestone that confirmed the balance. + 'milestoneIndex': int, + The index of the milestone that confirmed the most + recent balance. + 'duration': int, + Number of milliseconds it took to process the request. + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getbalances + """ + return await core.GetBalancesCommand(self.adapter)( + addresses=addresses, + threshold=threshold, + tips=tips, + ) + + async def get_inclusion_states(self, transactions, tips): + # type: (Iterable[TransactionHash], Iterable[TransactionHash]) -> dict + """ + Get the inclusion states of a set of transactions. This is for + determining if a transaction was accepted and confirmed by the + network or not. You can search for multiple tips (and thus, + milestones) to get past inclusion states of transactions. + + :param Iterable[TransactionHash] transactions: + List of transactions you want to get the inclusion state + for. + + :param Iterable[TransactionHash] tips: + List of tips (including milestones) you want to search for + the inclusion state. + + :return: + ``dict`` with the following structure:: + + { + 'states': List[bool], + List of boolean values in the same order as the + transactions parameters. A ``True`` value means the + transaction was confirmed. + 'duration': int, + Number of milliseconds it took to process the request. + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getinclusionstates + """ + return await core.GetInclusionStatesCommand(self.adapter)( + transactions=transactions, + tips=tips, + ) + + async def get_missing_transactions(self): + # type: () -> dict + """ + Returns all transaction hashes that a node is currently requesting + from its neighbors. + + :return: + ``dict`` with the following structure:: + + { + 'hashes': List[TransactionHash], + Array of missing transaction hashes. + 'duration': int, + Number of milliseconds it took to process the request. + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getmissingtransactions + """ + return await core.GetMissingTransactionsCommand(self.adapter)() + + async def get_neighbors(self): + # type: () -> dict + """ + Returns the set of neighbors the node is connected with, as well + as their activity count. + + The activity counter is reset after restarting IRI. + + :return: + ``dict`` with the following structure:: + + { + 'neighbors': List[dict], + Array of objects, including the following fields with + example values: + "address": "/8.8.8.8:14265", + "numberOfAllTransactions": 158, + "numberOfRandomTransactionRequests": 271, + "numberOfNewTransactions": 956, + "numberOfInvalidTransactions": 539, + "numberOfStaleTransactions": 663, + "numberOfSentTransactions": 672, + "connectiontype": "TCP" + 'duration': int, + Number of milliseconds it took to process the request. + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getneighbors + """ + return await core.GetNeighborsCommand(self.adapter)() + + async def get_node_api_configuration(self): + # type: () -> dict + """ + Returns a node's API configuration settings. + + :return: + ``dict`` with the following structure:: + + { + '': type, + Configuration parameters for a node. + ... + ... + ... + + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/iri-configuration-options + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getnodeapiconfiguration + """ + return await core.GetNodeAPIConfigurationCommand(self.adapter)() + + async def get_node_info(self): + # type: () -> dict + """ + Returns information about the node. + + :return: + ``dict`` with the following structure:: + + { + 'appName': Text, + Name of the IRI network. + 'appVersion': Text, + Version of the IRI. + 'jreAvailableProcessors': int, + Available CPU cores on the node. + 'jreFreeMemory': int, + Amount of free memory in the Java virtual machine. + 'jreMaxMemory': int, + Maximum amount of memory that the Java virtual machine + can use, + 'jreTotalMemory': int, + Total amount of memory in the Java virtual machine. + 'jreVersion': Text, + The version of the Java runtime environment. + 'latestMilestone': TransactionHash + Transaction hash of the latest milestone. + 'latestMilestoneIndex': int, + Index of the latest milestone. + 'latestSolidSubtangleMilestone': TransactionHash, + Transaction hash of the latest solid milestone. + 'latestSolidSubtangleMilestoneIndex': int, + Index of the latest solid milestone. + 'milestoneStartIndex': int, + Start milestone for the current version of the IRI. + 'neighbors': int, + Total number of connected neighbor nodes. + 'packetsQueueSize': int, + Size of the packet queue. + 'time': int, + Current UNIX timestamp. + 'tips': int, + Number of tips in the network. + 'transactionsToRequest': int, + Total number of transactions that the node is missing in + its ledger. + 'features': List[Text], + Enabled configuration options. + 'coordinatorAddress': Address, + Address (Merkle root) of the Coordinator. + 'duration': int, + Number of milliseconds it took to process the request. + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getnodeinfo + """ + return await core.GetNodeInfoCommand(self.adapter)() + + async def get_tips(self): + # type: () -> dict + """ + Returns the list of tips (transactions which have no other + transactions referencing them). + + :return: + ``dict`` with the following structure:: + + { + 'hashes': List[TransactionHash], + List of tip transaction hashes. + 'duration': int, + Number of milliseconds it took to complete the request. + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#gettips + - https://docs.iota.org/docs/dev-essentials/0.1/references/glossary + """ + return await core.GetTipsCommand(self.adapter)() + + async def get_transactions_to_approve(self, depth, reference=None): + # type: (int, Optional[TransactionHash]) -> dict + """ + Tip selection which returns ``trunkTransaction`` and + ``branchTransaction``. + + :param int depth: + Number of milestones to go back to start the tip selection algorithm. + + The higher the depth value, the more "babysitting" the node + will perform for the network (as it will confirm more + transactions that way). + + :param TransactionHash reference: + Transaction hash from which to start the weighted random walk. + Use this parameter to make sure the returned tip transaction hashes + approve a given reference transaction. + + :return: + ``dict`` with the following structure:: + + { + 'trunkTransaction': TransactionHash, + Valid trunk transaction hash. + 'branchTransaction': TransactionHash, + Valid branch transaction hash. + 'duration': int, + Number of milliseconds it took to complete the request. + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#gettransactionstoapprove + """ + return await core.GetTransactionsToApproveCommand(self.adapter)( + depth=depth, + reference=reference, + ) + + async def get_trytes(self, hashes): + # type: (Iterable[TransactionHash]) -> dict + """ + Returns the raw transaction data (trytes) of one or more + transactions. + + :return: + ``dict`` with the following structure:: + + { + 'trytes': List[TransactionTrytes], + List of transaction trytes for the given transaction + hashes (in the same order as the parameters). + 'duration': int, + Number of milliseconds it took to complete the request. + } + + .. note:: + If a node doesn't have the trytes for a given transaction hash in + its ledger, the value at the index of that transaction hash is either + ``null`` or a string of 9s. + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#gettrytes + """ + return await core.GetTrytesCommand(self.adapter)(hashes=hashes) + + async def interrupt_attaching_to_tangle(self): + # type: () -> dict + """ + Interrupts and completely aborts the :py:meth:`attach_to_tangle` + process. + + :return: + ``dict`` with the following structure:: + + { + 'duration': int, + Number of milliseconds it took to complete the request. + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#interruptattachingtotangle + """ + return await core.InterruptAttachingToTangleCommand(self.adapter)() + + async def remove_neighbors(self, uris): + # type: (Iterable[Text]) -> dict + """ + Removes one or more neighbors from the node. Lasts until the + node is restarted. + + :param Text uris: + Use format ``://:``. + Example: `remove_neighbors(['udp://example.com:14265'])` + + :return: + ``dict`` with the following structure:: + + { + 'removedNeighbors': int, + Total number of removed neighbors. + 'duration': int, + Number of milliseconds it took to complete the request. + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#removeneighbors + """ + return await core.RemoveNeighborsCommand(self.adapter)(uris=uris) + + async def store_transactions(self, trytes): + # type: (Iterable[TryteString]) -> dict + """ + Store transactions into local storage of the node. + + The input trytes for this call are provided by + :py:meth:`attach_to_tangle`. + + :param TransactionTrytes trytes: + Valid transaction trytes returned by :py:meth:`attach_to_tangle`. + + :return: + ``dict`` with the following structure:: + + { + 'trytes': TransactionTrytes, + Stored trytes. + 'duration': int, + Number of milliseconds it took to complete the request. + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#storetransactions + """ + return await core.StoreTransactionsCommand(self.adapter)(trytes=trytes) + + async def were_addresses_spent_from(self, addresses): + # type: (Iterable[Address]) -> dict + """ + Check if a list of addresses was ever spent from, in the current + epoch, or in previous epochs. + + If an address has a pending transaction, it's also considered 'spent'. + + :param Iterable[Address] addresses: + List of addresses to check. + + :return: + ``dict`` with the following structure:: + + { + 'states': List[bool], + States of the specified addresses in the same order as + the values in the addresses parameter. A ``True`` value + means that the address has been spent from. + 'duration': int, + Number of milliseconds it took to complete the request. + } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#wereaddressesspentfrom + """ + return await core.WereAddressesSpentFromCommand(self.adapter)( + addresses=addresses, + ) + +class AsyncIota(AsyncStrictIota): + """ + Implements the async core API, plus additional async wrapper methods for + common operations. + + :param AdapterSpec adapter: + URI string or BaseAdapter instance. + + :param Optional[Seed] seed: + Seed used to generate new addresses. + If not provided, a random one will be generated. + + .. note:: + This value is never transferred to the node/network. + + :param Optional[bool] devnet: + Whether to use devnet settings for this instance. + On the devnet, minimum weight magnitude is decreased, on mainnet + it is 14 by default. + + For more info on the Mainnet and the Devnet, visit + `the official docs site`. + + :param Optional[bool] local_pow: + Whether to perform proof-of-work locally by redirecting all calls + to :py:meth:`attach_to_tangle` to + `ccurl pow interface `_. + + See :ref:`Optional Local Pow` for more info and + :ref:`find out` how to use it. + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md + """ + + def __init__(self, adapter, seed=None, devnet=False, local_pow=False): + # type: (AdapterSpec, Optional[TrytesCompatible], bool, bool) -> None + """ + :param seed: + Seed used to generate new addresses. + If not provided, a random one will be generated. + + .. note:: + This value is never transferred to the node/network. + """ + super().__init__(adapter, devnet, local_pow) + + self.seed = Seed(seed) if seed else Seed.random() + + async def broadcast_and_store(self, trytes): + # type: (Iterable[TransactionTrytes]) -> dict + """ + Broadcasts and stores a set of transaction trytes. + + :param Iterable[TransactionTrytes] trytes: + Transaction trytes to broadcast and store. + + :return: + ``dict`` with the following structure:: + + { + 'trytes': List[TransactionTrytes], + List of TransactionTrytes that were broadcast. + Same as the input ``trytes``. + } + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#broadcastandstore + """ + return await extended.BroadcastAndStoreCommand(self.adapter)( + trytes=trytes, + ) + + async def broadcast_bundle(self, tail_transaction_hash): + # type (TransactionHash) -> dict + """ + Re-broadcasts all transactions in a bundle given the tail transaction hash. + It might be useful when transactions did not properly propagate, + particularly in the case of large bundles. + + :param TransactionHash tail_transaction_hash: + Tail transaction hash of the bundle. + + :return: + ``dict`` with the following structure:: + + { + 'trytes': List[TransactionTrytes], + List of TransactionTrytes that were broadcast. + } + + References: + + - https://github.com/iotaledger/iota.js/blob/next/api_reference.md#module_core.broadcastBundle + """ + + return await extended.BroadcastBundleCommand(self.adapter)( + tail_hash=tail_transaction_hash, + ) + + async def find_transaction_objects( + self, + bundles=None, # type: Optional[Iterable[BundleHash]] + addresses=None, # type: Optional[Iterable[Address]] + tags=None, # type: Optional[Iterable[Tag]] + approvees=None, # type: Optional[Iterable[TransactionHash]] + ): + # type: (...) -> dict + """ + A more extensive version of :py:meth:`find_transactions` that + returns transaction objects instead of hashes. + + Effectively, this is :py:meth:`find_transactions` + + :py:meth:`get_trytes` + converting the trytes into + transaction objects. + + It accepts the same parameters as :py:meth:`find_transactions`. + + Find the transactions which match the specified input. + All input values are lists, for which a list of return values + (transaction hashes), in the same order, is returned for all + individual elements. Using multiple of these input fields returns the + intersection of the values. + + :param Optional[Iterable[BundleHash]] bundles: + List of bundle IDs. + + :param Optional[Iterable[Address]] addresses: + List of addresses. + + :param Optional[Iterable[Tag]] tags: + List of tags. + + :param Optional[Iterable[TransactionHash]] approvees: + List of approvee transaction IDs. + + :return: + ``dict`` with the following structure:: + + { + 'transactions': List[Transaction], + List of Transaction objects that match the input. + } + + """ + return await extended.FindTransactionObjectsCommand(self.adapter)( + bundles=bundles, + addresses=addresses, + tags=tags, + approvees=approvees, + ) + + async def get_account_data(self, start=0, stop=None, inclusion_states=False, security_level=None): + # type: (int, Optional[int], bool, Optional[int]) -> dict + """ + More comprehensive version of :py:meth:`get_transfers` that + returns addresses and account balance in addition to bundles. + + This function is useful in getting all the relevant information + of your account. + + :param int start: + Starting key index. + + :param Optional[int] stop: + Stop before this index. + + Note that this parameter behaves like the ``stop`` attribute + in a :py:class:`slice` object; the stop index is *not* + included in the result. + + If ``None`` (default), then this method will check every + address until it finds one that is unused. + + .. note:: + An unused address is an address that **has not been spent from** + and **has no transactions** referencing it on the Tangle. + + A snapshot removes transactions from the Tangle. As a + consequence, after a snapshot, it may happen that this API does + not return the correct account data with ``stop`` being ``None``. + + As a workaround, you can save your used addresses and their + ``key_index`` attribute in a local database. Use the + ``start`` and ``stop`` parameters to tell the API from where to + start checking and where to stop. + + :param bool inclusion_states: + Whether to also fetch the inclusion states of the transfers. + + This requires an additional API call to the node, so it is + disabled by default. + + :param Optional[int] security_level: + Number of iterations to use when generating new addresses + (see :py:meth:`get_new_addresses`). + + This value must be between 1 and 3, inclusive. + + If not set, defaults to + :py:attr:`AddressGenerator.DEFAULT_SECURITY_LEVEL`. + + :return: + ``dict`` with the following structure:: + + { + 'addresses': List[Address], + List of generated addresses. + + Note that this list may include unused + addresses. + + 'balance': int, + Total account balance. Might be 0. + + 'bundles': List[Bundle], + List of bundles with transactions to/from this + account. + } + + """ + return await extended.GetAccountDataCommand(self.adapter)( + seed=self.seed, + start=start, + stop=stop, + inclusionStates=inclusion_states, + security_level=security_level + ) + + async def get_bundles(self, transactions): + # type: (Iterable[TransactionHash]) -> dict + """ + Returns the bundle(s) associated with the specified transaction + hashes. + + :param Iterable[TransactionHash] transactions: + Transaction hashes. Must be a tail transaction. + + :return: + ``dict`` with the following structure:: + + { + 'bundles': List[Bundle], + List of matching bundles. Note that this value is + always a list, even if only one bundle was found. + } + + :raise :py:class:`iota.adapter.BadApiResponse`: + - if any of the bundles fails validation. + - if any of the bundles is not visible on the Tangle. + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getbundle + """ + return await extended.GetBundlesCommand(self.adapter)( + transactions=transactions, + ) + + async def get_inputs( + self, + start=0, + stop=None, + threshold=None, + security_level=None, + ): + # type: (int, Optional[int], Optional[int], Optional[int]) -> dict + """ + Gets all possible inputs of a seed and returns them, along with + the total balance. + + This is either done deterministically (by generating all + addresses until :py:meth:`find_transactions` returns an empty + result), or by providing a key range to search. + + :param int start: + Starting key index. + Defaults to 0. + + :param Optional[int] stop: + Stop before this index. + + Note that this parameter behaves like the ``stop`` attribute + in a :py:class:`slice` object; the stop index is *not* + included in the result. + + If ``None`` (default), then this method will not stop until + it finds an unused address. + + .. note:: + An unused address is an address that **has not been spent from** + and **has no transactions** referencing it on the Tangle. + + A snapshot removes transactions from the Tangle. As a + consequence, after a snapshot, it may happen that this API does + not return the correct inputs with ``stop`` being ``None``. + + As a workaround, you can save your used addresses and their + ``key_index`` attribute in a local database. Use the + ``start`` and ``stop`` parameters to tell the API from where to + start checking for inputs and where to stop. + + :param Optional[int] threshold: + If set, determines the minimum threshold for a successful + result: + + - As soon as this threshold is reached, iteration will stop. + - If the command runs out of addresses before the threshold + is reached, an exception is raised. + + .. note:: + This method does not attempt to "optimize" the result + (e.g., smallest number of inputs, get as close to + ``threshold`` as possible, etc.); it simply accumulates + inputs in order until the threshold is met. + + If ``threshold`` is 0, the first address in the key range + with a non-zero balance will be returned (if it exists). + + If ``threshold`` is ``None`` (default), this method will + return **all** inputs in the specified key range. + + :param Optional[int] security_level: + Number of iterations to use when generating new addresses + (see :py:meth:`get_new_addresses`). + + This value must be between 1 and 3, inclusive. + + If not set, defaults to + :py:attr:`AddressGenerator.DEFAULT_SECURITY_LEVEL`. + + :return: + ``dict`` with the following structure:: + + { + 'inputs': List[Address], + Addresses with nonzero balances that can be used + as inputs. + + 'totalBalance': int, + Aggregate balance from all matching addresses. + } + + Note that each :py:class:`Address` in the result has its + :py:attr:`Address.balance` attribute set. + + Example: + + .. code-block:: python + + response = iota.get_inputs(...) + + input0 = response['inputs'][0] # type: Address + input0.balance # 42 + + :raise: + - :py:class:`iota.adapter.BadApiResponse` if ``threshold`` + is not met. Not applicable if ``threshold`` is ``None``. + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getinputs + """ + return await extended.GetInputsCommand(self.adapter)( + seed=self.seed, + start=start, + stop=stop, + threshold=threshold, + securityLevel=security_level + ) + + async def get_latest_inclusion(self, hashes): + # type: (Iterable[TransactionHash]) -> Dict[TransactionHash, bool] + """ + Fetches the inclusion state for the specified transaction + hashes, as of the latest milestone that the node has processed. + + Effectively, this is :py:meth:`get_node_info` + + :py:meth:`get_inclusion_states`. + + :param Iterable[TransactionHash] hashes: + List of transaction hashes. + + :return: + ``dict`` with the following structure:: + + { + "states": Dict[TransactionHash, bool] + ``dict`` with one boolean per transaction hash in + ``hashes``. + } + + """ + return await extended.GetLatestInclusionCommand(self.adapter)(hashes=hashes) + + async def get_new_addresses( + self, + index=0, + count=1, + security_level=AddressGenerator.DEFAULT_SECURITY_LEVEL, + checksum=False, + ): + # type: (int, int, int, bool) -> dict + """ + Generates one or more new addresses from the seed. + + :param int index: + The key index of the first new address to generate (must be + >= 0). + + :param int count: + Number of addresses to generate (must be >= 1). + + .. tip:: + This is more efficient than calling :py:meth:`get_new_addresses` + inside a loop. + + If ``None``, this method will progressively generate + addresses and scan the Tangle until it finds one that has no + transactions referencing it and was never spent from. + + .. note:: + A snapshot removes transactions from the Tangle. As a + consequence, after a snapshot, it may happen that when ``count`` + is ``None``, this API call returns a "new" address that used to + have transactions before the snapshot. + As a workaround, you can save your used addresses and their + ``key_index`` attribute in a local database. Use the + ``index`` parameter to tell the API from where to start + generating and checking new addresses. + + :param int security_level: + Number of iterations to use when generating new addresses. + + Larger values take longer, but the resulting signatures are + more secure. + + This value must be between 1 and 3, inclusive. + + :param bool checksum: + Specify whether to return the address with the checksum. + Defaults to ``False``. + + :return: + ``dict`` with the following structure:: + + { + 'addresses': List[Address], + Always a list, even if only one address was + generated. + } + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getnewaddress + """ + return await extended.GetNewAddressesCommand(self.adapter)( + count=count, + index=index, + securityLevel=security_level, + checksum=checksum, + seed=self.seed, + ) + + async def get_transaction_objects( + self, + hashes, # type: [Iterable[TransactionHash]] + ): + # type: (...) -> dict + """ + Fetches transaction objects from the Tangle given their + transaction IDs (hashes). + + Effectively, this is :py:meth:`get_trytes` + + converting the trytes into transaction objects. + + Similar to :py:meth:`find_transaction_objects`, but accepts + list of transaction hashes as input. + + :param Iterable[TransactionHash] hashes: + List of transaction IDs (transaction hashes). + + :return: + ``dict`` with the following structure:: + + { + 'transactions': List[Transaction], + List of Transaction objects that match the input. + } + """ + return await extended.GetTransactionObjectsCommand(self.adapter)( + hashes=hashes, + ) + + async def get_transfers(self, start=0, stop=None, inclusion_states=False): + # type: (int, Optional[int], bool) -> dict + """ + Returns all transfers associated with the seed. + + :param int start: + Starting key index. + + :param Optional[int] stop: + Stop before this index. + + Note that this parameter behaves like the ``stop`` attribute + in a :py:class:`slice` object; the stop index is *not* + included in the result. + + If ``None`` (default), then this method will check every + address until it finds one that is unused. + + .. note:: + An unused address is an address that **has not been spent from** + and **has no transactions** referencing it on the Tangle. + + A snapshot removes transactions from the Tangle. As a + consequence, after a snapshot, it may happen that this API does + not return the expected transfers with ``stop`` being ``None``. + + As a workaround, you can save your used addresses and their + ``key_index`` attribute in a local database. Use the + ``start`` and ``stop`` parameters to tell the API from where to + start checking for transfers and where to stop. + + :param bool inclusion_states: + Whether to also fetch the inclusion states of the transfers. + + This requires an additional API call to the node, so it is + disabled by default. + + :return: + ``dict`` with the following structure:: + + { + 'bundles': List[Bundle], + Matching bundles, sorted by tail transaction + timestamp. + + This value is always a list, even if only one + bundle was found. + } + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#gettransfers + """ + return await extended.GetTransfersCommand(self.adapter)( + seed=self.seed, + start=start, + stop=stop, + inclusionStates=inclusion_states, + ) + + async def is_promotable( + self, + tails, # type: Iterable[TransactionHash] + ): + # type: (Iterable(TransactionHash)] -> dict + """ + Checks if tail transaction(s) is promotable by calling + :py:meth:`check_consistency` and verifying that ``attachmentTimestamp`` + is above a lower bound. + Lower bound is calculated based on number of milestones issued + since transaction attachment. + + :param Iterable(TransactionHash) tails: + List of tail transaction hashes. + + :return: + The return type mimics that of :py:meth:`check_consistency`. + ``dict`` with the following structure:: + + { + 'promotable': bool, + If ``True``, all tails are promotable. If ``False``, see + `info` field. + + 'info': Optional(List[Text]) + If `promotable` is ``False``, this contains info about what + went wrong. + Note that when 'promotable' is ``True``, 'info' does not + exist. + + } + + References: + - https://github.com/iotaledger/iota.js/blob/next/api_reference.md#module_core.isPromotable + """ + return await extended.IsPromotableCommand(self.adapter)( + tails=tails, + ) + + async def prepare_transfer( + self, + transfers, # type: Iterable[ProposedTransaction] + inputs=None, # type: Optional[Iterable[Address]] + change_address=None, # type: Optional[Address] + security_level=None, # type: Optional[int] + ): + # type: (...) -> dict + """ + Prepares transactions to be broadcast to the Tangle, by + generating the correct bundle, as well as choosing and signing + the inputs (for value transfers). + + :param Iterable[ProposedTransaction] transfers: + Transaction objects to prepare. + + :param Optional[Iterable[Address]] inputs: + List of addresses used to fund the transfer. + Ignored for zero-value transfers. + + If not provided, addresses will be selected automatically by + scanning the Tangle for unspent inputs. Depending on how + many transfers you've already sent with your seed, this + process could take awhile. + + :param Optional[Address] change_address: + If inputs are provided, any unspent amount will be sent to + this address. + + If not specified, a change address will be generated + automatically. + + :param Optional[int] security_level: + Number of iterations to use when generating new addresses + (see :py:meth:`get_new_addresses`). + + This value must be between 1 and 3, inclusive. + + If not set, defaults to + :py:attr:`AddressGenerator.DEFAULT_SECURITY_LEVEL`. + + :return: + ``dict`` with the following structure:: + + { + 'trytes': List[TransactionTrytes], + Raw trytes for the transactions in the bundle, + ready to be provided to :py:meth:`send_trytes`. + } + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#preparetransfers + """ + return await extended.PrepareTransferCommand(self.adapter)( + seed=self.seed, + transfers=transfers, + inputs=inputs, + changeAddress=change_address, + securityLevel=security_level, + ) + + async def promote_transaction( + self, + transaction, + depth=3, + min_weight_magnitude=None, + ): + # type: (TransactionHash, int, Optional[int]) -> dict + """ + Promotes a transaction by adding spam on top of it. + + :param TransactionHash transaction: + Transaction hash. Must be a tail transaction. + + :param int depth: + Depth at which to attach the bundle. + Defaults to 3. + + :param Optional[int] min_weight_magnitude: + Min weight magnitude, used by the node to calibrate Proof of + Work. + + If not provided, a default value will be used. + + :return: + ``dict`` with the following structure:: + + { + 'bundle': Bundle, + The newly-published bundle. + } + """ + if min_weight_magnitude is None: + min_weight_magnitude = self.default_min_weight_magnitude + + return await extended.PromoteTransactionCommand(self.adapter)( + transaction=transaction, + depth=depth, + minWeightMagnitude=min_weight_magnitude, + ) + + async def replay_bundle( + self, + transaction, + depth=3, + min_weight_magnitude=None, + ): + # type: (TransactionHash, int, Optional[int]) -> dict + """ + Takes a tail transaction hash as input, gets the bundle + associated with the transaction and then replays the bundle by + attaching it to the Tangle. + + :param TransactionHash transaction: + Transaction hash. Must be a tail. + + :param int depth: + Depth at which to attach the bundle. + Defaults to 3. + + :param Optional[int] min_weight_magnitude: + Min weight magnitude, used by the node to calibrate Proof of + Work. + + If not provided, a default value will be used. + + :return: + ``dict`` with the following structure:: + + { + 'trytes': List[TransactionTrytes], + Raw trytes that were published to the Tangle. + } + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#replaytransfer + """ + if min_weight_magnitude is None: + min_weight_magnitude = self.default_min_weight_magnitude + + return await extended.ReplayBundleCommand(self.adapter)( + transaction=transaction, + depth=depth, + minWeightMagnitude=min_weight_magnitude, + ) + + async def send_transfer( + self, + transfers, # type: Iterable[ProposedTransaction] + depth=3, # type: int + inputs=None, # type: Optional[Iterable[Address]] + change_address=None, # type: Optional[Address] + min_weight_magnitude=None, # type: Optional[int] + security_level=None, # type: Optional[int] + ): + # type: (...) -> dict + """ + Prepares a set of transfers and creates the bundle, then + attaches the bundle to the Tangle, and broadcasts and stores the + transactions. + + :param Iterable[ProposedTransaction] transfers: + Transfers to include in the bundle. + + :param int depth: + Depth at which to attach the bundle. + Defaults to 3. + + :param Optional[Iterable[Address]] inputs: + List of inputs used to fund the transfer. + Not needed for zero-value transfers. + + :param Optional[Address] change_address: + If inputs are provided, any unspent amount will be sent to + this address. + + If not specified, a change address will be generated + automatically. + + :param Optional[int] min_weight_magnitude: + Min weight magnitude, used by the node to calibrate Proof of + Work. + + If not provided, a default value will be used. + + :param Optional[int] security_level: + Number of iterations to use when generating new addresses + (see :py:meth:`get_new_addresses`). + + This value must be between 1 and 3, inclusive. + + If not set, defaults to + :py:attr:`AddressGenerator.DEFAULT_SECURITY_LEVEL`. + + :return: + ``dict`` with the following structure:: + + { + 'bundle': Bundle, + The newly-published bundle. + } + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#sendtransfer + """ + if min_weight_magnitude is None: + min_weight_magnitude = self.default_min_weight_magnitude + + return await extended.SendTransferCommand(self.adapter)( + seed=self.seed, + depth=depth, + transfers=transfers, + inputs=inputs, + changeAddress=change_address, + minWeightMagnitude=min_weight_magnitude, + securityLevel=security_level, + ) + + async def send_trytes(self, trytes, depth=3, min_weight_magnitude=None): + # type: (Iterable[TransactionTrytes], int, Optional[int]) -> dict + """ + Attaches transaction trytes to the Tangle, then broadcasts and + stores them. + + :param Iterable[TransactionTrytes] trytes: + Transaction encoded as a tryte sequence. + + :param int depth: + Depth at which to attach the bundle. + Defaults to 3. + + :param Optional[int] min_weight_magnitude: + Min weight magnitude, used by the node to calibrate Proof of + Work. + + If not provided, a default value will be used. + + :return: + ``dict`` with the following structure:: + + { + 'trytes': List[TransactionTrytes], + Raw trytes that were published to the Tangle. + } + + References: + + - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#sendtrytes + """ + if min_weight_magnitude is None: + min_weight_magnitude = self.default_min_weight_magnitude + + return await extended.SendTrytesCommand(self.adapter)( + trytes=trytes, + depth=depth, + minWeightMagnitude=min_weight_magnitude, + ) + + async def is_reattachable(self, addresses): + # type: (Iterable[Address]) -> dict + """ + This API function helps you to determine whether you should + replay a transaction or make a new one (either with the same + input, or a different one). + + This method takes one or more input addresses (i.e. from spent + transactions) as input and then checks whether any transactions + with a value transferred are confirmed. + + If yes, it means that this input address has already been + successfully used in a different transaction, and as such you + should no longer replay the transaction. + + :param Iterable[Address] addresses: + List of addresses. + + :return: + ``dict`` with the following structure:: + + { + 'reattachable': List[bool], + Always a list, even if only one address was queried. + } + + """ + return await extended.IsReattachableCommand(self.adapter)( + addresses=addresses + ) + + async def traverse_bundle(self, tail_hash): + # type: (TransactionHash) -> dict + """ + Fetches and traverses a bundle from the Tangle given a tail transaction + hash. + Recursively traverse the Tangle, collecting transactions until + we hit a new bundle. + + This method is (usually) faster than :py:meth:`find_transactions`, and + it ensures we don't collect transactions from replayed bundles. + + :param TransactionHash tail_hash: + Tail transaction hash of the bundle. + + :return: + ``dict`` with the following structure:: + + { + 'bundle': List[Bundle], + List of matching bundles. Note that this value is + always a list, even if only one bundle was found. + } + + """ + return await extended.TraverseBundleCommand(self.adapter)( + transaction=tail_hash + ) \ No newline at end of file From 0207cbc8dca29e7a8294fe73eacba81eca156714 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 17 Feb 2020 12:05:08 +0100 Subject: [PATCH 35/69] Remove ``requests`` dependency --- examples/hello_world.py | 5 ++--- setup.py | 4 ---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/examples/hello_world.py b/examples/hello_world.py index bf24d34..b21b8c3 100644 --- a/examples/hello_world.py +++ b/examples/hello_world.py @@ -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__ @@ -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) ) diff --git a/setup.py b/setup.py index f6d2079..cd23928 100644 --- a/setup.py +++ b/setup.py @@ -66,10 +66,6 @@ 'httpx', 'phx-filters; python_version >= "3.5"', 'pysha3', - - # ``security`` extra wasn't introduced until 2.4.1 - # http://docs.python-requests.org/en/latest/community/updates/#id35 - 'requests[security] >= 2.4.1', 'six', 'typing; python_version < "3.0"', ], From ce55c6bf1dbac76cde6c47f29fac66bac9477378 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 17 Feb 2020 15:27:23 +0100 Subject: [PATCH 36/69] code formatting, type hints --- iota/api.py | 7 ++++--- iota/api_async.py | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/iota/api.py b/iota/api.py index 50110db..4976a30 100644 --- a/iota/api.py +++ b/iota/api.py @@ -1,3 +1,5 @@ +from typing import Dict, Iterable, Optional, Text + from iota import AdapterSpec, Address, BundleHash, ProposedTransaction, Tag, \ TransactionHash, TransactionTrytes, TryteString from iota.crypto.addresses import AddressGenerator @@ -40,6 +42,7 @@ class InvalidCommand(ValueError): # suggestions, intellisense, code completion, etc. for the user. # Therefore we keep the manual approach. + class StrictIota(AsyncStrictIota): """ Synchronous API to send HTTP requests for communicating with an IOTA node. @@ -90,7 +93,6 @@ def __init__(self, adapter, devnet=False, local_pow=False): """ super().__init__(adapter, devnet, local_pow) - def add_neighbors(self, uris): # type: (Iterable[Text]) -> dict """ @@ -215,7 +217,6 @@ def broadcast_transactions(self, trytes): ) ) - def check_consistency(self, tails): # type: (Iterable[TransactionHash]) -> dict """ @@ -1756,4 +1757,4 @@ def traverse_bundle(self, tail_hash): super().traverse_bundle( tail_hash, ) - ) \ No newline at end of file + ) diff --git a/iota/api_async.py b/iota/api_async.py index 7ec4781..705e2e7 100644 --- a/iota/api_async.py +++ b/iota/api_async.py @@ -1,3 +1,5 @@ +from typing import Dict, Iterable, Optional, Text + from iota import AdapterSpec, Address, BundleHash, ProposedTransaction, Tag, \ TransactionHash, TransactionTrytes, TryteString, TrytesCompatible from iota.adapter import BaseAdapter, resolve_adapter @@ -97,7 +99,7 @@ def set_local_pow(self, local_pow): # type: (bool) -> None """ Sets the :py:attr:`local_pow` attribute of the adapter of the api - instance. If it is ``True``, :py:meth:`attach_to_tangle` command calls + instance. If it is ``True``, :py:meth:`~Iota.attach_to_tangle` command calls external interface to perform proof of work, instead of sending the request to a node. From a2630fda69f6f4f6498c33637d1abfd9a3ee64ad Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Tue, 18 Feb 2020 12:00:17 +0100 Subject: [PATCH 37/69] Add connection pooling to HttpAdapter - AsyncClient instance attribute in HttpAdapter class - Client will keep and reuse connections to the same host --- iota/adapter/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iota/adapter/__init__.py b/iota/adapter/__init__.py index 5eb1b56..ae0a12f 100644 --- a/iota/adapter/__init__.py +++ b/iota/adapter/__init__.py @@ -280,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 @@ -389,7 +390,7 @@ async def _send_http_request(self, url, payload, method='post', **kwargs): 'request_url': url, }, ) - response = await AsyncClient().request(method=method, url=url, data=payload, **kwargs) + response = await self.client.request(method=method, url=url, data=payload, **kwargs) self._log( level=DEBUG, From e3153352a0dc6aa2567a4ac883f5900b54075f15 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Tue, 18 Feb 2020 14:55:22 +0100 Subject: [PATCH 38/69] docs: update API docs with async --- docs/api.rst | 56 ++++++++++++++++++++++++++++++++++--------- docs/core_api.rst | 39 +++++++++++++++++++++++++++++- docs/extended_api.rst | 37 ++++++++++++++++++++++++++++ iota/api_async.py | 2 +- 4 files changed, 121 insertions(+), 13 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index e74743e..b339a8e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -15,8 +15,12 @@ The available methods can be grouped into two categories:** | | | +------------------------------------+------------------------------------+ +**PyOTA supports both synchronous and asynchronous communication with the network, +therefore the Core and Extended API classes are available in synchronous and +asynchronous versions.** + To use the API in your Python application or script, declare an -API instance of any of the two above. +API instance of any of the API classes. **Since the Extended API incorporates the Core API, usually you end up only using the Extended API,** but if for some reason you need only the core functionality, the library is there to help you. @@ -25,12 +29,13 @@ functionality, the library is there to help you. :linenos: :emphasize-lines: 3,4 + # Synchronous API classes from iota import Iota, StrictIota - # This is how you declare an Extended API, use the methods of this object. + # This is how you declare a sync Extended API, use the methods of this object. api = Iota('adapter-specification') - # This is how you declare a Core API, use the methods of this object. + # This is how you declare a sync Core API, use the methods of this object. api = StrictIota('adapter-specification') .. py:module:: iota @@ -41,19 +46,48 @@ implementation point of view, :py:class:`Iota` is a subclass of :py:class:`StrictIota`, therefore it inherits every method and attribute the latter has. -Take a look on the class definitions and notice that :py:class:`Iota` -has a :py:class:`Seed` attribute. This is becasue the Extended API is able -to generate private keys, addresses and signatures from your seed. -**Your seed never leaves the library and your machine!** +To use the functionally same, but asynchronous API classes, you can do the +following: + +.. code-block:: + :linenos: + :emphasize-lines: 3,4 + + # Asynchronous API classes + from iota import AsyncIota, AsyncStrictIota -Core API Class --------------- + # This is how you declare an async Extended API, use the methods of this object. + api = AsyncIota('adapter-specification') + # This is how you declare an async Core API, use the methods of this object. + api = AsyncStrictIota('adapter-specification') + + +Take a look on the class definitions and notice that :py:class:`Iota` and +:py:class:`AsyncIota` have a :py:class:`Seed` attribute. This is because the +Extended API is able to generate private keys, addresses and signatures from +your seed. **Your seed never leaves the library and your machine!** + +Core API Classes +---------------- +Synchronous +^^^^^^^^^^^ .. autoclass:: StrictIota :members: set_local_pow -Extended API Class ------------------- +Asynchronous +^^^^^^^^^^^^ +.. autoclass:: AsyncStrictIota + :members: set_local_pow +Extended API Classes +-------------------- +Synchronous +^^^^^^^^^^^ .. autoclass:: Iota :members: set_local_pow + +Asynchronous +^^^^^^^^^^^^ +.. autoclass:: AsyncIota + :members: set_local_pow diff --git a/docs/core_api.rst b/docs/core_api.rst index 37d4d5d..529b71f 100644 --- a/docs/core_api.rst +++ b/docs/core_api.rst @@ -12,76 +12,113 @@ For the full documentation of all the Core API calls, please refer to the `official documentation `__. +.. note:: + Below you will find the documentation for both the synchronous and + asynchronous versions of the Core API methods. + + It should be made clear, that they do exactly the same IOTA related + operations, accept the same arguments and return the same structures. + Asynchronous API calls are non-blocking, so your application + can do other stuff while waiting for the result from the network. + + While synchronous API calls are regular Python methods, their respective + asynchronous versions are `Python coroutines`_. You can ``await`` their + results, schedule them for execution inside and event loop and much more. + PyOTA uses the built-in `asyncio`_ Python module for asynchronous operation. + For an overview of what you can do with it, head over to `this article`_. + .. py:currentmodule:: iota ``add_neighbors`` ----------------- .. automethod:: Iota.add_neighbors +.. automethod:: AsyncIota.add_neighbors ``attach_to_tangle`` -------------------- .. automethod:: Iota.attach_to_tangle +.. automethod:: AsyncIota.attach_to_tangle ``broadcast_transactions`` -------------------------- .. automethod:: Iota.broadcast_transactions +.. automethod:: AsyncIota.broadcast_transactions ``check_consistency`` --------------------- .. automethod:: Iota.check_consistency +.. automethod:: AsyncIota.check_consistency ``find_transactions`` --------------------- .. automethod:: Iota.find_transactions +.. automethod:: AsyncIota.find_transactions ``get_balances`` ---------------- .. automethod:: Iota.get_balances +.. automethod:: AsyncIota.get_balances ``get_inclusion_states`` ------------------------ .. automethod:: Iota.get_inclusion_states +.. automethod:: AsyncIota.get_inclusion_states ``get_missing_transactions`` ---------------------------- .. automethod:: Iota.get_missing_transactions +.. automethod:: AsyncIota.get_missing_transactions ``get_neighbors`` ----------------- .. automethod:: Iota.get_neighbors +.. automethod:: AsyncIota.get_neighbors ``get_node_api_configuration`` ------------------------------ .. automethod:: Iota.get_node_api_configuration +.. automethod:: AsyncIota.get_node_api_configuration ``get_node_info`` ----------------- .. automethod:: Iota.get_node_info +.. automethod:: AsyncIota.get_node_info ``get_tips`` ------------ .. automethod:: Iota.get_tips +.. automethod:: AsyncIota.get_tips ``get_transactions_to_approve`` ------------------------------- .. automethod:: Iota.get_transactions_to_approve +.. automethod:: AsyncIota.get_transactions_to_approve ``get_trytes`` -------------- .. automethod:: Iota.get_trytes +.. automethod:: AsyncIota.get_trytes ``interrupt_attaching_to_tangle`` --------------------------------- .. automethod:: Iota.interrupt_attaching_to_tangle +.. automethod:: AsyncIota.interrupt_attaching_to_tangle ``remove_neighbors`` -------------------- .. automethod:: Iota.remove_neighbors +.. automethod:: AsyncIota.remove_neighbors ``store_transactions`` ---------------------- .. automethod:: Iota.store_transactions +.. automethod:: AsyncIota.store_transactions ``were_addresses_spent_from`` ----------------------------- -.. automethod:: Iota.were_addresses_spent_from \ No newline at end of file +.. automethod:: Iota.were_addresses_spent_from +.. automethod:: AsyncIota.were_addresses_spent_from + +.. _Python coroutines: https://docs.python.org/3/library/asyncio-task.html +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _this article: https://realpython.com/async-io-python/ \ No newline at end of file diff --git a/docs/extended_api.rst b/docs/extended_api.rst index 59b23b8..abb437a 100644 --- a/docs/extended_api.rst +++ b/docs/extended_api.rst @@ -4,77 +4,114 @@ Extended API Methods The Extended API includes a number of "high level" commands to perform tasks such as sending and receiving transfers. +.. note:: + Below you will find the documentation for both the synchronous and + asynchronous versions of the Extebded API methods. + + It should be made clear, that they do exactly the same IOTA related + operations, accept the same arguments and return the same structures. + Asynchronous API calls are non-blocking, so your application + can do other stuff while waiting for the result from the network. + + While synchronous API calls are regular Python methods, their respective + asynchronous versions are `Python coroutines`_. You can ``await`` their + results, schedule them for execution inside and event loop and much more. + PyOTA uses the built-in `asyncio`_ Python module for asynchronous operation. + For an overview of what you can do with it, head over to `this article`_. + + .. py:currentmodule:: iota ``broadcast_and_store`` ----------------------- .. automethod:: Iota.broadcast_and_store +.. automethod:: AsyncIota.broadcast_and_store ``broadcast_bundle`` -------------------- .. automethod:: Iota.broadcast_bundle +.. automethod:: AsyncIota.broadcast_bundle ``find_transaction_objects`` ---------------------------- .. automethod:: Iota.find_transaction_objects +.. automethod:: AsyncIota.find_transaction_objects ``get_account_data`` -------------------- .. automethod:: Iota.get_account_data +.. automethod:: AsyncIota.get_account_data ``get_bundles`` --------------- .. automethod:: Iota.get_bundles +.. automethod:: AsyncIota.get_bundles ``get_inputs`` -------------- .. automethod:: Iota.get_inputs +.. automethod:: AsyncIota.get_inputs ``get_latest_inclusion`` ------------------------ .. automethod:: Iota.get_latest_inclusion +.. automethod:: AsyncIota.get_latest_inclusion ``get_new_addresses`` --------------------- .. automethod:: Iota.get_new_addresses +.. automethod:: AsyncIota.get_new_addresses ``get_transaction_objects`` --------------------------- .. automethod:: Iota.get_transaction_objects +.. automethod:: AsyncIota.get_transaction_objects ``get_transfers`` ----------------- .. automethod:: Iota.get_transfers +.. automethod:: AsyncIota.get_transfers ``is_promotable`` ----------------- .. automethod:: Iota.is_promotable +.. automethod:: AsyncIota.is_promotable ``is_reattachable`` ------------------- .. automethod:: Iota.is_reattachable +.. automethod:: AsyncIota.is_reattachable ``prepare_transfer`` -------------------- .. automethod:: Iota.prepare_transfer +.. automethod:: AsyncIota.prepare_transfer ``promote_transaction`` ----------------------- .. automethod:: Iota.promote_transaction +.. automethod:: AsyncIota.promote_transaction ``replay_bundle`` ----------------- .. automethod:: Iota.replay_bundle +.. automethod:: AsyncIota.replay_bundle ``send_transfer`` ----------------- .. automethod:: Iota.send_transfer +.. automethod:: AsyncIota.send_transfer ``send_trytes`` --------------- .. automethod:: Iota.send_trytes +.. automethod:: AsyncIota.send_trytes ``traverse_bundle`` ------------------- .. automethod:: Iota.traverse_bundle +.. automethod:: AsyncIota.traverse_bundle +.. _Python coroutines: https://docs.python.org/3/library/asyncio-task.html +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _this article: https://realpython.com/async-io-python/ \ No newline at end of file diff --git a/iota/api_async.py b/iota/api_async.py index 705e2e7..6004242 100644 --- a/iota/api_async.py +++ b/iota/api_async.py @@ -19,7 +19,7 @@ class AsyncStrictIota: Asynchronous API to send HTTP requests for communicating with an IOTA node. This implementation only exposes the "core" API methods. For a more - feature-complete implementation, use :py:class:`Iota` instead. + feature-complete implementation, use :py:class:`AsyncIota` instead. References: From 11474353cf1733f38edcac95f90151fa032582f0 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Wed, 19 Feb 2020 13:09:38 +0100 Subject: [PATCH 39/69] docs: Update multisigs, add info on async --- docs/multisig.rst | 156 ++++++++++++++++++++++++++++++----- iota/multisig/api.py | 98 +++++++++++++++------- iota/multisig/transaction.py | 23 +++++- iota/multisig/types.py | 39 +++++++++ 4 files changed, 264 insertions(+), 52 deletions(-) diff --git a/docs/multisig.rst b/docs/multisig.rst index 7b350c2..e533d0c 100644 --- a/docs/multisig.rst +++ b/docs/multisig.rst @@ -1,24 +1,98 @@ Multisignature ============== -Multisignature transactions are transactions which require multiple signatures before execution. In simplest example it means that, if there is token wallet which require 5 signatures from different parties, all 5 parties must sign spent transaction, before it will be processed. +Multisignature transactions are transactions which require multiple signatures +before execution. In simplest example it means that, if there is token wallet +which require 5 signatures from different parties, all 5 parties must sign spent +transaction, before it will be processed. -It is standard functionality in blockchain systems and it is also implemented in IOTA +It is standard functionality in blockchain systems and it is also implemented +in IOTA. .. note:: You can read more about IOTA multisignature on the `wiki`_. +First, we will take a look on what `Multisig API`_ (s) you can use, and what +`PyOTA Multisignature Types`_ are there for you if the standard API is not +enough for your application and you want to take more control. + +Starting from `Generating multisignature address`_, a tutorial follows to +show you how to use the multisignature API to execute multisig +transfers. The complete source code for the tutorial can be found `here`_. + +Multisig API +------------ +The multisignature API builds on top of the extended API to add multisignature +features. Just like for the regular APIs, there is both a synchronous and an +asynchronous version of the multisignature API, however, as there is no +networking required during the multisignature API calls, the difference between +them is only how you can call them. + +.. py:currentmodule:: iota.multisig + +Synchronous Multisignature API Class +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. autoclass:: MultisigIota + + +Asynchronous Multisignature API Class +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. autoclass:: AsyncMultisigIota + +``create_multisig_address`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. automethod:: MultisigIota.create_multisig_address +.. automethod:: AsyncMultisigIota.create_multisig_address + +``get_digests`` +~~~~~~~~~~~~~~~ +.. automethod:: MultisigIota.get_digests +.. automethod:: AsyncMultisigIota.get_digests + +``get_private_keys`` +~~~~~~~~~~~~~~~~~~~~ +.. automethod:: MultisigIota.get_private_keys +.. automethod:: AsyncMultisigIota.get_private_keys + +``prepare_multisig_transfer`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. automethod:: MultisigIota.prepare_multisig_transfer +.. automethod:: AsyncMultisigIota.prepare_multisig_transfer + +PyOTA Multisignature Types +-------------------------- + +There are some specific types defined in PyOTA to help you work with creating +multisignature addresses and bundles. + +Multisignature Address +~~~~~~~~~~~~~~~~~~~~~~ +.. autoclass:: iota.multisig.types.MultisigAddress + :members: as_json_compatible + +Multisignature ProposedBundle +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. autoclass:: iota.multisig.transaction.ProposedMultisigBundle + :members: add_inputs + Generating multisignature address --------------------------------- -In order to use multisignature functionality, a special multisignature address must be created. It is done by adding each key digest in agreed order into digests list. At the end, last participant is converting digests list (Curl state trits) into multisignature address. +In order to use multisignature functionality, a special multisignature address +must be created. It is done by adding each key digest in agreed order into +digests list. At the end, last participant is converting digests list (Kerl +state trits) into multisignature address. .. note:: - Each multisignature addresses participant has to create its own digest locally. Then, when it is created it can be safely shared with other participants, in order to build list of digests which then will be converted into multisignature address. + Each multisignature addresses participant has to create its own digest + locally. Then, when it is created it can be safely shared with other + participants, in order to build list of digests which then will be + converted into multisignature address. - Created digests should be shared with each multisignature participant, so each one of them could regenerate address and ensure it is OK. + Created digests should be shared with each multisignature participant, so + each one of them could regenerate address and ensure it is OK. Here is the example where digest is created: @@ -55,9 +129,9 @@ And here is example where digests are converted into multisignature address: .. note:: - As you can see in above example, multisignature addresses is created from list of digests, and in this case **order** is important. The same order need to be used in **signing transfer**. - - + As you can see in above example, multisignature addresses is created from + list of digests, and in this case **order** is important. The same order + need to be used in **signing transfer**. Prepare transfer @@ -65,9 +139,12 @@ Prepare transfer .. note:: - Since spending tokens from the same address more than once is insecure, remainder should be transferred to other address. So, this address should be created before as next to be used multisignature address. + Since spending tokens from the same address more than once is insecure, + remainder should be transferred to other address. So, this address should + be created before as next to be used multisignature address. -First signer for multisignature wallet is defining address where tokens should be transferred and next wallet address for reminder: +First signer for multisignature wallet is defining address where tokens should +be transferred and next wallet address for reminder: .. code-block:: python @@ -113,13 +190,18 @@ First signer for multisignature wallet is defining address where tokens should b Sign the inputs --------------- -When trytes are prepared, round of signing must be performed. Order of signing must be the same as in generate multisignature addresses procedure (as described above). +When trytes are prepared, round of signing must be performed. Order of signing +must be the same as in generate multisignature addresses procedure (as +described above). .. note:: - In example below, all signing is done on one local machine. In real case, each participant sign bundle locally and then passes it to next participant in previously defined order + In example below, all signing is done on one local machine. In real case, + each participant sign bundle locally and then passes it to next participant + in previously defined order - **index**, **count** and **security_lavel** parameters for each private key should be the same as used in **get_digests** function in previous steps. + **index**, **count** and **security_lavel** parameters for each private key + should be the same as used in **get_digests** function in previous steps. .. code-block:: python @@ -173,13 +255,25 @@ Full code `example`_. How M-of-N works - One of the key differences between IOTA multi-signatures is that M-of-N (e.g. 3 of 5) works differently. What this means is that in order to successfully spend inputs, all of the co-signers have to sign the transaction. As such, in order to enable M-of-N we have to make use of a simple trick: sharing of private keys. + One of the key differences between IOTA multi-signatures is that M-of-N + (e.g. 3 of 5) works differently. What this means is that in order to + successfully spend inputs, all of the co-signers have to sign the transaction. + As such, in order to enable M-of-N we have to make use of a simple trick: + sharing of private keys. This concept is best explained with a concrete example: - Lets say that we have a multi-signature between 3 parties: Alice, Bob and Carol. Each has their own private key, and they generated a new multi-signature address in the aforementioned order. Currently, this is a 3 of 3 multisig. This means that all 3 participants (Alice, Bob and Carol) need to sign the inputs with their private keys in order to successfully spend them. + Lets say that we have a multi-signature between 3 parties: Alice, Bob + and Carol. Each has their own private key, and they generated a new + multi-signature address in the aforementioned order. Currently, this is + a 3 of 3 multisig. This means that all 3 participants (Alice, Bob and + Carol) need to sign the inputs with their private keys in order to + successfully spend them. - In order to enable a 2 of 3 multisig, the cosigners need to share their private keys with the other parties in such a way that no single party can sign inputs alone, but that still enables an M-of-N multsig. In our example, the sharing of the private keys would look as follows: + In order to enable a 2 of 3 multisig, the cosigners need to share their + private keys with the other parties in such a way that no single party + can sign inputs alone, but that still enables an M-of-N multsig. In our + example, the sharing of the private keys would look as follows: Alice -> Bob @@ -187,27 +281,47 @@ Full code `example`_. Carol -> Alice - Now, each participant holds two private keys that he/she can use to collude with another party to successfully sign the inputs and make a transaction. But no single party holds enough keys (3 of 3) to be able to independently make the transaction. + Now, each participant holds two private keys that he/she can use to + collude with another party to successfully sign the inputs and make a + transaction. But no single party holds enough keys (3 of 3) to be able + to independently make the transaction. Important --------- -There are some general rules (repeated once again for convenience) which should be followed while working with multisignature addresses (and in general with IOTA): +There are some general rules (repeated once again for convenience) which should +be followed while working with multisignature addresses (and in general with IOTA): Signing order is important ~~~~~~~~~~~~~~~~~~~~~~~~~~ -When creating a multi-signature address and when signing a transaction for that address, it is important to follow the exact order that was used during the initial creation. If we have a multi-signature address that was signed in the following order: Alice -> Bob -> Carol. You will not be able to spend these inputs if you provide the signatures in a different order (e.g. Bob -> Alice -> Carol). As such, keep the signing order in mind. +When creating a multi-signature address and when signing a transaction for that +address, it is important to follow the exact order that was used during the +initial creation. If we have a multi-signature address that was signed in the +following order: Alice -> Bob -> Carol. You will not be able to spend these +inputs if you provide the signatures in a different order +(e.g. Bob -> Alice -> Carol). As such, keep the signing order in mind. Never re-use keys ~~~~~~~~~~~~~~~~~ -Probably the most important rule to keep in mind: absolutely never re-use private keys. IOTA uses one-time Winternitz signatures, which means that if you re-use private keys you significantly decrease the security of your private keys, up to the point where signing of another transaction can be done on a conventional computer within few days. Therefore, when generating a new multi-signature with your co-signers, always increase the private key **index counter** and only use a single private key once. Don't use it for any other multi-signatures and don't use it for any personal transactions. +Probably the most important rule to keep in mind: absolutely never re-use +private keys. IOTA uses one-time Winternitz signatures, which means that if you +re-use private keys you significantly decrease the security of your private +keys, up to the point where signing of another transaction can be done on a +conventional computer within few days. Therefore, when generating a new +multi-signature with your co-signers, always increase the private key +**index counter** and only use a single private key once. Don't use it for any +other multi-signatures and don't use it for any personal transactions. Never share your private keys ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Under no circumstances - other than wanting to reduce the requirements for a multi-signature (see section **How M-of-N works**) - should you share your private keys. Sharing your private keys with others means that they can sign your part of the multi-signature successfully. +Under no circumstances - other than wanting to reduce the requirements for a +multi-signature (see section **How M-of-N works**) - should you share your +private keys. Sharing your private keys with others means that they can sign +your part of the multi-signature successfully. .. _example: https://github.com/iotaledger/iota.py/blob/develop/examples/multisig.py .. _wiki: https://github.com/iotaledger/wiki/blob/master/multisigs.md +.. _here: https://github.com/iotaledger/iota.py/blob/master/examples/multisig.py diff --git a/iota/multisig/api.py b/iota/multisig/api.py index d345138..54ec62c 100644 --- a/iota/multisig/api.py +++ b/iota/multisig/api.py @@ -28,6 +28,24 @@ class AsyncMultisigIota(AsyncIota): security of your private keys, send IOTAs to unspendable addresses, etc. + Example Usage:: + + # Import API class + from iota.multisig import AsyncMultisigIota + + # Declare a multisig API instance + api = AsyncMultisigIota( + adapter = 'http://localhost:14265', + + seed = + Seed( + b'TESTVALUE9DONTUSEINPRODUCTION99999JYFRTI' + b'WMKVVBAIEIYZDWLUVOYTZBKPKLLUMPDF9PPFLO9KT', + ), + ) + + response = await api.get_digests(...) + References: - https://github.com/iotaledger/wiki/blob/master/multisigs.md @@ -38,7 +56,7 @@ async def create_multisig_address(self, digests): """ Generates a multisig address from a collection of digests. - :param digests: + :param Iterable[Digest] digests: Digests to use to create the multisig address. .. important:: @@ -71,13 +89,13 @@ async def get_digests( Digests are safe to share; use them to generate multisig addresses. - :param index: + :param int index: The starting key index. - :param count: + :param int count: Number of digests to generate. - :param security_level: + :param int security_level: Number of iterations to use when generating new addresses. Larger values take longer, but the resulting signatures are @@ -115,13 +133,13 @@ async def get_private_keys( However, in a few cases it may be necessary (e.g., for M-of-N transactions). - :param index: + :param int index: The starting key index. - :param count: + :param int count: Number of keys to generate. - :param security_level: + :param int security_level: Number of iterations to use when generating new keys. Larger values take longer, but the resulting signatures are @@ -168,18 +186,18 @@ async def prepare_multisig_transfer( If you want to spend IOTAs from non-multisig addresses, or if you want to create 0-value transfers (i.e., that don't require inputs), use - :py:meth:`iota.api.Iota.prepare_transfer` instead. + :py:meth:`~iota.Iota.prepare_transfer` instead. - :param transfers: + :param terable[ProposedTransaction] transfers: Transaction objects to prepare. .. important:: Must include at least one transaction that spends IOTAs (i.e., has a nonzero ``value``). If you want to prepare a bundle that does not spend any IOTAs, use - :py:meth:`iota.api.prepare_transfer` instead. + :py:meth:`~iota.Iota.prepare_transfer` instead. - :param multisig_input: + :param MultisigAddress multisig_input: The multisig address to use as the input for the transfers. .. note:: @@ -188,10 +206,10 @@ async def prepare_multisig_transfer( If you would like to spend from multiple multisig addresses in the same bundle, create the - :py:class:`iota.multisig.transaction.ProposedMultisigBundle` + :py:class:`~iota.multisig.transaction.ProposedMultisigBundle` object manually. - :param change_address: + :param Optional[Address] change_address: If inputs are provided, any unspent amount will be sent to this address. @@ -199,8 +217,9 @@ async def prepare_multisig_transfer( ignored. .. important:: - Unlike :py:meth:`iota.api.Iota.prepare_transfer`, this + Unlike :py:meth:`~iota.Iota.prepare_transfer`, this method will NOT generate a change address automatically. + If there are unspent inputs and ``change_address`` is empty, an exception will be raised. @@ -234,7 +253,7 @@ async def prepare_multisig_transfer( Once the correct signatures are applied, you can then perform proof of work (``attachToTangle``) and broadcast the bundle - using :py:meth:`iota.api.Iota.send_trytes`. + using :py:meth:`~iota.Iota.send_trytes`. """ return await commands.PrepareMultisigTransferCommand(self.adapter)( changeAddress=change_address, @@ -253,6 +272,24 @@ class MultisigIota(Iota, AsyncMultisigIota): security of your private keys, send IOTAs to unspendable addresses, etc. + Example Usage:: + + # Import API class + from iota.multisig import MultisigIota + + # Declare a multisig API instance + api = MultisigIota( + adapter = 'http://localhost:14265', + + seed = + Seed( + b'TESTVALUE9DONTUSEINPRODUCTION99999JYFRTI' + b'WMKVVBAIEIYZDWLUVOYTZBKPKLLUMPDF9PPFLO9KT', + ), + ) + + response = api.get_digests(...) + References: - https://github.com/iotaledger/wiki/blob/master/multisigs.md @@ -263,7 +300,7 @@ def create_multisig_address(self, digests): """ Generates a multisig address from a collection of digests. - :param digests: + :param Iterable[Digest] digests: Digests to use to create the multisig address. .. important:: @@ -296,13 +333,13 @@ def get_digests( Digests are safe to share; use them to generate multisig addresses. - :param index: + :param int index: The starting key index. - :param count: + :param int count: Number of digests to generate. - :param security_level: + :param int security_level: Number of iterations to use when generating new addresses. Larger values take longer, but the resulting signatures are @@ -341,13 +378,13 @@ def get_private_keys( However, in a few cases it may be necessary (e.g., for M-of-N transactions). - :param index: + :param int index: The starting key index. - :param count: + :param int count: Number of keys to generate. - :param security_level: + :param int security_level: Number of iterations to use when generating new keys. Larger values take longer, but the resulting signatures are @@ -395,18 +432,18 @@ def prepare_multisig_transfer( If you want to spend IOTAs from non-multisig addresses, or if you want to create 0-value transfers (i.e., that don't require inputs), use - :py:meth:`iota.api.Iota.prepare_transfer` instead. + :py:meth:`~iota.Iota.prepare_transfer` instead. - :param transfers: + :param terable[ProposedTransaction] transfers: Transaction objects to prepare. .. important:: Must include at least one transaction that spends IOTAs (i.e., has a nonzero ``value``). If you want to prepare a bundle that does not spend any IOTAs, use - :py:meth:`iota.api.prepare_transfer` instead. + :py:meth:`~iota.Iota.prepare_transfer` instead. - :param multisig_input: + :param MultisigAddress multisig_input: The multisig address to use as the input for the transfers. .. note:: @@ -415,10 +452,10 @@ def prepare_multisig_transfer( If you would like to spend from multiple multisig addresses in the same bundle, create the - :py:class:`iota.multisig.transaction.ProposedMultisigBundle` + :py:class:`~iota.multisig.transaction.ProposedMultisigBundle` object manually. - :param change_address: + :param Optional[Address] change_address: If inputs are provided, any unspent amount will be sent to this address. @@ -426,8 +463,9 @@ def prepare_multisig_transfer( ignored. .. important:: - Unlike :py:meth:`iota.api.Iota.prepare_transfer`, this + Unlike :py:meth:`~iota.Iota.prepare_transfer`, this method will NOT generate a change address automatically. + If there are unspent inputs and ``change_address`` is empty, an exception will be raised. @@ -461,7 +499,7 @@ def prepare_multisig_transfer( Once the correct signatures are applied, you can then perform proof of work (``attachToTangle``) and broadcast the bundle - using :py:meth:`iota.api.Iota.send_trytes`. + using :py:meth:`~iota.Iota.send_trytes`. """ return asyncio.get_event_loop().run_until_complete( super(MultisigIota, self).prepare_multisig_transfer( diff --git a/iota/multisig/transaction.py b/iota/multisig/transaction.py index 42e3d9d..2974a0a 100644 --- a/iota/multisig/transaction.py +++ b/iota/multisig/transaction.py @@ -12,12 +12,33 @@ 'ProposedMultisigBundle', ] + class ProposedMultisigBundle(ProposedBundle): """ A collection of proposed transactions, with multisig inputs. Note: at this time, only a single multisig input is supported per bundle. + + .. note:: + Usually you don't have to construct :py:class:`ProposedMultisigBundle` + bundle manually, :py:meth:`~iota.multisig.MultisigIota.prepare_multisig_transfer` + does it for you. + + :param Optional[Iterable[ProposedTransaction]] transactions: + Proposed transactions that should be put into the proposed bundle. + + :param Optional[Iterable[Address]] inputs: + Addresses that hold iotas to fund outgoing transactions in the bundle. + Currently PyOTA supports only one mutlisig input address per bundle. + + :param Optional[Address] change_address: + Due to the signatures scheme of IOTA, you can only spend once from an + address. Therefore the library will always deduct the full available + amount from an input address. The unused tokens will be sent to + ``change_address`` if provided. + + :return: :py:class:`ProposedMultisigBundle` object. """ def add_inputs(self, inputs): # type: (Iterable[MultisigAddress]) -> None @@ -27,7 +48,7 @@ def add_inputs(self, inputs): Note that each input may require multiple transactions, in order to hold the entire signature. - :param inputs: + :param Iterable[MultisigAddress] inputs: MultisigAddresses to use as the inputs for this bundle. Note: at this time, only a single multisig input is supported. diff --git a/iota/multisig/types.py b/iota/multisig/types.py index b9e9fa1..4b68a81 100644 --- a/iota/multisig/types.py +++ b/iota/multisig/types.py @@ -21,6 +21,29 @@ class MultisigAddress(Address): In order to spend inputs from a multisig address, the same private keys must be used, in the same order that the corresponding digests were used to generate the address. + + .. note:: + Usually, you don't have to create a MultisigAddress manually. Use + :py:meth:`~iota.multisig.MultisigIota.create_multisig_address` to + derive an address from a list of digests. + + :py:class:`MultisigAddress` is a subclass of + :py:class:`iota.Address`, so you can use all the regular + :py:class:`iota.Address` methods on a :py:class:`MultisigAddress` + object. + + :param TrytesCompatible trytes: + Address trytes (81 trytes long). + + :param Iterable[Digest] digests: + List of digests that were used to create the address. + Order is important! + + :param Optional[int] balance: + Available balance of the address. + + :return: + :py:class:`MultisigAddress` object. """ def __init__(self, trytes, digests, balance=None): @@ -35,6 +58,22 @@ def __init__(self, trytes, digests, balance=None): ) def as_json_compatible(self): + """ + Get a JSON represenation of the :py:class:`MultisigAddress` object. + + :return: + ``dict`` with the following structure:: + + { + 'trytes': Text, + String representation of the address trytes. + 'balance': int, + Balance of the address. + 'digests': Iterable[Digest] + Digests that were used to create the address. + } + + """ # type: () -> dict return { 'trytes': self._trytes.decode('ascii'), From 1aacb7053e0a64899363e3fa574900eb1ca15f66 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Thu, 20 Feb 2020 12:30:01 +0100 Subject: [PATCH 40/69] Configure sphinx to render coroutines in docs --- docs/conf.py | 8 ++++++-- setup.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 782a2b3..5ef8a4c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,7 +31,11 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosectionlabel'] +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.autosectionlabel', + 'sphinxcontrib.asyncio', +] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -47,7 +51,7 @@ # General information about the project. project = 'PyOTA' -copyright = '2019, Phoenix Zerin & Levente Pap' +copyright = '2020, Phoenix Zerin & Levente Pap' author = 'Phoenix Zerin, Levente Pap' # The version info for the project you're documenting, acts as replacement for diff --git a/setup.py b/setup.py index cd23928..0e55323 100644 --- a/setup.py +++ b/setup.py @@ -72,7 +72,7 @@ extras_require={ 'ccurl': ['pyota-ccurl'], - 'docs-builder': ['sphinx', 'sphinx_rtd_theme'], + 'docs-builder': ['sphinx', 'sphinx_rtd_theme', 'sphinxcontrib-asyncio'], 'pow': ['pyota-pow >= 1.0.2'], # tox is able to run the tests in parallel since version 3.7 'test-runner': ['tox >= 3.7'] + tests_require, From e9795a96234181c15bc5c09a83865a7be3f7e8fc Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Thu, 20 Feb 2020 12:59:16 +0100 Subject: [PATCH 41/69] Install requirements on RTD --- docs/requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/requirements.txt diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..17515c8 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +sphinxcontrib-asyncio \ No newline at end of file From 11bf6bb1a37997033db4f082fd07db8608c901d8 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Fri, 21 Feb 2020 10:25:53 +0100 Subject: [PATCH 42/69] Update sphinx on RTD and locally - RTD uses old sphinx that does not support async directive --- docs/requirements.txt | 3 ++- setup.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 17515c8..b975dc7 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,2 @@ -sphinxcontrib-asyncio \ No newline at end of file +sphinx==2.4.2 +sphinx_rtd_theme==0.4.3 \ No newline at end of file diff --git a/setup.py b/setup.py index 0e55323..c2f7f1e 100644 --- a/setup.py +++ b/setup.py @@ -72,7 +72,7 @@ extras_require={ 'ccurl': ['pyota-ccurl'], - 'docs-builder': ['sphinx', 'sphinx_rtd_theme', 'sphinxcontrib-asyncio'], + 'docs-builder': ['sphinx >= 2.4.2', 'sphinx_rtd_theme >= 0.4.3'], 'pow': ['pyota-pow >= 1.0.2'], # tox is able to run the tests in parallel since version 3.7 'test-runner': ['tox >= 3.7'] + tests_require, From edbea7d628f3748e9ee9e7434d3455c39de0826a Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Fri, 21 Feb 2020 10:35:22 +0100 Subject: [PATCH 43/69] Remove sphinxcontrib.asyncio from docs build - Broken for newer sphinx versions --- docs/conf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 5ef8a4c..75e7ef2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -34,7 +34,6 @@ extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosectionlabel', - 'sphinxcontrib.asyncio', ] # Add any paths that contain templates here, relative to this directory. From c04ea137c9cb47e58ecd1acd6a7007c04b2de5a4 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 24 Feb 2020 16:47:45 +0100 Subject: [PATCH 44/69] Add src file for Tutorial 8 - Send transfers and monior their confirmation with AsyncIota concurrently --- examples/tutorials/08_async_send_monitor.py | 115 ++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 examples/tutorials/08_async_send_monitor.py diff --git a/examples/tutorials/08_async_send_monitor.py b/examples/tutorials/08_async_send_monitor.py new file mode 100644 index 0000000..687bc33 --- /dev/null +++ b/examples/tutorials/08_async_send_monitor.py @@ -0,0 +1,115 @@ +from iota import AsyncIota, ProposedTransaction, Address, TryteString +from typing import List +import asyncio + +# Asynchronous API instance. +api = AsyncIota( + adapter='https://nodes.devnet.iota.org:443', + devnet=True, +) + +# An arbitrary address to send zero-value transactions to. +addy = Address('PZITJTHCIIANKQWEBWXUPHWPWVNBKW9GMNALMGGSIAUOYCKNWDLUUIGAVMJYCHZXHUBRIVPLFZHUVDLME') + +# Timeout after which confirmation monitoring stops (seconds). +timeout = 120 +# How often should we poll for confirmation? (seconds) +polling_interval = 5 + + +async def send_and_monitor( + transactions: List[ProposedTransaction] +) -> bool: + """ + Send a list of transactions as a bundle and monitor their confirmation + by the network. + """ + print('Sending bundle...') + st_response = await api.send_transfer(transactions) + + sent_tx_hashes = [tx.hash for tx in st_response['bundle']] + + print('Sent bundle with transactions: ') + print(*sent_tx_hashes, sep='\n') + + # Measure elapsed time + elapsed = 0 + + print('Checking confirmation...') + while len(sent_tx_hashes) > 0: + # Determine if transactions are confirmed + git_response = await api.get_inclusion_states(sent_tx_hashes, None) + + for i, (tx, is_confirmed) in enumerate(zip(sent_tx_hashes, git_response['states'])): + if is_confirmed: + print('Transaction %s is confirmed.' % tx) + # No need to check for this any more + del sent_tx_hashes[i] + del git_response['states'][i] + + if len(sent_tx_hashes) > 0: + if timeout <= elapsed: + # timeout reached, terminate checking + return False + # Show some progress on the screen + print('.') + # Put on hold for polling_interval. Non-blocking, so you can + # do other stuff in the meantime. + await asyncio.sleep(polling_interval) + elapsed = elapsed + polling_interval + + # All transactions in the bundle are confirmed + return True + + +async def do_something() -> None: + """ + While waiting for confirmation, you can execute arbitrary code here. + """ + for _ in range(5): + print('Doing something in the meantime...') + await asyncio.sleep(2) + + +async def main() -> None: + """ + A simple application that sends zero-value transactions to the Tangle and + monitors the confirmation by the network. While waiting for the + confirmation, we schedule a task (`do_something()`) to be executed concurrently. + """ + # Transactions to be sent. + transactions = [ + ProposedTransaction( + address=addy, + value=0, + message=TryteString.from_unicode('First'), + ), + ProposedTransaction( + address=addy, + value=0, + message=TryteString.from_unicode('Second'), + ), + ProposedTransaction( + address=addy, + value=0, + message=TryteString.from_unicode('Third'), + ), + ] + + # Schedule coroutines as tasks, wait for them to finish and gather their + # results. + result = await asyncio.gather( + send_and_monitor(transactions), + # Send the same content. Bundle will be different! + send_and_monitor(transactions), + do_something(), + ) + + if not (result[0] and result[1]): + print('Transactions did not confirm after %s seconds!' % timeout) + else: + print('All transactions are confirmed!') + +if __name__ == '__main__': + # Execute main() inside an event loop if the file is ran + asyncio.run(main()) From dd88a79b15ce2e0cf79d06d03331658e4c395ca6 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Tue, 25 Feb 2020 18:09:25 +0100 Subject: [PATCH 45/69] docs: add Tutorial 8: Async Send and Monitor --- docs/tutorials.rst | 171 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 170 insertions(+), 1 deletion(-) diff --git a/docs/tutorials.rst b/docs/tutorials.rst index 0e43dfe..d9921ab 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -716,6 +716,164 @@ Now you know how to use the Tangle for data storage while keeping privacy. When you need more granular access control on how and when one could read data from the Tangle, consider using `Masked Authenticated Messaging`_ (MAM). +8. Send and Monitor Concurrently +-------------------------------- + +In this example, you will learn how to: + +- **Use the asynchronous PyOTA API.** +- **Send transactions concurrently.** +- **Monitor confirmation of transactions concurrently.** +- **Execute arbitrary code concurrently while doing the former two.** + +.. warning:: + + If you are new to `coroutines`_ and asynchronous programming in Python, it + is strongly recommended that you check out this `article`_ and the official + `asyncio`_ documentation before proceeding. + +Code +~~~~ +.. literalinclude:: ../examples/tutorials/08_async_send_monitor.py + :linenos: + +Discussion +~~~~~~~~~~ +This example is divided into 4 logical parts: + + 1. Imports and constant declarations + 2. `Coroutine`_ to send and monitor a list of transactions as a bundle. + 3. `Coroutine`_ to execute arbitrary code concurrently. + 4. A main `coroutine`_ to schedule the execution of our application. + +Let's start with the most simple one: **Imports and Constants**. + +.. literalinclude:: ../examples/tutorials/08_async_send_monitor.py + :lines: 1-17 + :lineno-start: 1 + +Notice, that we import the :py:class:`AsyncIota` api class, because we +would like to use the asynchronous and concurrent features of PyOTA. +:py:class:`List` from the :py:class:`typing` library is needed for correct +type annotations, and we also import the `asyncio`_ library. This will come +handy when we want to schedule and run the coroutines. + +On line 6, we instantiate an asynchronous IOTA API. Functionally, it does the +same operations as :py:class:`Iota`, but the api calls are defined as +coroutines. For this tutorial, we connect to a devnet node, and explicitly tell +this as well to the api on line 8. + +On line 12, we declare an IOTA address. We will send our zero value transactions +to this address. Feel free to change it to your own address. + +Once we have sent the transactions, we start monitoring their confirmation by the +network. Confirmation time depends on current network activity, the referenced +tips, etc., therefore we set a ``timeout`` of 120 seconds on line 15. You might +have to modify this value later to see the confirmation of your transactions. + +You can also fine-tune the example code by tinkering with ``polling_interval``. +This is the interval between two subsequent confirmation checks. + +Let's move on to the next block, namely the **send and monitor coroutine**. + +.. literalinclude:: ../examples/tutorials/08_async_send_monitor.py + :lines: 20-62 + :lineno-start: 20 + +Notice, that coroutines are defined in python by the ``async def`` keywords. +This makes them `awaitable`_. + +From the type annotations, we see that :py:meth:`send_and_monitor` accepts a +list of :py:class:`ProposedTransaction` objects and return a ``bool``. + +On line 28, we send the transfers with the help of +:py:meth:`AsyncIota.send_transfer`. Since this is not a regular method, but a +coroutine, we have to ``await`` its result. :py:meth:`AsyncIota.send_transfer` +takes care of building the bundle, doing proof-of-work and sending the +transactions within the bundle to the network. + +Once we sent the transfer, we collect individual transaction hashes from the +bundle, which we will use for confirmation checking. + +On line 39, the so called confirmation checking starts. With the help of +:py:meth:`AsyncIota.get_inclusion_states`, we determine if our transactions +have been confirmed by the network. The ``None`` value for the ``tips`` +parameter in the argument list basically means that check against the latest +milestone. + +On line 43, we iterate over our original ``sent_tx_hashes`` list of sent +transaction hashes and ``git_response['states']``, which is a list of ``bool`` +values, at the same time using the built in `zip`_ method. We also employ +`enumerate`_, because we need the index of the elements in each iteration. + +If a transaction is confirmed, we delete the corresponding elements from the +lists. When all transactions are confirmed, ``sent_tx_hashes`` becomes empty, +and the loop condition becomes ``False``. + +If however, not all transactions have been confirmed, we should continue +checking for confirmation. Observe line 58, where we suspend the coroutine +with :py:meth:`asyncio.sleep` for ``polling_interval`` seconds. Awaiting the +result of :py:meth:`asyncio.sleep` will cause our coroutine to continue +execution in ``polling_interval`` time. While our coroutine is sleeping, +other coroutines can run concurrently, hence it is a non-blocking call. + +To do something in the meantime, we can **execute another coroutine concurrently**: + +.. literalinclude:: ../examples/tutorials/08_async_send_monitor.py + :lines: 65-71 + :lineno-start: 65 + +This is really just a dummy coroutine that prints something to the terminal and +then goes to sleep periodically, but in a real application, you could do +meaningful tasks here. + +Now let's look at how to **schedule the execution of our application with the +main coroutine**: + +.. literalinclude:: ../examples/tutorials/08_async_send_monitor.py + :lines: 74-115 + :lineno-start: 74 + +First, we declare a list of :py:meth:`ProposedTransaction` objects, that will +be the input for our :py:meth:`send_and_monitor` coroutine. + +The important stuff begins on line 101. We use :py:meth:`asyncio.gather` to +submit our coroutines for execution, wait for their results and then return +them in a list. `gather`_ takes our coroutines, transforms them into runnable +`tasks`_, and runs them concurrently. + +Notice, that we listed :py:meth:`send_and_monitor` twice in +:py:meth:`asyncio.gather` with the same list of :py:meth:`ProposedTransaction` +objects. This is to showcase how you can send and monitor multiple transfers +concurrently. In this example, two different bundles will be created from the +same :py:meth:`ProposedTransaction` objects. The two bundles post zero value +transactions to the same address, contain the same messages respectively, +but are not dependent on each other in any way. That is why we can send them +concurrently. + +As discussed previously, ``result`` will be a list of results of the coroutines +submitted to :py:meth:`asyncio.gather`, preserving their order. +``result[0]`` is the result from the first :py:meth:`send_and_monitor`, and +``result[1]`` is the result from the second :py:meth:`send_and_monitor` from the +argument list. If any of these are ``False``, confirmation did not happen +before ``timeout``. + +When you see the message from line 109 in your terminal, try increasing +``timeout``, or check the status of the network, maybe there is a temporary +downtime on the devnet due to maintenance. + +Lastly, observe lines 113-115. If the current file (python module) is run +from the terminal, we use :py:meth:`ayncio.run` to execute the main coroutine +inside an `event loop`_. + +To run this example, navigate to ``examples/tutorial`` inside the cloned +PyOTA repository, or download the source file of `Tutorial 8 from GitHub`_ +and run the following in a terminal: + +.. code-block:: sh + + $ python 08_async_send_monitor.py + .. _PyOTA Bug Tracker: https://github.com/iotaledger/iota.py/issues .. _bytestring: https://docs.python.org/3/library/stdtypes.html#bytes .. _tryte alphabet: https://docs.iota.org/docs/getting-started/0.1/introduction/ternary#tryte-encoding @@ -723,4 +881,15 @@ data from the Tangle, consider using `Masked Authenticated Messaging`_ (MAM). .. _Account Module: https://docs.iota.org/docs/client-libraries/0.1/account-module/introduction/overview .. _spending twice from the same address: https://docs.iota.org/docs/getting-started/0.1/clients/addresses#spent-addresses .. _Base64: https://en.wikipedia.org/wiki/Base64 -.. _Masked Authenticated Messaging: https://docs.iota.org/docs/client-libraries/0.1/mam/introduction/overview?q=masked%20auth&highlights=author;authent \ No newline at end of file +.. _Masked Authenticated Messaging: https://docs.iota.org/docs/client-libraries/0.1/mam/introduction/overview?q=masked%20auth&highlights=author;authent +.. _coroutine: https://docs.python.org/3/glossary.html#term-coroutine +.. _coroutines: https://docs.python.org/3/glossary.html#term-coroutine +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _article: https://realpython.com/async-io-python/ +.. _awaitable: https://docs.python.org/3/library/asyncio-task.html#awaitables +.. _zip: https://docs.python.org/3.3/library/functions.html#zip +.. _enumerate: https://docs.python.org/3.3/library/functions.html#enumerate +.. _gather: https://docs.python.org/3/library/asyncio-task.html#running-tasks-concurrently +.. _tasks: https://docs.python.org/3/library/asyncio-task.html#asyncio.Task +.. _event loop: https://docs.python.org/3/library/asyncio-eventloop.html +.. _Tutorial 8 from GitHub: https://github.com/iotaledger/iota.py/blob/master/examples/tutorials/08_async_send_monitor.py \ No newline at end of file From 955d94b3e96f720ece5b5ff2563ea2e61f37c94c Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Wed, 26 Feb 2020 11:36:50 +0100 Subject: [PATCH 46/69] Minor fixes after PR review - links to coordinator and coordicide - fixing typos --- docs/tutorials.rst | 31 ++++++++++++++++----- examples/tutorials/08_async_send_monitor.py | 6 ++-- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/docs/tutorials.rst b/docs/tutorials.rst index d9921ab..dcbbdc8 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -756,9 +756,9 @@ Notice, that we import the :py:class:`AsyncIota` api class, because we would like to use the asynchronous and concurrent features of PyOTA. :py:class:`List` from the :py:class:`typing` library is needed for correct type annotations, and we also import the `asyncio`_ library. This will come -handy when we want to schedule and run the coroutines. +in handy when we want to schedule and run the coroutines. -On line 6, we instantiate an asynchronous IOTA API. Functionally, it does the +On line 6, we instantiate an asynchronous IOTA api. Functionally, it does the same operations as :py:class:`Iota`, but the api calls are defined as coroutines. For this tutorial, we connect to a devnet node, and explicitly tell this as well to the api on line 8. @@ -795,15 +795,28 @@ transactions within the bundle to the network. Once we sent the transfer, we collect individual transaction hashes from the bundle, which we will use for confirmation checking. -On line 39, the so called confirmation checking starts. With the help of +On line 39, the so-called confirmation checking starts. With the help of :py:meth:`AsyncIota.get_inclusion_states`, we determine if our transactions -have been confirmed by the network. The ``None`` value for the ``tips`` -parameter in the argument list basically means that check against the latest +have been confirmed by the network. + +.. note:: + + You might wonder how your transactions get accepted by the network, that is, + how they become confirmed. + + - Pre-`Coordicide`_ (current state), transactions are confirmed by + directly or indirectly being referenced by a `milestone`_. + A milestone is a special transaction issued by the `Coordinator`_. + - Post-`Coordicide`_ , confirmation is the result of nodes reaching + consensus by a `voting mechanism`_. + +The ``None`` value for the ``tips`` +parameter in the argument list basically means that we check against the latest milestone. On line 43, we iterate over our original ``sent_tx_hashes`` list of sent -transaction hashes and ``git_response['states']``, which is a list of ``bool`` -values, at the same time using the built in `zip`_ method. We also employ +transaction hashes and ``gis_response['states']``, which is a list of ``bool`` +values, at the same time using the built-in `zip`_ method. We also employ `enumerate`_, because we need the index of the elements in each iteration. If a transaction is confirmed, we delete the corresponding elements from the @@ -887,6 +900,10 @@ and run the following in a terminal: .. _asyncio: https://docs.python.org/3/library/asyncio.html .. _article: https://realpython.com/async-io-python/ .. _awaitable: https://docs.python.org/3/library/asyncio-task.html#awaitables +.. _Coordicide: https://coordicide.iota.org/ +.. _milestone: https://docs.iota.org/docs/getting-started/0.1/network/the-coordinator#milestones +.. _coordinator: https://docs.iota.org/docs/getting-started/0.1/network/the-coordinator +.. _voting mechanism: https://coordicide.iota.org/module4.1 .. _zip: https://docs.python.org/3.3/library/functions.html#zip .. _enumerate: https://docs.python.org/3.3/library/functions.html#enumerate .. _gather: https://docs.python.org/3/library/asyncio-task.html#running-tasks-concurrently diff --git a/examples/tutorials/08_async_send_monitor.py b/examples/tutorials/08_async_send_monitor.py index 687bc33..09687c6 100644 --- a/examples/tutorials/08_async_send_monitor.py +++ b/examples/tutorials/08_async_send_monitor.py @@ -38,14 +38,14 @@ async def send_and_monitor( print('Checking confirmation...') while len(sent_tx_hashes) > 0: # Determine if transactions are confirmed - git_response = await api.get_inclusion_states(sent_tx_hashes, None) + gis_response = await api.get_inclusion_states(sent_tx_hashes, None) - for i, (tx, is_confirmed) in enumerate(zip(sent_tx_hashes, git_response['states'])): + for i, (tx, is_confirmed) in enumerate(zip(sent_tx_hashes, gis_response['states'])): if is_confirmed: print('Transaction %s is confirmed.' % tx) # No need to check for this any more del sent_tx_hashes[i] - del git_response['states'][i] + del gis_response['states'][i] if len(sent_tx_hashes) > 0: if timeout <= elapsed: From 3a3904fbc186bd3fb2621946c48721e005b22f78 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Wed, 26 Feb 2020 15:25:01 +0100 Subject: [PATCH 47/69] Fix error message in PromoteTransactionCommand --- iota/commands/extended/promote_transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iota/commands/extended/promote_transaction.py b/iota/commands/extended/promote_transaction.py index 564d147..51f48e7 100644 --- a/iota/commands/extended/promote_transaction.py +++ b/iota/commands/extended/promote_transaction.py @@ -39,7 +39,7 @@ async def _execute(self, request): if cc_response['state'] is False: raise BadApiResponse( 'Transaction {transaction} is not promotable. ' - 'You should reattach first.'.format(transaction=transaction) + 'Info: {reason}'.format(transaction=transaction, reason=cc_response['info']) ) spam_transfer = ProposedTransaction( From daacbd6a34a75e5b9d0ad5e34a44c257ec27be9b Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Wed, 26 Feb 2020 16:11:02 +0100 Subject: [PATCH 48/69] Fix PromoteTransactionCommandTestCase - mocked cc call didn't contain 'info' key --- test/commands/extended/promote_transaction_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/commands/extended/promote_transaction_test.py b/test/commands/extended/promote_transaction_test.py index b8e9c4e..99a4647 100644 --- a/test/commands/extended/promote_transaction_test.py +++ b/test/commands/extended/promote_transaction_test.py @@ -401,6 +401,7 @@ async def test_not_promotable(self): self.adapter.seed_response('checkConsistency', { 'state': False, + 'info': 'Something went terribly wrong.', }) with self.assertRaises(BadApiResponse): From b80d36012f154c08a7326d8edfb22c6d2f60f113 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Wed, 26 Feb 2020 17:58:47 +0100 Subject: [PATCH 49/69] Add tests for get_bundles_from_transactions_hashes util method --- iota/commands/extended/utils.py | 18 +- test/commands/extended/utils_test.py | 562 ++++++++++++++++++++++++++- 2 files changed, 575 insertions(+), 5 deletions(-) diff --git a/iota/commands/extended/utils.py b/iota/commands/extended/utils.py index 0989850..62f962b 100644 --- a/iota/commands/extended/utils.py +++ b/iota/commands/extended/utils.py @@ -5,8 +5,9 @@ from typing import Generator, Iterable, List, Optional, Tuple from iota import Address, Bundle, Transaction, \ - TransactionHash + TransactionHash, TransactionTrytes, BadApiResponse from iota.adapter import BaseAdapter +from iota.exceptions import with_context from iota.commands.core.find_transactions import FindTransactionsCommand from iota.commands.core.get_trytes import GetTrytesCommand from iota.commands.core.were_addresses_spent_from import \ @@ -79,6 +80,21 @@ async def get_bundles_from_transaction_hashes( non_tail_bundle_hashes = set() gt_response = await GetTrytesCommand(adapter)(hashes=transaction_hashes) + for tx_hash, tx_trytes in zip(transaction_hashes, gt_response['trytes']): + # If no tx was found by the node for tx_hash, it returns 9s, + # so we check here if it returned all 9s trytes. + if tx_trytes == TransactionTrytes(''): + raise with_context( + exc=BadApiResponse( + 'Could not get trytes of transaction {hash} from the Tangle. ' + '(``exc.context`` has more info).'.format(hash=tx_hash), + ), + + context={ + 'transaction_hash': tx_hash, + 'returned_transaction_trytes': tx_trytes, + }, + ) all_transactions = list(map( Transaction.from_tryte_string, gt_response['trytes'], diff --git a/test/commands/extended/utils_test.py b/test/commands/extended/utils_test.py index 480c924..229fc5a 100644 --- a/test/commands/extended/utils_test.py +++ b/test/commands/extended/utils_test.py @@ -3,10 +3,12 @@ unicode_literals from unittest import TestCase -from iota.commands.extended.utils import iter_used_addresses -from iota import MockAdapter +from iota.commands.extended.utils import iter_used_addresses, \ + get_bundles_from_transaction_hashes +from iota.adapter import MockAdapter, async_return from iota.crypto.types import Seed -from test import mock, async_test +from test import mock, async_test, MagicMock +from iota import TransactionTrytes, TransactionHash, Bundle, BadApiResponse class IterUsedAddressesTestCase(TestCase): @@ -246,4 +248,556 @@ async def test_multiple_addresses_return(self): ] ) -# TODO: add tests for `get_bundles_from_transaction_hashes` \ No newline at end of file + +class GetBundlesFromTransactionHashesTestCase(TestCase): + def setUp(self) -> None: + # Need two valid bundles + super().setUp() + self.adapter = MockAdapter() + + self.single_bundle = Bundle.from_tryte_strings([ + TransactionTrytes( + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999POALVTTGQHJFGINKJ9' + 'EWRJZBQLLWMMNMNRUT9VFWDDMDWHPJMNDOFZXUQUABGCXZRH9OI9NWEUSHVYXDO' + '999999999999999999999999999C99999999999999999999999999RIGEHBD99' + '999999999999999999RPCKQTYDOV9IYVYYALBTBLHRFCLFMTCC9ZLOKKGENTDFY' + 'COKFUITXUIUJLBNWAEKBJKBYDSRLVHSGELCCCZGNHCYEAKJ9OPRZFIBYEEBTRFT' + 'QTWJUKRDKNSEESICPJRTDNZQQYNXOFVXI9CPRNBO9APJMEXATA9999CZGNHCYEA' + 'KJ9OPRZFIBYEEBTRFTQTWJUKRDKNSEESICPJRTDNZQQYNXOFVXI9CPRNBO9APJM' + 'EXATA9999C99999999999999999999999999FQFFNIHPF999999999MMMMMMMMM' + 'BCDJOVFVODAQEPAXIWDRFKCTOFI' + ) + ]) + + self.three_tx_bundle = Bundle.from_tryte_strings(([ + TransactionTrytes( + 'PBXCFDGDHDEAHDFDPCBDGDPCRCHDXCCDBDEAXCBDEAHDWCTCEAQCIDBDSC9DTCS' + 'A99999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999M9OVNPOWKUNQYDHFN9' + 'YAL9WIQJDVAFKBU9ZPIHSGTZLGFJODRZINZMDALS9ERTNAJ9VTENWYLBSYALQQL' + '999999999999999999999999999EYOTA9TESTS9999999999999999BOPIHBD99' + '999999999B99999999JWFDGHYGEQIKSPCWEAHHQACOYHQWINSA9GELCEZNQEUHV' + 'DH9UAYJVSTIIKW9URTHHIJYGWXGE9AEWISYWZSLPKSJETGKZEQVPISQSNDHIAXQ' + 'RZVFJXFOXZAVMRUGALCQRHUEZPDFNLCIKQGWEKDJURLZLMUZVA99999BSJCSWTG' + 'RTJSGZPOXRPICUDATCLCVTF9BEDHSZZRLSH9IRMTFRVAMSSHC9TRYZGHPWRDVTX' + 'EXWTZ9999PYOTA9TESTS9999999999999999OSZRBMHPF999999999MMMMMMMMM' + 'IVL9PTSTAIRGJLGXFQGIWOJHBKF' + ), + TransactionTrytes( + 'BCTCRCCDBDSCEAHDFDPCBDGDPCRCHDXCCDBDEAXCBDEAHDWCTCEAQCIDBDSC9DT' + 'CSA999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999LSTTHILAJWQEXWVOJQ' + 'GRANRLNHQLKYXVQFBYJ9QDFRISQR9WJYMSSZUBOCVLXF9TACHKGQUEGMJPICXVY' + '999999999999999999999999999PYOTA9TESTS9999999999999999BOPIHBD99' + 'A99999999B99999999JWFDGHYGEQIKSPCWEAHHQACOYHQWINSA9GELCEZNQEUHV' + 'DH9UAYJVSTIIKW9URTHHIJYGWXGE9AEWISYWQQAWNWHDSGZWFTKTYSV99PJIFFM' + 'OPFWONAOTRBUEDGLORTHNMXM9EZNILYEIWCQIAVMAGDBHYWWOA99999BSJCSWTG' + 'RTJSGZPOXRPICUDATCLCVTF9BEDHSZZRLSH9IRMTFRVAMSSHC9TRYZGHPWRDVTX' + 'EXWTZ9999PYOTA9TESTS9999999999999999EMSRBMHPF999999999MMMMMMMMM' + 'NXTVOIJXAAJUS9SRVJEVDVOSIUE' + ), + TransactionTrytes( + 'CCWCXCFDSCEAHDFDPCBDGDPCRCHDXCCDBDEAXCBDEAHDWCTCEAQCIDBDSC9DTCS' + 'A99999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999999999999999999999' + '999999999999999999999999999999999999999999999FSXLFSGAHTGSFPK9FH' + 'HURWZJAWQDQCRIFUHMSZWUTNRAIDNGEHGPHLNJOEAIDGLYQRCYSCYDTBZQFDGQK' + '999999999999999999999999999PYOTA9TESTS9999999999999999BOPIHBD99' + 'B99999999B99999999JWFDGHYGEQIKSPCWEAHHQACOYHQWINSA9GELCEZNQEUHV' + 'DH9UAYJVSTIIKW9URTHHIJYGWXGE9AEWISYW9BSJCSWTGRTJSGZPOXRPICUDATC' + 'LCVTF9BEDHSZZRLSH9IRMTFRVAMSSHC9TRYZGHPWRDVTXEXWTZ99999BSJCSWTG' + 'RTJSGZPOXRPICUDATCLCVTF9BEDHSZZRLSH9IRMTFRVAMSSHC9TRYZGHPWRDVTX' + 'EXWTZ9999PYOTA9TESTS9999999999999999LUSRBMHPF999999999MMMMMMMMM' + 'BOCWSYQAKMZXDR9ZPHXTXZORELC' + ), + ])) + + @async_test + async def test_happy_path(self): + """ + A bundle is successfully fetched with inclusion state. + """ + self.adapter.seed_response( + 'getTrytes', + { + 'trytes': self.single_bundle.as_tryte_strings() + } + ) + + with mock.patch( + 'iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand.__call__', + MagicMock(return_value=async_return({ + 'states': {self.single_bundle.tail_transaction.hash: True}})) + ) as mocked_glis: + with mock.patch( + 'iota.commands.extended.get_bundles.GetBundlesCommand.__call__', + MagicMock(return_value=async_return({'bundles': [self.single_bundle]})) + ) as mocked_get_bundles: + response = await get_bundles_from_transaction_hashes( + adapter=self.adapter, + transaction_hashes=[self.single_bundle.tail_transaction.hash], + inclusion_states=True, + ) + + self.assertListEqual( + response, + [self.single_bundle], + ) + + mocked_glis.assert_called_once_with( + hashes=[self.single_bundle.tail_transaction.hash] + ) + + mocked_get_bundles.assert_called_once_with( + transactions=[self.single_bundle.tail_transaction.hash] + ) + + self.assertTrue( + response[0].is_confirmed + ) + + @async_test + async def test_happy_path_no_inclusion(self): + """ + A bundle is successfully fetched without inclusion states. + """ + self.adapter.seed_response( + 'getTrytes', + { + 'trytes': self.single_bundle.as_tryte_strings() + } + ) + + with mock.patch( + 'iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand.__call__', + MagicMock(return_value=async_return({'states': { + self.single_bundle.tail_transaction.hash: True + }})) + ) as mocked_glis: + with mock.patch( + 'iota.commands.extended.get_bundles.GetBundlesCommand.__call__', + MagicMock(return_value=async_return({'bundles': [self.single_bundle]})) + ) as mocked_get_bundles: + response = await get_bundles_from_transaction_hashes( + adapter=self.adapter, + transaction_hashes=[self.single_bundle.tail_transaction.hash], + inclusion_states=False, + ) + + self.assertListEqual( + response, + [self.single_bundle], + ) + + self.assertFalse( + mocked_glis.called + ) + + mocked_get_bundles.assert_called_once_with( + transactions=[self.single_bundle.tail_transaction.hash] + ) + + self.assertFalse( + response[0].is_confirmed + ) + + @async_test + async def test_empty_list(self): + """ + Called with empty list of hashes. + """ + response = await get_bundles_from_transaction_hashes( + adapter=self.adapter, + transaction_hashes=[], + inclusion_states=True, + ) + + self.assertListEqual( + response, + [] + ) + + @async_test + async def test_no_transaction_trytes(self): + """ + Node doesn't have the requested transaction trytes. + """ + self.adapter.seed_response( + 'getTrytes', + { + 'trytes': [ + self.single_bundle.tail_transaction.as_tryte_string(), + TransactionTrytes(''), + ] + } + ) + with self.assertRaises(BadApiResponse): + response = await get_bundles_from_transaction_hashes( + adapter=self.adapter, + transaction_hashes=[ + self.single_bundle.tail_transaction.hash, + TransactionHash('') + ], + inclusion_states=False, + ) + + @async_test + async def test_multiple_tail_transactions(self): + """ + Multiple tail transactions are requested. + """ + self.adapter.seed_response( + 'getTrytes', + { + 'trytes': [ + self.single_bundle.tail_transaction.as_tryte_string(), + self.three_tx_bundle.tail_transaction.as_tryte_string(), + ] + } + ) + + with mock.patch( + 'iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand.__call__', + MagicMock(return_value=async_return({'states': { + self.single_bundle.tail_transaction.hash: True, + self.three_tx_bundle.tail_transaction.hash: True + }})) + ) as mocked_glis: + with mock.patch( + 'iota.commands.extended.get_bundles.GetBundlesCommand.__call__', + MagicMock(return_value=async_return({ + 'bundles': [ + self.single_bundle, + self.three_tx_bundle, + ] + })) + ) as mocked_get_bundles: + response = await get_bundles_from_transaction_hashes( + adapter=self.adapter, + transaction_hashes=[ + self.single_bundle.tail_transaction.hash, + self.three_tx_bundle.tail_transaction.hash + ], + inclusion_states=True, + ) + + self.assertListEqual( + response, + [ + self.single_bundle, + self.three_tx_bundle, + ], + ) + + # Check if it was called only once + mocked_glis.assert_called_once() + + # Get the keyword arguments from that call + _, _, mocked_glis_kwargs = mocked_glis.mock_calls[0] + + # 'hashes' keyword's value should be a list of hashes it was called + # with. Due to the set -> list conversion in the src code, we can't + # be sure of the order of the elements, so we check by value. + self.assertCountEqual( + mocked_glis_kwargs.get('hashes'), + [ + self.three_tx_bundle.tail_transaction.hash, + self.single_bundle.tail_transaction.hash, + ] + ) + + mocked_get_bundles.assert_called_once_with( + transactions=[ + self.single_bundle.tail_transaction.hash, + self.three_tx_bundle.tail_transaction.hash, + ] + ) + + self.assertTrue( + response[0].is_confirmed + ) + self.assertTrue( + response[1].is_confirmed + ) + + @async_test + async def test_non_tail(self): + """ + Called with a non-tail transaction. + """ + # For mocking GetTrytesCommand call + self.adapter.seed_response( + 'getTrytes', + { + # Tx with ID=1 + 'trytes': [self.three_tx_bundle[1].as_tryte_string()] + } + ) + + # For mocking FindTransactionObjectsCommand call + self.adapter.seed_response( + 'findTransactions', + { + 'hashes': [tx.hash for tx in self.three_tx_bundle] + } + ) + + self.adapter.seed_response( + 'getTrytes', + { + 'trytes': [tx.as_tryte_string() for tx in self.three_tx_bundle] + } + ) + + with mock.patch( + 'iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand.__call__', + MagicMock(return_value=async_return({'states': { + self.three_tx_bundle.tail_transaction.hash: True + }})) + ) as mocked_glis: + with mock.patch( + 'iota.commands.extended.get_bundles.GetBundlesCommand.__call__', + MagicMock(return_value=async_return({ + 'bundles': [ + self.three_tx_bundle, + ] + })) + ) as mocked_get_bundles: + response = await get_bundles_from_transaction_hashes( + adapter=self.adapter, + transaction_hashes=[self.three_tx_bundle[1].hash], + inclusion_states=True, + ) + + self.assertListEqual( + response, + [ + self.three_tx_bundle, + ], + ) + + self.assertTrue( + response[0].is_confirmed + ) + + mocked_glis.assert_called_once_with( + hashes=[self.three_tx_bundle.tail_transaction.hash] + ) + + mocked_get_bundles.assert_called_once_with( + transactions=[ + self.three_tx_bundle.tail_transaction.hash + ] + ) + + @async_test + async def test_ordered_by_timestamp(self): + """ + Returned bundles are sorted by tail transaction timestamp. + """ + self.adapter.seed_response( + 'getTrytes', + { + 'trytes': [ + self.three_tx_bundle.tail_transaction.as_tryte_string(), + self.single_bundle.tail_transaction.as_tryte_string(), + ] + } + ) + + with mock.patch( + 'iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand.__call__', + MagicMock(return_value=async_return({'states': { + self.three_tx_bundle.tail_transaction.hash: True, + self.single_bundle.tail_transaction.hash: True, + }})) + ) as mocked_glis: + with mock.patch( + 'iota.commands.extended.get_bundles.GetBundlesCommand.__call__', + MagicMock(return_value=async_return({ + 'bundles': [ + self.three_tx_bundle, + self.single_bundle, + ] + })) + ) as mocked_get_bundles: + response = await get_bundles_from_transaction_hashes( + adapter=self.adapter, + # three_tx_bundle is the first now, which should be newer + # than single_bundle + transaction_hashes=[ + self.three_tx_bundle.tail_transaction.hash, + self.single_bundle.tail_transaction.hash + ], + inclusion_states=True, + ) + + self.assertListEqual( + response, + [ + # Response is sorted in ascending order based on timestamp! + # (single_bundle is older than three_tx_bundle) + self.single_bundle, + self.three_tx_bundle, + ], + ) + + # Check if it was called only once + mocked_glis.assert_called_once() + + # Get the keyword arguments from that call + _, _, mocked_glis_kwargs = mocked_glis.mock_calls[0] + + # 'hashes' keyword's value should be a list of hashes it was called + # with. Due to the set -> list conversion in the src code, we can't + # be sure of the order of the elements, so we check by value. + self.assertCountEqual( + mocked_glis_kwargs.get('hashes'), + [ + self.three_tx_bundle.tail_transaction.hash, + self.single_bundle.tail_transaction.hash, + ] + ) + + mocked_get_bundles.assert_called_once_with( + transactions=[ + self.three_tx_bundle.tail_transaction.hash, + self.single_bundle.tail_transaction.hash, + ] + ) + + self.assertTrue( + response[0].is_confirmed + ) + self.assertTrue( + response[1].is_confirmed + ) \ No newline at end of file From 45df9fb3f227d808d4d46d76747e987ecf6145df Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Tue, 3 Mar 2020 15:53:42 +0100 Subject: [PATCH 50/69] docs: append file name to auto generated section label --- docs/commands.rst | 8 ++++---- docs/conf.py | 3 +++ docs/transfers.rst | 0 docs/tutorials.rst | 10 +++++----- iota/api.py | 6 +++--- iota/api_async.py | 6 +++--- 6 files changed, 18 insertions(+), 15 deletions(-) create mode 100644 docs/transfers.rst diff --git a/docs/commands.rst b/docs/commands.rst index a860a44..a3a5771 100644 --- a/docs/commands.rst +++ b/docs/commands.rst @@ -10,15 +10,15 @@ Advanced: PyOTA Commands However, if you are a curious mind or happen to do development on the library, the following information might be useful. -PyOTA provides the API interface (:ref:`Core API Methods` and -:ref:`Extended API Methods`) for users of the library. These handle +PyOTA provides the API interface (:ref:`core_api:Core API Methods` and +:ref:`extended_api:Extended API Methods`) for users of the library. These handle constructing and sending HTTP requests to the specified node through adapters, furthermore creating, transforming and translating between PyOTA-specific types and (JSON-encoded) raw data. They also filter outgoing requests and incoming responses to ensure that only appropriate data is communicated with the node. PyOTA implements the `Command Design Pattern`_. High level API interface -methods (:ref:`Core API Methods` and :ref:`Extended API Methods`) +methods (:ref:`core_api:Core API Methods` and :ref:`extended_api:Extended API Methods`) internally call PyOTA commands to get the job done. Most PyOTA commands are sub-classed from :py:class:`FilterCommand` class, which @@ -142,7 +142,7 @@ Extended Commands Core commands, like :py:meth:`~Iota.find_transactions` in the example above, are for direct communication with the node for simple tasks such as finding a transaction on the Tangle or getting info about the node. -Extended commands (that serve :ref:`Extended API Methods`) on the other hand +Extended commands (that serve :ref:`extended_api:Extended API Methods`) on the other hand carry out more complex operations such as combining core commands, building objects, etc... diff --git a/docs/conf.py b/docs/conf.py index 75e7ef2..be5b331 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -36,6 +36,9 @@ 'sphinx.ext.autosectionlabel', ] +# Add a document prefix to the created section lables +autosectionlabel_prefix_document = True + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/transfers.rst b/docs/transfers.rst new file mode 100644 index 0000000..e69de29 diff --git a/docs/tutorials.rst b/docs/tutorials.rst index dcbbdc8..02c3799 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -8,7 +8,7 @@ to help you understand how to carry out specific tasks with PyOTA. The example scripts displayed here can also be found under ``examples/tutorials/`` directory in the repository. Run them in a Python environment that has PyOTA -installed. See :ref:`Install PyOTA` for more info. +installed. See :ref:`README:Install PyOTA` for more info. If you feel that something is missing or not clear, please post your questions and suggestions in the `PyOTA Bug Tracker`_. @@ -43,7 +43,7 @@ something from the library, you need to import it from there. Notice, how we import the :py:class:`Iota` object, that defines a so-called extended API object. We will use this to send and receive data from -the network. Read more about API objects at :ref:`PyOTA API Classes`. +the network. Read more about API objects at :ref:`api:PyOTA API Classes`. We also import the ``pprint`` method that prettifies the output before printing it to the console. @@ -141,7 +141,7 @@ therefore we are restricted to the `tryte alphabet`_. :lines: 16-22 :lineno-start: 16 -It's time to construct the transaction. According to :ref:`Transaction Types`, +It's time to construct the transaction. According to :ref:`types:Transaction Types`, PyOTA uses :py:class:`ProposedTransaction` to build transactions that are not yet broadcast to the network. Oberve, that the ``value=0`` means this is a zero-value transaction. @@ -284,7 +284,7 @@ that has no transactions referencing it on the Tangle and was never spent from. If we were to generate more addresses starting from a desired index, we could specify the ``start`` and ``count`` parameters. Read more about how to -generate addresses in PyOTA at :ref:`Generating Addresses`. +generate addresses in PyOTA at :ref:`addresses:Generating Addresses`. On line 20 we access the first element of the list of addresses in the response dictionary. @@ -588,7 +588,7 @@ An address is also needed, so we generate one with the help of index of the generated address, and don't forget, that the method returns a ``dict`` with a list of addresses, even if it contains only one. For more detailed explanation on how addresses are generated in PyOTA, -refer to the :ref:`Generating Addresses` page. +refer to the :ref:`adresses:Generating Addresses` page. We also attach a custom :py:class:`Tag` to our :py:class:`ProposedTransaction`. Note, that if our ``trytes_encrypted_data`` was longer than the maximum payload diff --git a/iota/api.py b/iota/api.py index 4976a30..2c1a3e6 100644 --- a/iota/api.py +++ b/iota/api.py @@ -67,7 +67,7 @@ class StrictIota(AsyncStrictIota): to :py:meth:`attach_to_tangle` to `ccurl pow interface `_. - See :ref:`Optional Local Pow` for more info and + See :ref:`README:Optional Local Pow` for more info and :ref:`find out` how to use it. """ @@ -88,7 +88,7 @@ def __init__(self, adapter, devnet=False, local_pow=False): to :py:meth:`attach_to_tangle` to `ccurl pow interface `_. - See :ref:`Optional Local Pow` for more info and + See :ref:`README:Optional Local Pow` for more info and :ref:`find out` how to use it. """ super().__init__(adapter, devnet, local_pow) @@ -828,7 +828,7 @@ class Iota(StrictIota, AsyncIota): to :py:meth:`attach_to_tangle` to `ccurl pow interface `_. - See :ref:`Optional Local Pow` for more info and + See :ref:`README:Optional Local Pow` for more info and :ref:`find out` how to use it. References: diff --git a/iota/api_async.py b/iota/api_async.py index 6004242..c013cde 100644 --- a/iota/api_async.py +++ b/iota/api_async.py @@ -38,7 +38,7 @@ class AsyncStrictIota: to :py:meth:`attach_to_tangle` to `ccurl pow interface `_. - See :ref:`Optional Local Pow` for more info and + See :ref:`README:Optional Local Pow` for more info and :ref:`find out` how to use it. """ @@ -59,7 +59,7 @@ def __init__(self, adapter, devnet=False, local_pow=False): to :py:meth:`attach_to_tangle` to `ccurl pow interface `_. - See :ref:`Optional Local Pow` for more info and + See :ref:`README:Optional Local Pow` for more info and :ref:`find out` how to use it. """ super().__init__() @@ -769,7 +769,7 @@ class AsyncIota(AsyncStrictIota): to :py:meth:`attach_to_tangle` to `ccurl pow interface `_. - See :ref:`Optional Local Pow` for more info and + See :ref:`README:Optional Local Pow` for more info and :ref:`find out` how to use it. References: From ef3164614f61417babf1d894cb770238076921b0 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Tue, 3 Mar 2020 15:54:27 +0100 Subject: [PATCH 51/69] Rename transaction's method - get_signature_validation_trytes() renamed to get_bundle_essence_trytes() fo clarity. --- docs/types.rst | 6 +++--- iota/transaction/base.py | 18 +++++++++++++++--- iota/transaction/creation.py | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index 0b24971..e3b3044 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -249,9 +249,9 @@ See the class documentation below: ^^^^^^^^^^^^^^^^^^^^^ .. automethod:: Transaction.from_tryte_string -**get_signature_validation_trytes** -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automethod:: Transaction.get_signature_validation_trytes +**get_bundle_essence_trytes** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. automethod:: Transaction.get_bundle_essence_trytes ProposedTransaction ~~~~~~~~~~~~~~~~~~~ diff --git a/iota/transaction/base.py b/iota/transaction/base.py index c904c70..2cf3ecd 100644 --- a/iota/transaction/base.py +++ b/iota/transaction/base.py @@ -570,11 +570,23 @@ def as_tryte_string(self): + self.nonce ) - def get_signature_validation_trytes(self): + def get_bundle_essence_trytes(self): # type: () -> TryteString """ - Returns the values needed to validate the transaction's - ``signature_message_fragment`` value. + Returns the values needed for calculating bundle hash. + The bundle hash is the hash of the bundle essence, which is itself + the hash of the following fields of transactions in the bundle: + + - ``address``, + - ``value``, + - ``legacy_tag``, + - ``current_index``, + - ``last_index``, + - and ``timestamp``. + + The transaction's ``signature_message_fragment`` field contains + the signature generated by signing the bundle hash with the address's + private key. :return: :py:class:`TryteString` object. diff --git a/iota/transaction/creation.py b/iota/transaction/creation.py index 302e5f8..3d5bc5a 100644 --- a/iota/transaction/creation.py +++ b/iota/transaction/creation.py @@ -476,7 +476,7 @@ def finalize(self): txn.current_index = i txn.last_index = last_index - sponge.absorb(txn.get_signature_validation_trytes().as_trits()) + sponge.absorb(txn.get_bundle_essence_trytes().as_trits()) bundle_hash_trits = [0] * HASH_LENGTH sponge.squeeze(bundle_hash_trits) From 9ea1f1625b70d53ede5ceee08eedf555eee33571 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Tue, 3 Mar 2020 15:56:15 +0100 Subject: [PATCH 52/69] docs: Add section `Creating Transfers` - Explanation of how transfers are created - Two new figures on transfer creation and available API methods. - 3 code examples to show the different ways of transaction creation. --- docs/images/create_transfer.svg | 3 + docs/images/transfer_api.svg | 3 + docs/index.rst | 1 + docs/transfers.rst | 296 ++++++++++++++++++++++++++++++++ 4 files changed, 303 insertions(+) create mode 100644 docs/images/create_transfer.svg create mode 100644 docs/images/transfer_api.svg diff --git a/docs/images/create_transfer.svg b/docs/images/create_transfer.svg new file mode 100644 index 0000000..f88ba79 --- /dev/null +++ b/docs/images/create_transfer.svg @@ -0,0 +1,3 @@ + + +
Bundle
Bundle
- Create individual transactions.
- Specify:
  • address,
  • value,
  • message,
  • and tag.
Timestamp is usually auto-generated.
- Create individual transactions....
- Create a bundle from the transactions.
- Bundle has to be balanced. Sum of the
  values of transactions is zero. 
- Index transactions within the bundle.
- Sign inputs if needed.
- Compute a unique identifier of the bundle,
  the bundle hash.
- Create a bundle from the transactions....

- Obtain two tips from the network.
- These will be the transactions referenced
  and hence validated by your bundle. 

- Obtain two tips from the network....

- Link together transactions in the bundle
  through their branch and trunk references.
- Find the nonce for each transaction
  individually.
- Transactions are finalized, transaction hash
  is available.

- Link together transactions in the bundle...
Create Transactions
Create Transactions
1
1
Create and Finalize Bundle
Create and Finalize Bundle
2
2
Select Two Tips
Select Two Tips
3
3
Do Proof-of-Work
Do Proof-of-Work
4
4
Broadcast & Store
Broadcast & Store
5
5

- Send transactions to the node.

- Transactions will be:
  • broadcast to the network, and
  • stored in the node's local database.
 - Once confirmed, transactions are part
   of the Tangle.
- Send transactions to the node....
List of Transactions
List of Transactions
Finalized Bundle
Finalized Bundle
Bundle and References
Bundle and References
Attached Transactions
Attached Transactions
Transactions become part of the Tangle
Transactions become part of the Tangle
- address
- value
- message
- tag
- timestamp
- current index
- last index
- signature
- bundle hash
- trunk hash
- branch hash
nonce
- address...
Transaction
Transaction
- address
- value
- message
- tag
- timestamp
- current index
- last index
- signature
- bundle hash
- trunk hash
- branch hash
nonce
- address...
Transaction
Transaction
- address
- value
- message
- tag
- timestamp
- current index
- last index
- signature
- bundle hash
- trunk hash
- branch hash
nonce
- address...
Transaction
Transaction
Bundle
Bundle
- address
- value
- message
- tag
- timestamp
current index
last index
signature
bundle hash
- address...
Transaction
Transaction
- address
- value
- message
- tag
- timestamp
current index
last index
signature
bundle hash
- address...
Transaction
Transaction
- address
- value
- message
- tag
- timestamp
current index
last index
signature
bundle hash
- address...
Transaction
Transaction
Bundle
Bundle
Transaction
Transaction
- address
- value
- message
- tag
- timestamp
- current index
- last index
- signature
- bundle hash
- trunk hash
- branch hash
- address...
Transaction
Transaction
- address
- value
- message
- tag
- timestamp
- current index
- last index
- signature
- bundle hash
- trunk hash
- branch hash
- address...
Transaction
Transaction
- address
- value
- message
- tag
- timestamp
- current index
- last index
- signature
- bundle hash
- trunk hash
- branch hash
- address...
Transaction
Transaction
- address
- value

- message
- tag
- timestamp
- address...
Transaction
Transaction
address
- value

- message
- tag
timestamp
- address...
Transaction
Transaction
address
- value

- message
- tag
timestamp
- address...
Transaction
Transaction
The Tangle
The Tangle
Transaction
Transaction
Transaction
Transaction
Transaction
Transaction
Trunk
Trunk
Branch
Branch
Branch Reference
Branch Reference
Trunk Reference
Trunk Reference
Bundle
Bundle
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/images/transfer_api.svg b/docs/images/transfer_api.svg new file mode 100644 index 0000000..9b2d2f7 --- /dev/null +++ b/docs/images/transfer_api.svg @@ -0,0 +1,3 @@ + + +
Extended API Methods
Extended API Methods
Core API Methods
Core API Methods
PyOTA API
PyOTA API
send_transfer()
send_transfer()
send_trytes()
send_trytes()
prepare_transfer()
prepare_transfer()
broadcast_and_store()
broadcast_and_store()
broadcast_transactions()
broadcast_transactions()
store_transactions()
store_transactions()
attach_to_tangle()
attach_to_tangle()
get_transactions_to_approve()
get_transactions_to_approve()
ProposedBundle(...)
ProposedBundle(...)
ProposedBundle.finalize()
ProposedBundle.finalize()
ProposedTransaction(...)
ProposedTransaction(...)
Create Transactions
Create Transactions
1
1
Select Two Tips
Select Two Tips
3
3
Do Proof-of-Work
Do Proof-of-Work
4
4
Broadcast & Store
Broadcast & Store
5
5
Create and Finalize Bundle
Create and Finalize Bundle
2
2
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 2a6a0da..bf8652e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,6 +10,7 @@ core_api extended_api addresses + transfers multisig commands tutorials diff --git a/docs/transfers.rst b/docs/transfers.rst index e69de29..bf0b1cb 100644 --- a/docs/transfers.rst +++ b/docs/transfers.rst @@ -0,0 +1,296 @@ +Creating transfers +================== + +IOTA is a permissionless DLT solution, therefore anyone can send transactions +to the network and initiate transfers. The IOTA client libraries help you to +abstract away low level operations required to construct and send a transfer +to the Tangle. + +In this section, we will explore in depth how to create transactions and +bundles with IOTA, furthermore what tools you can use in PyOTA to ease your +development process. + +.. note:: + + Before proceeding, make sure you read and understood the + :ref:`basic_concepts:Basic Concepts` and :ref:`types:PyOTA Types` sections! + +Anatomy of a Transfer +--------------------- + +We already now that the Tangle consists of :ref:`transactions ` +referencing each other, each of them two others to be more precise. +Transactions can be grouped together in :ref:`bundles `. +`Zero-value bundles`_ contain only zero value transactions, while +`transfer bundles`_ may also contain input and output transactions. + +But how to construct these bundles and send them to the network? + +The process can be boiled down to 5 steps: + + 1. Create individual transaction(s). + 2. Construct a bundle from the transaction(s). + 3. Obtain references to two transactions waiting to be confirmed (tips) from the Tangle. + 4. Do proof-of-work for each transaction in the bundle. + 5. Send the bundle to the network. + + +.. figure:: images/create_transfer.svg + :scale: 100 % + :alt: Process of sending a transfer in IOTA. + + Process of creating and sending a transfer to the Tangle. + +.. py:currentmodule:: iota + +1. Create Transactions +~~~~~~~~~~~~~~~~~~~~~~ +The first step is to create the individual transaction objects. You have to +specify ``address`` and ``value`` for each transaction. A negative ``value`` +means spending from ``address``. Furthermore, you can define a ``tag``, and for +zero-value transactions, a ``message``. ``timestamp`` is usually auto-generated +by the IOTA libraries. + +In PyOTA, use :py:class:`ProposedTransaction` to declare transactions. + +2. Create Bundle from Transactions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +A bundle is a collection of transactions, treated as an atomic unit +when sent to the network. A bundle makes a value (token) transfer possible by +grouping together input an output transactions. + +A bundle always has to be balanced: the sum of ``value`` attributes of the +transactions in the bundle should always be zero. Transactions in the bundle +are indexed individually, and also contain information on how many other +transactions there are in the bundle. + +Once complete, a bundle has to be finalized to generate the bundle hash based +on the `bundle essence`_. The bundle hash is the unique identifier of the +bundle. + +After finalization, input transactions in the bundle need to be signed to prove +ownership of tokens being transferred. + +:py:class:`ProposedBundle` helps you in PyOTA to create bundles, add transactions, +finalize the bundle and sign the inputs. + +3. Select two tips +~~~~~~~~~~~~~~~~~~ + +Tips are transactions that are yet to be confirmed by the network. We can +obtain two tips by requesting them from a node. In PyOTA, :py:meth:`~Iota.get_transactions_to_approve` +does the job: it returns a ``trunk`` and a ``branch`` :py:class:`TransactionHash`. + +Our bundle will validate these two transactions once in the Tangle. + +4. Do Proof-of-Work +~~~~~~~~~~~~~~~~~~~ + +The bundle has been finalized, inputs have been signed, we have two tips, +now it's time to prepare the bundle to be attached to the Tangle. All +transactions reference two other transactions in the Tangle, therefore we need +to select these references for each transaction in our bundle. + +We also know that transactions `within the bundle are linked together`_ through +their trunk references. So how do we construct the correct bundle structure +and also reference two tips from the network? + +.. figure:: images/bundle-structure.png + :scale: 100 % + :alt: Bundle structure with four transactions. + + Structure of a bundle with four transactions. Numbers in brackets denote + (``currentIndex``, ``lastIndex``) fields. Head of the bundle has index 3, + while tail has index 0. + +For all non-head transactions in the bundle, the trunk reference is the next +transaction in the bundle, while the branch reference is the trunk transaction +hash, one of the tips. + +The head transaction is different: the trunk reference is the trunk tip, while +the branch reference is the branch tip. + +The proof-of-work calculation has to be done for each transaction individually, +therefore the more transactions you have in the bundle, the more time it will +take. The difficulty of the calculation also depends on the `minimum weight magnitude`_ +set by the network. + +The output of the proof-of-work algorithm is a ``nonce`` value that is appended +to the the transaction, resulting in the attached transaction trytes. +Nodes validate the proof-of-work of a transaction by calculating the transaction's +hash from the attached transaction trytes. If the resulting hash has at least +``minimum weight magnitude`` number of trailing zero trits, it is correct. + +In PyOTA, use :py:meth:`~Iota.attach_to_tangle` to carry out this step. + +5. Broadcast and Store +~~~~~~~~~~~~~~~~~~~~~~ + +The final step is to send the bundle to the network. Nodes will broadcast +the transactions in the network, and store them in their local database. + +In PyOTA, use :py:meth:`~Iota.broadcast_and_store` to achieve this. + +Observe the bird-eye view of the Tangle depicted at the last step of the +process. Our transactions are part of the Tangle, reference each other and +the two tips. Newer transactions may reference our transactions as branch or +trunk. + +Use the Library +--------------- + +The IOTA libraries help you to abstract away the low-level operations needed +to create transfers. The figure below illustrates the different ways you can +build and send a transfer. + +.. figure:: images/transfer_api.svg + :scale: 100 % + :alt: Different ways of sending a transfer in IOTA. + + API commands for sending transfers. + +Let's look at some code snippets on how to perform the above with an imaginary +bundle that has 3 fictional transactions. + +1. Level Padawan +~~~~~~~~~~~~~~~~ +The easiest and most convenient way is to use :py:meth:`~Iota.send_transfer` +extended API method. You still need to create the transactions yourself +with :py:class:`ProposedTransaction`. + +.. code-block:: + + from iota import Iota, ProposedTransaction, Address + + api = Iota('https://nodes.devnet.iota.org:443') + + fictional_transactions = [ + ProposedTransaction( + address=Address(b'FIRSTRANDOMADDRESS'), + value=0, + # You could add a tag or message here too! + ), + ProposedTransaction( + address=Address(b'SECONDRANDOMADDRESS'), + value=0, + ), + ProposedTransaction( + address=Address(b'THIRDRANDOMADDRESS'), + value=0, + ) + ] + + imaginary_bundle = api.send_transfer( + transfers=transactions + )['bundle'] + +As all API methods in PyOTA, :py:meth:`~Iota.send_transfer` also returns +a ``dict``. The ``bundle`` key holds the value of :py:class:`Bundle`. + +It's important to note, that for value transfers, you will need your seed as well. +:py:meth:`~Iota.send_transfer` will look for ``input addresses`` to fund outgoing +transactions in the bundle, and auto-generate an unused ``change address`` if +there is a remainder amount of tokens. It will also take care of finalizing the +bundle and signing the necessary input transactions. + +2. Level Obi-Wan +~~~~~~~~~~~~~~~~ +Instead of :py:meth:`~Iota.send_transfer`, you can use the combination of +:py:meth:`~Iota.prepare_transfer` and :py:meth:`~Iota.send_trytes` to achieve +the same result. + +.. code-block:: + + from iota import Iota, ProposedTransaction, Address + + api = Iota('https://nodes.devnet.iota.org:443') + + transactions = [ + ProposedTransaction( + address=Address(b'FIRSTRANDOMADDRESS'), + value=0, + ), + ProposedTransaction( + address=Address(b'SECONDRANDOMADDRESS'), + value=0, + ), + ProposedTransaction( + address=Address(b'THIRDRANDOMADDRESS'), + value=0, + ) + ] + + prepared_trytes = api.prepare_transfer( + transfers=transactions + )['trytes'] + + imaginary_bundle_trytes = api.send_trytes( + trytes=prepared_trytes + )['trytes'] + +A difference here is that the end result, ``imaginary_bundle_trytes`` is a list +of :py:class:`TransactionTrytes`, and not a :py:class:`Bundle` object. + +3. Level Yoda +~~~~~~~~~~~~~ +Being the master Jedi of the PyOTA universe means that you know the most about +the force of low-level API methods. Use it wisely! + +.. code-block:: + + from iota import Iota, ProposedTransaction, Address, ProposedBundle + + api = Iota('https://nodes.devnet.iota.org:443') + + transactions = [ + ProposedTransaction( + address=Address(b'FIRSTRANDOMADDRESS'), + value=0, + ), + ProposedTransaction( + address=Address(b'SECONDRANDOMADDRESS'), + value=0, + ), + ProposedTransaction( + address=Address(b'THIRDRANDOMADDRESS'), + value=0, + ) + ] + + bundle = ProposedBundle() + + for tx in transactions: + bundle.add_transaction(tx) + + # If it was a value transfer, we could + # bundle.add_inputs() + # bundle.send_unspent_inputs_to() + + bundle.finalize() + + # Again, for value transfers, we could: + # bundle.sign_inputs(KeyGenerator(b'SEEDGOESHERE')) + + gtta_response = api.get_transactions_to_approve(depth=3) + + trunk = gtta_response['trunkTransaction'] + branch = gtta_response['branchTransaction'] + + attached_trytes = api.attach_to_tangle( + trunk_transaction=trunk, + branch_transaction=branch, + trytes=bundle.as_tryte_strings() + )['trytes'] + + api.broadcast_transactions(attached_trytes) + + api.store_transactions(attached_trytes) + + imaginary_bundle = Bundle.from_tryte_strings(attached_trytes) + + +.. _transfer bundles: https://docs.iota.org/docs/getting-started/0.1/transactions/bundles#transfer-bundles +.. _zero-value bundles: https://docs.iota.org/docs/getting-started/0.1/transactions/bundles#zero-value-bundle +.. _bundle essence: https://docs.iota.org/docs/getting-started/0.1/transactions/bundles#bundle-essence +.. _within the bundle are linked together: https://docs.iota.org/docs/getting-started/0.1/transactions/bundles +.. _minimum weight magnitude: https://docs.iota.org/docs/getting-started/0.1/network/minimum-weight-magnitude \ No newline at end of file From 676f913176345194accfbcb63fa2bb3f6fffd36b Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Thu, 5 Mar 2020 18:51:21 +0100 Subject: [PATCH 53/69] Apply suggestions from code review Co-Authored-By: Phoenix --- docs/transfers.rst | 57 ++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/docs/transfers.rst b/docs/transfers.rst index bf0b1cb..fbf8fd0 100644 --- a/docs/transfers.rst +++ b/docs/transfers.rst @@ -3,7 +3,7 @@ Creating transfers IOTA is a permissionless DLT solution, therefore anyone can send transactions to the network and initiate transfers. The IOTA client libraries help you to -abstract away low level operations required to construct and send a transfer +abstract away low-level operations required to construct and send a transfer to the Tangle. In this section, we will explore in depth how to create transactions and @@ -18,7 +18,7 @@ development process. Anatomy of a Transfer --------------------- -We already now that the Tangle consists of :ref:`transactions ` +We already know that the Tangle consists of :ref:`transactions ` referencing each other, each of them two others to be more precise. Transactions can be grouped together in :ref:`bundles `. `Zero-value bundles`_ contain only zero value transactions, while @@ -30,7 +30,7 @@ The process can be boiled down to 5 steps: 1. Create individual transaction(s). 2. Construct a bundle from the transaction(s). - 3. Obtain references to two transactions waiting to be confirmed (tips) from the Tangle. + 3. Obtain references to two unconfirmed transactions ("tips") from the Tangle. 4. Do proof-of-work for each transaction in the bundle. 5. Send the bundle to the network. @@ -46,22 +46,27 @@ The process can be boiled down to 5 steps: 1. Create Transactions ~~~~~~~~~~~~~~~~~~~~~~ The first step is to create the individual transaction objects. You have to -specify ``address`` and ``value`` for each transaction. A negative ``value`` -means spending from ``address``. Furthermore, you can define a ``tag``, and for -zero-value transactions, a ``message``. ``timestamp`` is usually auto-generated +specify ``address`` and ``value`` for each transaction. Furthermore, you can define a ``tag``, and for +zero-value transactions, a ``message``. A ``timestamp`` is also required, though this value is usually auto-generated by the IOTA libraries. +.. note:: + Unlike on other decentralised ledgers, IOTA transactions can have positive *or* negative ``value`` amounts. In order to send iotas from one address to another, at least two transactions are required: + + * one with *positive* ``value`` (to increment the balance of the receiver), and + * one with *negative* ``value`` (to decrement the balance of the sender). + In PyOTA, use :py:class:`ProposedTransaction` to declare transactions. 2. Create Bundle from Transactions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A bundle is a collection of transactions, treated as an atomic unit -when sent to the network. A bundle makes a value (token) transfer possible by -grouping together input an output transactions. +when sent to the network. A bundle makes a value (iota token) transfer possible by +grouping together input and output transactions. A bundle always has to be balanced: the sum of ``value`` attributes of the transactions in the bundle should always be zero. Transactions in the bundle -are indexed individually, and also contain information on how many other +are also indexed individually and contain information on how many other transactions there are in the bundle. Once complete, a bundle has to be finalized to generate the bundle hash based @@ -69,10 +74,12 @@ on the `bundle essence`_. The bundle hash is the unique identifier of the bundle. After finalization, input transactions in the bundle need to be signed to prove -ownership of tokens being transferred. +ownership of iotas being transferred. -:py:class:`ProposedBundle` helps you in PyOTA to create bundles, add transactions, -finalize the bundle and sign the inputs. +.. tip: + :py:class:`ProposedBundle` helps you in PyOTA to create bundles, add transactions, + finalize the bundle and sign the inputs. We'll see how to use :py:class:`ProposedBundle` in + :ref:`Use the Library` below. 3. Select two tips ~~~~~~~~~~~~~~~~~~ @@ -81,14 +88,14 @@ Tips are transactions that are yet to be confirmed by the network. We can obtain two tips by requesting them from a node. In PyOTA, :py:meth:`~Iota.get_transactions_to_approve` does the job: it returns a ``trunk`` and a ``branch`` :py:class:`TransactionHash`. -Our bundle will validate these two transactions once in the Tangle. +Because our bundle references these two transactions, it will validate them once it is added to the Tangle. 4. Do Proof-of-Work ~~~~~~~~~~~~~~~~~~~ -The bundle has been finalized, inputs have been signed, we have two tips, -now it's time to prepare the bundle to be attached to the Tangle. All -transactions reference two other transactions in the Tangle, therefore we need +The bundle has been finalized, inputs have been signed, and we have two tips; +now it's time to prepare the bundle to be attached to the Tangle. As noted in the previous section, every +transaction references two other transactions in the Tangle; therefore we need to select these references for each transaction in our bundle. We also know that transactions `within the bundle are linked together`_ through @@ -119,7 +126,7 @@ The output of the proof-of-work algorithm is a ``nonce`` value that is appended to the the transaction, resulting in the attached transaction trytes. Nodes validate the proof-of-work of a transaction by calculating the transaction's hash from the attached transaction trytes. If the resulting hash has at least -``minimum weight magnitude`` number of trailing zero trits, it is correct. +``minimum weight magnitude`` number of trailing zero trits, the transaction is valid. In PyOTA, use :py:meth:`~Iota.attach_to_tangle` to carry out this step. @@ -131,8 +138,8 @@ the transactions in the network, and store them in their local database. In PyOTA, use :py:meth:`~Iota.broadcast_and_store` to achieve this. -Observe the bird-eye view of the Tangle depicted at the last step of the -process. Our transactions are part of the Tangle, reference each other and +Observe the bird's-eye view of the Tangle depicted at the last step of the +process. Our transactions are part of the Tangle, referencing each other and the two tips. Newer transactions may reference our transactions as branch or trunk. @@ -199,6 +206,9 @@ Instead of :py:meth:`~Iota.send_transfer`, you can use the combination of :py:meth:`~Iota.prepare_transfer` and :py:meth:`~Iota.send_trytes` to achieve the same result. +.. tip:: + This can be useful if you want to prepare the transactions (including signing inputs) on one device, but you want to then transfer the data to another device for transmission to the Tangle. For example, you might :py:meth:`~Iota.prepare_transfer` on an air-gapped computer that has your seed stored on it, but then transfer the resulting trytes to a networked computer (that does not have your seed) to :py:meth:`~Iota.send_trytes`. + .. code-block:: from iota import Iota, ProposedTransaction, Address @@ -236,6 +246,9 @@ of :py:class:`TransactionTrytes`, and not a :py:class:`Bundle` object. Being the master Jedi of the PyOTA universe means that you know the most about the force of low-level API methods. Use it wisely! +.. tip:: + You generally won't need to split out the process explicitly like this in your application code, but it is useful to understand what :py:meth:`~Iota.send_transfer` does under-the-hood, so that you are better-equipped to troubleshoot any issues that may occur during the process. + .. code-block:: from iota import Iota, ProposedTransaction, Address, ProposedBundle @@ -262,13 +275,13 @@ the force of low-level API methods. Use it wisely! for tx in transactions: bundle.add_transaction(tx) - # If it was a value transfer, we could + # If it was a value transfer, we would also need to: # bundle.add_inputs() # bundle.send_unspent_inputs_to() bundle.finalize() - # Again, for value transfers, we could: + # Again, for value transfers, we would need to: # bundle.sign_inputs(KeyGenerator(b'SEEDGOESHERE')) gtta_response = api.get_transactions_to_approve(depth=3) @@ -293,4 +306,4 @@ the force of low-level API methods. Use it wisely! .. _zero-value bundles: https://docs.iota.org/docs/getting-started/0.1/transactions/bundles#zero-value-bundle .. _bundle essence: https://docs.iota.org/docs/getting-started/0.1/transactions/bundles#bundle-essence .. _within the bundle are linked together: https://docs.iota.org/docs/getting-started/0.1/transactions/bundles -.. _minimum weight magnitude: https://docs.iota.org/docs/getting-started/0.1/network/minimum-weight-magnitude \ No newline at end of file +.. _minimum weight magnitude: https://docs.iota.org/docs/getting-started/0.1/network/minimum-weight-magnitude From 06d5a94ce0262d2c36e3c1de964a712d6bc30967 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Fri, 6 Mar 2020 10:37:56 +0100 Subject: [PATCH 54/69] docs: Improvements on `Creating Transfers` --- docs/transfers.rst | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/docs/transfers.rst b/docs/transfers.rst index fbf8fd0..26d6361 100644 --- a/docs/transfers.rst +++ b/docs/transfers.rst @@ -46,12 +46,15 @@ The process can be boiled down to 5 steps: 1. Create Transactions ~~~~~~~~~~~~~~~~~~~~~~ The first step is to create the individual transaction objects. You have to -specify ``address`` and ``value`` for each transaction. Furthermore, you can define a ``tag``, and for -zero-value transactions, a ``message``. A ``timestamp`` is also required, though this value is usually auto-generated +specify ``address`` and ``value`` for each transaction. Furthermore, you can +define a ``tag``, and for zero-value transactions, a ``message``. A +``timestamp`` is also required, though this value is usually auto-generated by the IOTA libraries. .. note:: - Unlike on other decentralised ledgers, IOTA transactions can have positive *or* negative ``value`` amounts. In order to send iotas from one address to another, at least two transactions are required: + Unlike on other decentralised ledgers, IOTA transactions can have positive + *or* negative ``value`` amounts. In order to send iotas from one address to + another, at least two transactions are required: * one with *positive* ``value`` (to increment the balance of the receiver), and * one with *negative* ``value`` (to decrement the balance of the sender). @@ -76,10 +79,10 @@ bundle. After finalization, input transactions in the bundle need to be signed to prove ownership of iotas being transferred. -.. tip: +.. tip:: :py:class:`ProposedBundle` helps you in PyOTA to create bundles, add transactions, - finalize the bundle and sign the inputs. We'll see how to use :py:class:`ProposedBundle` in - :ref:`Use the Library` below. + finalize the bundle and sign the inputs. We'll see how to use + :py:class:`ProposedBundle` in :ref:`transfers:Use the Library` below. 3. Select two tips ~~~~~~~~~~~~~~~~~~ @@ -88,15 +91,17 @@ Tips are transactions that are yet to be confirmed by the network. We can obtain two tips by requesting them from a node. In PyOTA, :py:meth:`~Iota.get_transactions_to_approve` does the job: it returns a ``trunk`` and a ``branch`` :py:class:`TransactionHash`. -Because our bundle references these two transactions, it will validate them once it is added to the Tangle. +Because our bundle references these two transactions, it will validate them once +it is added to the Tangle. 4. Do Proof-of-Work ~~~~~~~~~~~~~~~~~~~ The bundle has been finalized, inputs have been signed, and we have two tips; -now it's time to prepare the bundle to be attached to the Tangle. As noted in the previous section, every -transaction references two other transactions in the Tangle; therefore we need -to select these references for each transaction in our bundle. +now it's time to prepare the bundle to be attached to the Tangle. As noted in +the previous section, every transaction references two other transactions in +the Tangle; therefore we need to select these references for each transaction +in our bundle. We also know that transactions `within the bundle are linked together`_ through their trunk references. So how do we construct the correct bundle structure @@ -143,6 +148,13 @@ process. Our transactions are part of the Tangle, referencing each other and the two tips. Newer transactions may reference our transactions as branch or trunk. +.. note:: + As more transactions are added to the Tangle that reference our transactions + – and then more are added that reference those transactions, and so on – this + increases the `cumulative weight`_ of our transactions. The higher the + cumulative weight of our transactions, the higher the chance for them to + get confirmed. + Use the Library --------------- @@ -307,3 +319,4 @@ the force of low-level API methods. Use it wisely! .. _bundle essence: https://docs.iota.org/docs/getting-started/0.1/transactions/bundles#bundle-essence .. _within the bundle are linked together: https://docs.iota.org/docs/getting-started/0.1/transactions/bundles .. _minimum weight magnitude: https://docs.iota.org/docs/getting-started/0.1/network/minimum-weight-magnitude +.. _cumulative weight: https://blog.iota.org/the-tangle-an-illustrated-introduction-f359b8b2ec80 From 10f3f4143a21cd8002ea8834f25bd0b89dbd501f Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Tue, 10 Mar 2020 13:54:10 +0100 Subject: [PATCH 55/69] Remove PY2 compatibility code Python 2 is no longer supported in PyOTA. Remove glue code that made it possible to run the same code on both versions, as it is no longer needed. Changes: - Remove imports from `six` package - Remove imports from `__future__` package - Add some Py3 syntax - Remove encoding spec from files as PY3's default is utf-8 - Update setup.py, simplify dependencies, include metadata in the built sdist and wheel about supported python version - Remove `noinspection` comments --- examples/address_generator.py | 18 ++--- examples/hello_world.py | 7 +- examples/local_pow.py | 3 - examples/mam_js_send.py | 14 ++-- examples/multisig.py | 8 --- examples/routingwrapper_pow.py | 2 - examples/send_transfer.py | 10 ++- iota/__init__.py | 3 - iota/adapter/__init__.py | 35 ++-------- iota/adapter/wrappers.py | 9 +-- iota/bin/__init__.py | 15 ++-- iota/bin/repl.py | 14 +--- iota/codecs.py | 22 ++---- iota/commands/__init__.py | 12 +--- iota/commands/core/__init__.py | 4 -- iota/commands/core/add_neighbors.py | 4 -- iota/commands/core/attach_to_tangle.py | 4 -- iota/commands/core/broadcast_transactions.py | 4 -- iota/commands/core/check_consistency.py | 4 -- iota/commands/core/find_transactions.py | 7 +- iota/commands/core/get_balances.py | 7 +- iota/commands/core/get_inclusion_states.py | 4 -- .../commands/core/get_missing_transactions.py | 4 -- iota/commands/core/get_neighbors.py | 4 -- .../core/get_node_api_configuration.py | 4 -- iota/commands/core/get_node_info.py | 4 -- iota/commands/core/get_tips.py | 4 -- .../core/get_transactions_to_approve.py | 4 -- iota/commands/core/get_trytes.py | 4 -- .../core/interrupt_attaching_to_tangle.py | 4 -- iota/commands/core/remove_neighbors.py | 4 -- iota/commands/core/store_transactions.py | 4 -- .../core/were_addresses_spent_from.py | 4 -- iota/commands/extended/__init__.py | 3 - iota/commands/extended/broadcast_and_store.py | 4 -- iota/commands/extended/broadcast_bundle.py | 4 -- .../extended/find_transaction_objects.py | 4 -- iota/commands/extended/get_account_data.py | 5 -- iota/commands/extended/get_bundles.py | 4 -- iota/commands/extended/get_inputs.py | 4 -- .../commands/extended/get_latest_inclusion.py | 4 -- iota/commands/extended/get_new_addresses.py | 4 -- .../extended/get_transaction_objects.py | 4 -- iota/commands/extended/get_transfers.py | 5 -- iota/commands/extended/is_promotable.py | 4 -- iota/commands/extended/is_reattachable.py | 4 -- iota/commands/extended/prepare_transfer.py | 4 -- iota/commands/extended/promote_transaction.py | 4 -- iota/commands/extended/replay_bundle.py | 4 -- iota/commands/extended/send_transfer.py | 4 -- iota/commands/extended/send_trytes.py | 4 -- iota/commands/extended/traverse_bundle.py | 4 -- iota/commands/extended/utils.py | 4 -- iota/crypto/__init__.py | 4 -- iota/crypto/addresses.py | 4 -- iota/crypto/kerl/__init__.py | 4 -- iota/crypto/kerl/conv.py | 4 -- iota/crypto/kerl/pykerl.py | 8 --- iota/crypto/pycurl.py | 5 -- iota/crypto/signing.py | 13 ---- iota/crypto/types.py | 4 -- iota/exceptions.py | 3 - iota/filters.py | 15 ++-- iota/json.py | 9 +-- iota/multisig/__init__.py | 4 -- iota/multisig/api.py | 4 -- iota/multisig/commands/__init__.py | 4 -- .../commands/create_multisig_address.py | 4 -- iota/multisig/commands/get_digests.py | 4 -- iota/multisig/commands/get_private_keys.py | 4 -- .../commands/prepare_multisig_transfer.py | 4 -- iota/multisig/crypto/__init__.py | 3 - iota/multisig/crypto/addresses.py | 4 -- iota/multisig/transaction.py | 4 -- iota/multisig/types.py | 4 -- iota/transaction/__init__.py | 4 -- iota/transaction/base.py | 4 -- iota/transaction/creation.py | 10 --- iota/transaction/types.py | 4 -- iota/transaction/utils.py | 4 -- iota/transaction/validator.py | 5 -- iota/trits.py | 4 -- iota/types.py | 67 +++++++----------- setup.py | 24 +++---- test/__init__.py | 18 +---- test/adapter/__init__.py | 3 - test/adapter/wrappers_test.py | 4 -- test/adapter_test.py | 25 ++----- test/api_test.py | 10 +-- test/codecs_test.py | 9 +-- test/commands/__init__.py | 3 - test/commands/core/__init__.py | 3 - test/commands/core/add_neighbors_test.py | 4 -- test/commands/core/attach_to_tangle_test.py | 22 ++---- .../core/broadcast_transactions_test.py | 16 ++--- test/commands/core/check_consistency_test.py | 6 -- test/commands/core/find_transactions_test.py | 56 +++++++-------- test/commands/core/get_balances_test.py | 6 -- .../core/get_inclusion_states_test.py | 6 -- .../core/get_missing_transactions_test.py | 6 -- test/commands/core/get_neighbors_test.py | 4 -- .../core/get_node_api_configuration_test.py | 4 -- test/commands/core/get_node_info_test.py | 5 -- test/commands/core/get_tips_test.py | 6 -- .../core/get_transactions_to_approve_test.py | 5 -- test/commands/core/get_trytes_test.py | 6 -- .../interrupt_attaching_to_tangle_test.py | 4 -- test/commands/core/remove_neighbors_test.py | 4 -- test/commands/core/store_transactions_test.py | 14 ++-- .../core/were_addresses_spent_from_test.py | 5 -- test/commands/extended/__init__.py | 3 - .../extended/broadcast_and_store_test.py | 11 +-- .../extended/broadcast_bundle_test.py | 5 -- .../extended/find_transaction_objects_test.py | 5 -- .../extended/get_account_data_test.py | 11 +-- test/commands/extended/get_bundles_test.py | 5 -- test/commands/extended/get_inputs_test.py | 21 ------ .../extended/get_latest_inclusion_test.py | 6 -- .../extended/get_new_addresses_test.py | 8 --- .../extended/get_transaction_objects_test.py | 5 -- test/commands/extended/get_transfers_test.py | 15 +--- test/commands/extended/is_promotable_test.py | 5 -- .../commands/extended/is_reattachable_test.py | 13 +--- .../extended/prepare_transfer_test.py | 31 ++++----- .../extended/promote_transaction_test.py | 9 +-- test/commands/extended/replay_bundle_test.py | 10 +-- test/commands/extended/send_transfer_test.py | 14 +--- test/commands/extended/send_trytes_test.py | 20 ++---- .../commands/extended/traverse_bundle_test.py | 6 -- test/commands/extended/utils_test.py | 4 -- test/crypto/__init__.py | 3 - test/crypto/addresses_test.py | 18 ----- test/crypto/kerl/__init__.py | 3 - test/crypto/kerl/pykerl_test.py | 10 --- test/crypto/pycurl_test.py | 16 ----- test/crypto/signing_test.py | 6 -- test/crypto/types_test.py | 10 +-- test/filters_test.py | 8 --- test/local_pow_test.py | 11 +-- test/multisig/__init__.py | 3 - test/multisig/commands/__init__.py | 3 - .../commands/create_multisig_address_test.py | 12 +--- test/multisig/commands/get_digests_test.py | 13 +--- .../commands/get_private_keys_test.py | 10 +-- .../prepare_multisig_transfer_test.py | 6 -- test/multisig/crypto/__init__.py | 3 - test/multisig/crypto/addresses_test.py | 8 --- test/multisig/transaction_test.py | 8 --- test/transaction/__init__.py | 3 - test/transaction/base_test.py | 9 --- test/transaction/creation_test.py | 39 ----------- test/transaction/types_test.py | 12 +--- test/transaction/utils_test.py | 6 -- test/transaction/validator_test.py | 8 --- test/trits_test.py | 4 -- test/types_test.py | 68 +++++++------------ 156 files changed, 191 insertions(+), 1147 deletions(-) diff --git a/examples/address_generator.py b/examples/address_generator.py index 3bb7876..cd2653f 100644 --- a/examples/address_generator.py +++ b/examples/address_generator.py @@ -1,18 +1,12 @@ -# coding=utf-8 """ Generates a shiny new IOTA address that you can use for transfers! """ -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from argparse import ArgumentParser from getpass import getpass as secure_input from sys import argv from typing import Optional, Text -from six import binary_type, moves as compat, text_type - from iota import Iota, __version__ from iota.crypto.addresses import AddressGenerator from iota.crypto.types import Seed @@ -38,13 +32,13 @@ def main(uri, index, count, security, checksum): # Here's where all the magic happens! api_response = api.get_new_addresses(index, count, security, checksum) for addy in api_response['addresses']: - print(binary_type(addy).decode('ascii')) + print(bytes(addy).decode('ascii')) print('') def get_seed(): - # type: () -> binary_type + # type: () -> bytes """ Prompts the user securely for their seed. """ @@ -66,10 +60,10 @@ def output_seed(seed): 'WARNING: Anyone who has your seed can spend your IOTAs! ' 'Clear the screen after recording your seed!' ) - compat.input('') + input('') print('Your seed is:') print('') - print(binary_type(seed).decode('ascii')) + print(bytes(seed).decode('ascii')) print('') print( @@ -77,7 +71,7 @@ def output_seed(seed): 'and press return to continue.' ) print('https://en.wikipedia.org/wiki/Shoulder_surfing_(computer_security)') - compat.input('') + input('') if __name__ == '__main__': @@ -88,7 +82,7 @@ def output_seed(seed): parser.add_argument( '--uri', - type=text_type, + type=str, default='http://localhost:14265/', help=( diff --git a/examples/hello_world.py b/examples/hello_world.py index b21b8c3..9139546 100644 --- a/examples/hello_world.py +++ b/examples/hello_world.py @@ -1,18 +1,13 @@ -# coding=utf-8 """ Simple "Hello, world!" example that sends a `getNodeInfo` command to your friendly neighborhood node. """ -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from argparse import ArgumentParser from pprint import pprint from sys import argv from typing import Text from httpx.exceptions import NetworkError -from six import text_type from iota import BadApiResponse, StrictIota, __version__ @@ -44,7 +39,7 @@ def main(uri): parser.add_argument( '--uri', - type=text_type, + type=str, default='http://localhost:14265/', help=( diff --git a/examples/local_pow.py b/examples/local_pow.py index 1421018..ee1c957 100644 --- a/examples/local_pow.py +++ b/examples/local_pow.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import iota from pprint import pprint diff --git a/examples/mam_js_send.py b/examples/mam_js_send.py index 23039eb..32d35c4 100644 --- a/examples/mam_js_send.py +++ b/examples/mam_js_send.py @@ -1,6 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals import codecs import json @@ -10,7 +7,6 @@ from typing import List, Optional, Text import filters as f -from six import binary_type, text_type from iota import Bundle, Iota, TransactionTrytes from iota.bin import IotaCommandLineApp @@ -71,14 +67,14 @@ def execute(self, api, **arguments): mam_encrypt_path, # Required arguments - binary_type(api.seed), + bytes(api.seed), message, # Options - '--channel-key-index', text_type(channel_key_index), - '--start', text_type(start), - '--count', text_type(count), - '--security-level', text_type(security_level), + '--channel-key-index', str(channel_key_index), + '--start', str(start), + '--count', str(count), + '--security-level', str(security_level), ], check=True, diff --git a/examples/multisig.py b/examples/multisig.py index ac1baba..a9427c1 100644 --- a/examples/multisig.py +++ b/examples/multisig.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Example of how to use PyOTA's multisig feature. @@ -10,9 +9,6 @@ - https://github.com/iotaledger/wiki/blob/master/multisigs.md """ -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import List from iota import Address, Bundle, BundleValidator, ProposedTransaction, Tag, \ @@ -35,7 +31,6 @@ ## # Create digest 1 of 3. # -# noinspection SpellCheckingInspection api_1 = MultisigIota( adapter='http://localhost:14265', @@ -64,7 +59,6 @@ ## # Create digest 2 of 3. # -# noinspection SpellCheckingInspection api_2 = MultisigIota( adapter='http://localhost:14265', @@ -83,7 +77,6 @@ ## # Create digest 3 of 3. # -# noinspection SpellCheckingInspection api_3 = MultisigIota( adapter='http://localhost:14265', @@ -128,7 +121,6 @@ the change from the transaction! """ -# noinspection SpellCheckingInspection pmt_result = api_1.prepare_multisig_transfer( # These are the transactions that will spend the IOTAs. # You can divide up the IOTAs to send to multiple addresses if you diff --git a/examples/routingwrapper_pow.py b/examples/routingwrapper_pow.py index 62da190..e0212d9 100644 --- a/examples/routingwrapper_pow.py +++ b/examples/routingwrapper_pow.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Simple example using the RoutingWrapper to route API requests to different nodes. @@ -18,7 +17,6 @@ api = Iota(router, seed=b'SEED9GOES9HERE') # Example of sending a transfer using the adapter. -# noinspection SpellCheckingInspection bundle = api.send_transfer( depth=3, diff --git a/examples/send_transfer.py b/examples/send_transfer.py index 22487da..ab8b372 100644 --- a/examples/send_transfer.py +++ b/examples/send_transfer.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Example script that shows how to use PyOTA to send a transfer to an address. """ @@ -13,7 +12,6 @@ Tag, TryteString, ) -from six import text_type from address_generator import get_seed, output_seed @@ -60,7 +58,7 @@ def main(address, depth, message, tag, uri, value): parser.add_argument( '--address', - type=text_type, + type=str, default=b'RECEIVINGWALLETADDRESSGOESHERE9WITHCHECKSUMANDSECURITYLEVELB999999999999999999999999999999', help= 'Receiving address' @@ -78,7 +76,7 @@ def main(address, depth, message, tag, uri, value): parser.add_argument( '--message', - type=text_type, + type=str, default='Hello World!', help= 'Transfer message.' @@ -87,7 +85,7 @@ def main(address, depth, message, tag, uri, value): parser.add_argument( '--tag', - type=text_type, + type=str, default=b'EXAMPLE', help= 'Transfer tag' @@ -96,7 +94,7 @@ def main(address, depth, message, tag, uri, value): parser.add_argument( '--uri', - type=text_type, + type=str, default='http://localhost:14265/', help= 'URI of the node to connect to.' diff --git a/iota/__init__.py b/iota/__init__.py index 65e1da7..2d1c26e 100644 --- a/iota/__init__.py +++ b/iota/__init__.py @@ -1,6 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals # Define a few magic constants. DEFAULT_PORT = 14265 diff --git a/iota/adapter/__init__.py b/iota/adapter/__init__.py index ae0a12f..d6a7a32 100644 --- a/iota/adapter/__init__.py +++ b/iota/adapter/__init__.py @@ -1,6 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals import json from abc import ABCMeta, abstractmethod as abstract_method @@ -11,8 +8,6 @@ from typing import Container, Dict, List, Optional, Text, Tuple, Union from httpx import AsyncClient, Response, codes, auth import asyncio -from six import PY2, binary_type, iteritems, moves as compat, text_type, \ - add_metaclass from iota.exceptions import with_context from iota.json import JsonEncoder @@ -28,13 +23,6 @@ 'resolve_adapter', ] -if PY2: - # Fix an error when importing this package using the ``imp`` library - # (note: ``imp`` is deprecated since Python 3.4 in favor of - # ``importlib``). - # https://docs.python.org/3/library/imp.html - # https://travis-ci.org/iotaledger/iota.py/jobs/191974244 - __all__ = map(binary_type, __all__) API_VERSION = '1' """ @@ -52,12 +40,7 @@ """ # Load SplitResult for IDE type hinting and autocompletion. -if PY2: - # noinspection PyCompatibility,PyUnresolvedReferences - from urlparse import SplitResult -else: - # noinspection PyCompatibility,PyUnresolvedReferences - from urllib.parse import SplitResult +from urllib.parse import SplitResult, urlsplit def async_return(result): """ @@ -97,7 +80,7 @@ def resolve_adapter(uri): if isinstance(uri, BaseAdapter): return uri - parsed = compat.urllib_parse.urlsplit(uri) # type: SplitResult + parsed = urlsplit(uri) # type: SplitResult if not parsed.scheme: raise with_context( @@ -133,7 +116,6 @@ class AdapterMeta(ABCMeta): Automatically registers new adapter classes in ``adapter_registry``. """ - # noinspection PyShadowingBuiltins def __init__(cls, what, bases=None, dict=None): super(AdapterMeta, cls).__init__(what, bases, dict) @@ -154,8 +136,7 @@ def configure(cls, parsed): return cls(parsed) -@add_metaclass(AdapterMeta) -class BaseAdapter(object): +class BaseAdapter(object, metaclass=AdapterMeta): """ Interface for IOTA API adapters. @@ -242,7 +223,7 @@ class HttpAdapter(BaseAdapter): :param AdapterSpec uri: URI or adapter instance. - If ``uri`` is a ``text_type``, it is parsed to extract ``scheme``, + If ``uri`` is a ``str``, it is parsed to extract ``scheme``, ``hostname`` and ``port``. :param Optional[int] timeout: @@ -284,8 +265,8 @@ def __init__(self, uri, timeout=None, authentication=None): self.timeout = timeout self.authentication = authentication - if isinstance(uri, text_type): - uri = compat.urllib_parse.urlsplit(uri) # type: SplitResult + if isinstance(uri, str): + uri = urlsplit(uri) # type: SplitResult if uri.scheme not in self.supported_protocols: raise with_context( @@ -312,7 +293,6 @@ def __init__(self, uri, timeout=None, authentication=None): ) try: - # noinspection PyStatementEffect uri.port except ValueError: raise with_context( @@ -344,7 +324,7 @@ def get_uri(self): async def send_request(self, payload, **kwargs): # type: (dict, dict) -> dict kwargs.setdefault('headers', {}) - for key, value in iteritems(self.DEFAULT_HEADERS): + for key, value in self.DEFAULT_HEADERS.items(): kwargs['headers'].setdefault(key, value) response = await self._send_http_request( @@ -539,7 +519,6 @@ class MockAdapter(BaseAdapter): """ supported_protocols = ('mock',) - # noinspection PyUnusedLocal @classmethod def configure(cls, uri): return cls() diff --git a/iota/adapter/wrappers.py b/iota/adapter/wrappers.py index 9fff672..d2a5f9b 100644 --- a/iota/adapter/wrappers.py +++ b/iota/adapter/wrappers.py @@ -1,12 +1,6 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from abc import ABCMeta, abstractmethod as abstract_method from typing import Dict, Text -from six import add_metaclass - from iota.adapter import AdapterSpec, BaseAdapter, resolve_adapter __all__ = [ @@ -14,8 +8,7 @@ ] -@add_metaclass(ABCMeta) -class BaseWrapper(BaseAdapter): +class BaseWrapper(BaseAdapter, metaclass=ABCMeta): """ Base functionality for "adapter wrappers", used to extend the functionality of IOTA adapters. diff --git a/iota/bin/__init__.py b/iota/bin/__init__.py index a505e79..5606d2e 100644 --- a/iota/bin/__init__.py +++ b/iota/bin/__init__.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import sys from abc import ABCMeta, abstractmethod as abstract_method from argparse import ArgumentParser @@ -10,8 +6,6 @@ from sys import exit from typing import Any, Optional, Text -from six import text_type, add_metaclass - from iota import Iota, __version__ from iota.crypto.types import Seed @@ -20,8 +14,7 @@ ] -@add_metaclass(ABCMeta) -class IotaCommandLineApp(object): +class IotaCommandLineApp(object, metaclass=ABCMeta): """ Base functionality for a PyOTA-powered command-line application. """ @@ -120,7 +113,7 @@ def create_argument_parser(self): parser.add_argument( '--uri', - type=text_type, + type=str, default='http://localhost:14265/', help=( @@ -132,7 +125,7 @@ def create_argument_parser(self): if self.requires_seed: parser.add_argument( '--seed-file', - type=text_type, + type=str, dest='seed_file', help=( @@ -173,7 +166,7 @@ def prompt_for_seed(): 'If no seed is specified, a random one will be used instead.\n' ) - if isinstance(seed, text_type): + if isinstance(seed, str): seed = seed.encode('ascii') return Seed(seed) if seed else Seed.random() diff --git a/iota/bin/repl.py b/iota/bin/repl.py index 3104125..3fa8acf 100755 --- a/iota/bin/repl.py +++ b/iota/bin/repl.py @@ -1,18 +1,11 @@ #!/usr/bin/env python -# coding=utf-8 """ Launches a Python shell with a configured API client ready to go. """ -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from argparse import ArgumentParser from logging import basicConfig, getLogger, DEBUG from sys import stderr -from six import text_type -from six.moves import http_client - # Import all IOTA symbols into module scope, so that it's more # convenient for the user. from iota import * @@ -43,6 +36,7 @@ def execute(self, api, **arguments): # If ``debug_requests`` is specified, log HTTP requests/responses. if debug_requests: # Inject a logger into the IOTA HTTP adapter. + # This will turn on logging for underlying httpx client as well basicConfig(level=DEBUG, stream=stderr) logger = getLogger(__name__) @@ -50,9 +44,6 @@ def execute(self, api, **arguments): api.adapter.set_logger(logger) - # Turn on debugging for the underlying HTTP library. - http_client.HTTPConnection.debuglevel = 1 - try: self._start_repl(api) except KeyboardInterrupt: @@ -66,7 +57,7 @@ def create_argument_parser(self): parser.add_argument( '--pow-uri', - type=text_type, + type=str, default=None, dest='pow_uri', help='URI of node to send POW requests to.' @@ -100,7 +91,6 @@ def _start_repl(api): scope_vars = {'api': api} try: - # noinspection PyUnresolvedReferences import IPython except ImportError: # IPython not available; use regular Python REPL. diff --git a/iota/codecs.py b/iota/codecs.py index afbddb0..6a670ab 100644 --- a/iota/codecs.py +++ b/iota/codecs.py @@ -1,12 +1,6 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from codecs import Codec, CodecInfo, register as lookup_function from warnings import warn -from six import PY3, binary_type - from iota.exceptions import with_context __all__ = [ @@ -46,7 +40,6 @@ class AsciiTrytesCodec(Codec): # :bc: Without the bytearray cast, Python 2 will populate the dict # with characters instead of integers. - # noinspection SpellCheckingInspection alphabet = dict(enumerate(bytearray(b'9ABCDEFGHIJKLMNOPQRSTUVWXYZ'))) """ Used to encode bytes into trytes. @@ -71,13 +64,11 @@ def get_codec_info(cls): } # In Python 2, all codecs are made equal. - # In Python 3, some codecs are more equal than others. - if PY3: - codec_info['_is_text_encoding'] = False + # In Python 3, some codecs are more equal than others + codec_info['_is_text_encoding'] = False return CodecInfo(**codec_info) - # noinspection PyShadowingBuiltins def encode(self, input, errors='strict'): """ Encodes a byte string into trytes. @@ -85,7 +76,7 @@ def encode(self, input, errors='strict'): if isinstance(input, memoryview): input = input.tobytes() - if not isinstance(input, (binary_type, bytearray)): + if not isinstance(input, (bytes, bytearray)): raise with_context( exc=TypeError( "Can't encode {type}; byte string expected.".format( @@ -110,9 +101,8 @@ def encode(self, input, errors='strict'): trytes.append(self.alphabet[first]) trytes.append(self.alphabet[second]) - return binary_type(trytes), len(input) + return bytes(trytes), len(input) - # noinspection PyShadowingBuiltins def decode(self, input, errors='strict'): """ Decodes a tryte string into bytes. @@ -120,7 +110,7 @@ def decode(self, input, errors='strict'): if isinstance(input, memoryview): input = input.tobytes() - if not isinstance(input, (binary_type, bytearray)): + if not isinstance(input, (bytes, bytearray)): raise with_context( exc=TypeError( "Can't decode {type}; byte string expected.".format( @@ -190,7 +180,7 @@ def decode(self, input, errors='strict'): elif errors == 'replace': bytes_ += b'?' - return binary_type(bytes_), len(input) + return bytes(bytes_), len(input) @lookup_function diff --git a/iota/commands/__init__.py b/iota/commands/__init__.py index d78eb1d..e789cd8 100644 --- a/iota/commands/__init__.py +++ b/iota/commands/__init__.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from abc import ABCMeta, abstractmethod as abstract_method from importlib import import_module from inspect import getmembers as get_members, isabstract as is_abstract, \ @@ -11,7 +7,6 @@ from typing import Any, Dict, Mapping, Optional, Text, Union import filters as f -import six from iota.adapter import BaseAdapter from iota.exceptions import with_context @@ -24,8 +19,8 @@ 'ResponseFilter', ] -@six.add_metaclass(ABCMeta) -class BaseCommand(object): + +class BaseCommand(object, metaclass=ABCMeta): """ An API command ready to send to the node. """ @@ -197,8 +192,7 @@ def _apply_none(self): return self._apply({}) -@six.add_metaclass(ABCMeta) -class FilterCommand(BaseCommand): +class FilterCommand(BaseCommand, metaclass=ABCMeta): """ Uses filters to manipulate request/response values. """ diff --git a/iota/commands/core/__init__.py b/iota/commands/core/__init__.py index 64a9874..8acf194 100644 --- a/iota/commands/core/__init__.py +++ b/iota/commands/core/__init__.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Core commands are defined by the node API. @@ -7,9 +6,6 @@ - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference """ -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from .add_neighbors import * from .attach_to_tangle import * from .broadcast_transactions import * diff --git a/iota/commands/core/add_neighbors.py b/iota/commands/core/add_neighbors.py index 85e1ff1..d7d418a 100644 --- a/iota/commands/core/add_neighbors.py +++ b/iota/commands/core/add_neighbors.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota.commands import FilterCommand, RequestFilter diff --git a/iota/commands/core/attach_to_tangle.py b/iota/commands/core/attach_to_tangle.py index 7fed061..6ad7510 100644 --- a/iota/commands/core/attach_to_tangle.py +++ b/iota/commands/core/attach_to_tangle.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota import TransactionHash, TransactionTrytes diff --git a/iota/commands/core/broadcast_transactions.py b/iota/commands/core/broadcast_transactions.py index a8227b5..7bd9eae 100644 --- a/iota/commands/core/broadcast_transactions.py +++ b/iota/commands/core/broadcast_transactions.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota import TransactionTrytes diff --git a/iota/commands/core/check_consistency.py b/iota/commands/core/check_consistency.py index 598322e..d835ec8 100644 --- a/iota/commands/core/check_consistency.py +++ b/iota/commands/core/check_consistency.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota import TransactionHash diff --git a/iota/commands/core/find_transactions.py b/iota/commands/core/find_transactions.py index 8c53114..0476e38 100644 --- a/iota/commands/core/find_transactions.py +++ b/iota/commands/core/find_transactions.py @@ -1,9 +1,4 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f -from six import iteritems from iota import BundleHash, Tag, TransactionHash from iota.commands import FilterCommand, RequestFilter, ResponseFilter @@ -70,7 +65,7 @@ def _apply(self, value): # https://github.com/iotaledger/iota.py/issues/96 search_terms = { term: query - for term, query in iteritems(value) + for term, query in value.items() if query is not None } diff --git a/iota/commands/core/get_balances.py b/iota/commands/core/get_balances.py index 5d55fd3..e75fc84 100644 --- a/iota/commands/core/get_balances.py +++ b/iota/commands/core/get_balances.py @@ -1,9 +1,4 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f -from six import iteritems from iota import TransactionHash from iota.commands import FilterCommand, RequestFilter, ResponseFilter @@ -66,7 +61,7 @@ def _apply(self, value): # Note: We will assume that empty lists are intentional. search_terms = { term: query - for term, query in iteritems(value) + for term, query in value.items() if query is not None } diff --git a/iota/commands/core/get_inclusion_states.py b/iota/commands/core/get_inclusion_states.py index cfcd602..0983b9b 100644 --- a/iota/commands/core/get_inclusion_states.py +++ b/iota/commands/core/get_inclusion_states.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota import TransactionHash diff --git a/iota/commands/core/get_missing_transactions.py b/iota/commands/core/get_missing_transactions.py index c0d3ee4..a5409e7 100644 --- a/iota/commands/core/get_missing_transactions.py +++ b/iota/commands/core/get_missing_transactions.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota import TransactionHash diff --git a/iota/commands/core/get_neighbors.py b/iota/commands/core/get_neighbors.py index 9d074f4..3ac6542 100644 --- a/iota/commands/core/get_neighbors.py +++ b/iota/commands/core/get_neighbors.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from iota.commands import FilterCommand, RequestFilter __all__ = [ diff --git a/iota/commands/core/get_node_api_configuration.py b/iota/commands/core/get_node_api_configuration.py index acaf9a0..0647a92 100644 --- a/iota/commands/core/get_node_api_configuration.py +++ b/iota/commands/core/get_node_api_configuration.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from iota.commands import FilterCommand, RequestFilter __all__ = [ diff --git a/iota/commands/core/get_node_info.py b/iota/commands/core/get_node_info.py index 85d4386..ca5c0d6 100644 --- a/iota/commands/core/get_node_info.py +++ b/iota/commands/core/get_node_info.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota import TransactionHash diff --git a/iota/commands/core/get_tips.py b/iota/commands/core/get_tips.py index f4ab15a..b77ddbb 100644 --- a/iota/commands/core/get_tips.py +++ b/iota/commands/core/get_tips.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota.commands import FilterCommand, RequestFilter, ResponseFilter diff --git a/iota/commands/core/get_transactions_to_approve.py b/iota/commands/core/get_transactions_to_approve.py index d9f9ac5..929249a 100644 --- a/iota/commands/core/get_transactions_to_approve.py +++ b/iota/commands/core/get_transactions_to_approve.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota import TransactionHash diff --git a/iota/commands/core/get_trytes.py b/iota/commands/core/get_trytes.py index f1a1851..9c1bd0a 100644 --- a/iota/commands/core/get_trytes.py +++ b/iota/commands/core/get_trytes.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota import TransactionHash diff --git a/iota/commands/core/interrupt_attaching_to_tangle.py b/iota/commands/core/interrupt_attaching_to_tangle.py index 837e87e..9c63fde 100644 --- a/iota/commands/core/interrupt_attaching_to_tangle.py +++ b/iota/commands/core/interrupt_attaching_to_tangle.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from iota.commands import FilterCommand, RequestFilter __all__ = [ diff --git a/iota/commands/core/remove_neighbors.py b/iota/commands/core/remove_neighbors.py index 6e0896c..809cf94 100644 --- a/iota/commands/core/remove_neighbors.py +++ b/iota/commands/core/remove_neighbors.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota.commands import FilterCommand, RequestFilter diff --git a/iota/commands/core/store_transactions.py b/iota/commands/core/store_transactions.py index 3171584..adf705a 100644 --- a/iota/commands/core/store_transactions.py +++ b/iota/commands/core/store_transactions.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota import TransactionTrytes diff --git a/iota/commands/core/were_addresses_spent_from.py b/iota/commands/core/were_addresses_spent_from.py index 0d41e17..8827bda 100644 --- a/iota/commands/core/were_addresses_spent_from.py +++ b/iota/commands/core/were_addresses_spent_from.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota.commands import FilterCommand, RequestFilter diff --git a/iota/commands/extended/__init__.py b/iota/commands/extended/__init__.py index 4a3fb18..1ab530e 100644 --- a/iota/commands/extended/__init__.py +++ b/iota/commands/extended/__init__.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Extended API commands encapsulate the core commands and provide additional functionality such as address generation and signatures. @@ -8,8 +7,6 @@ - ht§tps://github.com/iotaledger/wiki/blob/master/api-proposal.md """ -from __future__ import absolute_import, division, print_function, \ - unicode_literals from .broadcast_and_store import * from .broadcast_bundle import * diff --git a/iota/commands/extended/broadcast_and_store.py b/iota/commands/extended/broadcast_and_store.py index 50840b5..7054859 100644 --- a/iota/commands/extended/broadcast_and_store.py +++ b/iota/commands/extended/broadcast_and_store.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from iota.commands import FilterCommand from iota.commands.core.broadcast_transactions import \ BroadcastTransactionsCommand diff --git a/iota/commands/extended/broadcast_bundle.py b/iota/commands/extended/broadcast_bundle.py index 76deceb..c2934b7 100644 --- a/iota/commands/extended/broadcast_bundle.py +++ b/iota/commands/extended/broadcast_bundle.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota.filters import Trytes diff --git a/iota/commands/extended/find_transaction_objects.py b/iota/commands/extended/find_transaction_objects.py index 5bd4d44..64d8359 100644 --- a/iota/commands/extended/find_transaction_objects.py +++ b/iota/commands/extended/find_transaction_objects.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import Iterable, List, Optional from iota import Address, BundleHash, Tag, Transaction, TransactionHash diff --git a/iota/commands/extended/get_account_data.py b/iota/commands/extended/get_account_data.py index 4e1fcd2..0ee277a 100644 --- a/iota/commands/extended/get_account_data.py +++ b/iota/commands/extended/get_account_data.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from operator import attrgetter from typing import List, Optional @@ -117,7 +113,6 @@ def __init__(self): ) def _apply(self, value): - # noinspection PyProtectedMember filtered = super(GetAccountDataRequestFilter, self)._apply(value) if self._has_errors: diff --git a/iota/commands/extended/get_bundles.py b/iota/commands/extended/get_bundles.py index 5697014..76bb402 100644 --- a/iota/commands/extended/get_bundles.py +++ b/iota/commands/extended/get_bundles.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota import BadApiResponse, TransactionHash diff --git a/iota/commands/extended/get_inputs.py b/iota/commands/extended/get_inputs.py index f21a46f..0945c95 100644 --- a/iota/commands/extended/get_inputs.py +++ b/iota/commands/extended/get_inputs.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import Optional import filters as f diff --git a/iota/commands/extended/get_latest_inclusion.py b/iota/commands/extended/get_latest_inclusion.py index eaf7c17..65b7b1a 100644 --- a/iota/commands/extended/get_latest_inclusion.py +++ b/iota/commands/extended/get_latest_inclusion.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import List import filters as f diff --git a/iota/commands/extended/get_new_addresses.py b/iota/commands/extended/get_new_addresses.py index 49141b2..efba67f 100644 --- a/iota/commands/extended/get_new_addresses.py +++ b/iota/commands/extended/get_new_addresses.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import List, Optional import filters as f diff --git a/iota/commands/extended/get_transaction_objects.py b/iota/commands/extended/get_transaction_objects.py index cc4070c..be53542 100644 --- a/iota/commands/extended/get_transaction_objects.py +++ b/iota/commands/extended/get_transaction_objects.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import Iterable, List, Optional import filters as f diff --git a/iota/commands/extended/get_transfers.py b/iota/commands/extended/get_transfers.py index d7b1d70..565fac6 100644 --- a/iota/commands/extended/get_transfers.py +++ b/iota/commands/extended/get_transfers.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from itertools import chain from typing import Optional @@ -100,7 +96,6 @@ def __init__(self): ) def _apply(self, value): - # noinspection PyProtectedMember filtered = super(GetTransfersRequestFilter, self)._apply(value) if self._has_errors: diff --git a/iota/commands/extended/is_promotable.py b/iota/commands/extended/is_promotable.py index 4a623d2..0ff0e41 100644 --- a/iota/commands/extended/is_promotable.py +++ b/iota/commands/extended/is_promotable.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from iota.commands import FilterCommand, RequestFilter from iota.commands.core import CheckConsistencyCommand, GetTrytesCommand from iota.transaction import Transaction diff --git a/iota/commands/extended/is_reattachable.py b/iota/commands/extended/is_reattachable.py index 13aeeb9..8654b49 100644 --- a/iota/commands/extended/is_reattachable.py +++ b/iota/commands/extended/is_reattachable.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import List import filters as f diff --git a/iota/commands/extended/prepare_transfer.py b/iota/commands/extended/prepare_transfer.py index ed77525..ce25330 100644 --- a/iota/commands/extended/prepare_transfer.py +++ b/iota/commands/extended/prepare_transfer.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import List, Optional import filters as f diff --git a/iota/commands/extended/promote_transaction.py b/iota/commands/extended/promote_transaction.py index 51f48e7..dcdbf72 100644 --- a/iota/commands/extended/promote_transaction.py +++ b/iota/commands/extended/promote_transaction.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota import Address, BadApiResponse, ProposedTransaction, TransactionHash diff --git a/iota/commands/extended/replay_bundle.py b/iota/commands/extended/replay_bundle.py index c0eb3e6..5c1f204 100644 --- a/iota/commands/extended/replay_bundle.py +++ b/iota/commands/extended/replay_bundle.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from iota import Bundle, TransactionHash diff --git a/iota/commands/extended/send_transfer.py b/iota/commands/extended/send_transfer.py index 8efa482..347148d 100644 --- a/iota/commands/extended/send_transfer.py +++ b/iota/commands/extended/send_transfer.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import List, Optional import filters as f diff --git a/iota/commands/extended/send_trytes.py b/iota/commands/extended/send_trytes.py index 9126932..0c2bbea 100644 --- a/iota/commands/extended/send_trytes.py +++ b/iota/commands/extended/send_trytes.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import List, Optional import filters as f diff --git a/iota/commands/extended/traverse_bundle.py b/iota/commands/extended/traverse_bundle.py index a4ddc94..12f43d9 100644 --- a/iota/commands/extended/traverse_bundle.py +++ b/iota/commands/extended/traverse_bundle.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import List, Optional import filters as f diff --git a/iota/commands/extended/utils.py b/iota/commands/extended/utils.py index 62f962b..0380e7a 100644 --- a/iota/commands/extended/utils.py +++ b/iota/commands/extended/utils.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import Generator, Iterable, List, Optional, Tuple from iota import Address, Bundle, Transaction, \ diff --git a/iota/crypto/__init__.py b/iota/crypto/__init__.py index c76906f..9154991 100644 --- a/iota/crypto/__init__.py +++ b/iota/crypto/__init__.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - # Load curl library. # If a compiled c extension is available, we will prefer to load that; # otherwise fall back to pure-Python implementation. diff --git a/iota/crypto/addresses.py b/iota/crypto/addresses.py index 8425277..e3703cd 100644 --- a/iota/crypto/addresses.py +++ b/iota/crypto/addresses.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import Generator, Iterable, List from iota import Address, TRITS_PER_TRYTE, TrytesCompatible diff --git a/iota/crypto/kerl/__init__.py b/iota/crypto/kerl/__init__.py index 094b795..9cab14a 100644 --- a/iota/crypto/kerl/__init__.py +++ b/iota/crypto/kerl/__init__.py @@ -1,5 +1 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from .pykerl import * diff --git a/iota/crypto/kerl/conv.py b/iota/crypto/kerl/conv.py index 5541fa1..109969b 100644 --- a/iota/crypto/kerl/conv.py +++ b/iota/crypto/kerl/conv.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - BYTE_HASH_LENGTH = 48 TRIT_HASH_LENGTH = 243 diff --git a/iota/crypto/kerl/pykerl.py b/iota/crypto/kerl/pykerl.py index 86edcb5..365e022 100644 --- a/iota/crypto/kerl/pykerl.py +++ b/iota/crypto/kerl/pykerl.py @@ -1,11 +1,6 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import MutableSequence, Optional from sha3 import keccak_384 -from six import PY2 from iota.crypto.kerl import conv from iota.exceptions import with_context @@ -122,9 +117,6 @@ def squeeze(self, trits, offset=0, length=None): while offset < length: unsigned_hash = self.k.digest() - if PY2: - unsigned_hash = map(ord, unsigned_hash) # type: ignore - signed_hash = [conv.convert_sign(b) for b in unsigned_hash] trits_from_hash = conv.convertToTrits(signed_hash) diff --git a/iota/crypto/pycurl.py b/iota/crypto/pycurl.py index 48074c2..639f7c2 100644 --- a/iota/crypto/pycurl.py +++ b/iota/crypto/pycurl.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import List, MutableSequence, Optional, Sequence from iota.exceptions import with_context @@ -53,7 +49,6 @@ def __init__(self): # type: (Optional[Sequence[int]]) -> None self.reset() - # noinspection PyAttributeOutsideInit def reset(self): # type: () -> None """ diff --git a/iota/crypto/signing.py b/iota/crypto/signing.py index 8c2b2a7..f735047 100644 --- a/iota/crypto/signing.py +++ b/iota/crypto/signing.py @@ -1,11 +1,5 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import Iterator, List, Sequence -from six import PY2 - from iota import Hash, TRITS_PER_TRYTE, TryteString, TrytesCompatible from iota.crypto import FRAGMENT_LENGTH, HASH_LENGTH from iota.crypto.kerl import Kerl @@ -304,9 +298,6 @@ def __next__(self): return private_key - if PY2: - next = __next__ - def advance(self): """ Advances the generator without creating a key. @@ -396,10 +387,6 @@ def __next__(self): return TryteString.from_trits(signature_fragment) - if PY2: - next = __next__ - - def validate_signature_fragments( fragments, hash_, diff --git a/iota/crypto/types.py b/iota/crypto/types.py index c868940..64e9963 100644 --- a/iota/crypto/types.py +++ b/iota/crypto/types.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import warnings from typing import Optional diff --git a/iota/exceptions.py b/iota/exceptions.py index 31129c7..c10e106 100644 --- a/iota/exceptions.py +++ b/iota/exceptions.py @@ -1,6 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals __all__ = [ 'with_context', diff --git a/iota/filters.py b/iota/filters.py index 9025a81..66c4f72 100644 --- a/iota/filters.py +++ b/iota/filters.py @@ -1,12 +1,8 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import Text, Type import filters as f from filters.macros import filter_macro -from six import binary_type, moves as compat, text_type +from urllib.parse import urlparse from iota import Address, TryteString, TrytesCompatible from iota.crypto.addresses import AddressGenerator @@ -81,12 +77,12 @@ class NodeUri(f.BaseFilter): } def _apply(self, value): - value = self._filter(value, f.Type(text_type)) # type: Text + value = self._filter(value, f.Type(str)) # type: Text if self._has_errors: return None - parsed = compat.urllib_parse.urlparse(value) + parsed = urlparse(value) if parsed.scheme not in self.SCHEMES: return self._invalid_value(value, self.CODE_NOT_NODE_URI) @@ -94,7 +90,6 @@ def _apply(self, value): return value -# noinspection PyPep8Naming @filter_macro def SecurityLevel(): """ @@ -164,10 +159,9 @@ def __init__(self, result_type=TryteString): self.result_type = result_type def _apply(self, value): - # noinspection PyTypeChecker value = self._filter( filter_chain=f.Type( - (binary_type, bytearray, text_type, TryteString) + (bytes, bytearray, str, TryteString) ), value=value, @@ -211,7 +205,6 @@ def _apply(self, value): ) -# noinspection PyPep8Naming @filter_macro def StringifiedTrytesArray(trytes_type=TryteString): # type: (Type[TryteString]) -> f.FilterChain diff --git a/iota/json.py b/iota/json.py index f5ef54d..2651587 100644 --- a/iota/json.py +++ b/iota/json.py @@ -1,16 +1,9 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from abc import ABCMeta, abstractmethod as abstract_method from json.encoder import JSONEncoder as BaseJsonEncoder from typing import Iterable, Mapping -from six import add_metaclass - -@add_metaclass(ABCMeta) -class JsonSerializable(object): +class JsonSerializable(object, metaclass=ABCMeta): """ Interface for classes that can be safely converted to JSON. """ diff --git a/iota/multisig/__init__.py b/iota/multisig/__init__.py index b058d43..0a0e47b 100644 --- a/iota/multisig/__init__.py +++ b/iota/multisig/__init__.py @@ -1,5 +1 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from .api import * diff --git a/iota/multisig/api.py b/iota/multisig/api.py index 54ec62c..a337973 100644 --- a/iota/multisig/api.py +++ b/iota/multisig/api.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import Iterable, Optional from iota import Address, Iota, AsyncIota, ProposedTransaction diff --git a/iota/multisig/commands/__init__.py b/iota/multisig/commands/__init__.py index 80470a7..7ebc9cb 100644 --- a/iota/multisig/commands/__init__.py +++ b/iota/multisig/commands/__init__.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from .create_multisig_address import * from .get_digests import * from .get_private_keys import * diff --git a/iota/multisig/commands/create_multisig_address.py b/iota/multisig/commands/create_multisig_address.py index 9aa81b5..91fbb5a 100644 --- a/iota/multisig/commands/create_multisig_address.py +++ b/iota/multisig/commands/create_multisig_address.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import List import filters as f diff --git a/iota/multisig/commands/get_digests.py b/iota/multisig/commands/get_digests.py index 48cbba6..ff3bc49 100644 --- a/iota/multisig/commands/get_digests.py +++ b/iota/multisig/commands/get_digests.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import Optional import filters as f diff --git a/iota/multisig/commands/get_private_keys.py b/iota/multisig/commands/get_private_keys.py index 58b9f0f..d1affc9 100644 --- a/iota/multisig/commands/get_private_keys.py +++ b/iota/multisig/commands/get_private_keys.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import Optional import filters as f diff --git a/iota/multisig/commands/prepare_multisig_transfer.py b/iota/multisig/commands/prepare_multisig_transfer.py index cc339de..b7dd663 100644 --- a/iota/multisig/commands/prepare_multisig_transfer.py +++ b/iota/multisig/commands/prepare_multisig_transfer.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import List, Optional import filters as f diff --git a/iota/multisig/crypto/__init__.py b/iota/multisig/crypto/__init__.py index 725c9ff..e69de29 100644 --- a/iota/multisig/crypto/__init__.py +++ b/iota/multisig/crypto/__init__.py @@ -1,3 +0,0 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals diff --git a/iota/multisig/crypto/addresses.py b/iota/multisig/crypto/addresses.py index a12e861..24fb1ce 100644 --- a/iota/multisig/crypto/addresses.py +++ b/iota/multisig/crypto/addresses.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import List, Optional from iota.crypto import HASH_LENGTH diff --git a/iota/multisig/transaction.py b/iota/multisig/transaction.py index 2974a0a..53ba855 100644 --- a/iota/multisig/transaction.py +++ b/iota/multisig/transaction.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import Iterable from iota import ProposedBundle diff --git a/iota/multisig/types.py b/iota/multisig/types.py index 4b68a81..e9623aa 100644 --- a/iota/multisig/types.py +++ b/iota/multisig/types.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from operator import attrgetter from typing import Iterable, Optional diff --git a/iota/transaction/__init__.py b/iota/transaction/__init__.py index 2414a61..fa307a2 100644 --- a/iota/transaction/__init__.py +++ b/iota/transaction/__init__.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - # Import symbols to package namespace, for backwards-compatibility with # PyOTA 1.1.x. from .base import * diff --git a/iota/transaction/base.py b/iota/transaction/base.py index 2cf3ecd..b46fe3a 100644 --- a/iota/transaction/base.py +++ b/iota/transaction/base.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from operator import attrgetter from typing import Iterable, Iterator, List, MutableSequence, \ Optional, Sequence, Text diff --git a/iota/transaction/creation.py b/iota/transaction/creation.py index 3d5bc5a..b798b2e 100644 --- a/iota/transaction/creation.py +++ b/iota/transaction/creation.py @@ -1,11 +1,5 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import Iterable, Iterator, List, Optional, Sequence -from six import PY2 - from iota.crypto import HASH_LENGTH from iota.crypto.kerl import Kerl from iota.crypto.signing import KeyGenerator, normalize @@ -216,10 +210,6 @@ def __bool__(self): """ return bool(self._transactions) - # :bc: Magic methods have different names in Python 2. - if PY2: - __nonzero__ = __bool__ - def __contains__(self, transaction): # type: (ProposedTransaction) -> bool return transaction in self._transactions diff --git a/iota/transaction/types.py b/iota/transaction/types.py index caba973..c888c3d 100644 --- a/iota/transaction/types.py +++ b/iota/transaction/types.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from iota.crypto import FRAGMENT_LENGTH from iota.exceptions import with_context from iota.types import Hash, TryteString, TrytesCompatible diff --git a/iota/transaction/utils.py b/iota/transaction/utils.py index b9505f8..8eacc3f 100644 --- a/iota/transaction/utils.py +++ b/iota/transaction/utils.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from calendar import timegm as unix_timestamp from datetime import datetime from typing import Text diff --git a/iota/transaction/validator.py b/iota/transaction/validator.py index e9308cd..77710dc 100644 --- a/iota/transaction/validator.py +++ b/iota/transaction/validator.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import Generator, List, Optional, Text from iota.crypto.kerl import Kerl @@ -210,7 +206,6 @@ def _get_bundle_signature_errors(self, groups): # algo). if current_errors and LEGACY_SPONGE: for group in groups: - # noinspection PyTypeChecker if self._get_group_signature_error(group, LEGACY_SPONGE): # Legacy algo doesn't work, either; no point in # continuing. diff --git a/iota/trits.py b/iota/trits.py index 189af50..6f45029 100644 --- a/iota/trits.py +++ b/iota/trits.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Provides functions for manipulating sequences of trits. @@ -6,9 +5,6 @@ https://github.com/iotaledger/iota.lib.js/blob/v0.4.2/lib/crypto/helpers/adder.js """ -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from typing import Iterable, List, Optional, Sequence, Tuple __all__ = [ diff --git a/iota/types.py b/iota/types.py index abc7089..c01c12b 100644 --- a/iota/types.py +++ b/iota/types.py @@ -1,6 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals from codecs import decode, encode from itertools import chain @@ -10,9 +7,6 @@ MutableSequence, Optional, Text, Type, TypeVar, Union from warnings import warn -from six import PY2, binary_type, itervalues, python_2_unicode_compatible, \ - text_type - from iota import AsciiTrytesCodec, TRITS_PER_TRYTE from iota.crypto import HASH_LENGTH from iota.crypto.kerl import Kerl @@ -35,7 +29,6 @@ T = TypeVar('T', bound='TryteString') -@python_2_unicode_compatible class TryteString(JsonSerializable): """ A string representation of a sequence of trytes. @@ -83,7 +76,7 @@ def random(cls, length=None): - if ``length`` is negative, - if ``length`` is not defined, and the class doesn't have ``LEN`` attribute. """ - alphabet = list(itervalues(AsciiTrytesCodec.alphabet)) + alphabet = [chr(x) for x in AsciiTrytesCodec.alphabet.values()] generator = SystemRandom() try: if length is None: @@ -95,23 +88,19 @@ def random(cls, length=None): if length is None: raise TypeError("{class_name} does not define a length property".format(class_name=cls.__name__)) - # :py:meth:`SystemRandom.choices` wasn't added until Python 3.6; - # for compatibility, we will continue to use ``choice`` in a - # loop. - # https://docs.python.org/3/library/random.html#random.choices return cls( ''.join( - chr(generator.choice(alphabet)) for _ in range(length) + generator.choices(population=alphabet, k=length) ).encode('ascii') ) @classmethod def from_bytes(cls, bytes_, codec=AsciiTrytesCodec.name, *args, **kwargs): - # type: (Type[T], Union[binary_type, bytearray], Text, *Any, **Any) -> T + # type: (Type[T], Union[bytes, bytearray], Text, *Any, **Any) -> T """ Creates a TryteString from a sequence of bytes. - :param Union[binary_type,bytearray] bytes\_: + :param Union[bytes,bytearray] bytes\_: Source bytes. ASCII representation of a sequence of bytes. Note that only tryte alphabet supported! @@ -344,7 +333,7 @@ def __init__(self, trytes, pad=None): ) else: - if isinstance(trytes, text_type): + if isinstance(trytes, str): trytes = encode(trytes, 'ascii') if not isinstance(trytes, bytearray): @@ -373,13 +362,13 @@ def __init__(self, trytes, pad=None): def __hash__(self): # type: () -> int - return hash(binary_type(self._trytes)) + return hash(bytes(self._trytes)) def __repr__(self): # type: () -> Text return '{cls}({trytes!r})'.format( cls=type(self).__name__, - trytes=binary_type(self._trytes), + trytes=bytes(self._trytes), ) def __bytes__(self): @@ -396,7 +385,7 @@ def __bytes__(self): - ... encode trytes into bytes: use :py:meth:`encode`. - ... decode trytes into Unicode: use :py:meth:`decode`. """ - return binary_type(self._trytes) + return bytes(self._trytes) def __str__(self): """ @@ -406,40 +395,36 @@ def __str__(self): # This causes infinite recursion in Python 2. # return binary_type(self).decode('ascii') - return binary_type(self._trytes).decode('ascii') + return bytes(self._trytes).decode('ascii') def __bool__(self): # type: () -> bool return bool(self._trytes) and any(t != b'9' for t in self) - if PY2: - # Magic methods have different names in Python 2. - __nonzero__ = __bool__ - def __len__(self): # type: () -> int return len(self._trytes) def __iter__(self): - # type: () -> Generator[binary_type, None, None] + # type: () -> Generator[bytes, None, None] # :see: http://stackoverflow.com/a/14267935/ - return (binary_type(self._trytes[i:i + 1]) for i in range(len(self))) + return (bytes(self._trytes[i:i + 1]) for i in range(len(self))) def __contains__(self, other): # type: (TrytesCompatible) -> bool if isinstance(other, TryteString): return other._trytes in self._trytes - elif isinstance(other, text_type): + elif isinstance(other, str): return other.encode('ascii') in self._trytes - elif isinstance(other, (binary_type, bytearray)): + elif isinstance(other, (bytes, bytearray)): return other in self._trytes else: raise with_context( exc=TypeError( 'Invalid type for TryteString contains check ' - '(expected Union[TryteString, {binary_type}, bytearray], ' + '(expected Union[TryteString, {bytes}, bytearray], ' 'actual {type}).'.format( - binary_type=binary_type.__name__, + bytes=bytes.__name__, type=type(other).__name__, ), ), @@ -488,17 +473,17 @@ def __add__(self, other): # type: (TrytesCompatible) -> TryteString if isinstance(other, TryteString): return TryteString(self._trytes + other._trytes) - elif isinstance(other, text_type): + elif isinstance(other, str): return TryteString(self._trytes + other.encode('ascii')) - elif isinstance(other, (binary_type, bytearray)): + elif isinstance(other, (bytes, bytearray)): return TryteString(self._trytes + other) else: raise with_context( exc=TypeError( 'Invalid type for TryteString concatenation ' - '(expected Union[TryteString, {binary_type}, bytearray], ' + '(expected Union[TryteString, {bytes}, bytearray], ' 'actual {type}).'.format( - binary_type=binary_type.__name__, + bytes=bytes.__name__, type=type(other).__name__, ), ), @@ -512,17 +497,17 @@ def __eq__(self, other): # type: (TrytesCompatible) -> bool if isinstance(other, TryteString): return self._trytes == other._trytes - elif isinstance(other, text_type): + elif isinstance(other, str): return self._trytes == other.encode('ascii') - elif isinstance(other, (binary_type, bytearray)): + elif isinstance(other, (bytes, bytearray)): return self._trytes == other else: raise with_context( exc=TypeError( 'Invalid type for TryteString comparison ' - '(expected Union[TryteString, {binary_type}, bytearray], ' + '(expected Union[TryteString, {bytes}, bytearray], ' 'actual {type}).'.format( - binary_type=binary_type.__name__, + bytes=bytes.__name__, type=type(other).__name__, ), ), @@ -561,7 +546,7 @@ def iter_chunks(self, chunk_size): return ChunkIterator(self, chunk_size) def encode(self, errors='strict', codec=AsciiTrytesCodec.name): - # type: (Text, Text) -> binary_type + # type: (Text, Text) -> bytes """ Encodes the TryteString into a lower-level primitive (usually bytes). @@ -895,10 +880,6 @@ def __next__(self): return chunk - if PY2: - # In Python 2, iterator methods are named a little differently. - next = __next__ - class Hash(TryteString): """ diff --git a/setup.py b/setup.py index c2f7f1e..f68f0b1 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 -# :bc: Not importing unicode_literals because in Python 2 distutils, -# some values are expected to be byte strings. -from __future__ import absolute_import, division, print_function from codecs import StreamReader, open from distutils.version import LooseVersion @@ -12,11 +8,11 @@ ## # Because of the way PyOTA declares its dependencies, it requires a # more recent version of setuptools. -# https://www.python.org/dev/peps/pep-0508/#environment-markers -if LooseVersion(setuptools.__version__) < LooseVersion('20.5'): +# https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires +if LooseVersion(setuptools.__version__) < LooseVersion('24.2'): import sys - sys.exit('Installation failed: Upgrade setuptools to version 20.5 or later') + sys.exit('Installation failed: Upgrade setuptools to version 24.2 or later') ## # Load long description for PyPI. @@ -29,13 +25,11 @@ # (``pip install -e .[test-runner]``). tests_require = [ 'aiounittest', - 'mock; python_version < "3.0"', 'nose', ] ## # Off we go! -# noinspection SpellCheckingInspection setuptools.setup( name='PyOTA', description='IOTA API library for Python', @@ -58,16 +52,22 @@ ], }, + # Tell setuptools which python versions to support. Will include metadata + # in the built sdist and wheel that tells pypi to tell pip about supported + # python versions. + # 'python_requires' works from setuptools 24.2.0 (previous versions ignore + # it with a warning), pip understands it from 9.0.0. + # https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires + python_requires='>=3.6, <4', + # filters is no longer maintained and does not support Python 3.7 # phx-filters is a fork that supports 3.7 and 3.8 but not 2.7 install_requires=[ - 'filters; python_version < "3.5"', 'httpx', - 'phx-filters; python_version >= "3.5"', + 'phx-filters', 'pysha3', 'six', - 'typing; python_version < "3.0"', ], extras_require={ diff --git a/test/__init__.py b/test/__init__.py index 5d3d136..ef2f268 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,19 +1,5 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - -from six import PY3 - -if PY3: - # In Python 3 the ``mock`` library was moved into the stdlib. - # noinspection PyUnresolvedReferences - from unittest import mock - from unittest.mock import MagicMock, patch -else: - # In Python 2, the ``mock`` library is included as a dependency. - # noinspection PyUnresolvedReferences - import mock - from mock import MagicMock, patch +from unittest import mock +from unittest.mock import MagicMock, patch # Executes async test case within a loop from aiounittest import async_test diff --git a/test/adapter/__init__.py b/test/adapter/__init__.py index 3f3d02d..e69de29 100644 --- a/test/adapter/__init__.py +++ b/test/adapter/__init__.py @@ -1,3 +0,0 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals diff --git a/test/adapter/wrappers_test.py b/test/adapter/wrappers_test.py index e58cc11..1b3b138 100644 --- a/test/adapter/wrappers_test.py +++ b/test/adapter/wrappers_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase from iota.adapter import HttpAdapter, MockAdapter diff --git a/test/adapter_test.py b/test/adapter_test.py index 4b4c569..58977c5 100644 --- a/test/adapter_test.py +++ b/test/adapter_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import json import socket from typing import Text @@ -11,7 +7,6 @@ from iota import BadApiResponse, InvalidUri, TryteString from iota.adapter import API_VERSION, HttpAdapter, MockAdapter, \ resolve_adapter, async_return -from six import BytesIO, text_type from test import mock, async_test class ResolveAdapterTestCase(TestCase): @@ -143,7 +138,6 @@ async def test_success_response(self): mocked_response = create_http_response(json.dumps(expected_result)) mocked_sender = mock.Mock(return_value=async_return(mocked_response)) - # noinspection PyUnresolvedReferences with mock.patch.object(adapter, '_send_http_request', mocked_sender): result = await adapter.send_request(payload) @@ -181,7 +175,6 @@ async def test_error_response(self): mocked_sender = mock.Mock(return_value=async_return(mocked_response)) - # noinspection PyUnresolvedReferences with mock.patch.object(adapter, '_send_http_request', mocked_sender): with self.assertRaises(BadApiResponse) as context: await adapter.send_request({'command': 'helloWorld'}) @@ -212,13 +205,12 @@ async def test_exception_response(self): mocked_sender = mock.Mock(return_value=async_return(mocked_response)) - # noinspection PyUnresolvedReferences with mock.patch.object(adapter, '_send_http_request', mocked_sender): with self.assertRaises(BadApiResponse) as context: await adapter.send_request({'command': 'helloWorld'}) self.assertEqual( - text_type(context.exception), + str(context.exception), '500 response from node: {error}'.format(error=error_message), ) @@ -239,13 +231,12 @@ async def test_non_200_status(self): mocked_sender = mock.Mock(return_value=async_return(mocked_response)) - # noinspection PyUnresolvedReferences with mock.patch.object(adapter, '_send_http_request', mocked_sender): with self.assertRaises(BadApiResponse) as context: await adapter.send_request({'command': 'helloWorld'}) self.assertEqual( - text_type(context.exception), + str(context.exception), '429 response from node: {decoded}'.format(decoded=decoded_response), ) @@ -260,13 +251,12 @@ async def test_empty_response(self): mocked_sender = mock.Mock(return_value=async_return(mocked_response)) - # noinspection PyUnresolvedReferences with mock.patch.object(adapter, '_send_http_request', mocked_sender): with self.assertRaises(BadApiResponse) as context: await adapter.send_request({'command': 'helloWorld'}) self.assertEqual( - text_type(context.exception), + str(context.exception), 'Empty 200 response from node.', ) @@ -282,13 +272,12 @@ async def test_non_json_response(self): mocked_sender = mock.Mock(return_value=async_return(mocked_response)) - # noinspection PyUnresolvedReferences with mock.patch.object(adapter, '_send_http_request', mocked_sender): with self.assertRaises(BadApiResponse) as context: await adapter.send_request({'command': 'helloWorld'}) self.assertEqual( - text_type(context.exception), + str(context.exception), 'Non-JSON 200 response from node: ' + invalid_response, ) @@ -304,13 +293,12 @@ async def test_non_object_response(self): mocked_sender = mock.Mock(return_value=async_return(mocked_response)) - # noinspection PyUnresolvedReferences with mock.patch.object(adapter, '_send_http_request', mocked_sender): with self.assertRaises(BadApiResponse) as context: await adapter.send_request({'command': 'helloWorld'}) self.assertEqual( - text_type(context.exception), + str(context.exception), 'Malformed 200 response from node: {response!r}'.format( response = invalid_response, @@ -330,7 +318,6 @@ async def test_default_timeout(self): ) ) - # noinspection PyUnresolvedReferences with mock.patch('iota.adapter.AsyncClient.request', mocked_request): # test with default timeout await adapter.send_request(payload=mock_payload) @@ -399,7 +386,6 @@ async def test_argument_overriding_init_timeout(self): _, kwargs = mocked_request.call_args self.assertEqual(kwargs['timeout'], 99) - # noinspection SpellCheckingInspection @async_test async def test_trytes_in_request(self): """ @@ -411,7 +397,6 @@ async def test_trytes_in_request(self): # sure that the request is converted correctly. mocked_sender = mock.Mock(return_value=async_return(create_http_response('{}'))) - # noinspection PyUnresolvedReferences with mock.patch.object(adapter, '_send_http_request', mocked_sender): await adapter.send_request({ 'command': 'helloWorld', diff --git a/test/api_test.py b/test/api_test.py index 745b4f2..ec4ee13 100644 --- a/test/api_test.py +++ b/test/api_test.py @@ -1,12 +1,6 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from abc import ABCMeta from unittest import TestCase -from six import add_metaclass - from iota import InvalidCommand, StrictIota from iota.adapter import MockAdapter from iota.commands import CustomCommand @@ -133,7 +127,5 @@ class definition raises an exception. """ # This statement will raise an exception if the regression is # present; no assertions necessary. - # noinspection PyUnusedLocal - @add_metaclass(ABCMeta) - class CustomClient(object): + class CustomClient(object, metaclass=ABCMeta): client = StrictIota(MockAdapter()) diff --git a/test/codecs_test.py b/test/codecs_test.py index 32a5f30..a43c5ee 100644 --- a/test/codecs_test.py +++ b/test/codecs_test.py @@ -1,17 +1,10 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from codecs import decode, encode from unittest import TestCase from warnings import catch_warnings, simplefilter as simple_filter -from six import text_type - from iota.codecs import AsciiTrytesCodec, TrytesDecodeError -# noinspection SpellCheckingInspection class AsciiTrytesCodecTestCase(TestCase): def test_encode_byte_string(self): """ @@ -134,4 +127,4 @@ def test_compat_name(self): self.assertEqual(len(warnings), 1) self.assertEqual(warnings[0].category, DeprecationWarning) - self.assertIn('codec will be removed', text_type(warnings[0].message)) + self.assertIn('codec will be removed', str(warnings[0].message)) diff --git a/test/commands/__init__.py b/test/commands/__init__.py index 3f3d02d..e69de29 100644 --- a/test/commands/__init__.py +++ b/test/commands/__init__.py @@ -1,3 +0,0 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals diff --git a/test/commands/core/__init__.py b/test/commands/core/__init__.py index 3f3d02d..e69de29 100644 --- a/test/commands/core/__init__.py +++ b/test/commands/core/__init__.py @@ -1,3 +0,0 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals diff --git a/test/commands/core/add_neighbors_test.py b/test/commands/core/add_neighbors_test.py index a65a6f3..0cf64e1 100644 --- a/test/commands/core/add_neighbors_test.py +++ b/test/commands/core/add_neighbors_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f diff --git a/test/commands/core/attach_to_tangle_test.py b/test/commands/core/attach_to_tangle_test.py index 8321295..176f3a3 100644 --- a/test/commands/core/attach_to_tangle_test.py +++ b/test/commands/core/attach_to_tangle_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -11,7 +7,6 @@ from iota.adapter import MockAdapter, async_return from iota.commands.core.attach_to_tangle import AttachToTangleCommand from iota.filters import Trytes -from six import binary_type, text_type from test import patch, MagicMock, async_test @@ -19,7 +14,6 @@ class AttachToTangleRequestFilterTestCase(BaseFilterTestCase): filter_type = AttachToTangleCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(AttachToTangleRequestFilterTestCase, self).setUp() @@ -39,14 +33,14 @@ def test_pass_happy_path(self): The incoming request is valid. """ request = { - 'trunkTransaction': text_type(TransactionHash(self.txn_id)), - 'branchTransaction': text_type(TransactionHash(self.txn_id)), + 'trunkTransaction': str(TransactionHash(self.txn_id)), + 'branchTransaction': str(TransactionHash(self.txn_id)), 'minWeightMagnitude': 20, 'trytes': [ # Raw trytes are extracted to match the IRI's JSON protocol. - text_type(TransactionTrytes(self.trytes1)), - text_type(TransactionTrytes(self.trytes2)), + str(TransactionTrytes(self.trytes1)), + str(TransactionTrytes(self.trytes2)), ], } @@ -55,7 +49,6 @@ def test_pass_happy_path(self): self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, request) - # noinspection SpellCheckingInspection def test_pass_compatible_types(self): """ Incoming values can be converted into the expected types. @@ -69,7 +62,7 @@ def test_pass_compatible_types(self): 'trytes': [ # ``trytes`` can contain any value that can be converted into a # TryteString. - binary_type(TransactionTrytes(self.trytes1)), + bytes(TransactionTrytes(self.trytes1)), # This is probably wrong (s/b :py:class:`TransactionTrytes`), # but technically it's valid. @@ -94,9 +87,9 @@ def test_pass_compatible_types(self): 'minWeightMagnitude': 30, 'trytes': [ - text_type(TransactionTrytes(self.trytes1)), + str(TransactionTrytes(self.trytes1)), - text_type(TransactionTrytes( + str(TransactionTrytes( b'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHD' b'WCTCEAKDCDFD9DSCSA99999999999999999999999', )), @@ -386,7 +379,6 @@ class AttachToTangleResponseFilterTestCase(BaseFilterTestCase): filter_type = AttachToTangleCommand(MockAdapter()).get_response_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(AttachToTangleResponseFilterTestCase, self).setUp() diff --git a/test/commands/core/broadcast_transactions_test.py b/test/commands/core/broadcast_transactions_test.py index 2d20440..f185529 100644 --- a/test/commands/core/broadcast_transactions_test.py +++ b/test/commands/core/broadcast_transactions_test.py @@ -1,12 +1,7 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f from filters.test import BaseFilterTestCase -from six import binary_type, text_type from iota import Iota, AsyncIota, TransactionTrytes, TryteString from iota.adapter import MockAdapter, async_return @@ -19,7 +14,6 @@ class BroadcastTransactionsRequestFilterTestCase(BaseFilterTestCase): filter_type = BroadcastTransactionsCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(BroadcastTransactionsRequestFilterTestCase, self).setUp() @@ -36,8 +30,8 @@ def test_pass_happy_path(self): """ request = { 'trytes': [ - text_type(self.trytes1), - text_type(self.trytes2), + str(self.trytes1), + str(self.trytes2), ], } @@ -54,7 +48,7 @@ def test_pass_compatible_types(self): # Any values that can be converted into TryteStrings are accepted. filter_ = self._filter({ 'trytes': [ - binary_type(self.trytes1), + bytes(self.trytes1), self.trytes2, ], }) @@ -66,8 +60,8 @@ def test_pass_compatible_types(self): { 'trytes': [ # Raw trytes are extracted to match the IRI's JSON protocol. - text_type(self.trytes1), - text_type(self.trytes2), + str(self.trytes1), + str(self.trytes2), ], }, ) diff --git a/test/commands/core/check_consistency_test.py b/test/commands/core/check_consistency_test.py index b328788..9f971f7 100644 --- a/test/commands/core/check_consistency_test.py +++ b/test/commands/core/check_consistency_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -18,7 +14,6 @@ class CheckConsistencyRequestFilterTestCase(BaseFilterTestCase): filter_type = CheckConsistencyCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(CheckConsistencyRequestFilterTestCase, self).setUp() @@ -172,7 +167,6 @@ def test_fail_tails_contents_invalid(self): class CheckConsistencyCommandTestCase(TestCase): - # noinspection SpellCheckingInspection def setUp(self): super(CheckConsistencyCommandTestCase, self).setUp() diff --git a/test/commands/core/find_transactions_test.py b/test/commands/core/find_transactions_test.py index cf4c795..f8e106e 100644 --- a/test/commands/core/find_transactions_test.py +++ b/test/commands/core/find_transactions_test.py @@ -1,12 +1,7 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f from filters.test import BaseFilterTestCase -from six import text_type from iota import Address, Iota, Tag, BundleHash, TransactionHash, TryteString, \ AsyncIota @@ -21,7 +16,6 @@ class FindTransactionsRequestFilterTestCase(BaseFilterTestCase): filter_type = FindTransactionsCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(FindTransactionsRequestFilterTestCase, self).setUp() @@ -38,23 +32,23 @@ def test_pass_all_parameters(self): # Raw trytes are extracted to match the IRI's JSON protocol. request = { 'bundles': [ - text_type(BundleHash(self.trytes1)), - text_type(BundleHash(self.trytes2)), + str(BundleHash(self.trytes1)), + str(BundleHash(self.trytes2)), ], 'addresses': [ - text_type(Address(self.trytes1)), - text_type(Address(self.trytes2)), + str(Address(self.trytes1)), + str(Address(self.trytes2)), ], 'tags': [ - text_type(Tag(self.trytes1)), - text_type(Tag(self.trytes3)), + str(Tag(self.trytes1)), + str(Tag(self.trytes3)), ], 'approvees': [ - text_type(TransactionHash(self.trytes1)), - text_type(TransactionHash(self.trytes3)), + str(TransactionHash(self.trytes1)), + str(TransactionHash(self.trytes3)), ], } @@ -97,23 +91,23 @@ def test_pass_compatible_types(self): { # Raw trytes are extracted to match the IRI's JSON protocol. 'bundles': [ - text_type(BundleHash(self.trytes1)), - text_type(BundleHash(self.trytes2)), + str(BundleHash(self.trytes1)), + str(BundleHash(self.trytes2)), ], 'addresses': [ - text_type(Address(self.trytes1)), - text_type(Address(self.trytes2)), + str(Address(self.trytes1)), + str(Address(self.trytes2)), ], 'tags': [ - text_type(Tag(self.trytes1)), - text_type(Tag(self.trytes3)), + str(Tag(self.trytes1)), + str(Tag(self.trytes3)), ], 'approvees': [ - text_type(TransactionHash(self.trytes1)), - text_type(TransactionHash(self.trytes3)), + str(TransactionHash(self.trytes1)), + str(TransactionHash(self.trytes3)), ], }, ) @@ -137,8 +131,8 @@ def test_pass_bundles_only(self): { 'bundles': [ - text_type(BundleHash(self.trytes1)), - text_type(BundleHash(self.trytes2)), + str(BundleHash(self.trytes1)), + str(BundleHash(self.trytes2)), ], # Null criteria are not included in the request. @@ -168,8 +162,8 @@ def test_pass_addresses_only(self): { 'addresses': [ - text_type(Address(self.trytes1)), - text_type(Address(self.trytes2)), + str(Address(self.trytes1)), + str(Address(self.trytes2)), ], # Null criteria are not included in the request. @@ -199,8 +193,8 @@ def test_pass_tags_only(self): { 'tags': [ - text_type(Tag(self.trytes1)), - text_type(Tag(self.trytes3)), + str(Tag(self.trytes1)), + str(Tag(self.trytes3)), ], # Null criteria are not included in the request. @@ -230,8 +224,8 @@ def test_pass_approvees_only(self): { 'approvees': [ - text_type(TransactionHash(self.trytes1)), - text_type(TransactionHash(self.trytes3)), + str(TransactionHash(self.trytes1)), + str(TransactionHash(self.trytes3)), ], # Null criteria are not included in the request. @@ -491,7 +485,6 @@ class FindTransactionsResponseFilterTestCase(BaseFilterTestCase): filter_type = FindTransactionsCommand(MockAdapter()).get_response_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(FindTransactionsResponseFilterTestCase, self).setUp() @@ -515,7 +508,6 @@ def test_no_results(self): self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, response) - # noinspection SpellCheckingInspection def test_search_results(self): """ The incoming response contains lots of hashes. diff --git a/test/commands/core/get_balances_test.py b/test/commands/core/get_balances_test.py index 7bb5c91..acbe919 100644 --- a/test/commands/core/get_balances_test.py +++ b/test/commands/core/get_balances_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -18,7 +14,6 @@ class GetBalancesRequestFilterTestCase(BaseFilterTestCase): filter_type = GetBalancesCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(GetBalancesRequestFilterTestCase, self).setUp() @@ -305,7 +300,6 @@ def test_fail_tips_contents_invalid(self): ) -# noinspection SpellCheckingInspection class GetBalancesResponseFilterTestCase(BaseFilterTestCase): filter_type = GetBalancesCommand(MockAdapter()).get_response_filter skip_value_check = True diff --git a/test/commands/core/get_inclusion_states_test.py b/test/commands/core/get_inclusion_states_test.py index 31fb77f..dd49efc 100644 --- a/test/commands/core/get_inclusion_states_test.py +++ b/test/commands/core/get_inclusion_states_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -10,7 +6,6 @@ from iota.adapter import MockAdapter, async_return from iota.commands.core.get_inclusion_states import GetInclusionStatesCommand from iota.filters import Trytes -from six import binary_type, text_type from test import patch, MagicMock, async_test @@ -18,7 +13,6 @@ class GetInclusionStatesRequestFilterTestCase(BaseFilterTestCase): filter_type = GetInclusionStatesCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(GetInclusionStatesRequestFilterTestCase, self).setUp() diff --git a/test/commands/core/get_missing_transactions_test.py b/test/commands/core/get_missing_transactions_test.py index 859c69b..68a2faf 100644 --- a/test/commands/core/get_missing_transactions_test.py +++ b/test/commands/core/get_missing_transactions_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -50,7 +46,6 @@ class GetMissingTransactionsResponseFilterTestCase(BaseFilterTestCase): GetMissingTransactionsCommand(MockAdapter()).get_response_filter skip_value_check = True - # noinspection SpellCheckingInspection def test_no_results(self): """ The incoming response contains no hashes. @@ -64,7 +59,6 @@ def test_no_results(self): self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, response) - # noinspection SpellCheckingInspection def test_search_results(self): """ The incoming response contains lots of hashes. diff --git a/test/commands/core/get_neighbors_test.py b/test/commands/core/get_neighbors_test.py index 4a890e6..7554e60 100644 --- a/test/commands/core/get_neighbors_test.py +++ b/test/commands/core/get_neighbors_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f diff --git a/test/commands/core/get_node_api_configuration_test.py b/test/commands/core/get_node_api_configuration_test.py index a24794f..a0a83ee 100644 --- a/test/commands/core/get_node_api_configuration_test.py +++ b/test/commands/core/get_node_api_configuration_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f diff --git a/test/commands/core/get_node_info_test.py b/test/commands/core/get_node_info_test.py index b04a780..1b3249a 100644 --- a/test/commands/core/get_node_info_test.py +++ b/test/commands/core/get_node_info_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -47,7 +43,6 @@ class GetNodeInfoResponseFilterTestCase(BaseFilterTestCase): filter_type = GetNodeInfoCommand(MockAdapter()).get_response_filter skip_value_check = True - # noinspection SpellCheckingInspection def test_pass_happy_path(self): """ The incoming response contains valid values. diff --git a/test/commands/core/get_tips_test.py b/test/commands/core/get_tips_test.py index 11cb03b..8894baf 100644 --- a/test/commands/core/get_tips_test.py +++ b/test/commands/core/get_tips_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -49,7 +45,6 @@ class GetTipsResponseFilterTestCase(BaseFilterTestCase): filter_type = GetTipsCommand(MockAdapter()).get_response_filter skip_value_check = True - # noinspection SpellCheckingInspection def test_pass_lots_of_hashes(self): """ The response contains lots of hashes. @@ -167,7 +162,6 @@ def test_type_coercion(self): https://github.com/iotaledger/iota.py/issues/130 """ - # noinspection SpellCheckingInspection self.adapter.seed_response('getTips', { 'duration': 42, 'hashes': [ diff --git a/test/commands/core/get_transactions_to_approve_test.py b/test/commands/core/get_transactions_to_approve_test.py index 652831b..ab8c321 100644 --- a/test/commands/core/get_transactions_to_approve_test.py +++ b/test/commands/core/get_transactions_to_approve_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -178,7 +174,6 @@ class GetTransactionsToApproveResponseFilterTestCase(BaseFilterTestCase): GetTransactionsToApproveCommand(MockAdapter()).get_response_filter skip_value_check = True - # noinspection SpellCheckingInspection def test_pass_happy_path(self): """ Typical ``getTransactionsToApprove`` response. diff --git a/test/commands/core/get_trytes_test.py b/test/commands/core/get_trytes_test.py index 99eba59..7d15dee 100644 --- a/test/commands/core/get_trytes_test.py +++ b/test/commands/core/get_trytes_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -18,7 +14,6 @@ class GetTrytesRequestFilterTestCase(BaseFilterTestCase): filter_type = GetTrytesCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(GetTrytesRequestFilterTestCase, self).setUp() @@ -180,7 +175,6 @@ class GetTrytesResponseFilter(BaseFilterTestCase): filter_type = GetTrytesCommand(MockAdapter()).get_response_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(GetTrytesResponseFilter, self).setUp() diff --git a/test/commands/core/interrupt_attaching_to_tangle_test.py b/test/commands/core/interrupt_attaching_to_tangle_test.py index c5e5cde..b4b6fa2 100644 --- a/test/commands/core/interrupt_attaching_to_tangle_test.py +++ b/test/commands/core/interrupt_attaching_to_tangle_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f diff --git a/test/commands/core/remove_neighbors_test.py b/test/commands/core/remove_neighbors_test.py index 571745b..75de850 100644 --- a/test/commands/core/remove_neighbors_test.py +++ b/test/commands/core/remove_neighbors_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f diff --git a/test/commands/core/store_transactions_test.py b/test/commands/core/store_transactions_test.py index a034fce..6d98652 100644 --- a/test/commands/core/store_transactions_test.py +++ b/test/commands/core/store_transactions_test.py @@ -1,12 +1,7 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f from filters.test import BaseFilterTestCase -from six import text_type from iota import Iota, TransactionTrytes, TryteString, AsyncIota from iota.adapter import MockAdapter, async_return @@ -19,7 +14,6 @@ class StoreTransactionsRequestFilterTestCase(BaseFilterTestCase): filter_type = StoreTransactionsCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(StoreTransactionsRequestFilterTestCase, self).setUp() @@ -36,8 +30,8 @@ def test_pass_happy_path(self): request = { # Raw trytes are extracted to match the IRI's JSON protocol. 'trytes': [ - text_type(TransactionTrytes(self.trytes1)), - text_type(TransactionTrytes(self.trytes2)), + str(TransactionTrytes(self.trytes1)), + str(TransactionTrytes(self.trytes2)), ], } @@ -67,8 +61,8 @@ def test_pass_compatible_types(self): { # Raw trytes are extracted to match the IRI's JSON protocol. 'trytes': [ - text_type(TransactionTrytes(self.trytes1)), - text_type(TransactionTrytes(self.trytes2)), + str(TransactionTrytes(self.trytes1)), + str(TransactionTrytes(self.trytes2)), ], }, ) diff --git a/test/commands/core/were_addresses_spent_from_test.py b/test/commands/core/were_addresses_spent_from_test.py index e7b1c28..b6ae7f8 100644 --- a/test/commands/core/were_addresses_spent_from_test.py +++ b/test/commands/core/were_addresses_spent_from_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -19,7 +15,6 @@ class WereAddressesSpentFromRequestFilterTestCase(BaseFilterTestCase): WereAddressesSpentFromCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(WereAddressesSpentFromRequestFilterTestCase, self).setUp() diff --git a/test/commands/extended/__init__.py b/test/commands/extended/__init__.py index 3f3d02d..e69de29 100644 --- a/test/commands/extended/__init__.py +++ b/test/commands/extended/__init__.py @@ -1,3 +0,0 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals diff --git a/test/commands/extended/broadcast_and_store_test.py b/test/commands/extended/broadcast_and_store_test.py index 6cd19f1..52626dc 100644 --- a/test/commands/extended/broadcast_and_store_test.py +++ b/test/commands/extended/broadcast_and_store_test.py @@ -1,18 +1,11 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase -from six import text_type - from iota import Iota, AsyncIota, TransactionTrytes from iota.adapter import MockAdapter, async_return from iota.commands.extended.broadcast_and_store import BroadcastAndStoreCommand from test import patch, MagicMock, async_test class BroadcastAndStoreCommandTestCase(TestCase): - # noinspection SpellCheckingInspection def setUp(self): super(BroadcastAndStoreCommandTestCase, self).setUp() @@ -76,8 +69,8 @@ async def test_happy_path(self): """ self.adapter.seed_response('broadcastTransactions', { 'trytes': [ - text_type(self.trytes1, 'ascii'), - text_type(self.trytes2, 'ascii'), + str(self.trytes1, 'ascii'), + str(self.trytes2, 'ascii'), ], }) diff --git a/test/commands/extended/broadcast_bundle_test.py b/test/commands/extended/broadcast_bundle_test.py index a4af43e..e7a7821 100644 --- a/test/commands/extended/broadcast_bundle_test.py +++ b/test/commands/extended/broadcast_bundle_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -23,7 +19,6 @@ class BroadcastBundleRequestFilterTestCase(BaseFilterTestCase): def setUp(self): super(BroadcastBundleRequestFilterTestCase, self).setUp() - # noinspection SpellCheckingInspection self.transaction = ( 'TESTVALUE9DONTUSEINPRODUCTION99999KPZOTR' 'VDB9GZDJGZSSDCBIX9QOK9PAV9RMDBGDXLDTIZTWQ' diff --git a/test/commands/extended/find_transaction_objects_test.py b/test/commands/extended/find_transaction_objects_test.py index ddc15e0..670742b 100644 --- a/test/commands/extended/find_transaction_objects_test.py +++ b/test/commands/extended/find_transaction_objects_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase from iota import Iota, AsyncIota, MockAdapter, Transaction @@ -11,7 +7,6 @@ class FindTransactionObjectsCommandTestCase(TestCase): - # noinspection SpellCheckingInspection def setUp(self): super(FindTransactionObjectsCommandTestCase, self).setUp() diff --git a/test/commands/extended/get_account_data_test.py b/test/commands/extended/get_account_data_test.py index 0d53adf..82af9c4 100644 --- a/test/commands/extended/get_account_data_test.py +++ b/test/commands/extended/get_account_data_test.py @@ -1,12 +1,6 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase - import filters as f from filters.test import BaseFilterTestCase -from six import binary_type from iota import Address, Bundle, Iota, AsyncIota, TransactionHash from iota.adapter import MockAdapter, async_return @@ -22,7 +16,6 @@ class GetAccountDataRequestFilterTestCase(BaseFilterTestCase): filter_type = GetAccountDataCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(GetAccountDataRequestFilterTestCase, self).setUp() @@ -54,7 +47,7 @@ def test_pass_compatible_types(self): filter_ = self._filter({ # ``seed`` can be any value that is convertible into a # TryteString. - 'seed': binary_type(self.seed), + 'seed': bytes(self.seed), # These values must still be integers/bools, however. 'start': 42, @@ -336,7 +329,6 @@ async def __aiter__(self): yield item class GetAccountDataCommandTestCase(TestCase): - # noinspection SpellCheckingInspection def setUp(self): super(GetAccountDataCommandTestCase, self).setUp() @@ -422,7 +414,6 @@ async def test_happy_path(self): """ Loading account data for an account. """ - # noinspection PyUnusedLocal async def mock_iter_used_addresses(adapter, seed, start, security_level): """ Mocks the ``iter_used_addresses`` function, so that we can diff --git a/test/commands/extended/get_bundles_test.py b/test/commands/extended/get_bundles_test.py index f079bec..769d106 100644 --- a/test/commands/extended/get_bundles_test.py +++ b/test/commands/extended/get_bundles_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -22,7 +18,6 @@ class GetBundlesRequestFilterTestCase(BaseFilterTestCase): def setUp(self): super(GetBundlesRequestFilterTestCase, self).setUp() - # noinspection SpellCheckingInspection self.transactions = [ ( 'TESTVALUE9DONTUSEINPRODUCTION99999KPZOTR' diff --git a/test/commands/extended/get_inputs_test.py b/test/commands/extended/get_inputs_test.py index 6ac6697..c05a711 100644 --- a/test/commands/extended/get_inputs_test.py +++ b/test/commands/extended/get_inputs_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -21,7 +17,6 @@ class GetInputsRequestFilterTestCase(BaseFilterTestCase): filter_type = GetInputsCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(GetInputsRequestFilterTestCase, self).setUp() @@ -403,7 +398,6 @@ def test_fail_security_level_wrong_type(self): class GetInputsCommandTestCase(TestCase): - # noinspection SpellCheckingInspection def setUp(self): super(GetInputsCommandTestCase, self).setUp() @@ -635,7 +629,6 @@ async def test_no_stop_threshold_met(self): # ``getInputs`` uses ``findTransactions`` and # ``wereAddressesSpentFrom`` to identify unused addresses. - # noinspection SpellCheckingInspection self.adapter.seed_response('findTransactions', { 'hashes': [ TransactionHash( @@ -645,7 +638,6 @@ async def test_no_stop_threshold_met(self): ], }) - # noinspection SpellCheckingInspection self.adapter.seed_response('findTransactions', { 'hashes': [ TransactionHash( @@ -670,7 +662,6 @@ async def test_no_stop_threshold_met(self): # To keep the unit test nice and speedy, we will mock the address # generator. We already have plenty of unit tests for that # functionality, so we can get away with mocking it here. - # noinspection PyUnusedLocal def mock_address_generator(ag, start, step=1): for addy in [self.addy0, self.addy1, self.addy2][start::step]: yield addy @@ -714,7 +705,6 @@ async def test_no_stop_threshold_not_met(self): # To keep the unit test nice and speedy, we will mock the address # generator. We already have plenty of unit tests for that # functionality, so we can get away with mocking it here. - # noinspection PyUnusedLocal def mock_address_generator(ag, start, step=1): for addy in [self.addy0, self.addy1, self.addy2][start::step]: yield addy @@ -739,7 +729,6 @@ async def test_no_stop_threshold_zero(self): # ``getInputs`` uses ``findTransactions`` and # ``wereAddressesSpentFrom`` to identify unused addresses. # addresses. - # noinspection SpellCheckingInspection self.adapter.seed_response('findTransactions', { 'hashes': [ TransactionHash( @@ -749,7 +738,6 @@ async def test_no_stop_threshold_zero(self): ], }) - # noinspection SpellCheckingInspection self.adapter.seed_response('findTransactions', { 'hashes': [ TransactionHash( @@ -775,7 +763,6 @@ async def test_no_stop_threshold_zero(self): # To keep the unit test nice and speedy, we will mock the address # generator. We already have plenty of unit tests for that # functionality, so we can get away with mocking it here. - # noinspection PyUnusedLocal def mock_address_generator(ag, start, step=1): for addy in [self.addy0, self.addy1, self.addy2][start::step]: yield addy @@ -809,7 +796,6 @@ async def test_no_stop_no_threshold(self): # ``getInputs`` uses ``findTransactions`` and # ``wereAddressesSpentFrom`` to identify unused addresses. - # noinspection SpellCheckingInspection self.adapter.seed_response('findTransactions', { 'hashes': [ TransactionHash( @@ -819,7 +805,6 @@ async def test_no_stop_no_threshold(self): ], }) - # noinspection SpellCheckingInspection self.adapter.seed_response('findTransactions', { 'hashes': [ TransactionHash( @@ -844,7 +829,6 @@ async def test_no_stop_no_threshold(self): # To keep the unit test nice and speedy, we will mock the address # generator. We already have plenty of unit tests for that # functionality, so we can get away with mocking it here. - # noinspection PyUnusedLocal def mock_address_generator(ag, start, step=1): for addy in [self.addy0, self.addy1, self.addy2][start::step]: yield addy @@ -883,7 +867,6 @@ async def test_start(self): # ``getInputs`` uses ``findTransactions`` and # ``wereAddressesSpentFrom`` to identify unused addresses. - # noinspection SpellCheckingInspection self.adapter.seed_response('findTransactions', { 'hashes': [ TransactionHash( @@ -908,7 +891,6 @@ async def test_start(self): # To keep the unit test nice and speedy, we will mock the address # generator. We already have plenty of unit tests for that # functionality, so we can get away with mocking it here. - # noinspection PyUnusedLocal def mock_address_generator(ag, start, step=1): # If ``start`` has the wrong value, return garbage to make the # test asplode. @@ -945,7 +927,6 @@ async def test_start_stop(self): # To keep the unit test nice and speedy, we will mock the address # generator. We already have plenty of unit tests for that # functionality, so we can get away with mocking it here. - # noinspection PyUnusedLocal def mock_address_generator(ag, start, step=1): # returning up to 3 addresses, depending on stop value @@ -997,7 +978,6 @@ async def test_security_level_1_no_stop(self): # ``getInputs`` uses ``findTransactions`` and # ``wereAddressesSpentFrom`` to identify unused addresses. - # noinspection SpellCheckingInspection self.adapter.seed_response('findTransactions', { 'hashes': [ TransactionHash( @@ -1050,7 +1030,6 @@ async def test_security_level_1_with_stop(self): }) # ``getInputs`` uses ``findTransactions`` to identify unused # addresses. - # noinspection SpellCheckingInspection self.adapter.seed_response('findTransactions', { 'hashes': [ TransactionHash( diff --git a/test/commands/extended/get_latest_inclusion_test.py b/test/commands/extended/get_latest_inclusion_test.py index 611f79f..130fd40 100644 --- a/test/commands/extended/get_latest_inclusion_test.py +++ b/test/commands/extended/get_latest_inclusion_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -19,7 +15,6 @@ class GetLatestInclusionRequestFilterTestCase(BaseFilterTestCase): filter_type = GetLatestInclusionCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(GetLatestInclusionRequestFilterTestCase, self).setUp() @@ -176,7 +171,6 @@ def test_fail_hashes_contents_invalid(self): class GetLatestInclusionCommandTestCase(TestCase): - # noinspection SpellCheckingInspection def setUp(self): super(GetLatestInclusionCommandTestCase, self).setUp() diff --git a/test/commands/extended/get_new_addresses_test.py b/test/commands/extended/get_new_addresses_test.py index 51bc514..9969774 100644 --- a/test/commands/extended/get_new_addresses_test.py +++ b/test/commands/extended/get_new_addresses_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -20,7 +16,6 @@ class GetNewAddressesRequestFilterTestCase(BaseFilterTestCase): filter_type = GetNewAddressesCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(GetNewAddressesRequestFilterTestCase, self).setUp() @@ -331,7 +326,6 @@ def test_fail_checksum_wrong_type(self): class GetNewAddressesCommandTestCase(TestCase): - # noinspection SpellCheckingInspection def setUp(self): super(GetNewAddressesCommandTestCase, self).setUp() @@ -442,7 +436,6 @@ async def test_security_level(self): seed = self.seed, ) - # noinspection SpellCheckingInspection self.assertDictEqual( response, @@ -541,7 +534,6 @@ async def test_get_addresses_online_has_transaction(self): self.adapter.seed_response('wereAddressesSpentFrom', { 'states': [False], }) - # noinspection SpellCheckingInspection self.adapter.seed_response('findTransactions', { 'duration': 18, 'hashes': [ diff --git a/test/commands/extended/get_transaction_objects_test.py b/test/commands/extended/get_transaction_objects_test.py index f283957..4879f5b 100644 --- a/test/commands/extended/get_transaction_objects_test.py +++ b/test/commands/extended/get_transaction_objects_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase from iota import Iota, AsyncIota, MockAdapter, Transaction @@ -11,7 +7,6 @@ class GetTransactionObjectsCommandTestCase(TestCase): - # noinspection SpellCheckingInspection def setUp(self): super(GetTransactionObjectsCommandTestCase, self).setUp() diff --git a/test/commands/extended/get_transfers_test.py b/test/commands/extended/get_transfers_test.py index 896ec0f..4b1cd48 100644 --- a/test/commands/extended/get_transfers_test.py +++ b/test/commands/extended/get_transfers_test.py @@ -1,13 +1,7 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f from filters.test import BaseFilterTestCase -from six import binary_type - from iota import Address, Bundle, Iota, AsyncIota, Tag, Transaction, TryteString from iota.adapter import MockAdapter, async_return from iota.commands.extended.get_transfers import GetTransfersCommand, \ @@ -22,7 +16,6 @@ class GetTransfersRequestFilterTestCase(BaseFilterTestCase): filter_type = GetTransfersCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(GetTransfersRequestFilterTestCase, self).setUp() @@ -316,7 +309,6 @@ def test_fail_inclusion_states_wrong_type(self): ) -# noinspection SpellCheckingInspection class GetTransfersCommandTestCase(TestCase): def setUp(self): super(GetTransfersCommandTestCase, self).setUp() @@ -391,7 +383,6 @@ async def test_full_scan(self): # :py:class:`iota.crypto.addresses.AddressGenerator` already has # its own test case, so this does not impact the stability of the # codebase. - # noinspection PyUnusedLocal def create_generator(ag, start, step=1): for addy in [self.addy1, self.addy2][start::step]: yield addy @@ -493,7 +484,6 @@ async def test_no_transactions(self): # :py:class:`iota.crypto.addresses.AddressGenerator` already has # its own test case, so this does not impact the stability of the # codebase. - # noinspection PyUnusedLocal def create_generator(ag, start, step=1): for addy in [self.addy1][start::step]: yield addy @@ -526,7 +516,6 @@ async def test_start(self): """ Scanning the Tangle for all transfers, with start index. """ - # noinspection PyUnusedLocal def create_generator(ag, start, step=1): # Inject an invalid value into the generator, to ensure it is # skipped. @@ -622,7 +611,6 @@ async def test_stop(self): """ Scanning the Tangle for all transfers, with stop index. """ - # noinspection PyUnusedLocal def create_generator(ag, start, step=1): # Inject an invalid value into the generator, to ensure it is # skipped. @@ -701,7 +689,6 @@ async def test_get_inclusion_states(self): """ Fetching inclusion states with transactions. """ - # noinspection PyUnusedLocal def create_generator(ag, start, step=1): for addy in [self.addy1][start::step]: yield addy @@ -771,7 +758,7 @@ def create_generator(ag, start, step=1): { 'duration': 99, - 'trytes': [binary_type(transaction_trytes)], + 'trytes': [bytes(transaction_trytes)], }, ) diff --git a/test/commands/extended/is_promotable_test.py b/test/commands/extended/is_promotable_test.py index f0c7095..3d81964 100644 --- a/test/commands/extended/is_promotable_test.py +++ b/test/commands/extended/is_promotable_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -20,7 +16,6 @@ class IsPromotableRequestFilterTestCase(BaseFilterTestCase): filter_type = IsPromotableCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(IsPromotableRequestFilterTestCase, self).setUp() diff --git a/test/commands/extended/is_reattachable_test.py b/test/commands/extended/is_reattachable_test.py index 641f83f..965d597 100644 --- a/test/commands/extended/is_reattachable_test.py +++ b/test/commands/extended/is_reattachable_test.py @@ -1,13 +1,7 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f from filters.test import BaseFilterTestCase -from six import text_type - from iota import Address, Iota, AsyncIota from iota.adapter import MockAdapter, async_return from iota.commands.extended.is_reattachable import IsReattachableCommand @@ -18,7 +12,6 @@ class IsReattachableRequestFilterTestCase(BaseFilterTestCase): filter_type = IsReattachableCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(IsReattachableRequestFilterTestCase, self).setUp() @@ -54,8 +47,8 @@ def test_pass_happy_path(self): filter_.cleaned_data, { 'addresses': [ - text_type(Address(self.address_1)), - text_type(Address(self.address_2)) + str(Address(self.address_1)), + str(Address(self.address_2)) ], }, ) @@ -129,12 +122,10 @@ def test_fail_single_address(self): ) -# noinspection SpellCheckingInspection class IsReattachableResponseFilterTestCase(BaseFilterTestCase): filter_type = IsReattachableCommand(MockAdapter()).get_response_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(IsReattachableResponseFilterTestCase, self).setUp() diff --git a/test/commands/extended/prepare_transfer_test.py b/test/commands/extended/prepare_transfer_test.py index e25c73c..3ce6bea 100644 --- a/test/commands/extended/prepare_transfer_test.py +++ b/test/commands/extended/prepare_transfer_test.py @@ -1,12 +1,7 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f from filters.test import BaseFilterTestCase -from six import binary_type, iterkeys from iota import Address, BadApiResponse, Iota, ProposedTransaction, Tag, \ TryteString, Transaction, TransactionHash, AsyncIota @@ -23,7 +18,6 @@ class PrepareTransferRequestFilterTestCase(BaseFilterTestCase): filter_type = PrepareTransferCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(PrepareTransferRequestFilterTestCase, self).setUp() @@ -98,7 +92,7 @@ def test_pass_compatible_types(self): """ filter_ = self._filter({ # Any TrytesCompatible value works here. - 'changeAddress': binary_type(self.trytes1), + 'changeAddress': bytes(self.trytes1), 'seed': bytearray(self.trytes2), # These have to be :py:class:`Address` instances, so that we can @@ -370,7 +364,7 @@ def test_fail_inputs_contents_invalid(self): { 'inputs': [ None, - binary_type(self.trytes1), + bytes(self.trytes1), # This is actually valid; I just added it to make sure the # filter isn't cheating! @@ -455,7 +449,6 @@ def test_fail_security_level_wrong_type(self): ) -# noinspection SpellCheckingInspection class PrepareTransferCommandTestCase(TestCase): """ Generating validation data using the JS lib: @@ -656,7 +649,7 @@ async def test_pass_inputs_not_needed(self): ], ) - self.assertEqual(set(iterkeys(response)), {'trytes'}) + self.assertEqual(set(response.keys()), {'trytes'}) self.assertEqual(len(response['trytes']), 2) # Note that the transactions are returned in reverse order. @@ -731,7 +724,7 @@ async def test_pass_inputs_explicit_no_change(self): ], ) - self.assertEqual(set(iterkeys(response)), {'trytes'}) + self.assertEqual(set(response.keys()), {'trytes'}) self.assertEqual(len(response['trytes']), 5) # Note that the transactions are returned in reverse order. @@ -817,7 +810,7 @@ async def test_pass_inputs_explicit_with_change(self): ), ) - self.assertEqual(set(iterkeys(response)), {'trytes'}) + self.assertEqual(set(response.keys()), {'trytes'}) self.assertEqual(len(response['trytes']), 4) # Note that the transactions are returned in reverse order. @@ -955,7 +948,7 @@ async def test_pass_inputs_implicit_no_change(self): ], ) - self.assertEqual(set(iterkeys(response)), {'trytes'}) + self.assertEqual(set(response.keys()), {'trytes'}) self.assertEqual(len(response['trytes']), 5) # Note that the transactions are returned in reverse order. @@ -1051,7 +1044,7 @@ async def test_pass_inputs_implicit_with_change(self): ), ) - self.assertEqual(set(iterkeys(response)), {'trytes'}) + self.assertEqual(set(response.keys()), {'trytes'}) self.assertEqual(len(response['trytes']), 4) # Note that the transactions are returned in reverse order. @@ -1187,7 +1180,7 @@ async def test_pass_change_address_auto_generated(self): ], ) - self.assertEqual(set(iterkeys(response)), {'trytes'}) + self.assertEqual(set(response.keys()), {'trytes'}) self.assertEqual(len(response['trytes']), 4) # Note that the transactions are returned in reverse order. @@ -1243,7 +1236,7 @@ async def test_pass_message_short(self): ], ) - self.assertEqual(set(iterkeys(response)), {'trytes'}) + self.assertEqual(set(response.keys()), {'trytes'}) self.assertEqual(len(response['trytes']), 1) self.assertEqual( @@ -1303,7 +1296,7 @@ async def test_pass_message_long(self): ], ) - self.assertEqual(set(iterkeys(response)), {'trytes'}) + self.assertEqual(set(response.keys()), {'trytes'}) self.assertEqual(len(response['trytes']), 3) # Note that the transactions are returned in reverse order. @@ -1389,7 +1382,7 @@ def mock_get_balances_execute(adapter, request): securityLevel=security_level ) - self.assertEqual(set(iterkeys(response)), {'trytes'}) + self.assertEqual(set(response.keys()), {'trytes'}) EXPECTED_NUMBER_OF_TX = 2 + security_level # signature requires as many transactions as security_level EXPECTED_CHANGE_VALUE = security_level * 11 # what has left depends on security_level @@ -1479,7 +1472,7 @@ def mock_get_balances_execute(adapter, request): securityLevel=security_level ) - self.assertEqual(set(iterkeys(response)), {'trytes'}) + self.assertEqual(set(response.keys()), {'trytes'}) EXPECTED_NUMBER_OF_TX = 2 + security_level # signature requires as many transactions as security_level EXPECTED_CHANGE_VALUE = security_level * 11 # what has left depends on security_level diff --git a/test/commands/extended/promote_transaction_test.py b/test/commands/extended/promote_transaction_test.py index 99a4647..7b46509 100644 --- a/test/commands/extended/promote_transaction_test.py +++ b/test/commands/extended/promote_transaction_test.py @@ -1,13 +1,7 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f from filters.test import BaseFilterTestCase -from six import binary_type - from iota import Bundle, Iota, TransactionHash, TransactionTrytes, \ BadApiResponse, AsyncIota from iota.adapter import MockAdapter, async_return @@ -21,7 +15,6 @@ class PromoteTransactionRequestFilterTestCase(BaseFilterTestCase): filter_type = PromoteTransactionCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(PromoteTransactionRequestFilterTestCase, self).setUp() @@ -52,7 +45,7 @@ def test_pass_compatible_types(self): """ filter_ = self._filter({ # This can be any TrytesCompatible value. - 'transaction': binary_type(self.trytes1), + 'transaction': bytes(self.trytes1), # These values must still be ints, however. 'depth': 100, diff --git a/test/commands/extended/replay_bundle_test.py b/test/commands/extended/replay_bundle_test.py index 07f5765..43b1d05 100644 --- a/test/commands/extended/replay_bundle_test.py +++ b/test/commands/extended/replay_bundle_test.py @@ -1,13 +1,7 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f from filters.test import BaseFilterTestCase -from six import binary_type - from iota import Address, Bundle, BundleHash, Fragment, Iota, Nonce, Tag, \ Transaction, TransactionHash, AsyncIota from iota.adapter import MockAdapter, async_return @@ -21,7 +15,6 @@ class ReplayBundleRequestFilterTestCase(BaseFilterTestCase): filter_type = ReplayBundleCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(ReplayBundleRequestFilterTestCase, self).setUp() @@ -52,7 +45,7 @@ def test_pass_compatible_types(self): """ filter_ = self._filter({ # This can be any TrytesCompatible value. - 'transaction': binary_type(self.trytes1), + 'transaction': bytes(self.trytes1), # These values must still be ints, however. 'depth': 100, @@ -352,7 +345,6 @@ async def test_happy_path(self): """ Successfully replaying a bundle. """ - # noinspection SpellCheckingInspection bundle = Bundle([ # "Spend" transaction, Part 1 of 1 Transaction( diff --git a/test/commands/extended/send_transfer_test.py b/test/commands/extended/send_transfer_test.py index c9ab2fd..7b6b570 100644 --- a/test/commands/extended/send_transfer_test.py +++ b/test/commands/extended/send_transfer_test.py @@ -1,13 +1,7 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f from filters.test import BaseFilterTestCase -from six import binary_type - from iota import Address, Bundle, Iota, ProposedTransaction, TransactionHash, \ TransactionTrytes, TryteString, AsyncIota from iota.adapter import MockAdapter, async_return @@ -23,7 +17,6 @@ class SendTransferRequestFilterTestCase(BaseFilterTestCase): filter_type = SendTransferCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(SendTransferRequestFilterTestCase, self).setUp() @@ -106,12 +99,12 @@ def test_pass_compatible_types(self): """ filter_ = self._filter({ # Any TrytesCompatible values will work here. - 'changeAddress': binary_type(self.trytes1), + 'changeAddress': bytes(self.trytes1), 'seed': bytearray(self.trytes2), - 'reference': binary_type(self.trytes1), + 'reference': bytes(self.trytes1), 'inputs': [ - binary_type(self.trytes3), + bytes(self.trytes3), bytearray(self.trytes4), ], @@ -720,7 +713,6 @@ async def test_happy_path(self): """ Sending a transfer successfully. """ - # noinspection SpellCheckingInspection transaction1 =\ TransactionTrytes( b'GYPRVHBEZOOFXSHQBLCYW9ICTCISLHDBNMMVYD9JJHQMPQCTIQAQTJNNNJ9IDXLRCC' diff --git a/test/commands/extended/send_trytes_test.py b/test/commands/extended/send_trytes_test.py index 12989ee..e3129fe 100644 --- a/test/commands/extended/send_trytes_test.py +++ b/test/commands/extended/send_trytes_test.py @@ -1,13 +1,7 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f from filters.test import BaseFilterTestCase -from six import binary_type, text_type - from iota import Iota, TransactionTrytes, TryteString, TransactionHash, \ AsyncIota from iota.adapter import MockAdapter, async_return @@ -20,7 +14,6 @@ class SendTrytesRequestFilterTestCase(BaseFilterTestCase): filter_type = SendTrytesCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(SendTrytesRequestFilterTestCase, self).setUp() @@ -59,10 +52,10 @@ def test_pass_compatible_types(self): filter_ = self._filter({ # This can accept any TrytesCompatible values. 'trytes': [ - binary_type(self.trytes1), + bytes(self.trytes1), bytearray(self.trytes2), ], - 'reference': binary_type(self.trytes2), + 'reference': bytes(self.trytes2), # These still have to be ints, however. 'depth': 100, @@ -348,7 +341,6 @@ def test_fail_trytes_contents_invalid(self): class SendTrytesCommandTestCase(TestCase): - # noinspection SpellCheckingInspection def setUp(self): super(SendTrytesCommandTestCase, self).setUp() @@ -421,14 +413,14 @@ async def test_happy_path(self): Successful invocation of ``sendTrytes``. """ self.adapter.seed_response('getTransactionsToApprove', { - 'trunkTransaction': text_type(self.transaction1, 'ascii'), - 'branchTransaction': text_type(self.transaction2, 'ascii'), + 'trunkTransaction': str(self.transaction1, 'ascii'), + 'branchTransaction': str(self.transaction2, 'ascii'), }) self.adapter.seed_response('attachToTangle', { 'trytes': [ - text_type(self.trytes1, 'ascii'), - text_type(self.trytes2, 'ascii'), + str(self.trytes1, 'ascii'), + str(self.trytes2, 'ascii'), ], }) diff --git a/test/commands/extended/traverse_bundle_test.py b/test/commands/extended/traverse_bundle_test.py index 383b225..2b77f6a 100644 --- a/test/commands/extended/traverse_bundle_test.py +++ b/test/commands/extended/traverse_bundle_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -22,7 +18,6 @@ class TraverseBundleRequestFilterTestCase(BaseFilterTestCase): def setUp(self): super(TraverseBundleRequestFilterTestCase, self).setUp() - # noinspection SpellCheckingInspection self.transaction = ( 'TESTVALUE9DONTUSEINPRODUCTION99999KPZOTR' 'VDB9GZDJGZSSDCBIX9QOK9PAV9RMDBGDXLDTIZTWQ' @@ -119,7 +114,6 @@ def test_fail_transaction_not_trytes(self): ) -# noinspection SpellCheckingInspection class TraverseBundleCommandTestCase(TestCase): def setUp(self): super(TraverseBundleCommandTestCase, self).setUp() diff --git a/test/commands/extended/utils_test.py b/test/commands/extended/utils_test.py index 229fc5a..23c87c4 100644 --- a/test/commands/extended/utils_test.py +++ b/test/commands/extended/utils_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase from iota.commands.extended.utils import iter_used_addresses, \ get_bundles_from_transaction_hashes diff --git a/test/crypto/__init__.py b/test/crypto/__init__.py index 3f3d02d..e69de29 100644 --- a/test/crypto/__init__.py +++ b/test/crypto/__init__.py @@ -1,3 +0,0 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals diff --git a/test/crypto/addresses_test.py b/test/crypto/addresses_test.py index 64487ca..ce0944d 100644 --- a/test/crypto/addresses_test.py +++ b/test/crypto/addresses_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase from iota import Address @@ -12,7 +8,6 @@ class AddressGeneratorTestCase(TestCase): maxDiff = None - # noinspection SpellCheckingInspection def setUp(self): super(AddressGeneratorTestCase, self).setUp() @@ -34,7 +29,6 @@ def test_get_addresses_single(self): """ ag = AddressGenerator(self.seed_1) - # noinspection SpellCheckingInspection self.assertListEqual( ag.get_addresses(start=0), @@ -46,7 +40,6 @@ def test_get_addresses_single(self): ], ) - # noinspection SpellCheckingInspection self.assertListEqual( ag.get_addresses(start=10), @@ -64,7 +57,6 @@ def test_get_addresses_multiple(self): """ ag = AddressGenerator(self.seed_2) - # noinspection SpellCheckingInspection self.assertListEqual( ag.get_addresses(start=0, count=3), @@ -86,7 +78,6 @@ def test_get_addresses_multiple(self): ], ) - # noinspection SpellCheckingInspection self.assertListEqual( ag.get_addresses(start=10, count=3), @@ -149,7 +140,6 @@ def test_get_addresses_step_negative(self): """ ag = AddressGenerator(self.seed_1) - # noinspection SpellCheckingInspection self.assertListEqual( ag.get_addresses(start=1, count=2, step=-1), @@ -174,7 +164,6 @@ def test_generator(self): generator = ag.create_iterator() - # noinspection SpellCheckingInspection self.assertEqual( next(generator), @@ -184,7 +173,6 @@ def test_generator(self): ), ) - # noinspection SpellCheckingInspection self.assertEqual( next(generator), @@ -204,7 +192,6 @@ def test_generator_with_offset(self): generator = ag.create_iterator(start=1, step=2) - # noinspection SpellCheckingInspection self.assertEqual( next(generator), @@ -214,7 +201,6 @@ def test_generator_with_offset(self): ), ) - # noinspection SpellCheckingInspection self.assertEqual( next(generator), @@ -230,7 +216,6 @@ def test_security_level_lowered(self): """ ag = AddressGenerator(self.seed_1, security_level=1) - # noinspection SpellCheckingInspection self.assertListEqual( ag.get_addresses(start=0, count=3), @@ -258,7 +243,6 @@ def test_security_level_elevated(self): """ ag = AddressGenerator(self.seed_1, security_level=3) - # noinspection SpellCheckingInspection self.assertListEqual( ag.get_addresses(start=0, count=3), @@ -292,7 +276,6 @@ def test_generator_checksum(self): generator = ag.create_iterator() - # noinspection SpellCheckingInspection self.assertEqual( next(generator), @@ -303,7 +286,6 @@ def test_generator_checksum(self): ), ) - # noinspection SpellCheckingInspection self.assertEqual( next(generator), diff --git a/test/crypto/kerl/__init__.py b/test/crypto/kerl/__init__.py index 3f3d02d..e69de29 100644 --- a/test/crypto/kerl/__init__.py +++ b/test/crypto/kerl/__init__.py @@ -1,3 +0,0 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals diff --git a/test/crypto/kerl/pykerl_test.py b/test/crypto/kerl/pykerl_test.py index 3e48169..631f8f9 100644 --- a/test/crypto/kerl/pykerl_test.py +++ b/test/crypto/kerl/pykerl_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from csv import DictReader from os.path import dirname, join from random import randrange @@ -27,7 +23,6 @@ def test_correct_hash_function(self): ) def test_correct_first(self): - # noinspection SpellCheckingInspection inp = ( 'EMIDYNHBWMBCXVDEFOFWINXTERALUKYYPPHKP9JJ' 'FGJEIUY9MUDVNFZHMMWZUYUSWAIOWEVTHNWMHANBH' @@ -42,7 +37,6 @@ def test_correct_first(self): trytes_out = trits_to_trytes(trits_out) - # noinspection SpellCheckingInspection self.assertEqual( trytes_out, @@ -51,7 +45,6 @@ def test_correct_first(self): ) def test_output_greater_243(self): - # noinspection SpellCheckingInspection inp = ( '9MIDYNHBWMBCXVDEFOFWINXTERALUKYYPPHKP9JJ' 'FGJEIUY9MUDVNFZHMMWZUYUSWAIOWEVTHNWMHANBH' @@ -66,7 +59,6 @@ def test_output_greater_243(self): trytes_out = trits_to_trytes(trits_out) - # noinspection SpellCheckingInspection self.assertEqual( trytes_out, @@ -76,7 +68,6 @@ def test_output_greater_243(self): ) def test_input_greater_243(self): - # noinspection SpellCheckingInspection inp = ( 'G9JYBOMPUXHYHKSNRNMMSSZCSHOFYOYNZRSZMAAYWDYEIMVVOGKPJB' 'VBM9TDPULSFUNMTVXRKFIDOHUXXVYDLFSZYZTWQYTE9SPYYWYTXJYQ' @@ -92,7 +83,6 @@ def test_input_greater_243(self): trytes_out = trits_to_trytes(trits_out) - # noinspection SpellCheckingInspection self.assertEqual( trytes_out, diff --git a/test/crypto/pycurl_test.py b/test/crypto/pycurl_test.py index 7c38269..5109c7d 100644 --- a/test/crypto/pycurl_test.py +++ b/test/crypto/pycurl_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase from iota import TryteString @@ -25,7 +21,6 @@ def test_happy_path(self): """ Typical use case. """ - # noinspection SpellCheckingInspection input_ = ( 'EMIDYNHBWMBCXVDEFOFWINXTERALUKYYPPHKP9JJ' 'FGJEIUY9MUDVNFZHMMWZUYUSWAIOWEVTHNWMHANBH' @@ -40,7 +35,6 @@ def test_happy_path(self): trits_out = TryteString.from_trits(trits_out) - # noinspection SpellCheckingInspection self.assertEqual( trits_out, @@ -52,7 +46,6 @@ def test_length_greater_than_243(self): """ The input is longer than 1 hash. """ - # noinspection SpellCheckingInspection input_ = ( 'G9JYBOMPUXHYHKSNRNMMSSZCSHOFYOYNZRSZMAAYWDYEIMVVOGKPJB' 'VBM9TDPULSFUNMTVXRKFIDOHUXXVYDLFSZYZTWQYTE9SPYYWYTXJYQ' @@ -68,7 +61,6 @@ def test_length_greater_than_243(self): trits_out = TryteString.from_trits(trits_out) - # noinspection SpellCheckingInspection self.assertEqual( trits_out, @@ -80,7 +72,6 @@ def test_length(self): """ Specifying different values for the ``length`` argument. """ - # noinspection SpellCheckingInspection input_ = ( 'G9JYBOMPUXHYHKSNRNMMSSZCSHOFYOYNZRSZMAAYWDYEIMVVOGKPJB' 'VBM9TDPULSFUNMTVXRKFIDOHUXXVYDLFSZYZTWQYTE9SPYYWYTXJYQ' @@ -97,7 +88,6 @@ def test_length(self): trits_out = TryteString.from_trits(trits_out) - # noinspection SpellCheckingInspection self.assertEqual( trits_out, @@ -109,7 +99,6 @@ def test_absorb_offset(self): """ Passing an ``offset`` argument to :py:meth:`Curl.absorb`. """ - # noinspection SpellCheckingInspection input_ = ( 'G9JYBOMPUXHYHKSNRNMMSSZCSHOFYOYNZRSZMAAYWDYEIMVVOGKPJB' 'VBM9TDPULSFUNMTVXRKFIDOHUXXVYDLFSZYZTWQYTE9SPYYWYTXJYQ' @@ -126,7 +115,6 @@ def test_absorb_offset(self): trits_out = TryteString.from_trits(trits_out) - # noinspection SpellCheckingInspection self.assertEqual( trits_out, @@ -141,7 +129,6 @@ def test_squeeze_offset(self): Example use case: https://github.com/iotaledger/iri/blob/v1.4.1.6/src/main/java/com/iota/iri/hash/ISS.java#L83 """ - # noinspection SpellCheckingInspection input_ = ( 'CDLFODMOGMQAWXDURDXTUAOO9BFESHYGZLBUWIIHPTLNZCUNHZAAXSUPUIBW' 'IRLOVKCVWJSWEKRJQZUVRDZGZRNANUNCSGANCJWVHMZMVNJVUAZNFZKDAIVV' @@ -169,7 +156,6 @@ def test_squeeze_offset(self): trits_out = TryteString.from_trits(trits_out) - # noinspection SpellCheckingInspection self.assertEqual( trits_out, @@ -181,7 +167,6 @@ def test_squeeze_multiple_hashes(self): """ Squeezing more than 1 hash from the sponge. """ - # noinspection SpellCheckingInspection input_ = ( 'EMIDYNHBWMBCXVDEFOFWINXTERALUKYYPPHKP9JJ' 'FGJEIUY9MUDVNFZHMMWZUYUSWAIOWEVTHNWMHANBH' @@ -196,7 +181,6 @@ def test_squeeze_multiple_hashes(self): trits_out = TryteString.from_trits(trits_out) - # noinspection SpellCheckingInspection self.assertEqual( trits_out, diff --git a/test/crypto/signing_test.py b/test/crypto/signing_test.py index df7d586..3a6c82e 100644 --- a/test/crypto/signing_test.py +++ b/test/crypto/signing_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import warnings from unittest import TestCase @@ -11,7 +7,6 @@ from iota.crypto.types import PrivateKey -# noinspection SpellCheckingInspection class KeyGeneratorTestCase(TestCase): """ Generating validation data using the JS lib: @@ -838,7 +833,6 @@ def test_generator_with_security_level(self): ) -# noinspection SpellCheckingInspection class SignatureFragmentGeneratorTestCase(TestCase): """ Generating values for this test case using the JS lib: diff --git a/test/crypto/types_test.py b/test/crypto/types_test.py index 55682f4..65cc202 100644 --- a/test/crypto/types_test.py +++ b/test/crypto/types_test.py @@ -1,12 +1,5 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import warnings from unittest import TestCase - -from six import text_type - from iota import Hash, TryteString from iota.crypto import SeedWarning from iota.crypto.types import Digest, PrivateKey, Seed @@ -52,7 +45,7 @@ def test_random_seed_too_long(self): self.assertIs(catched_warnings[-1].category, SeedWarning) self.assertIn( "inappropriate length", - text_type(catched_warnings[-1].message), + str(catched_warnings[-1].message), ) self.assertEqual(len(seed), Hash.LEN + 1) @@ -86,7 +79,6 @@ def test_random(self): with self.assertRaises(TypeError): random_digest = Digest.random() -# noinspection SpellCheckingInspection class PrivateKeyTestCase(TestCase): """ Generating validation data using the JS lib: diff --git a/test/filters_test.py b/test/filters_test.py index 14cd0c4..b630d66 100644 --- a/test/filters_test.py +++ b/test/filters_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - import filters as f from filters.test import BaseFilterTestCase @@ -48,7 +44,6 @@ def test_fail_wrong_type(self): """ Incoming value is not an :py:class:`Address` instance. """ - # noinspection SpellCheckingInspection self.assertFilterErrors( # The only way to ensure ``key_index`` is set is to require that # the incoming value is an :py:class:`Address` instance. @@ -121,7 +116,6 @@ def test_fail_wrong_type(self): ) -# noinspection SpellCheckingInspection class TrytesTestCase(BaseFilterTestCase): filter_type = Trytes @@ -227,11 +221,9 @@ def test_fail_wrong_type(self): ) -# noinspection SpellCheckingInspection class AddressNoChecksumTestCase(BaseFilterTestCase): filter_type = AddressNoChecksum - # noinspection SpellCheckingInspection def setUp(self): super(AddressNoChecksumTestCase, self).setUp() diff --git a/test/local_pow_test.py b/test/local_pow_test.py index 5b46231..a3ee70d 100644 --- a/test/local_pow_test.py +++ b/test/local_pow_test.py @@ -1,18 +1,9 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from iota import Iota, TryteString, TransactionHash, TransactionTrytes, \ HttpAdapter, MockAdapter from iota.adapter.wrappers import RoutingWrapper from unittest import TestCase import sys -from six import PY2 - -if PY2: - from mock import MagicMock, patch -else: - from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, patch # Load mocked package on import from pow pkg. # Therefore we can test without having to install it. diff --git a/test/multisig/__init__.py b/test/multisig/__init__.py index 3f3d02d..e69de29 100644 --- a/test/multisig/__init__.py +++ b/test/multisig/__init__.py @@ -1,3 +0,0 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals diff --git a/test/multisig/commands/__init__.py b/test/multisig/commands/__init__.py index 3f3d02d..e69de29 100644 --- a/test/multisig/commands/__init__.py +++ b/test/multisig/commands/__init__.py @@ -1,3 +0,0 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals diff --git a/test/multisig/commands/create_multisig_address_test.py b/test/multisig/commands/create_multisig_address_test.py index 954dce6..76d8127 100644 --- a/test/multisig/commands/create_multisig_address_test.py +++ b/test/multisig/commands/create_multisig_address_test.py @@ -1,13 +1,6 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase - import filters as f from filters.test import BaseFilterTestCase -from six import binary_type - from iota import TryteString from iota.adapter import MockAdapter, async_return from iota.crypto.types import Digest @@ -19,7 +12,6 @@ class CreateMultisigAddressCommandTestCase(TestCase): - # noinspection SpellCheckingInspection def setUp(self): super(CreateMultisigAddressCommandTestCase, self).setUp() @@ -99,7 +91,6 @@ async def test_happy_path(self): """ result = await self.command(digests=[self.digest_1, self.digest_2]) - # noinspection SpellCheckingInspection self.assertDictEqual( result, @@ -120,7 +111,6 @@ class CreateMultisigAddressRequestFilterTestCase(BaseFilterTestCase): filter_type = CreateMultisigAddressCommand(MockAdapter()).get_request_filter skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(CreateMultisigAddressRequestFilterTestCase, self).setUp() @@ -166,7 +156,7 @@ def test_pass_compatible_types(self): filter_ = self._filter({ # ``digests`` may contain any values that can be converted into # :py:class:`Digest` objects. - 'digests': [binary_type(self.digest_1), TryteString(self.digest_2)], + 'digests': [bytes(self.digest_1), TryteString(self.digest_2)], }) self.assertFilterPasses(filter_) diff --git a/test/multisig/commands/get_digests_test.py b/test/multisig/commands/get_digests_test.py index f201ef7..b75c2ad 100644 --- a/test/multisig/commands/get_digests_test.py +++ b/test/multisig/commands/get_digests_test.py @@ -1,13 +1,7 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f from filters.test import BaseFilterTestCase -from six import binary_type - from iota import Hash, TryteString from iota.adapter import MockAdapter, async_return from iota.crypto import FRAGMENT_LENGTH @@ -20,7 +14,6 @@ class GetDigestsCommandTestCase(TestCase): - # noinspection SpellCheckingInspection def setUp(self): super(GetDigestsCommandTestCase, self).setUp() @@ -92,7 +85,6 @@ async def test_generate_single_digest(self): 'iota.multisig.commands.get_private_keys.GetPrivateKeysCommand._execute', mock_get_private_keys ): - # noinspection PyUnresolvedReferences with mock.patch.object(self.key1, 'get_digest') as mock_get_digest_1: # type: mock.MagicMock mock_get_digest_1.return_value = self.digest1 @@ -121,11 +113,9 @@ async def test_generate_multiple_digests(self): 'iota.multisig.commands.get_private_keys.GetPrivateKeysCommand._execute', mock_get_private_keys ): - # noinspection PyUnresolvedReferences with mock.patch.object(self.key1, 'get_digest') as mock_get_digest_1: # type: mock.MagicMock mock_get_digest_1.return_value = self.digest1 - # noinspection PyUnresolvedReferences with mock.patch.object(self.key2, 'get_digest') as mock_get_digest_2: # type: mock.MagicMock mock_get_digest_2.return_value = self.digest2 @@ -149,7 +139,6 @@ def setUp(self): super(GetDigestsRequestFilterTestCase, self).setUp() # Define some tryte sequences that we can reuse between tests. - # noinspection SpellCheckingInspection self.seed = b'HELLOIOTA' def test_pass_happy_path(self): @@ -195,7 +184,7 @@ def test_pass_compatible_types(self): """ filter_ = self._filter({ # ``seed`` can be any value that is convertible to TryteString. - 'seed': binary_type(self.seed), + 'seed': bytes(self.seed), # These values must be integers, however. 'index': 100, diff --git a/test/multisig/commands/get_private_keys_test.py b/test/multisig/commands/get_private_keys_test.py index 8c541e2..3f84b77 100644 --- a/test/multisig/commands/get_private_keys_test.py +++ b/test/multisig/commands/get_private_keys_test.py @@ -1,13 +1,6 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase - import filters as f from filters.test import BaseFilterTestCase -from six import binary_type - from iota import TryteString from iota.adapter import MockAdapter, async_return from iota.crypto import FRAGMENT_LENGTH @@ -20,7 +13,6 @@ class GetPrivateKeysCommandTestCase(TestCase): - # noinspection SpellCheckingInspection def setUp(self): super(GetPrivateKeysCommandTestCase, self).setUp() @@ -179,7 +171,7 @@ def test_pass_compatible_types(self): """ filter_ = self._filter({ # ``seed`` can be any value that is convertible to TryteString. - 'seed': binary_type(self.seed), + 'seed': bytes(self.seed), # These values must be integers, however. 'index': 100, diff --git a/test/multisig/commands/prepare_multisig_transfer_test.py b/test/multisig/commands/prepare_multisig_transfer_test.py index 46dd373..6fc4484 100644 --- a/test/multisig/commands/prepare_multisig_transfer_test.py +++ b/test/multisig/commands/prepare_multisig_transfer_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase import filters as f @@ -22,7 +18,6 @@ class PrepareMultisigTransferRequestFilterTestCase(BaseFilterTestCase): maxDiff = None skip_value_check = True - # noinspection SpellCheckingInspection def setUp(self): super(PrepareMultisigTransferRequestFilterTestCase, self).setUp() @@ -482,7 +477,6 @@ def test_fail_changeAddress_wrong_type(self): class PrepareMultisigTransferCommandTestCase(TestCase): - # noinspection SpellCheckingInspection def setUp(self): super(PrepareMultisigTransferCommandTestCase, self).setUp() diff --git a/test/multisig/crypto/__init__.py b/test/multisig/crypto/__init__.py index 3f3d02d..e69de29 100644 --- a/test/multisig/crypto/__init__.py +++ b/test/multisig/crypto/__init__.py @@ -1,3 +0,0 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals diff --git a/test/multisig/crypto/addresses_test.py b/test/multisig/crypto/addresses_test.py index ad5795e..866f7ee 100644 --- a/test/multisig/crypto/addresses_test.py +++ b/test/multisig/crypto/addresses_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase from iota import Address @@ -25,7 +21,6 @@ class MultisigAddressBuilderTestCase(TestCase): var addy = new Multisig(digests); console.log(addy.finalize()); """ - # noinspection SpellCheckingInspection def setUp(self): super(MultisigAddressBuilderTestCase, self).setUp() @@ -71,7 +66,6 @@ def test_success_multiple_digests(self): self.assertIsInstance(addy, MultisigAddress) - # noinspection SpellCheckingInspection self.assertEqual( addy, @@ -98,7 +92,6 @@ def test_success_single_digest(self): self.assertIsInstance(addy, MultisigAddress) - # noinspection SpellCheckingInspection self.assertEqual( addy, @@ -142,7 +135,6 @@ def test_success_duplicate_digest(self): self.assertIsInstance(addy, MultisigAddress) - # noinspection SpellCheckingInspection self.assertEqual( addy, diff --git a/test/multisig/transaction_test.py b/test/multisig/transaction_test.py index 3c1fecd..f446ad5 100644 --- a/test/multisig/transaction_test.py +++ b/test/multisig/transaction_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase from iota import Address, ProposedTransaction @@ -11,7 +7,6 @@ class ProposedMultisigBundleTestCase(TestCase): - # noinspection SpellCheckingInspection def setUp(self): super(ProposedMultisigBundleTestCase, self).setUp() @@ -57,7 +52,6 @@ def test_add_inputs_happy_path(self): """ Adding a multisig input to a bundle. """ - # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address = Address(self.trytes_1), @@ -126,7 +120,6 @@ def test_add_inputs_error_multiple(self): This is not currently supported. """ with self.assertRaises(ValueError): - # noinspection SpellCheckingInspection self.bundle.add_inputs([ MultisigAddress( trytes = self.trytes_1, @@ -147,7 +140,6 @@ def test_add_inputs_error_not_multisig(self): This is not currently supported. """ with self.assertRaises(TypeError): - # noinspection SpellCheckingInspection,PyTypeChecker self.bundle.add_inputs([ Address( trytes = self.trytes_1, diff --git a/test/transaction/__init__.py b/test/transaction/__init__.py index 3f3d02d..e69de29 100644 --- a/test/transaction/__init__.py +++ b/test/transaction/__init__.py @@ -1,3 +0,0 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals diff --git a/test/transaction/base_test.py b/test/transaction/base_test.py index 092a479..d9717ac 100644 --- a/test/transaction/base_test.py +++ b/test/transaction/base_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase from iota import Address, Bundle, BundleHash, Fragment, Hash, Nonce, Tag, \ @@ -12,7 +8,6 @@ class BundleTestCase(TestCase): def setUp(self): super(BundleTestCase, self).setUp() - # noinspection SpellCheckingInspection self.bundle = Bundle([ # This transaction does not have a message. Transaction( @@ -303,7 +298,6 @@ def test_get_messages_errors_drop(self): self.assertEqual(messages[0], 'Hello, world!') - # noinspection SpellCheckingInspection self.assertEqual( messages[1], @@ -386,7 +380,6 @@ class TransactionTestCase(TestCase): - ``lib/utils/utils.js:transactionTrytes``: Convert an object back into a tryte sequence. """ - # noinspection SpellCheckingInspection def test_from_tryte_string(self): """ Initializing a Transaction object from a TryteString. @@ -553,7 +546,6 @@ def test_from_tryte_string_with_hash(self): Initializing a Transaction object from a TryteString, with a pre-computed hash. """ - # noinspection SpellCheckingInspection txn_hash =\ TransactionHash( b'TESTVALUE9DONTUSEINPRODUCTION99999VALCXC' @@ -564,7 +556,6 @@ def test_from_tryte_string_with_hash(self): self.assertEqual(txn.hash, txn_hash) - # noinspection SpellCheckingInspection def test_as_tryte_string(self): """ Converting a Transaction into a TryteString. diff --git a/test/transaction/creation_test.py b/test/transaction/creation_test.py index 9112593..d0964fd 100644 --- a/test/transaction/creation_test.py +++ b/test/transaction/creation_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase from iota import Address, Fragment, ProposedBundle, ProposedTransaction, Tag, \ @@ -18,7 +14,6 @@ def setUp(self): # We will use a seed to generate addresses and private keys, to # ensure a realistic scenario (and because the alternative is to # inject mocks all over the place!). - # noinspection SpellCheckingInspection self.seed =\ Seed( b'TESTVALUE9DONTUSEINPRODUCTION99999RLC9CS' @@ -28,7 +23,6 @@ def setUp(self): # To speed things up a little bit, though, we can pre-generate a # few addresses to use as inputs. - # noinspection SpellCheckingInspection self.input_0_bal_eq_42 =\ Address( balance = 42, @@ -40,7 +34,6 @@ def setUp(self): b'9KKMHXFMIXHLKQQAVTTNPRCZENGLIPALHKLNKTXCU', ) - # noinspection SpellCheckingInspection self.input_1_bal_eq_40 =\ Address( balance = 40, @@ -52,7 +45,6 @@ def setUp(self): b'DSMZXPL9KXREBBYHJGRBCYVGPJQEHEDPXLBDJNQNX', ) - # noinspection SpellCheckingInspection self.input_2_bal_eq_2 =\ Address( balance = 2, @@ -64,7 +56,6 @@ def setUp(self): b'TRRJPNTSQRZTASRBTQCRFAIDOGTWSHIDGOUUULQIG', ) - # noinspection SpellCheckingInspection self.input_3_bal_eq_100 =\ Address( balance = 100, @@ -76,7 +67,6 @@ def setUp(self): b'YLOAZNKJR9VDYSONVAJRIPVWCOZKFMEKUSWHPSDDZ', ) - # noinspection SpellCheckingInspection self.input_4_bal_eq_42_sl_2 =\ Address( balance = 42, @@ -88,7 +78,6 @@ def setUp(self): b'EMMJ9BCDVVHJJLSTQW9JEJXUUX9JNFGALBNASRDUD', ) - # noinspection SpellCheckingInspection self.input_5_bal_eq_42_sl_3 =\ Address( balance = 42, @@ -107,7 +96,6 @@ def test_add_transaction_short_message(self): Adding a transaction to a bundle, with a message short enough to fit inside a single transaction. """ - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -128,7 +116,6 @@ def test_add_transaction_long_message(self): Adding a transaction to a bundle, with a message so long that it has to be split into multiple transactions. """ - # noinspection SpellCheckingInspection address = Address( b'TESTVALUE9DONTUSEINPRODUCTION99999N9GIUF' b'HCFIUGLBSCKELC9IYENFPHCEWHIDCHCGGEH9OFZBN' @@ -203,7 +190,6 @@ def test_add_transaction_error_already_finalized(self): Attempting to add a transaction to a bundle that is already finalized. """ - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -237,7 +223,6 @@ def test_add_inputs_no_change(self): """ Adding inputs to cover the exact amount of the bundle spend. """ - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -248,7 +233,6 @@ def test_add_inputs_no_change(self): value = 29, )) - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -266,7 +250,6 @@ def test_add_inputs_no_change(self): # Just to be tricky, add an unnecessary change address, just to # make sure the bundle ignores it. - # noinspection SpellCheckingInspection self.bundle.send_unspent_inputs_to( Address( b'TESTVALUE9DONTUSEINPRODUCTION99999FDCDFD' @@ -290,7 +273,6 @@ def test_add_inputs_with_change(self): """ tag = Tag(b'CHANGE9TXN') - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -301,7 +283,6 @@ def test_add_inputs_with_change(self): value = 29, )) - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -315,7 +296,6 @@ def test_add_inputs_with_change(self): self.bundle.add_inputs([self.input_3_bal_eq_100]) - # noinspection SpellCheckingInspection change_address =\ Address( b'TESTVALUE9DONTUSEINPRODUCTION99999KAFGVC' @@ -339,7 +319,6 @@ def test_add_inputs_security_level(self): Each input's security level determines the number of transactions we will need in order to store the entire signature. """ - # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address = @@ -369,7 +348,6 @@ def test_add_inputs_error_already_finalized(self): Attempting to add inputs to a bundle that is already finalized. """ # Add 1 transaction so that we can finalize the bundle. - # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address = @@ -396,7 +374,6 @@ def test_send_unspent_inputs_to_error_already_finalized(self): finalized. """ # Add 1 transaction so that we can finalize the bundle. - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -417,7 +394,6 @@ def test_finalize_error_already_finalized(self): Attempting to finalize a bundle that is already finalized. """ # Add 1 transaction so that we can finalize the bundle. - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -444,7 +420,6 @@ def test_finalize_error_negative_balance(self): """ Attempting to finalize a bundle with unspent inputs. """ - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -469,7 +444,6 @@ def test_finalize_error_positive_balance(self): """ Attempting to finalize a bundle with insufficient inputs. """ - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -497,7 +471,6 @@ def test_finalize_insecure_bundle(self): References: - https://github.com/iotaledger/iota.py/issues/84 """ - # noinspection SpellCheckingInspection bundle =\ ProposedBundle([ ProposedTransaction( @@ -517,15 +490,12 @@ def test_finalize_insecure_bundle(self): # The resulting bundle hash is insecure (contains a [1, 1, 1]), so # the legacy tag is manipulated until a secure hash is generated. - # noinspection SpellCheckingInspection self.assertEqual(bundle[0].legacy_tag, Tag('ZTDIDNQDJZGUQKOWJ9JZRCKOVGP')) # The proper tag is left alone, however. - # noinspection SpellCheckingInspection self.assertEqual(bundle[0].tag, Tag('PPDIDNQDJZGUQKOWJ9JZRCKOVGP')) # The bundle hash takes the modified legacy tag into account. - # noinspection SpellCheckingInspection self.assertEqual( bundle.hash, @@ -539,7 +509,6 @@ def test_sign_inputs(self): """ Signing inputs in a finalized bundle, using a key generator. """ - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -590,7 +559,6 @@ def test_sign_inputs_security_level(self): You may include inputs with different security levels in the same bundle. """ - # noinspection SpellCheckingInspection self.bundle.add_transaction( ProposedTransaction( address = @@ -642,7 +610,6 @@ def test_sign_inputs_error_not_finalized(self): yet. """ # Add a transaction so that we can finalize the bundle. - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -666,7 +633,6 @@ def test_sign_input_at_single_fragment(self): Signing an input at the specified index, only 1 fragment needed. """ # Add a transaction so that we can finalize the bundle. - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -715,7 +681,6 @@ def test_sign_input_at_multiple_fragments(self): Signing an input at the specified index, multiple fragments needed. """ # Add a transaction so that we can finalize the bundle. - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -763,7 +728,6 @@ def test_sign_input_at_error_not_finalized(self): Cannot sign inputs because the bundle isn't finalized yet. """ # Add a transaction so that we can finalize the bundle. - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -790,7 +754,6 @@ def test_sign_input_at_error_index_invalid(self): The specified index doesn't exist in the bundle. """ # Add a transaction so that we can finalize the bundle. - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -815,7 +778,6 @@ def test_sign_input_at_error_index_not_input(self): The specified index references a transaction that is not an input. """ # Add a transaction so that we can finalize the bundle. - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( @@ -841,7 +803,6 @@ def test_sign_input_at_error_already_signed(self): Attempting to sign an input that is already signed. """ # Add a transaction so that we can finalize the bundle. - # noinspection SpellCheckingInspection self.bundle.add_transaction(ProposedTransaction( address = Address( diff --git a/test/transaction/types_test.py b/test/transaction/types_test.py index c39570b..756d193 100644 --- a/test/transaction/types_test.py +++ b/test/transaction/types_test.py @@ -1,11 +1,4 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase - -from six import binary_type - from iota import TransactionHash, BundleHash, Fragment, TransactionTrytes, \ Nonce @@ -15,15 +8,13 @@ def test_init_automatic_pad(self): """ Transaction hashes are automatically padded to 81 trytes. """ - # noinspection SpellCheckingInspection txn = TransactionHash( b'JVMTDGDPDFYHMZPMWEKKANBQSLSDTIIHAYQUMZOK' b'HXXXGJHJDQPOMDOMNRDKYCZRUFZROZDADTHZC' ) - # noinspection SpellCheckingInspection self.assertEqual( - binary_type(txn), + bytes(txn), # Note the extra 9's added to the end. b'JVMTDGDPDFYHMZPMWEKKANBQSLSDTIIHAYQUMZOK' @@ -35,7 +26,6 @@ def test_init_error_too_long(self): Attempting to create a transaction hash longer than 81 trytes. """ with self.assertRaises(ValueError): - # noinspection SpellCheckingInspection TransactionHash( b'JVMTDGDPDFYHMZPMWEKKANBQSLSDTIIHAYQUMZOK' b'HXXXGJHJDQPOMDOMNRDKYCZRUFZROZDADTHZC99999' diff --git a/test/transaction/utils_test.py b/test/transaction/utils_test.py index 6fd09e8..17def9b 100644 --- a/test/transaction/utils_test.py +++ b/test/transaction/utils_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase from iota import convert_value_to_standard_unit @@ -48,7 +44,6 @@ def test_convert_type_list(self): Attempting to convert invalid type: list. """ with self.assertRaises(ValueError): - # noinspection PyTypeChecker convert_value_to_standard_unit(['3.141592', 'Pi'], 'Gi') def test_convert_type_float(self): @@ -56,7 +51,6 @@ def test_convert_type_float(self): Attempting to convert invalid type: float. """ with self.assertRaises(ValueError): - # noinspection PyTypeChecker convert_value_to_standard_unit(3.141592, 'Pi') def test_convert_value_no_space(self): diff --git a/test/transaction/validator_test.py b/test/transaction/validator_test.py index e88c2a9..116b036 100644 --- a/test/transaction/validator_test.py +++ b/test/transaction/validator_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase from iota import Address, Bundle, BundleHash, BundleValidator, TransactionTrytes @@ -110,7 +106,6 @@ class BundleValidatorTestCase(TestCase): consider the bundle to be valid. """ - # noinspection SpellCheckingInspection def setUp(self): super(BundleValidatorTestCase, self).setUp() @@ -205,7 +200,6 @@ def test_fail_bundle_hash_invalid(self): """ One of the transactions has an invalid ``bundle_hash`` value. """ - # noinspection SpellCheckingInspection self.bundle.transactions[3].bundle_hash =\ BundleHash( b'NFDPEEZCWVYLKZGSLCQNOFUSENIXRHWWTZFBXMPS' @@ -294,7 +288,6 @@ def test_fail_signature_fragment_address_wrong(self): One of the signature fragments for an input is associated with the wrong address. """ - # noinspection SpellCheckingInspection self.bundle[5].address =\ Address( b'QHEDFWZULBZFEOMNLRNIDQKDNNIELAOXOVMYEI9P' @@ -426,7 +419,6 @@ def setUp(self): super(BundleValidatorMultisigTestCase, self).setUp() # This is the result from ``examples/multisig.py``. - # noinspection SpellCheckingInspection self.bundle =\ Bundle.from_tryte_strings([ # Spend transaction. diff --git a/test/trits_test.py b/test/trits_test.py index 23124d7..e5674ec 100644 --- a/test/trits_test.py +++ b/test/trits_test.py @@ -1,7 +1,3 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase from iota import trits_from_int diff --git a/test/types_test.py b/test/types_test.py index 2127d6c..6230024 100644 --- a/test/types_test.py +++ b/test/types_test.py @@ -1,24 +1,17 @@ -# coding=utf-8 -from __future__ import absolute_import, division, print_function, \ - unicode_literals - from unittest import TestCase from warnings import catch_warnings, simplefilter as simple_filter -from six import binary_type, text_type - from iota import Address, AddressChecksum, AsciiTrytesCodec, Hash, Tag, \ TryteString, TrytesDecodeError -# noinspection SpellCheckingInspection class TryteStringTestCase(TestCase): def test_ascii_bytes(self): """ Getting an ASCII representation of a TryteString, as bytes. """ self.assertEqual( - binary_type(TryteString(b'HELLOIOTA')), + bytes(TryteString(b'HELLOIOTA')), b'HELLOIOTA', ) @@ -28,7 +21,7 @@ def test_ascii_str(self): string. """ self.assertEqual( - text_type(TryteString(b'HELLOIOTA')), + str(TryteString(b'HELLOIOTA')), 'HELLOIOTA', ) @@ -74,7 +67,6 @@ def test_comparison(self): self.assertFalse(trytes3 == bytearray(b'RBTC9D9DCDQAEASBYBCCKBFA')) self.assertTrue(trytes3 != bytearray(b'RBTC9D9DCDQAEASBYBCCKBFA')) - # noinspection PyTypeChecker def test_comparison_error_wrong_type(self): """ Attempting to compare a TryteString with something that is not a @@ -85,7 +77,6 @@ def test_comparison_error_wrong_type(self): with self.assertRaises(TypeError): # TryteString is not a numeric type, so comparing against a # numeric value doesn't make any sense. - # noinspection PyStatementEffect trytes == 42 # Identity comparison still works though. @@ -141,26 +132,22 @@ def test_container_error_wrong_type(self): with self.assertRaises(TypeError): # TryteString is not a numeric type, so this makes about as much # sense as ``16 in b'Hello, world!'``. - # noinspection PyStatementEffect,PyTypeChecker 16 in trytes with self.assertRaises(TypeError): # This is too ambiguous. Is this a list of trit values that can # appar anywhere in the tryte sequence, or does it have to match # a tryte exactly? - # noinspection PyStatementEffect,PyTypeChecker [0, 1, 1, 0, -1, 0] in trytes with self.assertRaises(TypeError): # This makes more sense than the previous example, but for # consistency, we will not allow checking for trytes inside # of a TryteString. - # noinspection PyStatementEffect,PyTypeChecker [[0, 0, 0], [1, 1, 0]] in trytes with self.assertRaises(TypeError): # Did I miss something? When did we get to DisneyLand? - # noinspection PyStatementEffect,PyTypeChecker None in trytes def test_concatenation(self): @@ -172,21 +159,21 @@ def test_concatenation(self): concat = trytes1 + trytes2 self.assertIsInstance(concat, TryteString) - self.assertEqual(binary_type(concat), b'RBTC9D9DCDQAEASBYBCCKBFA') + self.assertEqual(bytes(concat), b'RBTC9D9DCDQAEASBYBCCKBFA') # You can also concatenate a TryteString with any TrytesCompatible. self.assertEqual( - binary_type(trytes1 + b'EASBYBCCKBFA'), + bytes(trytes1 + b'EASBYBCCKBFA'), b'RBTC9D9DCDQAEASBYBCCKBFA', ) self.assertEqual( - binary_type(trytes1 + 'EASBYBCCKBFA'), + bytes(trytes1 + 'EASBYBCCKBFA'), b'RBTC9D9DCDQAEASBYBCCKBFA', ) self.assertEqual( - binary_type(trytes1 + bytearray(b'EASBYBCCKBFA')), + bytes(trytes1 + bytearray(b'EASBYBCCKBFA')), b'RBTC9D9DCDQAEASBYBCCKBFA', ) @@ -220,7 +207,6 @@ def test_slice_accessor(self): self.assertEqual(ts[4:-4:4], TryteString(b'9CEY')) with self.assertRaises(IndexError): - # noinspection PyStatementEffect ts[42] # To match the behavior of built-in types, TryteString will allow @@ -329,7 +315,7 @@ def test_init_from_tryte_string_error_wrong_subclass(self): addy = Address(TryteString(tag)) self.assertEqual( - binary_type(addy), + bytes(addy), b'RBTC9D9DCDQAEASBYBCCKBFA9999999999999999' b'99999999999999999999999999999999999999999', @@ -348,7 +334,7 @@ def test_init_padding(self): ) self.assertEqual( - binary_type(trytes), + bytes(trytes), # Note the additional Tryte([-1, -1, -1]) values appended to the # end of the sequence (represented in ASCII as '9'). @@ -367,7 +353,7 @@ def test_init_from_tryte_string_with_padding(self): self.assertFalse(trytes1 is trytes2) self.assertFalse(trytes1 == trytes2) - self.assertEqual(binary_type(trytes2), b'RBTC9D9DCDQAEASBYBCCKBFA999') + self.assertEqual(bytes(trytes2), b'RBTC9D9DCDQAEASBYBCCKBFA999') def test_init_error_invalid_characters(self): """ @@ -377,7 +363,6 @@ def test_init_error_invalid_characters(self): with self.assertRaises(ValueError): TryteString(b'not valid') - # noinspection PyTypeChecker def test_init_error_int(self): """ Attempting to reset a TryteString from an int. @@ -773,7 +758,7 @@ def test_from_bytes(self): Converting a sequence of bytes into a TryteString. """ self.assertEqual( - binary_type(TryteString.from_bytes(b'Hello, IOTA!')), + bytes(TryteString.from_bytes(b'Hello, IOTA!')), b'RBTC9D9DCDQAEASBYBCCKBFA', ) @@ -782,7 +767,7 @@ def test_from_unicode(self): Converting a Unicode string into a TryteString. """ self.assertEqual( - binary_type(TryteString.from_unicode('你好,世界!')), + bytes(TryteString.from_unicode('你好,世界!')), b'LH9GYEMHCF9GWHZFEELHVFOEOHNEEEWHZFUD', ) @@ -804,7 +789,7 @@ def test_from_string_deprecated(self): ) self.assertEqual( - binary_type(trytes), + bytes(trytes), b'LH9GYEMHCF9GWHZFEELHVFOEOHNEEEWHZFUD', ) @@ -840,7 +825,7 @@ def test_from_trytes(self): ] self.assertEqual( - binary_type(TryteString.from_trytes(trytes)), + bytes(TryteString.from_trytes(trytes)), b'RBTC9D9DCDQAEASBYBCCKBFA', ) @@ -876,7 +861,7 @@ def test_from_trits(self): ] self.assertEqual( - binary_type(TryteString.from_trits(trits)), + bytes(TryteString.from_trits(trits)), b'RBTC9D9DCDQAEASBYBCCKBFA', ) @@ -893,7 +878,7 @@ def test_from_trits_wrong_length_padded(self): ] self.assertEqual( - binary_type(TryteString.from_trits(trits)), + bytes(TryteString.from_trits(trits)), b'RBTC', ) @@ -906,7 +891,6 @@ def test_random(self): self.assertEqual(len(rand), Hash.LEN) -# noinspection SpellCheckingInspection class AddressTestCase(TestCase): def test_init_automatic_pad(self): """ @@ -918,7 +902,7 @@ def test_init_automatic_pad(self): ) self.assertEqual( - binary_type(addy), + bytes(addy), # Note the extra 9's added to the end. b'JVMTDGDPDFYHMZPMWEKKANBQSLSDTIIHAYQUMZOK' @@ -928,7 +912,7 @@ def test_init_automatic_pad(self): # This attribute will make more sense once we start working with # address checksums. self.assertEqual( - binary_type(addy.address), + bytes(addy.address), b'JVMTDGDPDFYHMZPMWEKKANBQSLSDTIIHAYQUMZOK' b'HXXXGJHJDQPOMDOMNRDKYCZRUFZROZDADTHZC9999', @@ -960,21 +944,21 @@ def test_init_with_checksum(self): ) self.assertEqual( - binary_type(addy), + bytes(addy), b'RVORZ9SIIP9RCYMREUIXXVPQIPHVCNPQ9HZWYKFWYWZRE' b'9JQKG9REPKIASHUUECPSQO9JT9XNMVKWYGVAFOXM9MUBX', ) self.assertEqual( - binary_type(addy.address), + bytes(addy.address), b'RVORZ9SIIP9RCYMREUIXXVPQIPHVCNPQ9HZWYKFWYWZRE' b'9JQKG9REPKIASHUUECPSQO9JT9XNMVKWYGVA', ) self.assertEqual( - binary_type(addy.checksum), + bytes(addy.checksum), b'FOXM9MUBX', ) @@ -1003,7 +987,7 @@ def test_checksum_valid(self): self.assertTrue(addy.is_checksum_valid()) self.assertEqual( - binary_type(addy.with_valid_checksum()), + bytes(addy.with_valid_checksum()), b'RVORZ9SIIP9RCYMREUIXXVPQIPHVCNPQ9HZWYKFWYWZRE' b'9JQKG9REPKIASHUUECPSQO9JT9XNMVKWYGVAITCOXAQSD', @@ -1025,7 +1009,7 @@ def test_checksum_invalid(self): self.assertFalse(addy.is_checksum_valid()) self.assertEqual( - binary_type(addy.with_valid_checksum()), + bytes(addy.with_valid_checksum()), b'IGKUOZGEFNSVJXETLIBKRSUZAWMYSVDPMHGQPCETEFNZP' b'XSJLZMBLAWDRLUBWPIPKFNEPADIWMXMYYRKQXYYNAFRMA', @@ -1045,7 +1029,7 @@ def test_checksum_null(self): self.assertFalse(addy.is_checksum_valid()) self.assertEqual( - binary_type(addy.with_valid_checksum()), + bytes(addy.with_valid_checksum()), b'ZKIUDZXQYQAWSHPKSAATJXPAQZPGYCDCQDRSMWWCGQJNI' b'PCOORMDRNREDUDKBMUYENYTFVUNEWDBAKXMVJJJGBARPB', @@ -1153,13 +1137,12 @@ def test_random(self): addy = Address.random() self.assertEqual(len(addy), Address.LEN) -# noinspection SpellCheckingInspection class AddressChecksumTestCase(TestCase): def test_init_happy_path(self): """ Creating a valid address checksum. """ - self.assertEqual(binary_type(AddressChecksum(b'FOXM9MUBX')), b'FOXM9MUBX') + self.assertEqual(bytes(AddressChecksum(b'FOXM9MUBX')), b'FOXM9MUBX') def test_init_error_too_short(self): """ @@ -1185,7 +1168,6 @@ def test_random(self): self.assertEqual(len(checksum), AddressChecksum.LEN) -# noinspection SpellCheckingInspection class TagTestCase(TestCase): def test_init_automatic_pad(self): """ @@ -1193,7 +1175,7 @@ def test_init_automatic_pad(self): """ tag = Tag(b'COLOREDCOINS') - self.assertEqual(binary_type(tag), b'COLOREDCOINS999999999999999') + self.assertEqual(bytes(tag), b'COLOREDCOINS999999999999999') def test_init_error_too_long(self): """ From d5450d0edde6a1a51fc3807d1f51720fbebee0b2 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Fri, 13 Mar 2020 10:33:29 +0100 Subject: [PATCH 56/69] Remove 'six' from package deps --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index f68f0b1..ee9c74a 100644 --- a/setup.py +++ b/setup.py @@ -67,7 +67,6 @@ 'httpx', 'phx-filters', 'pysha3', - 'six', ], extras_require={ From 6916516d100784b2eeafcca5dac9b4a4d9dc249f Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Mon, 16 Mar 2020 14:26:40 +0000 Subject: [PATCH 57/69] Add conttributing documentation --- .github/CODE_OF_CONDUCT.md | 58 ++++++++ .github/CONTRIBUTING.md | 154 ++++++++++++++++++++ .github/ISSUE_TEMPLATE/bug_report.md | 51 +++++++ .github/ISSUE_TEMPLATE/config.yml | 8 ++ .github/ISSUE_TEMPLATE/feature_request.md | 28 ++++ .github/SECURITY.md | 23 +++ .github/SUPPORT.md | 8 ++ .github/pull_request_template.md | 29 ++++ CONTRIBUTING.rst | 167 ---------------------- README.md | 11 +- docs/commands.rst | 2 +- 11 files changed, 366 insertions(+), 173 deletions(-) create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/SECURITY.md create mode 100644 .github/SUPPORT.md create mode 100644 .github/pull_request_template.md delete mode 100644 CONTRIBUTING.rst diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..c7672f8 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,58 @@ +# Community Code of Conduct + +In the IOTA community, participants from all over the world come together to create. This is made possible by the support, hard work and enthusiasm of thousands of people, including those who create and use the IOTA technology. + +This document offers some guidance to ensure IOTA participants can cooperate effectively in a positive and inspiring atmosphere, and to explain how together we can strengthen and support each other. + +This Code of Conduct is shared by all contributors and users who engage with the IOTA Foundation team and its community services. + +## Overview + +This Code of Conduct presents a summary of the shared values and “common sense” thinking in our community. The basic social ingredients that hold our project together include: + +- Being considerate +- Being respectful +- Being collaborative +- Being pragmatic +- Supporting others in the community +- Getting support from others in the community + +This Code of Conduct reflects the agreed standards of behavior for members of the IOTA community, in any social media platform, forum, mailing list, wiki, web site, discord channel, public meeting or private correspondence within the context of the IOTA Foundation team and the IOTA Tangle technology. The community acts according to the standards written down in this Code of Conduct and will defend these standards for the benefit of the community. Leaders of any group, such as moderators of social media groups, mailing lists, discord channels, forums, etc., will exercise the right to suspend access to any person who persistently breaks our shared Code of Conduct. + +## Be considerate + +Your actions and work will affect and be used by other people and you, in turn, will depend on the work and actions of others. Any decision you take will affect other community members, and we expect you to take those consequences into account when making decisions. + +As a user, remember that community members work hard on their part of IOTA and take great pride in it. + +## Be respectful + +In order for the IOTA community to stay healthy, its members must feel comfortable and accepted. Treating one another with respect is absolutely necessary for this. In a disagreement, in the first instance, assume that people mean well. + +We do not tolerate personal attacks, racism, sexism or any other form of discrimination. Disagreement is inevitable, from time to time, but respect for the views of others will go a long way to winning respect for your own view. Respecting other people, their work, their contributions and assuming well-meaning motivation will make community members feel comfortable and safe and will result in motivation and productivity. + +We expect members of our community to be respectful when dealing with other contributors, users, and communities. Remember that IOTA is an international project and that you may be unaware of important aspects of other cultures. + +## Be collaborative + +Your feedback is important, as is its form. Poorly thought out comments can cause pain and the demotivation of other community members, but considerate discussion of problems can bring positive results. An encouraging word works wonders. + +## Be pragmatic + +The IOTA community is pragmatic and fair. We value tangible results over having the last word in a discussion. We defend our core values like freedom and respectful collaboration, but we don’t let arguments about minor issues get in the way of achieving more important results. We are open to suggestions and welcome solutions regardless of their origin. When in doubt support a solution which helps to get things done over one which has theoretical merits, but isn’t being worked on. Use the tools and methods which help to get the job done. Let decisions be taken by those who do the work. + +## Support others in the community + +The IOTA community is made strong by mutual respect, collaboration and pragmatic, responsible behavior. Sometimes there are situations where this has to be defended and other community members need help. + +If you witness others being attacked, think first about how you can offer them personal support. If you feel that the situation is beyond your ability to help individually, go privately to the victim and ask if some form of official intervention is needed. + +When problems do arise, consider respectfully reminding those involved of our shared Code of Conduct as a first action. Leaders are defined by their actions and can help set a good example by working to resolve issues in the spirit of this Code of Conduct before they escalate. + +## Get support from others in the community + +Disagreements, both political and technical, happen all the time. Our community is no exception to the rule. The goal is not to avoid disagreements or differing views but to resolve them constructively. You should turn to the community to seek advice and to resolve disagreements and where possible consult the team most directly involved. + +Think deeply before turning a disagreement into a public dispute. If necessary, request mediation, and try to resolve differences in a less emotional medium. If you do feel that you or your work is being attacked, take your time to think things through before writing heated replies. Consider a 24-hour moratorium if emotional language is being used – a cooling-off period is sometimes all that is needed. If you really want to go a different way, then we encourage you to publish your ideas and your work, so that it can be tried and tested. + +This work, "IOTA Community Guidelines", is a derivative of the [Community code of conduct by ownCloud](https://owncloud.org/community/code-of-conduct/), used under [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/). "IOTA Community Guidelines" is licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) by IOTA Foundation. \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..87aa8fd --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,154 @@ +# Contribute to the Python client library + +This document describes how to contribute to the Python client library. + +We encourage everyone with knowledge of IOTA technology to contribute. + +Thanks! :heart: + +
+Do you have a question :question: +
+ +If you have a general or technical question, you can use one of the following resources instead of submitting an issue: + +- [**Developer documentation:**](https://docs.iota.org/) For official information about developing with IOTA technology +- [**Discord:**](https://discord.iota.org/) For real-time chats with the developers and community members +- [**IOTA cafe:**](https://iota.cafe/) For technical discussions with the Research and Development Department at the IOTA Foundation +- [**StackExchange:**](https://iota.stackexchange.com/) For technical and troubleshooting questions +
+ +
+ +
+Ways to contribute :mag: +
+ +To contribute to the Python client library on GitHub, you can: + +- Report a bug +- Suggest a new feature +- Build a new feature +- Contribute to the documentation +
+ +
+ +
+Report a bug :bug: +
+ +This section guides you through reporting a bug. Following these guidelines helps maintainers and the community understand the bug, reproduce the behavior, and find related bugs. + +### Before reporting a bug + +Please check the following list: + +- **Do not open a GitHub issue for [security vulnerabilities](SECURITY.MD)**, instead, please contact us at [security@iota.org](mailto:security@iota.org). + +- **Ensure the bug was not already reported** by searching on GitHub under [**Issues**](https://github.com/iotaledger/iota.py/issues). If the bug has already been reported **and the issue is still open**, add a comment to the existing issue instead of opening a new one. You can also find related issues by their [label](https://github.com/iotaledger/iota.py/labels?page=1&sort=name-asc). + +**Note:** If you find a **Closed** issue that seems similar to what you're experiencing, open a new issue and include a link to the original issue in the body of your new one. + +### Submitting A Bug Report + +To report a bug, [open a new issue](https://github.com/iotaledger/iota.py/issues/new), and be sure to include as many details as possible, using the template. + +**Note:** Minor changes such as fixing a typo can but do not need an open issue. + +If you also want to fix the bug, submit a [pull request](#pull-requests) and reference the issue. +
+ +
+ +
+Suggest a new feature :bulb: +
+ +This section guides you through suggesting a new feature. Following these guidelines helps maintainers and the community collaborate to find the best possible way forward with your suggestion. + +### Before suggesting a new feature + +**Ensure the feature has not already been suggested** by searching on GitHub under [**Issues**](https://github.com/iotaledger/iota.py/issues). + +### Suggesting a new feature + +To suggest a new feature, talk to the IOTA community and IOTA Foundation members on [Discord](https://discord.iota.org/). + +If the team members approves your feature, they will create an issue for it. +
+ +
+ +
+Build a new feature :hammer: +
+ +This section guides you through building a new feature. Following these guidelines helps give your feature the best chance of being approved and merged. + +### Before building a new feature + +Make sure to discuss the feature with the developers on [Discord](https://discord.iota.org/). + +Otherwise, your feature may not be approved at all. + +### Building a new feature + +To build a new feature, check out a new branch based on the `master` branch, and be sure to consider the following: + +- If the feature has a public facing API, make sure to document it, using [Sphinx](https://www.sphinx-doc.org/en/master/) code comments + +
+ +
+ +
+Contribute to the documentation :black_nib: +
+ +The Python client library documentation is hosted on https://docs.iota.org, which is built from content in the [documentation](https://github.com/iotaledger/documentation) repository. + +Please see the [guidelines](https://github.com/iotaledger/documentation/CONTRIBUTING.md) on the documentation repository for information on how to contribute to the documentation. +
+ +
+ +
+Pull requests :mega: +
+ +This section guides you through submitting a pull request (PR). Following these guidelines helps give your PR the best chance of being approved and merged. + +### Before submitting a pull request + +When creating a pull request, please follow these steps to have your contribution considered by the maintainers: + +- A pull request should have only one concern (for example one feature or one bug). If a PR addresses more than one concern, it should be split into two or more PRs. + +- A pull request can be merged only if it references an open issue + + **Note:** Minor changes such as fixing a typo can but do not need an open issue. + +- All code should be well tested + +### Submitting a pull request + +The following is a typical workflow for submitting a new pull request: + +1. Fork this repository +2. Create a new branch based on your fork +3. Commit changes and push them to your fork +4. Create a pull request against the `master` branch + +If all [status checks](https://help.github.com/articles/about-status-checks/) pass, and the maintainer approves the PR, it will be merged. + +**Note:** Reviewers may ask you to complete additional work, tests, or other changes before your pull request can be approved and merged. +
+ +
+ +
+Code of Conduct :clipboard: +
+ +This project and everyone participating in it is governed by the [IOTA Code of Conduct](CODE_OF_CONDUCT.md). diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..f4fc2e2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,51 @@ +--- +name: Report a bug in the Python client library +about: Report a bug +labels: bug +--- + +## Bug description + +Briefly describe the bug. + +## Version + +Which version of the library are you running? + +- Version: + +## IOTA network + +Which node are you connected to and which IOTA network is it in? + +- Node URL: +- Network: + +## Hardware specification + +What hardware are you using? + +- Operating system: +- RAM: +- Number of cores: +- Device type: + +## Steps To reproduce the bug + +Explain how the maintainer can reproduce the bug. + +1. +2. +3. + +## Expected behaviour + +Describe what you expect to happen. + +## Actual behaviour + +Describe what actually happens. + +## Errors + +Paste any errors that you see. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..596e17a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Discord + url: https://discord.iota.org/ + about: Please ask and answer questions here. + - name: Security vulnerabilities + url: security@iota.org + about: Please report security vulnerabilities here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..f94bd08 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,28 @@ +--- +name: Request a feature for the Python client library +about: Request a feature +--- + +## Description + +Briefly describe the feature that you are requesting. + +## Motivation + +Explain why this feature is needed. + +## Requirements + +Write a list of what you want this feature to do. + +1. +2. +3. + +## Open questions (optional) + +Use this section to ask any questions that are related to the feature. + +## Are you planning to do it yourself in a pull request? + +Yes/No. diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..5cccaa5 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,23 @@ +

Responsible disclosure policy

+ +At the IOTA Foundation, we consider the security of our systems a top priority. But no matter how much effort we put into system security, there can still be vulnerabilities present. If you've discovered a vulnerability, please follow the guidelines below to report it to our security team: +
    +
  • E-mail your findings to security@iota.org. If the report contains highly sensitive information, please consider encrypting your findings using our contact@iota.org (466385BD0B40D9550F93C04746A440CCE5664A64) PGP key.
  • +
+Please follow these rules when testing/reporting vulnerabilities: +
    +
  • Do not take advantage of the vulnerability you have discovered, for example by downloading more data than is necessary to demonstrate the vulnerability.
  • +
  • Do not read, modify or delete data that isn't your own.
  • +
  • We ask that you do not to disclosure the problem to third parties until it has been resolved.
  • +
  • The scope of the program is limited to technical vulnerabilities in IOTA Foundations's web applications and open source software packages distributed through GitHub, please do not try to test physical security or attempt phishing attacks against our employees, and so on.
  • +
  • Out of concern for the availability of our services to all users, please do not attempt to carry out DoS attacks, leverage black hat SEO techniques, spam people, and do other similarly questionable things. We also discourage the use of any vulnerability testing tools that automatically generate significant volumes of traffic.
  • +
+What we promise: +
    +
  • We will respond to your report within 3 business days with our evaluation of the report and an expected resolution date.
  • +
  • If you have followed the instructions above, we will not take any legal action against you in regard to the report.
  • +
  • We will keep you informed during all stages of resolving the problem.
  • +
  • To show our appreciation for your effort and cooperation during the report, we will list your name and a link to a personal website/social network profile on the page below so that the public can know you've helped keep the IOTA Foundation secure.
  • +
+We sincerely appreciate the efforts of security researchers in keeping our community safe. + diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md new file mode 100644 index 0000000..7f052de --- /dev/null +++ b/.github/SUPPORT.md @@ -0,0 +1,8 @@ +# Community resources + +If you have a general or technical question, you can use one of the following resources instead of submitting an issue: + +- [**Developer documentation:**](https://docs.iota.org/) For official information about developing with IOTA technology +- [**Discord:**](https://discord.iota.org/) For real-time chats with the developers and community members +- [**IOTA cafe:**](https://iota.cafe/) For technical discussions with the Research and Development Department at the IOTA Foundation +- [**StackExchange:**](https://iota.stackexchange.com/) For technical and troubleshooting questions \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..9bf7214 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,29 @@ +# Description of change + +Please write a summary of your changes and why you made them. Be sure to reference any related issues by adding `fixes # (issue)`. + +## Type of change + +Choose a type of change, and delete any options that are not relevant. + +- Bug fix (a non-breaking change which fixes an issue) +- Enhancement (a non-breaking change which adds functionality) +- Breaking change (fix or feature that would cause existing functionality to not work as expected) +- Documentation Fix + +## How the change has been tested + +Describe the tests that you ran to verify your changes. + +Make sure to provide instructions for the maintainer as well as any relevant configurations. + +## Change checklist + +Add an `x` to the boxes that are relevant to your changes, and delete any items that are not. + +- [ ] My code follows the contribution guidelines for this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] I have added tests using ginkgo that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index 9642866..0000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,167 +0,0 @@ -===================== -Contributing to PyOTA -===================== -So, you wanna contribute to PyOTA? Awesome! PyOTA is an important part of the IOTA ecosystem, and it's all thanks to people like you! - -PyOTA is largely built and maintained by volunteers; by following a few "common sense" ground rules, we can make PyOTA stand out as a fun, exciting and rewarding project to work on. - -Please take a few moments to read this guide to familiarize yourself with PyOTA's contributor code of conduct, and some of the ways you can contribute! - -.. contents:: - :depth: 2 - - -Ways You Can Help! -================== -There are lots of ways to get involved with PyOTA. Many of them don't require writing any code (but if that's your thing, we've got you covered, too!). - -- Improving Documentation -- Writing Tutorials -- Reporting Bugs -- Helping Users on the ``#python`` Channel on `Discord`_ -- Fixing Bugs and Implementing New Features -- Writing Unit and Functional Tests - -A Few Things that We Can't Accept ---------------------------------- -We're pretty open about how people contribute to PyOTA, but there are a few things that we can't accept: - -- Please do not post support requests here. Use the ``#python`` channel on `Discord`_ -- Please do not propose new API methods here. There are multiple IOTA API libraries out there, and they must all have the same functionality. - - - That said, if you have an idea for a new API method, please share it on the ``#clients-discussion`` channel in `Discord`_ so that IOTA Foundation members can evaluate it! - - -Need Some Inspiration? -====================== -If you would like to help out but don't know how to get started, here are some -places you can look for inspiration: - -- Look for issues marked `help wanted`_ in the `PyOTA Bug Tracker`_ -- Introduce yourself in the `#python` channel in `Discord`_ and watch for questions or issues that you can help with. -- Browse existing `tutorials`_ for other programming languages and create Python versions. - -Is This Your First Contribution? --------------------------------- -Never contributed to an open-source project before? No problem! We're excited that you are considering PyOTA for your first contribution! - -Please take a few minutes to read GitHub's guide on `How to Contribute to Open Source`_. It's a quick read, and it's a great way to introduce yourself to how things work behind the scenes in open-source projects. - - -Guidelines for Reporting Bugs -============================= -Found a bug in the PyOTA code? Great! We can't fix bugs we don't know about; your bug report will go a long way toward helping PyOTA flourish. - -Instructions ------------- -1. Make sure it really is a PyOTA bug. - - - Check the traceback, and see if you can narrow down the cause of the bug. - - If the error is not directly caused by PyOTA, or if you are unable to figure out what is causing the problem, we're still here for for you! Post in the ``#python`` channel in `Discord`_ for assistance. - -2. Is it safe to publish details about this bug publicly? - - - If the bug is security-related (e.g., could compromise a user's seed if exploited), or if it requires sensitive information in order to reproduce (e.g., the private key for an address), please do not post in in the PyOTA Bug Tracker! - - To report security-related bugs, please contact ``@phx`` directly in `Discord`_. - -3. Is this a known issue? - - - Before posting a bug report, check the `PyOTA Bug Tracker`_ to see if there is an existing issue for this bug. - -4. Create a new issue in the `PyOTA Bug Tracker`_. - - - Be sure to include the following information: - - - Which version of PyOTA you are using. - - Which version of Python you are using. - - Which operating system you are using. - - Instructions to reproduce the bug. - - The expected behavior, if applicable. - - The full exception traceback, if available. - - If the exception also has a context object, please include it. - -5. Please be nice! - - - It's frustrating when things don't work the way you expect them to. We promise we didn't put that bug in there on purpose; we're all human, and we all make mistakes sometimes. - -6. Please be patient! - - - We're committed to making to making PyOTA better, but we've also got jobs and other commitments. We'll respond as soon as we can, but it might be a few days. - -7. Please be responsive if follow-up is needed. - - - We may request additional information to help us identify/fix the bug. The faster you respond to follow-up comments in your bug report, the sooner we can squash that bug! - - If someone adds a comment to your bug report, it will appear in the `Notifications`_ page in GitHub. You can also configure GitHub to `email you`_ when a new comment is posted. - -What You Can Expect -------------------- -When you submit a bug report, here's what you can expect from the individual who reviews it: - -- You can expect a response within one week of submission. -- If any additional information is needed, or if we are having trouble reproducing the issue you reported, you can expect a respectful and constructive response. - - -Guidelines for Developers -========================= -If you would like to contribute code to the PyOTA project, this section is for you! - -Instructions ------------- -1. Find an issue in the `PyOTA Bug Tracker`_ to work on. - - - If you want to work on a bug or feature that doesn't have a GitHub issue yet, create a new one before starting to work on it. That will give other developers an opportunity to provide feedback and/or suggest changes that will make it integrate better with the rest of the code. - -2. Create a fork of the PyOTA repository. -3. Create a new branch just for the bug/feature you are working on. - - - If you want to work on multiple bugs/features, you can use branches to keep them separate, so that you can submit a separate Pull Request for each one. - -4. Once you have completed your work, create a Pull Request, ensuring that it meets the requirements listed below. - -Requirements for Pull Requests ------------------------------- -PyOTA is a critical component for many applications, and as such its code must be of exceptionally high quality. To help maintain reliability and code quality, there are a few requirements for contributions. - -This is a big list, but don't let it intimidate you! Many of these are "common sense" things that you probably do already, but we have to list them here anyway, just so that there's no confusion. - -If you have any questions, please feel free to post in the ``#python`` channel in `Discord`_! - -- Please create Pull Requests against the ``develop`` branch. -- Please limit each Pull Request to a single bugfix/enhancement. -- Please limit the scope of each Pull Request to just the changes needed for that bugfix/enhancement. - - - If you would like to refactor existing code, please create separate Pull Request(s) just for the refactoring. - -- Please ensure your code works in all supported versions of Python (this includes versions of Python 2 and Python 3). - - - See ``README.rst`` for the list of supported Python versions. - -- Please ensure that your Pull Request includes full test coverage. -- Please do not introduce new dependencies unless absolutely necessary. -- When introducing new classes/functions, please write comprehensive and meaningful docstrings. It should be clear to anyone reading your code what your new class/function does and why it exists. - - Similarly, please be liberal about adding comments to your code. If you have any knowledge and/or had to do any research that would make your code easier to understand, add it as comment. Future developers will be very grateful for the extra context! - - - Please ensure that your comments and docstrings use proper English grammar and spelling. - -- Please ensure that your code conforms to `PEP-8`_. - - - Much of the existing code is not currently formatted for PEP-8; where practical, you may prefer PEP-8 over being consistent with the existing code. - - We are currently converting the codebase over to PEP-8; `come on over and help us out!`_ - -What You Can Expect -------------------- -When you submit a Pull Request, here is what you can expect from the individual who reviews it: - -- You can expect a response within one week of submission. -- If any changes are needed, or if we cannot accept your submission, we will provide a respectful and constructive explanation. - - -.. _come on over and help us out!: https://github.com/iotaledger/iota.py/issues/145 -.. _email you: https://help.github.com/articles/managing-notification-delivery-methods/ -.. _help wanted: https://github.com/iotaledger/iota.py/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22 -.. _how to contribute to open source: https://opensource.guide/how-to-contribute/ -.. _notifications: https://github.com/notifications -.. _pep-8: https://www.python.org/dev/peps/pep-0008/ -.. _pyota bug tracker: https://github.com/iotaledger/iota.py/issues -.. _discord: https://discord.iota.org -.. _tutorials: https://docs.iota.org diff --git a/README.md b/README.md index 6d19663..7280c64 100644 --- a/README.md +++ b/README.md @@ -149,8 +149,9 @@ print(result['bundle'].hash) ## Supporting the project -If the IOTA Python client library has been useful to you and you feel like contributing, consider posting a [bug report](https://github.com/iotaledger/iota.py/issues/new-issue), feature request or a [pull request](https://github.com/iotaledger/iota.py/pulls/). -We have some [basic contribution guidelines](CONTRIBUTING.rst) to keep our code base stable and consistent. +If the IOTA Python client library has been useful to you and you feel like contributing, consider posting a [bug report](https://github.com/iotaledger/iota.py/issues/new-issue), feature request or a [pull request](https://github.com/iotaledger/iota.py/pulls/). + +We have some [basic contribution guidelines](.github/CONTRIBUTING.md) to keep our code base stable and consistent. ### Running test cases @@ -173,9 +174,9 @@ pip install -e .[test-runner] tox -v -p all ``` -### Building the auto-generated documentation +### Building the autogenerated documentation -The auto-generated documentation can be generated on your local device by doing the following: +The autogenerated documentation can be generated on your local device by doing the following: ```bash # Install extra dependencies (you only have to do this once) @@ -187,4 +188,4 @@ make html ## Joining the discussion -If you want to get involved in the community, need help with getting setup, have any issues related with the library or just want to discuss blockchain, distributed ledgers, and IoT with other people, feel free to join our [Discord](https://discord.iota.org/). +If you want to get involved in the community, need help with getting set up, have any issues related with the library or just want to discuss blockchain, distributed ledgers, and IoT with other people, feel free to join our [Discord](https://discord.iota.org/). diff --git a/docs/commands.rst b/docs/commands.rst index a860a44..b1dab96 100644 --- a/docs/commands.rst +++ b/docs/commands.rst @@ -214,4 +214,4 @@ it constructs a list of transaction objects that are returned to .. _filters documentation site: https://filters.readthedocs.io/en/latest/ .. _create custom filters: https://filters.readthedocs.io/en/latest/writing_filters.html .. _PyOTA Bug Tracker: https://github.com/iotaledger/iota.py/issues -.. _Contributing to PyOTA: https://github.com/iotaledger/iota.py/blob/master/CONTRIBUTING.rst \ No newline at end of file +.. _Contributing to PyOTA: https://github.com/iotaledger/iota.py/blob/master/.github/CONTRIBUTING.md \ No newline at end of file From 459e6848079332f4f7fac09de777406a4f0ab4e8 Mon Sep 17 00:00:00 2001 From: Jake Cahill <45230295+JakeSCahill@users.noreply.github.com> Date: Tue, 17 Mar 2020 08:51:52 +0000 Subject: [PATCH 58/69] Apply suggestions from code review Co-Authored-By: Phoenix --- .github/CONTRIBUTING.md | 10 +++++----- .github/ISSUE_TEMPLATE/bug_report.md | 5 +++-- .github/SECURITY.md | 7 +++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 87aa8fd..a5f69ea 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -75,7 +75,7 @@ This section guides you through suggesting a new feature. Following these guidel To suggest a new feature, talk to the IOTA community and IOTA Foundation members on [Discord](https://discord.iota.org/). -If the team members approves your feature, they will create an issue for it. +If the team members approve your feature, they will create an issue for it.

@@ -94,7 +94,7 @@ Otherwise, your feature may not be approved at all. ### Building a new feature -To build a new feature, check out a new branch based on the `master` branch, and be sure to consider the following: +To build a new feature, check out a new branch based on the `develop` branch, and be sure to consider the following: - If the feature has a public facing API, make sure to document it, using [Sphinx](https://www.sphinx-doc.org/en/master/) code comments @@ -129,7 +129,7 @@ When creating a pull request, please follow these steps to have your contributio **Note:** Minor changes such as fixing a typo can but do not need an open issue. -- All code should be well tested +- All code should include comprehensive unit tests. ### Submitting a pull request @@ -142,7 +142,7 @@ The following is a typical workflow for submitting a new pull request: If all [status checks](https://help.github.com/articles/about-status-checks/) pass, and the maintainer approves the PR, it will be merged. -**Note:** Reviewers may ask you to complete additional work, tests, or other changes before your pull request can be approved and merged. +**Note:** Reviewers may request changes before your pull request can be approved and merged.
@@ -151,4 +151,4 @@ If all [status checks](https://help.github.com/articles/about-status-checks/) pa Code of Conduct :clipboard:
-This project and everyone participating in it is governed by the [IOTA Code of Conduct](CODE_OF_CONDUCT.md). +This project and everyone participating in it are governed by the [IOTA Code of Conduct](CODE_OF_CONDUCT.md). diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f4fc2e2..3237a58 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -12,7 +12,8 @@ Briefly describe the bug. Which version of the library are you running? -- Version: +- PyOTA Version: +- Python Version: ## IOTA network @@ -48,4 +49,4 @@ Describe what actually happens. ## Errors -Paste any errors that you see. +Paste here any error messages and/or stacktraces that you see. diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 5cccaa5..a272f5d 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -2,14 +2,14 @@ At the IOTA Foundation, we consider the security of our systems a top priority. But no matter how much effort we put into system security, there can still be vulnerabilities present. If you've discovered a vulnerability, please follow the guidelines below to report it to our security team:
    -
  • E-mail your findings to security@iota.org. If the report contains highly sensitive information, please consider encrypting your findings using our contact@iota.org (466385BD0B40D9550F93C04746A440CCE5664A64) PGP key.
  • +
  • E-mail your findings to security@iota.org. If the report contains highly sensitive information, please consider encrypting your findings using our contact@iota.org PGP key (fingerprint 466385BD0B40D9550F93C04746A440CCE5664A64).
Please follow these rules when testing/reporting vulnerabilities:
  • Do not take advantage of the vulnerability you have discovered, for example by downloading more data than is necessary to demonstrate the vulnerability.
  • Do not read, modify or delete data that isn't your own.
  • -
  • We ask that you do not to disclosure the problem to third parties until it has been resolved.
  • -
  • The scope of the program is limited to technical vulnerabilities in IOTA Foundations's web applications and open source software packages distributed through GitHub, please do not try to test physical security or attempt phishing attacks against our employees, and so on.
  • +
  • We ask that you not disclose the problem to third parties until it has been resolved.
  • +
  • The scope of the program is limited to technical vulnerabilities in IOTA Foundations's web applications and open source software packages distributed through GitHub — please do not try to test physical security or attempt phishing attacks against our employees, and so on.
  • Out of concern for the availability of our services to all users, please do not attempt to carry out DoS attacks, leverage black hat SEO techniques, spam people, and do other similarly questionable things. We also discourage the use of any vulnerability testing tools that automatically generate significant volumes of traffic.
What we promise: @@ -20,4 +20,3 @@ What we promise:
  • To show our appreciation for your effort and cooperation during the report, we will list your name and a link to a personal website/social network profile on the page below so that the public can know you've helped keep the IOTA Foundation secure.
  • We sincerely appreciate the efforts of security researchers in keeping our community safe. - From 5558c3292e9aa8c8acfd8fbfeafdc4b6713da00a Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Wed, 18 Mar 2020 12:22:28 +0100 Subject: [PATCH 59/69] Update .github/CONTRIBUTING.md --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index a5f69ea..131c698 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -138,7 +138,7 @@ The following is a typical workflow for submitting a new pull request: 1. Fork this repository 2. Create a new branch based on your fork 3. Commit changes and push them to your fork -4. Create a pull request against the `master` branch +4. Create a pull request against the `develop` branch If all [status checks](https://help.github.com/articles/about-status-checks/) pass, and the maintainer approves the PR, it will be merged. From 782873e9c684edef871abb9bf011a56bca7288e9 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Wed, 18 Mar 2020 12:22:42 +0100 Subject: [PATCH 60/69] Update .github/pull_request_template.md --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 9bf7214..3ced87e 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -25,5 +25,5 @@ Add an `x` to the boxes that are relevant to your changes, and delete any items - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation -- [ ] I have added tests using ginkgo that prove my fix is effective or that my feature works +- [ ] I have followed [PEP-8](https://www.python.org/dev/peps/pep-0008/) Style Guide in my code. - [ ] New and existing unit tests pass locally with my changes From 43f091180f4ee900fb446493ec7846c54ca81ad0 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Wed, 18 Mar 2020 12:23:22 +0100 Subject: [PATCH 61/69] Update .github/CONTRIBUTING.md Co-Authored-By: Phoenix --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 131c698..940a113 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -125,7 +125,7 @@ When creating a pull request, please follow these steps to have your contributio - A pull request should have only one concern (for example one feature or one bug). If a PR addresses more than one concern, it should be split into two or more PRs. -- A pull request can be merged only if it references an open issue +- A pull request can be merged only if it references an open issue. **Note:** Minor changes such as fixing a typo can but do not need an open issue. From 05d82de63ae4efe188bdb2e2dfe6694a935dd3dc Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Thu, 19 Mar 2020 11:03:53 +0100 Subject: [PATCH 62/69] Update .github/CONTRIBUTING.md Co-Authored-By: Phoenix --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 940a113..30b2aba 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -96,7 +96,7 @@ Otherwise, your feature may not be approved at all. To build a new feature, check out a new branch based on the `develop` branch, and be sure to consider the following: -- If the feature has a public facing API, make sure to document it, using [Sphinx](https://www.sphinx-doc.org/en/master/) code comments +- If the feature will become part of PyOTA's public interface (i.e., it includes methods that will be invoked by other developers in their applications), make sure to document it, using [Sphinx-style](https://www.sphinx-doc.org/en/master/) code comments. From 5b493732a575222915b257983deeaf7dcd090014 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Thu, 19 Mar 2020 11:08:46 +0100 Subject: [PATCH 63/69] Update .github/ISSUE_TEMPLATE/bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 3237a58..7a2541d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -22,14 +22,12 @@ Which node are you connected to and which IOTA network is it in? - Node URL: - Network: -## Hardware specification +## Environment -What hardware are you using? +What operating system are you using? - Operating system: -- RAM: -- Number of cores: -- Device type: + ## Steps To reproduce the bug From 29e01ff9dee71f80a3d91b3cc6912a409b04a608 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Fri, 13 Mar 2020 13:05:17 +0100 Subject: [PATCH 64/69] Introduce Type Annotations - Remove type hints. - Follow PEP-484 and PEP-8. --- iota/__init__.py | 8 +- iota/adapter/__init__.py | 102 ++-- iota/adapter/wrappers.py | 27 +- iota/api.py | 475 +++++++++--------- iota/api_async.py | 394 ++++++++------- iota/bin/__init__.py | 30 +- iota/bin/repl.py | 11 +- iota/codecs.py | 18 +- iota/commands/__init__.py | 54 +- iota/commands/core/add_neighbors.py | 2 +- iota/commands/core/attach_to_tangle.py | 9 +- iota/commands/core/broadcast_transactions.py | 2 +- iota/commands/core/check_consistency.py | 2 +- iota/commands/core/find_transactions.py | 10 +- iota/commands/core/get_balances.py | 10 +- iota/commands/core/get_inclusion_states.py | 2 +- .../commands/core/get_missing_transactions.py | 4 +- iota/commands/core/get_neighbors.py | 2 +- .../core/get_node_api_configuration.py | 2 +- iota/commands/core/get_node_info.py | 4 +- iota/commands/core/get_tips.py | 4 +- .../core/get_transactions_to_approve.py | 10 +- iota/commands/core/get_trytes.py | 4 +- .../core/interrupt_attaching_to_tangle.py | 2 +- iota/commands/core/remove_neighbors.py | 2 +- iota/commands/core/store_transactions.py | 2 +- .../core/were_addresses_spent_from.py | 2 +- iota/commands/extended/broadcast_and_store.py | 4 +- iota/commands/extended/broadcast_bundle.py | 14 +- .../extended/find_transaction_objects.py | 24 +- iota/commands/extended/get_account_data.py | 20 +- iota/commands/extended/get_bundles.py | 9 +- iota/commands/extended/get_inputs.py | 16 +- .../commands/extended/get_latest_inclusion.py | 8 +- iota/commands/extended/get_new_addresses.py | 26 +- .../extended/get_transaction_objects.py | 17 +- iota/commands/extended/get_transfers.py | 14 +- iota/commands/extended/is_promotable.py | 32 +- iota/commands/extended/is_reattachable.py | 12 +- iota/commands/extended/prepare_transfer.py | 18 +- iota/commands/extended/promote_transaction.py | 12 +- iota/commands/extended/replay_bundle.py | 14 +- iota/commands/extended/send_transfer.py | 22 +- iota/commands/extended/send_trytes.py | 14 +- iota/commands/extended/traverse_bundle.py | 23 +- iota/commands/extended/utils.py | 32 +- iota/crypto/addresses.py | 40 +- iota/crypto/kerl/conv.py | 23 +- iota/crypto/kerl/pykerl.py | 22 +- iota/crypto/pycurl.py | 29 +- iota/crypto/signing.py | 160 +++--- iota/crypto/types.py | 41 +- iota/exceptions.py | 3 +- iota/filters.py | 20 +- iota/multisig/api.py | 68 ++- .../commands/create_multisig_address.py | 10 +- iota/multisig/commands/get_digests.py | 16 +- iota/multisig/commands/get_private_keys.py | 16 +- .../commands/prepare_multisig_transfer.py | 104 ++-- iota/multisig/crypto/addresses.py | 12 +- iota/multisig/transaction.py | 3 +- iota/multisig/types.py | 13 +- iota/transaction/base.py | 255 +++++----- iota/transaction/creation.py | 89 ++-- iota/transaction/types.py | 9 +- iota/transaction/utils.py | 6 +- iota/transaction/validator.py | 30 +- iota/trits.py | 21 +- iota/types.py | 170 +++---- 69 files changed, 1354 insertions(+), 1331 deletions(-) diff --git a/iota/__init__.py b/iota/__init__.py index 2d1c26e..e311054 100644 --- a/iota/__init__.py +++ b/iota/__init__.py @@ -1,12 +1,14 @@ # Define a few magic constants. -DEFAULT_PORT = 14265 +from typing import Dict, Text + +DEFAULT_PORT: int = 14265 """ Default port to use when configuring an adapter, if the port is not specified. """ -TRITS_PER_TRYTE = 3 +TRITS_PER_TRYTE: int = 3 """ Number of trits in a tryte. Changing this will probably break everything, but there's a chance it @@ -14,7 +16,7 @@ In that way, it's kind of like toxic waste in a superhero story. """ -STANDARD_UNITS = { +STANDARD_UNITS: Dict[Text, int] = { # Valid IOTA unit suffixes. Example value '-273.15 Ki' 'i': 1, 'Ki': 1000, diff --git a/iota/adapter/__init__.py b/iota/adapter/__init__.py index d6a7a32..f155310 100644 --- a/iota/adapter/__init__.py +++ b/iota/adapter/__init__.py @@ -1,11 +1,12 @@ import json from abc import ABCMeta, abstractmethod as abstract_method +from asyncio import Future from collections import deque from inspect import isabstract as is_abstract from logging import DEBUG, Logger from socket import getdefaulttimeout as get_default_timeout -from typing import Container, Dict, List, Optional, Text, Tuple, Union +from typing import Container, Dict, List, Optional, Text, Tuple, Union, Any from httpx import AsyncClient, Response, codes, auth import asyncio @@ -42,7 +43,8 @@ # Load SplitResult for IDE type hinting and autocompletion. from urllib.parse import SplitResult, urlsplit -def async_return(result): + +def async_return(result: Any) -> Future: """ Turns 'result' into a `Future` object with 'result' value. @@ -52,6 +54,7 @@ def async_return(result): f.set_result(result) return f + class BadApiResponse(ValueError): """ Indicates that a non-success response was received from the node. @@ -66,21 +69,20 @@ class InvalidUri(ValueError): pass -adapter_registry = {} # type: Dict[Text, AdapterMeta] +adapter_registry: Dict[Text, 'AdapterMeta'] = {} """ Keeps track of available adapters and their supported protocols. """ -def resolve_adapter(uri): - # type: (AdapterSpec) -> BaseAdapter +def resolve_adapter(uri: AdapterSpec) -> 'BaseAdapter': """ Given a URI, returns a properly-configured adapter instance. """ if isinstance(uri, BaseAdapter): return uri - parsed = urlsplit(uri) # type: SplitResult + parsed: SplitResult = urlsplit(uri) if not parsed.scheme: raise with_context( @@ -116,7 +118,7 @@ class AdapterMeta(ABCMeta): Automatically registers new adapter classes in ``adapter_registry``. """ - def __init__(cls, what, bases=None, dict=None): + def __init__(cls, what, bases=None, dict=None) -> None: super(AdapterMeta, cls).__init__(what, bases, dict) if not is_abstract(cls): @@ -125,8 +127,7 @@ def __init__(cls, what, bases=None, dict=None): # adapters. adapter_registry.setdefault(protocol, cls) - def configure(cls, parsed): - # type: (Union[Text, SplitResult]) -> HttpAdapter + def configure(cls, parsed: Union[Text, SplitResult]) -> 'HttpAdapter': """ Creates a new instance using the specified URI. @@ -143,21 +144,20 @@ class BaseAdapter(object, metaclass=AdapterMeta): Adapters make it easy to customize the way an API instance communicates with a node. """ - supported_protocols = () # type: Tuple[Text] + supported_protocols: Tuple[Text] = () """ Protocols that ``resolve_adapter`` can use to identify this adapter type. """ - def __init__(self): + def __init__(self) -> None: super(BaseAdapter, self).__init__() - self._logger = None # type: Logger - self.local_pow = False # type: boolean + self._logger: Logger = None + self.local_pow: bool = False @abstract_method - def get_uri(self): - # type: () -> Text + def get_uri(self) -> Text: """ Returns the URI that this adapter will use. """ @@ -166,8 +166,7 @@ def get_uri(self): ) @abstract_method - def send_request(self, payload, **kwargs): - # type: (dict, dict) -> dict + def send_request(self, payload: Dict, **kwargs: Dict) -> Dict: """ Sends an API request to the node. @@ -188,8 +187,7 @@ def send_request(self, payload, **kwargs): 'Not implemented in {cls}.'.format(cls=type(self).__name__), ) - def set_logger(self, logger): - # type: (Logger) -> BaseAdapter + def set_logger(self, logger: Logger) -> 'BaseAdapter': """ Attaches a logger instance to the adapter. The adapter will send information about API requests/responses @@ -198,16 +196,19 @@ def set_logger(self, logger): self._logger = logger return self - def _log(self, level, message, context=None): - # type: (int, Text, Optional[dict]) -> None + def _log( + self, + level: int, + message: Text, + context: Optional[int] = None + ) -> None: """ Sends a message to the instance's logger, if configured. """ if self._logger: self._logger.log(level, message, extra={'context': context or {}}) - def set_local_pow(self, local_pow): - # type: (bool) -> None + def set_local_pow(self, local_pow: bool) -> None: """ Sets the local_pow attribute of the adapter. If it is true, attach_to_tangle command calls external interface to perform @@ -216,6 +217,7 @@ def set_local_pow(self, local_pow): """ self.local_pow = local_pow + class HttpAdapter(BaseAdapter): """ Sends standard HTTP(S) requests to the node. @@ -257,8 +259,12 @@ class HttpAdapter(BaseAdapter): in the ``headers`` kwarg. """ - def __init__(self, uri, timeout=None, authentication=None): - # type: (Union[Text, SplitResult], Optional[int]) -> None + def __init__( + self, + uri: Union[Text, SplitResult], + timeout: Optional[int] = None, + authentication: Optional[Dict] = None + ) -> None: super(HttpAdapter, self).__init__() self.client = AsyncClient() @@ -266,7 +272,7 @@ def __init__(self, uri, timeout=None, authentication=None): self.authentication = authentication if isinstance(uri, str): - uri = urlsplit(uri) # type: SplitResult + uri: SplitResult = urlsplit(uri) if uri.scheme not in self.supported_protocols: raise with_context( @@ -310,19 +316,16 @@ def __init__(self, uri, timeout=None, authentication=None): self.uri = uri @property - def node_url(self): - # type: () -> Text + def node_url(self) -> Text: """ Returns the node URL. """ return self.uri.geturl() - def get_uri(self): - # type: () -> Text + def get_uri(self) -> Text: return self.uri.geturl() - async def send_request(self, payload, **kwargs): - # type: (dict, dict) -> dict + async def send_request(self, payload: Dict, **kwargs: Dict) -> Dict: kwargs.setdefault('headers', {}) for key, value in self.DEFAULT_HEADERS.items(): kwargs['headers'].setdefault(key, value) @@ -338,8 +341,13 @@ async def send_request(self, payload, **kwargs): return self._interpret_response(response, payload, {codes['OK']}) - async def _send_http_request(self, url, payload, method='post', **kwargs): - # type: (Text, Optional[Text], Text, dict) -> Response + async def _send_http_request( + self, + url: Text, + payload: Optional[Text], + method: Text = 'post', + **kwargs: Any + ) -> Response: """ Sends the actual HTTP request. @@ -352,7 +360,7 @@ async def _send_http_request(self, url, payload, method='post', **kwargs): ) if self.authentication: - kwargs.setdefault('auth', auth.HTTPBasicAuth(*self.authentication)) + kwargs.setdefault('auth', auth.BasicAuth(*self.authentication)) self._log( level=DEBUG, @@ -394,8 +402,12 @@ async def _send_http_request(self, url, payload, method='post', **kwargs): return response - def _interpret_response(self, response, payload, expected_status): - # type: (Response, dict, Container[int]) -> dict + def _interpret_response( + self, + response: Response, + payload: Dict, + expected_status: Container[Dict] + ) -> Dict: """ Interprets the HTTP response from the node. @@ -425,7 +437,7 @@ def _interpret_response(self, response, payload, expected_status): ) try: - decoded = json.loads(raw_content) # type: dict + decoded: Dict = json.loads(raw_content) # :bc: py2k doesn't have JSONDecodeError except ValueError: raise with_context( @@ -523,17 +535,16 @@ class MockAdapter(BaseAdapter): def configure(cls, uri): return cls() - def __init__(self): + def __init__(self) -> None: super(MockAdapter, self).__init__() - self.responses = {} # type: Dict[Text, deque] - self.requests = [] # type: List[dict] + self.responses: Dict[Text, deque] = {} + self.requests: List[dict] = [] - def get_uri(self): + def get_uri(self) -> Text: return 'mock://' - def seed_response(self, command, response): - # type: (Text, dict) -> MockAdapter + def seed_response(self, command: Text, response: Dict) -> 'MockAdapter': """ Sets the response that the adapter will return for the specified command. @@ -573,11 +584,10 @@ def seed_response(self, command, response): self.responses[command].append(response) return self - async def send_request(self, payload, **kwargs): + async def send_request(self, payload: Dict, **kwargs: Dict) -> Dict: """ 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)) diff --git a/iota/adapter/wrappers.py b/iota/adapter/wrappers.py index d2a5f9b..c984373 100644 --- a/iota/adapter/wrappers.py +++ b/iota/adapter/wrappers.py @@ -14,22 +14,19 @@ class BaseWrapper(BaseAdapter, metaclass=ABCMeta): functionality of IOTA adapters. """ - def __init__(self, adapter): - # type: (AdapterSpec) -> None + def __init__(self, adapter: AdapterSpec) -> None: super(BaseWrapper, self).__init__() if not isinstance(adapter, BaseAdapter): adapter = resolve_adapter(adapter) - self.adapter = adapter # type: BaseAdapter + self.adapter: BaseAdapter = adapter - def get_uri(self): - # type: () -> Text + def get_uri(self) -> Text: return self.adapter.get_uri() @abstract_method - def send_request(self, payload, **kwargs): - # type: (dict, dict) -> dict + def send_request(self, payload: Dict, **kwargs: Dict) -> Dict: raise NotImplementedError( 'Not implemented in {cls}.'.format(cls=type(self).__name__), ) @@ -86,8 +83,7 @@ class RoutingWrapper(BaseWrapper): defined in :py:class:`RoutingWrapper`. """ - def __init__(self, default_adapter): - # type: (AdapterSpec) -> None + def __init__(self, default_adapter: AdapterSpec) -> None: """ :param default_adapter: Adapter to use for any routes not listed in ``routes``. @@ -96,12 +92,11 @@ def __init__(self, default_adapter): # Try to limit the number of distinct adapter instances we create # when resolving URIs. - self.adapter_aliases = {} # type: Dict[AdapterSpec, BaseAdapter] + self.adapter_aliases: Dict[AdapterSpec, BaseAdapter] = {} - self.routes = {} # type: Dict[Text, BaseAdapter] + self.routes: Dict[Text, BaseAdapter] = {} - def add_route(self, command, adapter): - # type: (Text, AdapterSpec) -> RoutingWrapper + def add_route(self, command: Text, adapter: AdapterSpec) -> 'RoutingWrapper': """ Adds a route to the wrapper. @@ -130,15 +125,13 @@ def add_route(self, command, adapter): return self - def get_adapter(self, command): - # type: (Text) -> BaseAdapter + def get_adapter(self, command: Text) -> BaseAdapter: """ Return the adapter for the specified command. """ return self.routes.get(command, self.adapter) - async def send_request(self, payload, **kwargs): - # type: (dict, dict) -> dict + async def send_request(self, payload: Dict, **kwargs: Dict) -> Dict: command = payload.get('command') return await self.get_adapter(command).send_request(payload, **kwargs) diff --git a/iota/api.py b/iota/api.py index 2c1a3e6..17ab405 100644 --- a/iota/api.py +++ b/iota/api.py @@ -1,7 +1,7 @@ from typing import Dict, Iterable, Optional, Text from iota import AdapterSpec, Address, BundleHash, ProposedTransaction, Tag, \ - TransactionHash, TransactionTrytes, TryteString + TransactionHash, TransactionTrytes, TryteString, TrytesCompatible from iota.crypto.addresses import AddressGenerator from iota.api_async import AsyncStrictIota, AsyncIota import asyncio @@ -19,6 +19,7 @@ class InvalidCommand(ValueError): """ pass + # There is a compact and easy way to create the synchronous version of the async # classes: @@ -72,8 +73,12 @@ class StrictIota(AsyncStrictIota): """ - def __init__(self, adapter, devnet=False, local_pow=False): - # type: (AdapterSpec, bool, bool) -> None + def __init__( + self, + adapter: AdapterSpec, + devnet: bool = False, + local_pow: bool = False + ) -> None: """ :param AdapterSpec adapter: URI string or BaseAdapter instance. @@ -93,8 +98,7 @@ def __init__(self, adapter, devnet=False, local_pow=False): """ super().__init__(adapter, devnet, local_pow) - def add_neighbors(self, uris): - # type: (Iterable[Text]) -> dict + def add_neighbors(self, uris: Iterable[Text]) -> Dict: """ Add one or more neighbors to the node. Lasts until the node is restarted. @@ -126,17 +130,16 @@ def add_neighbors(self, uris): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().add_neighbors(uris) + super().add_neighbors(uris) ) def attach_to_tangle( self, - trunk_transaction, # type: TransactionHash - branch_transaction, # type: TransactionHash - trytes, # type: Iterable[TryteString] - min_weight_magnitude=None, # type: Optional[int] - ): - # type: (...) -> dict + trunk_transaction: TransactionHash, + branch_transaction: TransactionHash, + trytes: Iterable[TryteString], + min_weight_magnitude: Optional[int] = None, + ) -> Dict: """ Attaches the specified transactions (trytes) to the Tangle by doing Proof of Work. You need to supply branchTransaction as @@ -177,16 +180,15 @@ def attach_to_tangle( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().attach_to_tangle( - trunk_transaction, - branch_transaction, - trytes, - min_weight_magnitude, - ) + super().attach_to_tangle( + trunk_transaction, + branch_transaction, + trytes, + min_weight_magnitude, + ) ) - def broadcast_transactions(self, trytes): - # type: (Iterable[TryteString]) -> dict + def broadcast_transactions(self, trytes: Iterable[TryteString]) -> Dict: """ Broadcast a list of transactions to all neighbors. @@ -212,13 +214,12 @@ def broadcast_transactions(self, trytes): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().broadcast_transactions( - trytes, - ) + super().broadcast_transactions( + trytes, + ) ) - def check_consistency(self, tails): - # type: (Iterable[TransactionHash]) -> dict + def check_consistency(self, tails: Iterable[TransactionHash]) -> Dict: """ Used to ensure tail resolves to a consistent ledger which is necessary to validate before attempting promotion. Checks @@ -251,19 +252,18 @@ def check_consistency(self, tails): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().check_consistency( - tails, - ) + super().check_consistency( + tails, + ) ) def find_transactions( self, - bundles=None, # type: Optional[Iterable[BundleHash]] - addresses=None, # type: Optional[Iterable[Address]] - tags=None, # type: Optional[Iterable[Tag]] - approvees=None, # type: Optional[Iterable[TransactionHash]] - ): - # type: (...) -> dict + bundles: Optional[Iterable[BundleHash]] = None, + addresses: Optional[Iterable[Address]] = None, + tags: Optional[Iterable[Tag]] = None, + approvees: Optional[Iterable[TransactionHash]] = None, + ) -> Dict: """ Find the transactions which match the specified input and return. @@ -303,21 +303,20 @@ def find_transactions( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().find_transactions( - bundles, - addresses, - tags, - approvees, - ) + super().find_transactions( + bundles, + addresses, + tags, + approvees, + ) ) def get_balances( self, - addresses, # type: Iterable[Address] - threshold=100, # type: int - tips=None, # type: Optional[Iterable[TransactionHash]] - ): - # type: (...) -> dict + addresses: Iterable[Address], + threshold: int = 100, + tips: Optional[Iterable[TransactionHash]] = None, + ) -> Dict: """ Similar to :py:meth:`get_inclusion_states`. Returns the confirmed balance which a list of addresses have at the latest @@ -363,15 +362,18 @@ def get_balances( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().get_balances( - addresses, - threshold, - tips, - ) + super().get_balances( + addresses, + threshold, + tips, + ) ) - def get_inclusion_states(self, transactions, tips): - # type: (Iterable[TransactionHash], Iterable[TransactionHash]) -> dict + def get_inclusion_states( + self, + transactions: Iterable[TransactionHash], + tips: Iterable[TransactionHash] + ) -> Dict: """ Get the inclusion states of a set of transactions. This is for determining if a transaction was accepted and confirmed by the @@ -406,14 +408,13 @@ def get_inclusion_states(self, transactions, tips): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().get_inclusion_states( - transactions, - tips, - ) + super().get_inclusion_states( + transactions, + tips, + ) ) - def get_missing_transactions(self): - # type: () -> dict + def get_missing_transactions(self) -> Dict: """ Returns all transaction hashes that a node is currently requesting from its neighbors. @@ -436,11 +437,10 @@ def get_missing_transactions(self): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().get_missing_transactions() + super().get_missing_transactions() ) - def get_neighbors(self): - # type: () -> dict + def get_neighbors(self) -> Dict: """ Returns the set of neighbors the node is connected with, as well as their activity count. @@ -474,11 +474,10 @@ def get_neighbors(self): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().get_neighbors() + super().get_neighbors() ) - def get_node_api_configuration(self): - # type: () -> dict + def get_node_api_configuration(self) -> Dict: """ Returns a node's API configuration settings. @@ -503,11 +502,10 @@ def get_node_api_configuration(self): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().get_node_api_configuration() + super().get_node_api_configuration() ) - def get_node_info(self): - # type: () -> dict + def get_node_info(self) -> Dict: """ Returns information about the node. @@ -567,11 +565,10 @@ def get_node_info(self): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().get_node_info() + super().get_node_info() ) - def get_tips(self): - # type: () -> dict + def get_tips(self) -> Dict: """ Returns the list of tips (transactions which have no other transactions referencing them). @@ -595,11 +592,14 @@ def get_tips(self): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().get_tips() + super().get_tips() ) - def get_transactions_to_approve(self, depth, reference=None): - # type: (int, Optional[TransactionHash]) -> dict + def get_transactions_to_approve( + self, + depth: int, + reference: Optional[TransactionHash] = None, + ) -> Dict: """ Tip selection which returns ``trunkTransaction`` and ``branchTransaction``. @@ -636,14 +636,13 @@ def get_transactions_to_approve(self, depth, reference=None): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().get_transactions_to_approve( - depth, - reference, - ) + super().get_transactions_to_approve( + depth, + reference, + ) ) - def get_trytes(self, hashes): - # type: (Iterable[TransactionHash]) -> dict + def get_trytes(self, hashes: Iterable[TransactionHash]) -> Dict: """ Returns the raw transaction data (trytes) of one or more transactions. @@ -672,13 +671,12 @@ def get_trytes(self, hashes): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().get_trytes( - hashes, - ) + super().get_trytes( + hashes, + ) ) - def interrupt_attaching_to_tangle(self): - # type: () -> dict + def interrupt_attaching_to_tangle(self) -> Dict: """ Interrupts and completely aborts the :py:meth:`attach_to_tangle` process. @@ -699,11 +697,10 @@ def interrupt_attaching_to_tangle(self): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().interrupt_attaching_to_tangle() + super().interrupt_attaching_to_tangle() ) - def remove_neighbors(self, uris): - # type: (Iterable[Text]) -> dict + def remove_neighbors(self, uris: Iterable[Text]) -> Dict: """ Removes one or more neighbors from the node. Lasts until the node is restarted. @@ -730,11 +727,10 @@ def remove_neighbors(self, uris): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().remove_neighbors(uris) + super().remove_neighbors(uris) ) - def store_transactions(self, trytes): - # type: (Iterable[TryteString]) -> dict + def store_transactions(self, trytes: Iterable[TryteString]) -> Dict: """ Store transactions into local storage of the node. @@ -762,11 +758,13 @@ def store_transactions(self, trytes): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().store_transactions(trytes) + super().store_transactions(trytes) ) - def were_addresses_spent_from(self, addresses): - # type: (Iterable[Address]) -> dict + def were_addresses_spent_from( + self, + addresses: Iterable[Address] + ) -> Dict: """ Check if a list of addresses was ever spent from, in the current epoch, or in previous epochs. @@ -796,7 +794,7 @@ def were_addresses_spent_from(self, addresses): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().were_addresses_spent_from(addresses) + super().were_addresses_spent_from(addresses) ) @@ -837,8 +835,13 @@ class Iota(StrictIota, AsyncIota): - https://github.com/iotaledger/wiki/blob/master/api-proposal.md """ - def __init__(self, adapter, seed=None, devnet=False, local_pow=False): - # type: (AdapterSpec, Optional[TrytesCompatible], bool, bool) -> None + def __init__( + self, + adapter: AdapterSpec, + seed: Optional[TrytesCompatible] = None, + devnet: bool = False, + local_pow: bool = False + ) -> None: """ :param seed: Seed used to generate new addresses. @@ -850,8 +853,10 @@ def __init__(self, adapter, seed=None, devnet=False, local_pow=False): # Explicitly call AsyncIota's init, as we need the seed AsyncIota.__init__(self, adapter, seed, devnet, local_pow) - def broadcast_and_store(self, trytes): - # type: (Iterable[TransactionTrytes]) -> dict + def broadcast_and_store( + self, + trytes: Iterable[TransactionTrytes] + ) -> Dict: """ Broadcasts and stores a set of transaction trytes. @@ -874,11 +879,13 @@ def broadcast_and_store(self, trytes): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().broadcast_and_store(trytes) + super().broadcast_and_store(trytes) ) - def broadcast_bundle(self, tail_transaction_hash): - # type (TransactionHash) -> dict + def broadcast_bundle( + self, + tail_transaction_hash: TransactionHash + ) -> Dict: """ Re-broadcasts all transactions in a bundle given the tail transaction hash. It might be useful when transactions did not properly propagate, @@ -903,17 +910,16 @@ def broadcast_bundle(self, tail_transaction_hash): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().broadcast_bundle(tail_transaction_hash) + super().broadcast_bundle(tail_transaction_hash) ) def find_transaction_objects( self, - bundles=None, # type: Optional[Iterable[BundleHash]] - addresses=None, # type: Optional[Iterable[Address]] - tags=None, # type: Optional[Iterable[Tag]] - approvees=None, # type: Optional[Iterable[TransactionHash]] - ): - # type: (...) -> dict + bundles: Optional[Iterable[BundleHash]] = None, + addresses: Optional[Iterable[Address]] = None, + tags: Optional[Iterable[Tag]] = None, + approvees: Optional[Iterable[TransactionHash]] = None, + ) -> Dict: """ A more extensive version of :py:meth:`find_transactions` that returns transaction objects instead of hashes. @@ -954,16 +960,21 @@ def find_transaction_objects( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().find_transaction_objects( - bundles, - addresses, - tags, - approvees, - ) + super().find_transaction_objects( + bundles, + addresses, + tags, + approvees, + ) ) - def get_account_data(self, start=0, stop=None, inclusion_states=False, security_level=None): - # type: (int, Optional[int], bool, Optional[int]) -> dict + def get_account_data( + self, + start: int = 0, + stop: Optional[int] = None, + inclusion_states: bool = False, + security_level: Optional[int] = None + ) -> Dict: """ More comprehensive version of :py:meth:`get_transfers` that returns addresses and account balance in addition to bundles. @@ -1034,16 +1045,18 @@ def get_account_data(self, start=0, stop=None, inclusion_states=False, security_ # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().get_account_data( - start, - stop, - inclusion_states, - security_level, - ) + super().get_account_data( + start, + stop, + inclusion_states, + security_level, + ) ) - def get_bundles(self, transactions): - # type: (Iterable[TransactionHash]) -> dict + def get_bundles( + self, + transactions: Iterable[TransactionHash] + ) -> Dict: """ Returns the bundle(s) associated with the specified transaction hashes. @@ -1071,17 +1084,16 @@ def get_bundles(self, transactions): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().get_bundles(transactions) + super().get_bundles(transactions) ) def get_inputs( self, - start=0, - stop=None, - threshold=None, - security_level=None, - ): - # type: (int, Optional[int], Optional[int], Optional[int]) -> dict + start: int = 0, + stop: Optional[int] = None, + threshold: Optional[int] = None, + security_level: Optional[int] = None, + ) -> Dict: """ Gets all possible inputs of a seed and returns them, along with the total balance. @@ -1181,16 +1193,18 @@ def get_inputs( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().get_inputs( - start, - stop, - threshold, - security_level, - ) + super().get_inputs( + start, + stop, + threshold, + security_level, + ) ) - def get_latest_inclusion(self, hashes): - # type: (Iterable[TransactionHash]) -> Dict[TransactionHash, bool] + def get_latest_inclusion( + self, + hashes: Iterable[TransactionHash] + ) -> Dict[Text, Dict[TransactionHash, bool]]: """ Fetches the inclusion state for the specified transaction hashes, as of the latest milestone that the node has processed. @@ -1214,17 +1228,16 @@ def get_latest_inclusion(self, hashes): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().get_latest_inclusion(hashes) + super().get_latest_inclusion(hashes) ) def get_new_addresses( self, - index=0, - count=1, - security_level=AddressGenerator.DEFAULT_SECURITY_LEVEL, - checksum=False, + index: int = 0, + count: int = 1, + security_level: int = AddressGenerator.DEFAULT_SECURITY_LEVEL, + checksum: bool = False, ): - # type: (int, int, int, bool) -> dict """ Generates one or more new addresses from the seed. @@ -1281,19 +1294,18 @@ def get_new_addresses( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().get_new_addresses( - count=count, - index=index, - security_level=security_level, - checksum=checksum, - ) + super().get_new_addresses( + count=count, + index=index, + security_level=security_level, + checksum=checksum, + ) ) def get_transaction_objects( self, - hashes, # type: [Iterable[TransactionHash]] - ): - # type: (...) -> dict + hashes: [Iterable[TransactionHash]], + ) -> Dict: """ Fetches transaction objects from the Tangle given their transaction IDs (hashes). @@ -1318,11 +1330,15 @@ def get_transaction_objects( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().get_transaction_objects(hashes) + super().get_transaction_objects(hashes) ) - def get_transfers(self, start=0, stop=None, inclusion_states=False): - # type: (int, Optional[int], bool) -> dict + def get_transfers( + self, + start: int = 0, + stop: Optional[int] = None, + inclusion_states: bool = False + ) -> Dict: """ Returns all transfers associated with the seed. @@ -1377,18 +1393,17 @@ def get_transfers(self, start=0, stop=None, inclusion_states=False): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().get_transfers( - start, - stop, - inclusion_states, - ) + super().get_transfers( + start, + stop, + inclusion_states, + ) ) def is_promotable( self, - tails, # type: Iterable[TransactionHash] - ): - # type: (Iterable(TransactionHash)] -> dict + tails: Iterable[TransactionHash], + ) -> Dict: """ Checks if tail transaction(s) is promotable by calling :py:meth:`check_consistency` and verifying that ``attachmentTimestamp`` @@ -1422,17 +1437,16 @@ def is_promotable( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().is_promotable(tails) + super().is_promotable(tails) ) def prepare_transfer( self, - transfers, # type: Iterable[ProposedTransaction] - inputs=None, # type: Optional[Iterable[Address]] - change_address=None, # type: Optional[Address] - security_level=None, # type: Optional[int] - ): - # type: (...) -> dict + transfers: Iterable[ProposedTransaction], + inputs: Optional[Iterable[Address]] = None, + change_address: Optional[Address] = None, + security_level: Optional[int] = None, + ) -> Dict: """ Prepares transactions to be broadcast to the Tangle, by generating the correct bundle, as well as choosing and signing @@ -1482,21 +1496,20 @@ def prepare_transfer( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().prepare_transfer( - transfers, - inputs, - change_address, - security_level, - ) + super().prepare_transfer( + transfers, + inputs, + change_address, + security_level, + ) ) def promote_transaction( self, - transaction, - depth=3, - min_weight_magnitude=None, - ): - # type: (TransactionHash, int, Optional[int]) -> dict + transaction: TransactionHash, + depth: int = 3, + min_weight_magnitude: Optional[int] = None, + ) -> Dict: """ Promotes a transaction by adding spam on top of it. @@ -1524,20 +1537,19 @@ def promote_transaction( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().promote_transaction( - transaction, - depth, - min_weight_magnitude, - ) + super().promote_transaction( + transaction, + depth, + min_weight_magnitude, + ) ) def replay_bundle( self, - transaction, - depth=3, - min_weight_magnitude=None, - ): - # type: (TransactionHash, int, Optional[int]) -> dict + transaction: TransactionHash, + depth: int = 3, + min_weight_magnitude: Optional[int] = None, + ) -> Dict: """ Takes a tail transaction hash as input, gets the bundle associated with the transaction and then replays the bundle by @@ -1571,23 +1583,22 @@ def replay_bundle( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().replay_bundle( - transaction, - depth, - min_weight_magnitude, - ) + super().replay_bundle( + transaction, + depth, + min_weight_magnitude, + ) ) def send_transfer( self, - transfers, # type: Iterable[ProposedTransaction] - depth=3, # type: int - inputs=None, # type: Optional[Iterable[Address]] - change_address=None, # type: Optional[Address] - min_weight_magnitude=None, # type: Optional[int] - security_level=None, # type: Optional[int] - ): - # type: (...) -> dict + transfers: Iterable[ProposedTransaction], + depth: int = 3, + inputs: Optional[Iterable[Address]] = None, + change_address: Optional[Address] = None, + min_weight_magnitude: Optional[int] = None, + security_level: Optional[int] = None, + ) -> Dict: """ Prepares a set of transfers and creates the bundle, then attaches the bundle to the Tangle, and broadcasts and stores the @@ -1641,18 +1652,22 @@ def send_transfer( # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().send_transfer( - transfers, - depth, - inputs, - change_address, - min_weight_magnitude, - security_level, - ) + super().send_transfer( + transfers, + depth, + inputs, + change_address, + min_weight_magnitude, + security_level, + ) ) - def send_trytes(self, trytes, depth=3, min_weight_magnitude=None): - # type: (Iterable[TransactionTrytes], int, Optional[int]) -> dict + def send_trytes( + self, + trytes: Iterable[TransactionTrytes], + depth: int = 3, + min_weight_magnitude: Optional[int] = None + ) -> Dict: """ Attaches transaction trytes to the Tangle, then broadcasts and stores them. @@ -1685,15 +1700,14 @@ def send_trytes(self, trytes, depth=3, min_weight_magnitude=None): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().send_trytes( - trytes, - depth, - min_weight_magnitude, - ) + super().send_trytes( + trytes, + depth, + min_weight_magnitude, + ) ) - def is_reattachable(self, addresses): - # type: (Iterable[Address]) -> dict + def is_reattachable(self, addresses: Iterable[Address]) -> Dict: """ This API function helps you to determine whether you should replay a transaction or make a new one (either with the same @@ -1722,13 +1736,12 @@ def is_reattachable(self, addresses): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().is_reattachable( - addresses, - ) + super().is_reattachable( + addresses, + ) ) - def traverse_bundle(self, tail_hash): - # type: (TransactionHash) -> dict + def traverse_bundle(self, tail_hash: TransactionHash) -> Dict: """ Fetches and traverses a bundle from the Tangle given a tail transaction hash. @@ -1754,7 +1767,7 @@ def traverse_bundle(self, tail_hash): # Execute original coroutine inside an event loop to make this method # synchronous return asyncio.get_event_loop().run_until_complete( - super().traverse_bundle( - tail_hash, - ) + super().traverse_bundle( + tail_hash, + ) ) diff --git a/iota/api_async.py b/iota/api_async.py index c013cde..9098461 100644 --- a/iota/api_async.py +++ b/iota/api_async.py @@ -43,8 +43,12 @@ class AsyncStrictIota: """ - def __init__(self, adapter, devnet=False, local_pow=False): - # type: (AdapterSpec, bool, bool) -> None + def __init__( + self, + adapter: AdapterSpec, + devnet: bool = False, + local_pow: bool = False + ) -> None: """ :param AdapterSpec adapter: URI string or BaseAdapter instance. @@ -67,7 +71,7 @@ def __init__(self, adapter, devnet=False, local_pow=False): if not isinstance(adapter, BaseAdapter): adapter = resolve_adapter(adapter) - self.adapter = adapter # type: BaseAdapter + self.adapter: BaseAdapter = adapter # Note that the `local_pow` parameter is passed to adapter, # the api class has no notion about it. The reason being, # that this parameter is used in `AttachToTangeCommand` calls, @@ -81,8 +85,7 @@ def __init__(self, adapter, devnet=False, local_pow=False): self.adapter.set_local_pow(local_pow) self.devnet = devnet - def create_command(self, command): - # type: (Text) -> CustomCommand + def create_command(self, command: Text) -> CustomCommand: """ Creates a pre-configured CustomCommand instance. @@ -95,8 +98,7 @@ def create_command(self, command): """ return CustomCommand(self.adapter, command) - def set_local_pow(self, local_pow): - # type: (bool) -> None + def set_local_pow(self, local_pow: bool) -> None: """ Sets the :py:attr:`local_pow` attribute of the adapter of the api instance. If it is ``True``, :py:meth:`~Iota.attach_to_tangle` command calls @@ -116,16 +118,14 @@ def set_local_pow(self, local_pow): self.adapter.set_local_pow(local_pow) @property - def default_min_weight_magnitude(self): - # type: () -> int + def default_min_weight_magnitude(self) -> int: """ Returns the default ``min_weight_magnitude`` value to use for API requests. """ return 9 if self.devnet else 14 - async def add_neighbors(self, uris): - # type: (Iterable[Text]) -> dict + async def add_neighbors(self, uris: Iterable[Text]) -> Dict: """ Add one or more neighbors to the node. Lasts until the node is restarted. @@ -157,12 +157,11 @@ async def add_neighbors(self, uris): async def attach_to_tangle( self, - trunk_transaction, # type: TransactionHash - branch_transaction, # type: TransactionHash - trytes, # type: Iterable[TryteString] - min_weight_magnitude=None, # type: Optional[int] - ): - # type: (...) -> dict + trunk_transaction: TransactionHash, + branch_transaction: TransactionHash, + trytes: Iterable[TryteString], + min_weight_magnitude: Optional[int] = None, + ) -> Dict: """ Attaches the specified transactions (trytes) to the Tangle by doing Proof of Work. You need to supply branchTransaction as @@ -203,14 +202,13 @@ async def attach_to_tangle( min_weight_magnitude = self.default_min_weight_magnitude return await core.AttachToTangleCommand(self.adapter)( - trunkTransaction=trunk_transaction, - branchTransaction=branch_transaction, - minWeightMagnitude=min_weight_magnitude, - trytes=trytes, + trunkTransaction=trunk_transaction, + branchTransaction=branch_transaction, + minWeightMagnitude=min_weight_magnitude, + trytes=trytes, ) - async def broadcast_transactions(self, trytes): - # type: (Iterable[TryteString]) -> dict + async def broadcast_transactions(self, trytes: Iterable[TryteString]) -> Dict: """ Broadcast a list of transactions to all neighbors. @@ -234,8 +232,7 @@ async def broadcast_transactions(self, trytes): """ return await core.BroadcastTransactionsCommand(self.adapter)(trytes=trytes) - async def check_consistency(self, tails): - # type: (Iterable[TransactionHash]) -> dict + async def check_consistency(self, tails: Iterable[TransactionHash]) -> Dict: """ Used to ensure tail resolves to a consistent ledger which is necessary to validate before attempting promotion. Checks @@ -265,17 +262,16 @@ async def check_consistency(self, tails): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#checkconsistency """ return await core.CheckConsistencyCommand(self.adapter)( - tails=tails, + tails=tails, ) async def find_transactions( self, - bundles=None, # type: Optional[Iterable[BundleHash]] - addresses=None, # type: Optional[Iterable[Address]] - tags=None, # type: Optional[Iterable[Tag]] - approvees=None, # type: Optional[Iterable[TransactionHash]] - ): - # type: (...) -> dict + bundles: Optional[Iterable[BundleHash]] = None, + addresses: Optional[Iterable[Address]] = None, + tags: Optional[Iterable[Tag]] = None, + approvees: Optional[Iterable[TransactionHash]] = None, + ) -> Dict: """ Find the transactions which match the specified input and return. @@ -312,19 +308,18 @@ async def find_transactions( - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#findtransactions """ return await core.FindTransactionsCommand(self.adapter)( - bundles=bundles, - addresses=addresses, - tags=tags, - approvees=approvees, + bundles=bundles, + addresses=addresses, + tags=tags, + approvees=approvees, ) async def get_balances( self, - addresses, # type: Iterable[Address] - threshold=100, # type: int - tips=None, # type: Optional[Iterable[TransactionHash]] - ): - # type: (...) -> dict + addresses: Iterable[Address], + threshold: int = 100, + tips: Optional[Iterable[TransactionHash]] = None, + ) -> Dict: """ Similar to :py:meth:`get_inclusion_states`. Returns the confirmed balance which a list of addresses have at the latest @@ -367,13 +362,16 @@ async def get_balances( - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getbalances """ return await core.GetBalancesCommand(self.adapter)( - addresses=addresses, - threshold=threshold, - tips=tips, + addresses=addresses, + threshold=threshold, + tips=tips, ) - async def get_inclusion_states(self, transactions, tips): - # type: (Iterable[TransactionHash], Iterable[TransactionHash]) -> dict + async def get_inclusion_states( + self, + transactions: Iterable[TransactionHash], + tips: Iterable[TransactionHash] + ) -> Dict: """ Get the inclusion states of a set of transactions. This is for determining if a transaction was accepted and confirmed by the @@ -405,12 +403,11 @@ async def get_inclusion_states(self, transactions, tips): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getinclusionstates """ return await core.GetInclusionStatesCommand(self.adapter)( - transactions=transactions, - tips=tips, + transactions=transactions, + tips=tips, ) - async def get_missing_transactions(self): - # type: () -> dict + async def get_missing_transactions(self) -> Dict: """ Returns all transaction hashes that a node is currently requesting from its neighbors. @@ -431,8 +428,7 @@ async def get_missing_transactions(self): """ return await core.GetMissingTransactionsCommand(self.adapter)() - async def get_neighbors(self): - # type: () -> dict + async def get_neighbors(self) -> Dict: """ Returns the set of neighbors the node is connected with, as well as their activity count. @@ -464,8 +460,7 @@ async def get_neighbors(self): """ return await core.GetNeighborsCommand(self.adapter)() - async def get_node_api_configuration(self): - # type: () -> dict + async def get_node_api_configuration(self) -> Dict: """ Returns a node's API configuration settings. @@ -488,8 +483,7 @@ async def get_node_api_configuration(self): """ return await core.GetNodeAPIConfigurationCommand(self.adapter)() - async def get_node_info(self): - # type: () -> dict + async def get_node_info(self) -> Dict: """ Returns information about the node. @@ -547,8 +541,7 @@ async def get_node_info(self): """ return await core.GetNodeInfoCommand(self.adapter)() - async def get_tips(self): - # type: () -> dict + async def get_tips(self) -> Dict: """ Returns the list of tips (transactions which have no other transactions referencing them). @@ -570,8 +563,11 @@ async def get_tips(self): """ return await core.GetTipsCommand(self.adapter)() - async def get_transactions_to_approve(self, depth, reference=None): - # type: (int, Optional[TransactionHash]) -> dict + async def get_transactions_to_approve( + self, + depth: int, + reference: Optional[TransactionHash] = None, + ) -> Dict: """ Tip selection which returns ``trunkTransaction`` and ``branchTransaction``. @@ -605,12 +601,11 @@ async def get_transactions_to_approve(self, depth, reference=None): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#gettransactionstoapprove """ return await core.GetTransactionsToApproveCommand(self.adapter)( - depth=depth, - reference=reference, + depth=depth, + reference=reference, ) - async def get_trytes(self, hashes): - # type: (Iterable[TransactionHash]) -> dict + async def get_trytes(self, hashes: Iterable[TransactionHash]) -> Dict: """ Returns the raw transaction data (trytes) of one or more transactions. @@ -637,8 +632,7 @@ async def get_trytes(self, hashes): """ return await core.GetTrytesCommand(self.adapter)(hashes=hashes) - async def interrupt_attaching_to_tangle(self): - # type: () -> dict + async def interrupt_attaching_to_tangle(self) -> Dict: """ Interrupts and completely aborts the :py:meth:`attach_to_tangle` process. @@ -657,8 +651,7 @@ async def interrupt_attaching_to_tangle(self): """ return await core.InterruptAttachingToTangleCommand(self.adapter)() - async def remove_neighbors(self, uris): - # type: (Iterable[Text]) -> dict + async def remove_neighbors(self, uris: Iterable[Text]) -> Dict: """ Removes one or more neighbors from the node. Lasts until the node is restarted. @@ -683,8 +676,7 @@ async def remove_neighbors(self, uris): """ return await core.RemoveNeighborsCommand(self.adapter)(uris=uris) - async def store_transactions(self, trytes): - # type: (Iterable[TryteString]) -> dict + async def store_transactions(self, trytes: Iterable[TryteString]) -> Dict: """ Store transactions into local storage of the node. @@ -710,8 +702,10 @@ async def store_transactions(self, trytes): """ return await core.StoreTransactionsCommand(self.adapter)(trytes=trytes) - async def were_addresses_spent_from(self, addresses): - # type: (Iterable[Address]) -> dict + async def were_addresses_spent_from( + self, + addresses: Iterable[Address] + ) -> Dict: """ Check if a list of addresses was ever spent from, in the current epoch, or in previous epochs. @@ -738,9 +732,10 @@ async def were_addresses_spent_from(self, addresses): - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#wereaddressesspentfrom """ return await core.WereAddressesSpentFromCommand(self.adapter)( - addresses=addresses, + addresses=addresses, ) + class AsyncIota(AsyncStrictIota): """ Implements the async core API, plus additional async wrapper methods for @@ -778,8 +773,13 @@ class AsyncIota(AsyncStrictIota): - https://github.com/iotaledger/wiki/blob/master/api-proposal.md """ - def __init__(self, adapter, seed=None, devnet=False, local_pow=False): - # type: (AdapterSpec, Optional[TrytesCompatible], bool, bool) -> None + def __init__( + self, + adapter: AdapterSpec, + seed: Optional[TrytesCompatible] = None, + devnet: bool = False, + local_pow: bool = False + ) -> None: """ :param seed: Seed used to generate new addresses. @@ -792,8 +792,10 @@ def __init__(self, adapter, seed=None, devnet=False, local_pow=False): self.seed = Seed(seed) if seed else Seed.random() - async def broadcast_and_store(self, trytes): - # type: (Iterable[TransactionTrytes]) -> dict + async def broadcast_and_store( + self, + trytes: Iterable[TransactionTrytes] + ) -> Dict: """ Broadcasts and stores a set of transaction trytes. @@ -814,11 +816,13 @@ async def broadcast_and_store(self, trytes): - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#broadcastandstore """ return await extended.BroadcastAndStoreCommand(self.adapter)( - trytes=trytes, + trytes=trytes, ) - async def broadcast_bundle(self, tail_transaction_hash): - # type (TransactionHash) -> dict + async def broadcast_bundle( + self, + tail_transaction_hash: TransactionHash + ) -> Dict: """ Re-broadcasts all transactions in a bundle given the tail transaction hash. It might be useful when transactions did not properly propagate, @@ -841,17 +845,16 @@ async def broadcast_bundle(self, tail_transaction_hash): """ return await extended.BroadcastBundleCommand(self.adapter)( - tail_hash=tail_transaction_hash, + tail_hash=tail_transaction_hash, ) async def find_transaction_objects( self, - bundles=None, # type: Optional[Iterable[BundleHash]] - addresses=None, # type: Optional[Iterable[Address]] - tags=None, # type: Optional[Iterable[Tag]] - approvees=None, # type: Optional[Iterable[TransactionHash]] - ): - # type: (...) -> dict + bundles: Optional[Iterable[BundleHash]] = None, + addresses: Optional[Iterable[Address]] = None, + tags: Optional[Iterable[Tag]] = None, + approvees: Optional[Iterable[TransactionHash]] = None, + ) -> Dict: """ A more extensive version of :py:meth:`find_transactions` that returns transaction objects instead of hashes. @@ -890,14 +893,19 @@ async def find_transaction_objects( """ return await extended.FindTransactionObjectsCommand(self.adapter)( - bundles=bundles, - addresses=addresses, - tags=tags, - approvees=approvees, + bundles=bundles, + addresses=addresses, + tags=tags, + approvees=approvees, ) - async def get_account_data(self, start=0, stop=None, inclusion_states=False, security_level=None): - # type: (int, Optional[int], bool, Optional[int]) -> dict + async def get_account_data( + self, + start: int = 0, + stop: Optional[int] = None, + inclusion_states: bool = False, + security_level: Optional[int] = None + ) -> Dict: """ More comprehensive version of :py:meth:`get_transfers` that returns addresses and account balance in addition to bundles. @@ -966,15 +974,17 @@ async def get_account_data(self, start=0, stop=None, inclusion_states=False, sec """ return await extended.GetAccountDataCommand(self.adapter)( - seed=self.seed, - start=start, - stop=stop, - inclusionStates=inclusion_states, - security_level=security_level + seed=self.seed, + start=start, + stop=stop, + inclusionStates=inclusion_states, + security_level=security_level ) - async def get_bundles(self, transactions): - # type: (Iterable[TransactionHash]) -> dict + async def get_bundles( + self, + transactions: Iterable[TransactionHash] + ) -> Dict: """ Returns the bundle(s) associated with the specified transaction hashes. @@ -1000,17 +1010,16 @@ async def get_bundles(self, transactions): - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getbundle """ return await extended.GetBundlesCommand(self.adapter)( - transactions=transactions, + transactions=transactions, ) async def get_inputs( self, - start=0, - stop=None, - threshold=None, - security_level=None, - ): - # type: (int, Optional[int], Optional[int], Optional[int]) -> dict + start: int = 0, + stop: Optional[int] = None, + threshold: Optional[int] = None, + security_level: Optional[int] = None, + ) -> Dict: """ Gets all possible inputs of a seed and returns them, along with the total balance. @@ -1108,15 +1117,17 @@ async def get_inputs( - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getinputs """ return await extended.GetInputsCommand(self.adapter)( - seed=self.seed, - start=start, - stop=stop, - threshold=threshold, - securityLevel=security_level + seed=self.seed, + start=start, + stop=stop, + threshold=threshold, + securityLevel=security_level ) - async def get_latest_inclusion(self, hashes): - # type: (Iterable[TransactionHash]) -> Dict[TransactionHash, bool] + async def get_latest_inclusion( + self, + hashes: Iterable[TransactionHash] + ) -> Dict[Text, Dict[TransactionHash, bool]]: """ Fetches the inclusion state for the specified transaction hashes, as of the latest milestone that the node has processed. @@ -1141,12 +1152,11 @@ async def get_latest_inclusion(self, hashes): async def get_new_addresses( self, - index=0, - count=1, - security_level=AddressGenerator.DEFAULT_SECURITY_LEVEL, - checksum=False, + index: int = 0, + count: int = 1, + security_level: int = AddressGenerator.DEFAULT_SECURITY_LEVEL, + checksum: bool = False, ): - # type: (int, int, int, bool) -> dict """ Generates one or more new addresses from the seed. @@ -1201,18 +1211,17 @@ async def get_new_addresses( - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getnewaddress """ return await extended.GetNewAddressesCommand(self.adapter)( - count=count, - index=index, - securityLevel=security_level, - checksum=checksum, - seed=self.seed, + count=count, + index=index, + securityLevel=security_level, + checksum=checksum, + seed=self.seed, ) async def get_transaction_objects( self, - hashes, # type: [Iterable[TransactionHash]] - ): - # type: (...) -> dict + hashes: [Iterable[TransactionHash]], + ) -> Dict: """ Fetches transaction objects from the Tangle given their transaction IDs (hashes). @@ -1235,11 +1244,15 @@ async def get_transaction_objects( } """ return await extended.GetTransactionObjectsCommand(self.adapter)( - hashes=hashes, + hashes=hashes, ) - async def get_transfers(self, start=0, stop=None, inclusion_states=False): - # type: (int, Optional[int], bool) -> dict + async def get_transfers( + self, + start: int = 0, + stop: Optional[int] = None, + inclusion_states: bool = False + ) -> Dict: """ Returns all transfers associated with the seed. @@ -1292,17 +1305,16 @@ async def get_transfers(self, start=0, stop=None, inclusion_states=False): - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#gettransfers """ return await extended.GetTransfersCommand(self.adapter)( - seed=self.seed, - start=start, - stop=stop, - inclusionStates=inclusion_states, + seed=self.seed, + start=start, + stop=stop, + inclusionStates=inclusion_states, ) async def is_promotable( self, - tails, # type: Iterable[TransactionHash] - ): - # type: (Iterable(TransactionHash)] -> dict + tails: Iterable[TransactionHash], + ) -> Dict: """ Checks if tail transaction(s) is promotable by calling :py:meth:`check_consistency` and verifying that ``attachmentTimestamp`` @@ -1334,17 +1346,16 @@ async def is_promotable( - https://github.com/iotaledger/iota.js/blob/next/api_reference.md#module_core.isPromotable """ return await extended.IsPromotableCommand(self.adapter)( - tails=tails, + tails=tails, ) async def prepare_transfer( self, - transfers, # type: Iterable[ProposedTransaction] - inputs=None, # type: Optional[Iterable[Address]] - change_address=None, # type: Optional[Address] - security_level=None, # type: Optional[int] - ): - # type: (...) -> dict + transfers: Iterable[ProposedTransaction], + inputs: Optional[Iterable[Address]] = None, + change_address: Optional[Address] = None, + security_level: Optional[int] = None, + ) -> Dict: """ Prepares transactions to be broadcast to the Tangle, by generating the correct bundle, as well as choosing and signing @@ -1392,20 +1403,19 @@ async def prepare_transfer( - https://github.com/iotaledger/wiki/blob/master/api-proposal.md#preparetransfers """ return await extended.PrepareTransferCommand(self.adapter)( - seed=self.seed, - transfers=transfers, - inputs=inputs, - changeAddress=change_address, - securityLevel=security_level, + seed=self.seed, + transfers=transfers, + inputs=inputs, + changeAddress=change_address, + securityLevel=security_level, ) async def promote_transaction( self, - transaction, - depth=3, - min_weight_magnitude=None, - ): - # type: (TransactionHash, int, Optional[int]) -> dict + transaction: TransactionHash, + depth: int = 3, + min_weight_magnitude: Optional[int] = None, + ) -> Dict: """ Promotes a transaction by adding spam on top of it. @@ -1434,18 +1444,17 @@ async def promote_transaction( min_weight_magnitude = self.default_min_weight_magnitude return await extended.PromoteTransactionCommand(self.adapter)( - transaction=transaction, - depth=depth, - minWeightMagnitude=min_weight_magnitude, + transaction=transaction, + depth=depth, + minWeightMagnitude=min_weight_magnitude, ) async def replay_bundle( self, - transaction, - depth=3, - min_weight_magnitude=None, - ): - # type: (TransactionHash, int, Optional[int]) -> dict + transaction: TransactionHash, + depth: int = 3, + min_weight_magnitude: Optional[int] = None, + ) -> Dict: """ Takes a tail transaction hash as input, gets the bundle associated with the transaction and then replays the bundle by @@ -1480,21 +1489,20 @@ async def replay_bundle( min_weight_magnitude = self.default_min_weight_magnitude return await extended.ReplayBundleCommand(self.adapter)( - transaction=transaction, - depth=depth, - minWeightMagnitude=min_weight_magnitude, + transaction=transaction, + depth=depth, + minWeightMagnitude=min_weight_magnitude, ) async def send_transfer( self, - transfers, # type: Iterable[ProposedTransaction] - depth=3, # type: int - inputs=None, # type: Optional[Iterable[Address]] - change_address=None, # type: Optional[Address] - min_weight_magnitude=None, # type: Optional[int] - security_level=None, # type: Optional[int] - ): - # type: (...) -> dict + transfers: Iterable[ProposedTransaction], + depth: int = 3, + inputs: Optional[Iterable[Address]] = None, + change_address: Optional[Address] = None, + min_weight_magnitude: Optional[int] = None, + security_level: Optional[int] = None, + ) -> Dict: """ Prepares a set of transfers and creates the bundle, then attaches the bundle to the Tangle, and broadcasts and stores the @@ -1549,17 +1557,21 @@ async def send_transfer( min_weight_magnitude = self.default_min_weight_magnitude return await extended.SendTransferCommand(self.adapter)( - seed=self.seed, - depth=depth, - transfers=transfers, - inputs=inputs, - changeAddress=change_address, - minWeightMagnitude=min_weight_magnitude, - securityLevel=security_level, + seed=self.seed, + depth=depth, + transfers=transfers, + inputs=inputs, + changeAddress=change_address, + minWeightMagnitude=min_weight_magnitude, + securityLevel=security_level, ) - async def send_trytes(self, trytes, depth=3, min_weight_magnitude=None): - # type: (Iterable[TransactionTrytes], int, Optional[int]) -> dict + async def send_trytes( + self, + trytes: Iterable[TransactionTrytes], + depth: int = 3, + min_weight_magnitude: Optional[int] = None + ) -> Dict: """ Attaches transaction trytes to the Tangle, then broadcasts and stores them. @@ -1593,13 +1605,12 @@ async def send_trytes(self, trytes, depth=3, min_weight_magnitude=None): min_weight_magnitude = self.default_min_weight_magnitude return await extended.SendTrytesCommand(self.adapter)( - trytes=trytes, - depth=depth, - minWeightMagnitude=min_weight_magnitude, + trytes=trytes, + depth=depth, + minWeightMagnitude=min_weight_magnitude, ) - async def is_reattachable(self, addresses): - # type: (Iterable[Address]) -> dict + async def is_reattachable(self, addresses: Iterable[Address]) -> Dict: """ This API function helps you to determine whether you should replay a transaction or make a new one (either with the same @@ -1626,11 +1637,10 @@ async def is_reattachable(self, addresses): """ return await extended.IsReattachableCommand(self.adapter)( - addresses=addresses + addresses=addresses ) - async def traverse_bundle(self, tail_hash): - # type: (TransactionHash) -> dict + async def traverse_bundle(self, tail_hash: TransactionHash) -> Dict: """ Fetches and traverses a bundle from the Tangle given a tail transaction hash. @@ -1654,5 +1664,5 @@ async def traverse_bundle(self, tail_hash): """ return await extended.TraverseBundleCommand(self.adapter)( - transaction=tail_hash - ) \ No newline at end of file + transaction=tail_hash + ) diff --git a/iota/bin/__init__.py b/iota/bin/__init__.py index 5606d2e..917d2a7 100644 --- a/iota/bin/__init__.py +++ b/iota/bin/__init__.py @@ -4,7 +4,7 @@ from getpass import getpass as secure_input from io import StringIO from sys import exit -from typing import Any, Optional, Text +from typing import Any, Optional, Text, Dict from iota import Iota, __version__ from iota.crypto.types import Seed @@ -23,8 +23,12 @@ class IotaCommandLineApp(object, metaclass=ABCMeta): Whether the command requires the user to provide a seed. """ - def __init__(self, stdout=sys.stdout, stderr=sys.stderr, stdin=sys.stdin): - # type: (StringIO, StringIO, StringIO) -> None + def __init__( + self, + stdout: StringIO = sys.stdout, + stderr: StringIO = sys.stderr, + stdin: StringIO = sys.stdin + ) -> None: super(IotaCommandLineApp, self).__init__() self.stdout = stdout @@ -32,8 +36,7 @@ def __init__(self, stdout=sys.stdout, stderr=sys.stderr, stdin=sys.stdin): self.stdin = stdin @abstract_method - def execute(self, api, **arguments): - # type: (Iota, **Any) -> Optional[int] + def execute(self, api: Iota, **arguments: Any) -> Optional[int]: """ Executes the command and (optionally) returns an exit code (used by the shell to determine if the application exited cleanly). @@ -48,14 +51,13 @@ def execute(self, api, **arguments): 'Not implemented in {cls}.'.format(cls=type(self).__name__), ) - def main(self): + def main(self) -> None: """ Executes the command from :py:data:`sys.argv` and exits. """ exit(self.run_from_argv()) - def run_from_argv(self, argv=None): - # type: (Optional[tuple]) -> int + def run_from_argv(self, argv: Optional[tuple] = None) -> int: """ Executes the command from a collection of arguments (e.g., :py:data`sys.argv`) and returns the exit code. @@ -71,8 +73,7 @@ def run_from_argv(self, argv=None): return exit_code - def parse_argv(self, argv=None): - # type: (Optional[tuple]) -> dict + def parse_argv(self, argv: Optional[tuple] = None) -> Dict: """ Parses arguments for the command. @@ -100,8 +101,7 @@ def parse_argv(self, argv=None): return arguments - def create_argument_parser(self): - # type: () -> ArgumentParser + def create_argument_parser(self) -> ArgumentParser: """ Returns the argument parser that will be used to interpret arguments and options from argv. @@ -145,8 +145,7 @@ def create_argument_parser(self): return parser @staticmethod - def seed_from_filepath(filepath): - # type: (Text) -> Seed + def seed_from_filepath(filepath: Text) -> Seed: """ Reads a seed from the first line of a text file. @@ -156,8 +155,7 @@ def seed_from_filepath(filepath): return Seed(f_.readline().strip()) @staticmethod - def prompt_for_seed(): - # type: () -> Seed + def prompt_for_seed() -> Seed: """ Prompts the user to enter their seed via stdin. """ diff --git a/iota/bin/repl.py b/iota/bin/repl.py index 3fa8acf..25b36af 100755 --- a/iota/bin/repl.py +++ b/iota/bin/repl.py @@ -8,6 +8,8 @@ # Import all IOTA symbols into module scope, so that it's more # convenient for the user. +from typing import Any + from iota import * from iota.adapter import resolve_adapter from iota.adapter.wrappers import RoutingWrapper @@ -19,8 +21,7 @@ class IotaReplCommandLineApp(IotaCommandLineApp): Creates an IOTA API instance and drops the user into a REPL. """ - def execute(self, api, **arguments): - # type: (Iota, ...) -> int + def execute(self, api: Iota, **arguments: Any) -> int: debug_requests = arguments['debug_requests'] pow_uri = arguments['pow_uri'] @@ -51,8 +52,7 @@ def execute(self, api, **arguments): return 0 - def create_argument_parser(self): - # type: () -> ArgumentParser + def create_argument_parser(self) -> ArgumentParser: parser = super(IotaReplCommandLineApp, self).create_argument_parser() parser.add_argument( @@ -74,8 +74,7 @@ def create_argument_parser(self): return parser @staticmethod - def _start_repl(api): - # type: (Iota) -> None + def _start_repl(api: Iota) -> None: """ Starts the REPL. """ diff --git a/iota/codecs.py b/iota/codecs.py index 6a670ab..3d4a553 100644 --- a/iota/codecs.py +++ b/iota/codecs.py @@ -1,4 +1,5 @@ from codecs import Codec, CodecInfo, register as lookup_function +from typing import Union, Text, Tuple from warnings import warn from iota.exceptions import with_context @@ -51,25 +52,26 @@ class AsciiTrytesCodec(Codec): """ @classmethod - def get_codec_info(cls): + def get_codec_info(cls) -> CodecInfo: """ Returns information used by the codecs library to configure the codec for use. """ codec = cls() + # In Python 2, all codecs are made equal. + # In Python 3, some codecs are more equal than others codec_info = { 'encode': codec.encode, 'decode': codec.decode, + '_is_text_encoding': False } - # In Python 2, all codecs are made equal. - # In Python 3, some codecs are more equal than others - codec_info['_is_text_encoding'] = False - return CodecInfo(**codec_info) - def encode(self, input, errors='strict'): + def encode(self, + input: Union[memoryview, bytes, bytearray], + errors: Text = 'strict') -> Tuple[bytes, int]: """ Encodes a byte string into trytes. """ @@ -103,7 +105,9 @@ def encode(self, input, errors='strict'): return bytes(trytes), len(input) - def decode(self, input, errors='strict'): + def decode(self, + input: Union[memoryview, bytes, bytearray], + errors: Text = 'strict') -> Tuple[bytes, int]: """ Decodes a tryte string into bytes. """ diff --git a/iota/commands/__init__.py b/iota/commands/__init__.py index e789cd8..56f8036 100644 --- a/iota/commands/__init__.py +++ b/iota/commands/__init__.py @@ -24,22 +24,20 @@ class BaseCommand(object, metaclass=ABCMeta): """ An API command ready to send to the node. """ - command = None # Text + command: Text = None - def __init__(self, adapter): - # type: (BaseAdapter) -> None + def __init__(self, adapter: BaseAdapter) -> None: """ :param adapter: Adapter that will send request payloads to the node. """ - self.adapter = adapter + self.adapter = adapter - self.called = False - self.request = None # type: dict - self.response = None # type: dict + self.called: bool = False + self.request: Dict = None + self.response: Dict = None - async def __call__(self, **kwargs): - # type: (**Any) -> dict + async def __call__(self, **kwargs: Any) -> Dict: """ Sends the command to the node. """ @@ -69,17 +67,15 @@ async def __call__(self, **kwargs): return self.response - def reset(self): - # type: () -> None + def reset(self) -> None: """ Resets the command, allowing it to be called again. """ - self.called = False - self.request = None # type: dict - self.response = None # type: dict + self.called = False + self.request = None + self.response = None - async def _execute(self, request): - # type: (dict) -> dict + async def _execute(self, request: Dict) -> Dict: """ Sends the request object to the adapter and returns the response. @@ -90,8 +86,7 @@ async def _execute(self, request): return await self.adapter.send_request(request) @abstract_method - def _prepare_request(self, request): - # type: (dict) -> Optional[dict] + def _prepare_request(self, request: Dict) -> Optional[Dict]: """ Modifies the request before sending it to the node. @@ -109,8 +104,7 @@ def _prepare_request(self, request): ) @abstract_method - def _prepare_response(self, response): - # type: (dict) -> Optional[dict] + def _prepare_response(self, response: Dict) -> Optional[Dict]: """ Modifies the response from the node. @@ -132,8 +126,7 @@ class CustomCommand(BaseCommand): Useful for executing experimental/undocumented commands. """ - def __init__(self, adapter, command): - # type: (BaseAdapter, Text) -> None + def __init__(self, adapter: BaseAdapter, command: Text) -> None: super(CustomCommand, self).__init__(adapter) self.command = command @@ -198,8 +191,7 @@ class FilterCommand(BaseCommand, metaclass=ABCMeta): """ @abstract_method - def get_request_filter(self): - # type: () -> Optional[RequestFilter] + def get_request_filter(self) -> Optional[RequestFilter]: """ Returns the filter that should be applied to the request (if any). @@ -212,8 +204,7 @@ def get_request_filter(self): ) @abstract_method - def get_response_filter(self): - # type: () -> Optional[ResponseFilter] + def get_response_filter(self) -> Optional[ResponseFilter]: """ Returns the filter that should be applied to the response (if any). @@ -225,14 +216,14 @@ def get_response_filter(self): 'Not implemented in {cls}.'.format(cls=type(self).__name__), ) - def _prepare_request(self, request): + def _prepare_request(self, request: Dict) -> Dict: return self._apply_filter( value = request, filter_ = self.get_request_filter(), failure_message = 'Request failed validation', ) - def _prepare_response(self, response): + def _prepare_response(self, response: Dict) -> Dict: return self._apply_filter( value = response, filter_ = self.get_response_filter(), @@ -240,8 +231,11 @@ def _prepare_response(self, response): ) @staticmethod - def _apply_filter(value, filter_, failure_message): - # type: (dict, Optional[f.BaseFilter], Text) -> dict + def _apply_filter( + value: Dict, + filter_: Optional[f.BaseFilter], + failure_message: Text + ) -> Dict: """ Applies a filter to a value. If the value does not pass the filter, an exception will be raised with lots of contextual info diff --git a/iota/commands/core/add_neighbors.py b/iota/commands/core/add_neighbors.py index d7d418a..d9634c5 100644 --- a/iota/commands/core/add_neighbors.py +++ b/iota/commands/core/add_neighbors.py @@ -24,7 +24,7 @@ def get_response_filter(self): class AddNeighborsRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(AddNeighborsRequestFilter, self).__init__({ 'uris': f.Required | f.Array | f.FilterRepeater(f.Required | NodeUri), diff --git a/iota/commands/core/attach_to_tangle.py b/iota/commands/core/attach_to_tangle.py index 6ad7510..64715d0 100644 --- a/iota/commands/core/attach_to_tangle.py +++ b/iota/commands/core/attach_to_tangle.py @@ -1,3 +1,5 @@ +from typing import Dict + import filters as f from iota import TransactionHash, TransactionTrytes @@ -24,7 +26,7 @@ def get_request_filter(self): def get_response_filter(self): return AttachToTangleResponseFilter() - async def _execute(self, request): + async def _execute(self, request: Dict) -> Dict: if self.adapter.local_pow is True: from pow import ccurl_interface powed_trytes = ccurl_interface.attach_to_tangle( @@ -37,8 +39,9 @@ async def _execute(self, request): else: return await super(FilterCommand, self)._execute(request) + class AttachToTangleRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(AttachToTangleRequestFilter, self).__init__({ 'branchTransaction': f.Required | Trytes(TransactionHash), 'trunkTransaction': f.Required | Trytes(TransactionHash), @@ -57,7 +60,7 @@ def __init__(self): class AttachToTangleResponseFilter(ResponseFilter): - def __init__(self): + def __init__(self) -> None: super(AttachToTangleResponseFilter, self).__init__({ 'trytes': f.FilterRepeater( diff --git a/iota/commands/core/broadcast_transactions.py b/iota/commands/core/broadcast_transactions.py index 7bd9eae..fae4607 100644 --- a/iota/commands/core/broadcast_transactions.py +++ b/iota/commands/core/broadcast_transactions.py @@ -25,7 +25,7 @@ def get_response_filter(self): class BroadcastTransactionsRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(BroadcastTransactionsRequestFilter, self).__init__({ 'trytes': StringifiedTrytesArray(TransactionTrytes) | f.Required, }) diff --git a/iota/commands/core/check_consistency.py b/iota/commands/core/check_consistency.py index d835ec8..ca0da90 100644 --- a/iota/commands/core/check_consistency.py +++ b/iota/commands/core/check_consistency.py @@ -25,7 +25,7 @@ def get_response_filter(self): class CheckConsistencyRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(CheckConsistencyRequestFilter, self).__init__({ 'tails': f.Required | diff --git a/iota/commands/core/find_transactions.py b/iota/commands/core/find_transactions.py index 0476e38..6b2f701 100644 --- a/iota/commands/core/find_transactions.py +++ b/iota/commands/core/find_transactions.py @@ -1,3 +1,5 @@ +from typing import Dict + import filters as f from iota import BundleHash, Tag, TransactionHash @@ -31,7 +33,7 @@ class FindTransactionsRequestFilter(RequestFilter): CODE_NO_SEARCH_VALUES: 'No search values specified.', } - def __init__(self): + def __init__(self) -> None: super(FindTransactionsRequestFilter, self).__init__( { 'addresses': @@ -53,9 +55,9 @@ def __init__(self): ) def _apply(self, value): - value = super(FindTransactionsRequestFilter, self)._apply( + value: Dict = super(FindTransactionsRequestFilter, self)._apply( value - ) # type: dict + ) if self._has_errors: return value @@ -78,7 +80,7 @@ def _apply(self, value): class FindTransactionsResponseFilter(ResponseFilter): - def __init__(self): + def __init__(self) -> None: super(FindTransactionsResponseFilter, self).__init__({ 'hashes': f.FilterRepeater( diff --git a/iota/commands/core/get_balances.py b/iota/commands/core/get_balances.py index e75fc84..661776c 100644 --- a/iota/commands/core/get_balances.py +++ b/iota/commands/core/get_balances.py @@ -1,3 +1,5 @@ +from typing import Dict + import filters as f from iota import TransactionHash @@ -25,7 +27,7 @@ def get_response_filter(self): class GetBalancesRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(GetBalancesRequestFilter, self).__init__( { 'addresses': @@ -50,9 +52,9 @@ def __init__(self): ) def _apply(self, value): - value = super(GetBalancesRequestFilter, self)._apply( + value: Dict = super(GetBalancesRequestFilter, self)._apply( value - ) # type: dict + ) if self._has_errors: return value @@ -69,7 +71,7 @@ def _apply(self, value): class GetBalancesResponseFilter(ResponseFilter): - def __init__(self): + def __init__(self) -> None: super(GetBalancesResponseFilter, self).__init__({ 'balances': f.Array | f.FilterRepeater(f.Int), diff --git a/iota/commands/core/get_inclusion_states.py b/iota/commands/core/get_inclusion_states.py index 0983b9b..c7f70ea 100644 --- a/iota/commands/core/get_inclusion_states.py +++ b/iota/commands/core/get_inclusion_states.py @@ -25,7 +25,7 @@ def get_response_filter(self): class GetInclusionStatesRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(GetInclusionStatesRequestFilter, self).__init__( { # Required parameters. diff --git a/iota/commands/core/get_missing_transactions.py b/iota/commands/core/get_missing_transactions.py index a5409e7..820a0bf 100644 --- a/iota/commands/core/get_missing_transactions.py +++ b/iota/commands/core/get_missing_transactions.py @@ -25,14 +25,14 @@ def get_response_filter(self): class GetMissingTransactionsRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: # ``getMissingTransactions`` does not accept any parameters. # Using a filter here just to enforce that the request is empty. super(GetMissingTransactionsRequestFilter, self).__init__({}) class GetMissingTransactionsResponseFilter(ResponseFilter): - def __init__(self): + def __init__(self) -> None: super(GetMissingTransactionsResponseFilter, self).__init__({ 'hashes': f.FilterRepeater( diff --git a/iota/commands/core/get_neighbors.py b/iota/commands/core/get_neighbors.py index 3ac6542..bda4172 100644 --- a/iota/commands/core/get_neighbors.py +++ b/iota/commands/core/get_neighbors.py @@ -21,7 +21,7 @@ def get_response_filter(self): class GetNeighborsRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: # ``getNeighbors`` does not accept any parameters. # Using a filter here just to enforce that the request is empty. super(GetNeighborsRequestFilter, self).__init__({}) diff --git a/iota/commands/core/get_node_api_configuration.py b/iota/commands/core/get_node_api_configuration.py index 0647a92..1b36e2d 100644 --- a/iota/commands/core/get_node_api_configuration.py +++ b/iota/commands/core/get_node_api_configuration.py @@ -21,7 +21,7 @@ def get_response_filter(self): class GetNodeAPIConfigurationRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: # ``getNodeAPIConfiguration`` does not accept any parameters. # Using a filter here just to enforce that the request is empty. super(GetNodeAPIConfigurationRequestFilter, self).__init__({}) diff --git a/iota/commands/core/get_node_info.py b/iota/commands/core/get_node_info.py index ca5c0d6..14f9471 100644 --- a/iota/commands/core/get_node_info.py +++ b/iota/commands/core/get_node_info.py @@ -25,14 +25,14 @@ def get_response_filter(self): class GetNodeInfoRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: # ``getNodeInfo`` does not accept any parameters. # Using a filter here just to enforce that the request is empty. super(GetNodeInfoRequestFilter, self).__init__({}) class GetNodeInfoResponseFilter(ResponseFilter): - def __init__(self): + def __init__(self) -> None: super(GetNodeInfoResponseFilter, self).__init__({ 'latestMilestone': f.ByteString(encoding='ascii') | Trytes(TransactionHash), diff --git a/iota/commands/core/get_tips.py b/iota/commands/core/get_tips.py index b77ddbb..77d7375 100644 --- a/iota/commands/core/get_tips.py +++ b/iota/commands/core/get_tips.py @@ -25,14 +25,14 @@ def get_response_filter(self): class GetTipsRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: # ``getTips`` doesn't accept any parameters. # Using a filter here just to enforce that the request is empty. super(GetTipsRequestFilter, self).__init__({}) class GetTipsResponseFilter(ResponseFilter): - def __init__(self): + def __init__(self) -> None: super(GetTipsResponseFilter, self).__init__({ 'hashes': f.Array | f.FilterRepeater( diff --git a/iota/commands/core/get_transactions_to_approve.py b/iota/commands/core/get_transactions_to_approve.py index 929249a..6533e9a 100644 --- a/iota/commands/core/get_transactions_to_approve.py +++ b/iota/commands/core/get_transactions_to_approve.py @@ -1,3 +1,5 @@ +from typing import Dict + import filters as f from iota import TransactionHash @@ -25,7 +27,7 @@ def get_response_filter(self): class GetTransactionsToApproveRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(GetTransactionsToApproveRequestFilter, self).__init__( { 'depth': f.Required | f.Type(int) | f.Min(1), @@ -38,9 +40,9 @@ def __init__(self): }) def _apply(self, value): - value = super(GetTransactionsToApproveRequestFilter, self)._apply( + value: Dict = super(GetTransactionsToApproveRequestFilter, self)._apply( value, - ) # type: dict + ) if self._has_errors: return value @@ -53,7 +55,7 @@ def _apply(self, value): class GetTransactionsToApproveResponseFilter(ResponseFilter): - def __init__(self): + def __init__(self) -> None: super(GetTransactionsToApproveResponseFilter, self).__init__({ 'branchTransaction': f.ByteString(encoding='ascii') | Trytes(TransactionHash), diff --git a/iota/commands/core/get_trytes.py b/iota/commands/core/get_trytes.py index 9c1bd0a..d714b2f 100644 --- a/iota/commands/core/get_trytes.py +++ b/iota/commands/core/get_trytes.py @@ -25,7 +25,7 @@ def get_response_filter(self): class GetTrytesRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(GetTrytesRequestFilter, self).__init__({ 'hashes': StringifiedTrytesArray(TransactionHash) | f.Required, @@ -33,7 +33,7 @@ def __init__(self): class GetTrytesResponseFilter(ResponseFilter): - def __init__(self): + def __init__(self) -> None: super(GetTrytesResponseFilter, self).__init__({ 'trytes': f.Array | f.FilterRepeater( diff --git a/iota/commands/core/interrupt_attaching_to_tangle.py b/iota/commands/core/interrupt_attaching_to_tangle.py index 9c63fde..e6cfb4f 100644 --- a/iota/commands/core/interrupt_attaching_to_tangle.py +++ b/iota/commands/core/interrupt_attaching_to_tangle.py @@ -21,7 +21,7 @@ def get_response_filter(self): class InterruptAttachingToTangleRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: # ``interruptAttachingToTangle`` takes no parameters. # Using a filter here just to enforce that the request is empty. super(InterruptAttachingToTangleRequestFilter, self).__init__({}) diff --git a/iota/commands/core/remove_neighbors.py b/iota/commands/core/remove_neighbors.py index 809cf94..ecd6e72 100644 --- a/iota/commands/core/remove_neighbors.py +++ b/iota/commands/core/remove_neighbors.py @@ -24,7 +24,7 @@ def get_response_filter(self): class RemoveNeighborsRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(RemoveNeighborsRequestFilter, self).__init__({ 'uris': f.Required | f.Array | f.FilterRepeater( f.Required | diff --git a/iota/commands/core/store_transactions.py b/iota/commands/core/store_transactions.py index adf705a..4bab99d 100644 --- a/iota/commands/core/store_transactions.py +++ b/iota/commands/core/store_transactions.py @@ -25,7 +25,7 @@ def get_response_filter(self): class StoreTransactionsRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(StoreTransactionsRequestFilter, self).__init__({ 'trytes': StringifiedTrytesArray(TransactionTrytes) | f.Required, diff --git a/iota/commands/core/were_addresses_spent_from.py b/iota/commands/core/were_addresses_spent_from.py index 8827bda..618ae0d 100644 --- a/iota/commands/core/were_addresses_spent_from.py +++ b/iota/commands/core/were_addresses_spent_from.py @@ -24,7 +24,7 @@ def get_response_filter(self): class WereAddressesSpentFromRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(WereAddressesSpentFromRequestFilter, self).__init__({ 'addresses': f.Required | f.Array | f.FilterRepeater( diff --git a/iota/commands/extended/broadcast_and_store.py b/iota/commands/extended/broadcast_and_store.py index 7054859..07b4bce 100644 --- a/iota/commands/extended/broadcast_and_store.py +++ b/iota/commands/extended/broadcast_and_store.py @@ -1,3 +1,5 @@ +from typing import Dict + from iota.commands import FilterCommand from iota.commands.core.broadcast_transactions import \ BroadcastTransactionsCommand @@ -23,7 +25,7 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request): + async def _execute(self, request: Dict) -> Dict: # Submit the two coroutines to the already running event loop await asyncio.gather( BroadcastTransactionsCommand(self.adapter)(**request), diff --git a/iota/commands/extended/broadcast_bundle.py b/iota/commands/extended/broadcast_bundle.py index c2934b7..e4de3d1 100644 --- a/iota/commands/extended/broadcast_bundle.py +++ b/iota/commands/extended/broadcast_bundle.py @@ -1,3 +1,5 @@ +from typing import Dict + import filters as f from iota.filters import Trytes @@ -27,19 +29,19 @@ def get_response_filter(self): # Return value is filtered before hitting us. pass - async def _execute(self, request): + async def _execute(self, request: Dict) -> Dict: # Given tail hash, fetches the bundle from the tangle # and validates it. - # Returns List[List[TransactionTrytes]] - # (outer list has one item in current implementation) - bundle = await GetBundlesCommand(self.adapter)(transactions=[request['tail_hash']]) + tail_hash: TransactionHash = request['tail_hash'] + bundle = await GetBundlesCommand(self.adapter)(transactions=[tail_hash]) await BroadcastTransactionsCommand(self.adapter)(trytes=bundle[0]) return { 'trytes': bundle[0], } + class BroadcastBundleRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(BroadcastBundleRequestFilter, self).__init__({ 'tail_hash': f.Required | Trytes(TransactionHash), - }) \ No newline at end of file + }) diff --git a/iota/commands/extended/find_transaction_objects.py b/iota/commands/extended/find_transaction_objects.py index 64d8359..24af87d 100644 --- a/iota/commands/extended/find_transaction_objects.py +++ b/iota/commands/extended/find_transaction_objects.py @@ -1,4 +1,4 @@ -from typing import Iterable, List, Optional +from typing import Iterable, List, Optional, Dict from iota import Address, BundleHash, Tag, Transaction, TransactionHash from iota.commands.core import GetTrytesCommand, FindTransactionsCommand @@ -19,15 +19,15 @@ class FindTransactionObjectsCommand(FindTransactionsCommand): def get_response_filter(self): pass - async def _execute(self, request): - bundles = request\ - .get('bundles') # type: Optional[Iterable[BundleHash]] - addresses = request\ - .get('addresses') # type: Optional[Iterable[Address]] - tags = request\ - .get('tags') # type: Optional[Iterable[Tag]] - approvees = request\ - .get('approvees') # type: Optional[Iterable[TransactionHash]] + async def _execute(self, request: Dict) -> Dict: + bundles: Optional[Iterable[BundleHash]] = request\ + .get('bundles') + addresses: Optional[Iterable[Address]] = request\ + .get('addresses') + tags: Optional[Iterable[Tag]] = request\ + .get('tags') + approvees: Optional[Iterable[TransactionHash]] = request\ + .get('approvees') ft_response = await FindTransactionsCommand(adapter=self.adapter)( bundles=bundles, @@ -41,10 +41,10 @@ async def _execute(self, request): if hashes: gt_response = await GetTrytesCommand(adapter=self.adapter)(hashes=hashes) - transactions = list(map( + transactions: List[Transaction] = list(map( Transaction.from_tryte_string, gt_response.get('trytes') or [], - )) # type: List[Transaction] + )) return { 'transactions': transactions, diff --git a/iota/commands/extended/get_account_data.py b/iota/commands/extended/get_account_data.py index 0ee277a..9aa07c7 100644 --- a/iota/commands/extended/get_account_data.py +++ b/iota/commands/extended/get_account_data.py @@ -1,5 +1,5 @@ from operator import attrgetter -from typing import List, Optional +from typing import List, Optional, Dict import filters as f @@ -32,16 +32,16 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request): - inclusion_states = request['inclusionStates'] # type: bool - seed = request['seed'] # type: Seed - start = request['start'] # type: int - stop = request['stop'] # type: Optional[int] - security_level = request['security_level'] # type: Optional[int] + async def _execute(self, request: Dict) -> Dict: + inclusion_states: bool = request['inclusionStates'] + seed: Seed = request['seed'] + start: int = request['start'] + stop: Optional[int] = request['stop'] + security_level: Optional[int] = request['security_level'] if stop is None: - my_addresses = [] # type: List[Address] - my_hashes = [] # type: List[TransactionHash] + my_addresses: List[Address] = [] + my_hashes: List[TransactionHash] = [] async for addy, hashes in iter_used_addresses(self.adapter, seed, start, security_level): my_addresses.append(addy) @@ -91,7 +91,7 @@ class GetAccountDataRequestFilter(RequestFilter): CODE_INTERVAL_TOO_BIG: '``stop`` - ``start`` must be <= {max_interval}', } - def __init__(self): + def __init__(self) -> None: super(GetAccountDataRequestFilter, self).__init__( { # Required parameters. diff --git a/iota/commands/extended/get_bundles.py b/iota/commands/extended/get_bundles.py index 76bb402..e1e65bc 100644 --- a/iota/commands/extended/get_bundles.py +++ b/iota/commands/extended/get_bundles.py @@ -1,3 +1,5 @@ +from typing import Dict, Iterable + import filters as f from iota import BadApiResponse, TransactionHash @@ -27,8 +29,8 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request): - transaction_hashes = request['transactions'] # type: Iterable[TransactionHash] + async def _execute(self, request: Dict) -> Dict: + transaction_hashes: Iterable[TransactionHash] = request['transactions'] async def fetch_and_validate(tx_hash): bundle = (await TraverseBundleCommand(self.adapter)( @@ -60,8 +62,9 @@ async def fetch_and_validate(tx_hash): 'bundles': bundles, } + class GetBundlesRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(GetBundlesRequestFilter, self).__init__({ 'transactions': f.Required | f.Array | f.FilterRepeater( diff --git a/iota/commands/extended/get_inputs.py b/iota/commands/extended/get_inputs.py index 0945c95..f36d785 100644 --- a/iota/commands/extended/get_inputs.py +++ b/iota/commands/extended/get_inputs.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Dict import filters as f @@ -30,12 +30,12 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request): - stop = request['stop'] # type: Optional[int] - seed = request['seed'] # type: Seed - start = request['start'] # type: int - threshold = request['threshold'] # type: Optional[int] - security_level = request['securityLevel'] # int + async def _execute(self, request: Dict) -> Dict: + stop: Optional[int] = request['stop'] + seed: Seed = request['seed'] + start: int = request['start'] + threshold: Optional[int] = request['threshold'] + security_level: int = request['securityLevel'] # Determine the addresses we will be scanning. if stop is None: @@ -115,7 +115,7 @@ class GetInputsRequestFilter(RequestFilter): CODE_INTERVAL_TOO_BIG: '``stop`` - ``start`` must be <= {max_interval}', } - def __init__(self): + def __init__(self) -> None: super(GetInputsRequestFilter, self).__init__( { # These arguments are optional. diff --git a/iota/commands/extended/get_latest_inclusion.py b/iota/commands/extended/get_latest_inclusion.py index 65b7b1a..1e5db6f 100644 --- a/iota/commands/extended/get_latest_inclusion.py +++ b/iota/commands/extended/get_latest_inclusion.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Dict import filters as f @@ -27,8 +27,8 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request): - hashes = request['hashes'] # type: List[TransactionHash] + async def _execute(self, request: Dict) -> Dict: + hashes: List[TransactionHash] = request['hashes'] gni_response = await GetNodeInfoCommand(self.adapter)() @@ -43,7 +43,7 @@ async def _execute(self, request): class GetLatestInclusionRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(GetLatestInclusionRequestFilter, self).__init__({ 'hashes': f.Required | f.Array | f.FilterRepeater( diff --git a/iota/commands/extended/get_new_addresses.py b/iota/commands/extended/get_new_addresses.py index efba67f..5b926a0 100644 --- a/iota/commands/extended/get_new_addresses.py +++ b/iota/commands/extended/get_new_addresses.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Optional, Dict import filters as f @@ -31,12 +31,12 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request): - checksum = request['checksum'] # type: bool - count = request['count'] # type: Optional[int] - index = request['index'] # type: int - security_level = request['securityLevel'] # type: int - seed = request['seed'] # type: Seed + async def _execute(self, request: Dict) -> Dict: + checksum: bool = request['checksum'] + count: Optional[int] = request['count'] + index: int = request['index'] + security_level: int = request['securityLevel'] + seed: Seed = request['seed'] return { 'addresses': @@ -49,8 +49,14 @@ async def _execute(self, request): ), } - async def _find_addresses(self, seed, index, count, security_level, checksum): - # type: (Seed, int, Optional[int], int, bool) -> List[Address] + async def _find_addresses( + self, + seed: Seed, + index: int, + count: Optional[int], + security_level: int, + checksum: bool + ) -> List[Address]: """ Find addresses matching the command parameters. """ @@ -81,7 +87,7 @@ async def _find_addresses(self, seed, index, count, security_level, checksum): class GetNewAddressesRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(GetNewAddressesRequestFilter, self).__init__( { # Everything except ``seed`` is optional. diff --git a/iota/commands/extended/get_transaction_objects.py b/iota/commands/extended/get_transaction_objects.py index be53542..be8412e 100644 --- a/iota/commands/extended/get_transaction_objects.py +++ b/iota/commands/extended/get_transaction_objects.py @@ -1,4 +1,4 @@ -from typing import Iterable, List, Optional +from typing import Iterable, List, Dict import filters as f @@ -26,26 +26,27 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request): - hashes = request\ - .get('hashes') # type: Iterable[TransactionHash] + async def _execute(self, request: Dict) -> Dict: + hashes: Iterable[TransactionHash] = request\ + .get('hashes') transactions = [] if hashes: gt_response = await GetTrytesCommand(adapter=self.adapter)(hashes=hashes) - transactions = list(map( + transactions: List[Transaction] = list(map( Transaction.from_tryte_string, gt_response.get('trytes') or [], - )) # type: List[Transaction] + )) return { 'transactions': transactions, } + class GetTransactionObjectsRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(GetTransactionObjectsRequestFilter, self).__init__({ 'hashes': StringifiedTrytesArray(TransactionHash) | f.Required - }) \ No newline at end of file + }) diff --git a/iota/commands/extended/get_transfers.py b/iota/commands/extended/get_transfers.py index 565fac6..ae36c04 100644 --- a/iota/commands/extended/get_transfers.py +++ b/iota/commands/extended/get_transfers.py @@ -1,5 +1,5 @@ from itertools import chain -from typing import Optional +from typing import Optional, Dict import filters as f @@ -30,11 +30,11 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request): - inclusion_states = request['inclusionStates'] # type: bool - seed = request['seed'] # type: Seed - start = request['start'] # type: int - stop = request['stop'] # type: Optional[int] + async def _execute(self, request: Dict) -> Dict: + inclusion_states: bool = request['inclusionStates'] + seed: Seed = request['seed'] + start: int = request['start'] + stop: Optional[int] = request['stop'] # Determine the addresses we will be scanning, and pull their # transaction hashes. @@ -75,7 +75,7 @@ class GetTransfersRequestFilter(RequestFilter): CODE_INTERVAL_TOO_BIG: '``stop`` - ``start`` must be <= {max_interval}', } - def __init__(self): + def __init__(self) -> None: super(GetTransfersRequestFilter, self).__init__( { # Required parameters. diff --git a/iota/commands/extended/is_promotable.py b/iota/commands/extended/is_promotable.py index 0ff0e41..e401a66 100644 --- a/iota/commands/extended/is_promotable.py +++ b/iota/commands/extended/is_promotable.py @@ -1,3 +1,5 @@ +from typing import Dict, Optional + from iota.commands import FilterCommand, RequestFilter from iota.commands.core import CheckConsistencyCommand, GetTrytesCommand from iota.transaction import Transaction @@ -33,6 +35,7 @@ Calculate current time in milliseconds. """ + class IsPromotableCommand(FilterCommand): """ Determines if a tail transaction is promotable. @@ -47,8 +50,8 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request): - tails = request['tails'] + async def _execute(self, request: Dict) -> Dict: + tails: TransactionHash = request['tails'] # First, check consistency # A transaction is consistent, if: @@ -62,18 +65,18 @@ async def _execute(self, request): if not cc_response['state']: # One or more transactions are inconsistent return { - 'promotable' : False, - 'info' : cc_response['info'], + 'promotable': False, + 'info': cc_response['info'], } transactions = [ Transaction.from_tryte_string(x) for x in - (await GetTrytesCommand(self.adapter)(hashes=tails))['trytes'] + (await GetTrytesCommand(self.adapter)(hashes=tails))['trytes'] ] response = { - 'promotable' : True, - 'info' : [], + 'promotable': True, + 'info': [], } # Check timestamps @@ -89,14 +92,15 @@ async def _execute(self, request): response['promotable'] = response['promotable'] and is_within # If there are no problems, we don't need 'info' field - # Delete info field to make it consistent with check_consistency repsonse. + # Delete info field to make it consistent with check_consistency response. if response['promotable']: del response['info'] return response + class IsPromotableRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(IsPromotableRequestFilter, self).__init__({ 'tails': f.Required | @@ -104,10 +108,14 @@ def __init__(self): f.FilterRepeater(f.Required | Trytes(TransactionHash)), }) -def is_within_depth(attachment_timestamp, now, depth=DEPTH): - # type (int, int, Optiona(int)) -> bool + +def is_within_depth( + attachment_timestamp: int, + now: int, + depth: Optional[int] = DEPTH +): """ Checks if `attachment_timestamp` is within limits of `depth`. """ return attachment_timestamp < now and \ - now - attachment_timestamp < depth * MILESTONE_INTERVAL - ONE_WAY_DELAY \ No newline at end of file + now - attachment_timestamp < depth * MILESTONE_INTERVAL - ONE_WAY_DELAY diff --git a/iota/commands/extended/is_reattachable.py b/iota/commands/extended/is_reattachable.py index 8654b49..95af4a5 100644 --- a/iota/commands/extended/is_reattachable.py +++ b/iota/commands/extended/is_reattachable.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Dict import filters as f @@ -6,7 +6,7 @@ from iota.commands import FilterCommand, RequestFilter, ResponseFilter from iota.commands.extended import FindTransactionObjectsCommand, \ GetLatestInclusionCommand -from iota.filters import Trytes, StringifiedTrytesArray +from iota.filters import StringifiedTrytesArray __all__ = [ 'IsReattachableCommand', @@ -25,8 +25,8 @@ def get_request_filter(self): def get_response_filter(self): return IsReattachableResponseFilter() - async def _execute(self, request): - addresses = request['addresses'] # type: List[Address] + async def _execute(self, request: Dict) -> Dict: + addresses: List[Address] = request['addresses'] # fetch full transaction objects transactions = (await FindTransactionObjectsCommand(adapter=self.adapter)( @@ -62,7 +62,7 @@ async def _execute(self, request): class IsReattachableRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(IsReattachableRequestFilter, self).__init__( { 'addresses': StringifiedTrytesArray(Address) | f.Required, @@ -71,7 +71,7 @@ def __init__(self): class IsReattachableResponseFilter(ResponseFilter): - def __init__(self): + def __init__(self) -> None: super(IsReattachableResponseFilter, self).__init__({ 'reattachable': f.Required | f.Array | f.FilterRepeater(f.Type(bool)), diff --git a/iota/commands/extended/prepare_transfer.py b/iota/commands/extended/prepare_transfer.py index ce25330..d694712 100644 --- a/iota/commands/extended/prepare_transfer.py +++ b/iota/commands/extended/prepare_transfer.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Optional, Dict import filters as f @@ -32,15 +32,15 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request): + async def _execute(self, request: Dict) -> Dict: # Required parameters. - seed = request['seed'] # type: Seed - bundle = ProposedBundle(request['transfers']) + seed: Seed = request['seed'] + bundle: ProposedBundle = ProposedBundle(request['transfers']) # Optional parameters. - change_address = request.get('changeAddress') # type: Optional[Address] - proposed_inputs = request.get('inputs') # type: Optional[List[Address]] - security_level = request['securityLevel'] # type: int + change_address: Optional[Address] = request.get('changeAddress') + proposed_inputs: Optional[List[Address]] = request.get('inputs') + security_level: int = request['securityLevel'] want_to_spend = bundle.balance if want_to_spend > 0: @@ -60,7 +60,7 @@ async def _execute(self, request): # Inputs provided. Check to make sure we have # sufficient balance. available_to_spend = 0 - confirmed_inputs = [] # type: List[Address] + confirmed_inputs: List[Address] = [] gb_response = await GetBalancesCommand(self.adapter)( addresses=[i.address for i in proposed_inputs], @@ -121,7 +121,7 @@ async def _execute(self, request): class PrepareTransferRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(PrepareTransferRequestFilter, self).__init__( { # Required parameters. diff --git a/iota/commands/extended/promote_transaction.py b/iota/commands/extended/promote_transaction.py index dcdbf72..210d3e7 100644 --- a/iota/commands/extended/promote_transaction.py +++ b/iota/commands/extended/promote_transaction.py @@ -1,3 +1,5 @@ +from typing import Dict + import filters as f from iota import Address, BadApiResponse, ProposedTransaction, TransactionHash @@ -26,10 +28,10 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request): - depth = request['depth'] # type: int - min_weight_magnitude = request['minWeightMagnitude'] # type: int - transaction = request['transaction'] # type: TransactionHash + async def _execute(self, request: Dict) -> Dict: + depth: int = request['depth'] + min_weight_magnitude: int = request['minWeightMagnitude'] + transaction: TransactionHash = request['transaction'] cc_response = await CheckConsistencyCommand(self.adapter)(tails=[transaction]) if cc_response['state'] is False: @@ -53,7 +55,7 @@ async def _execute(self, request): class PromoteTransactionRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(PromoteTransactionRequestFilter, self).__init__({ 'depth': f.Required | f.Type(int) | f.Min(1), 'transaction': f.Required | Trytes(TransactionHash), diff --git a/iota/commands/extended/replay_bundle.py b/iota/commands/extended/replay_bundle.py index 5c1f204..254d22c 100644 --- a/iota/commands/extended/replay_bundle.py +++ b/iota/commands/extended/replay_bundle.py @@ -1,3 +1,5 @@ +from typing import Dict + import filters as f from iota import Bundle, TransactionHash @@ -25,16 +27,16 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request): - depth = request['depth'] # type: int - min_weight_magnitude = request['minWeightMagnitude'] # type: int - transaction = request['transaction'] # type: TransactionHash + async def _execute(self, request: Dict) -> Dict: + depth: int = request['depth'] + min_weight_magnitude: int = request['minWeightMagnitude'] + transaction: TransactionHash = request['transaction'] gb_response = await GetBundlesCommand(self.adapter)(transactions=[transaction]) # Note that we only replay the first bundle returned by # ``getBundles``. - bundle = gb_response['bundles'][0] # type: Bundle + bundle: Bundle = gb_response['bundles'][0] return await SendTrytesCommand(self.adapter)( depth=depth, @@ -44,7 +46,7 @@ async def _execute(self, request): class ReplayBundleRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(ReplayBundleRequestFilter, self).__init__({ 'depth': f.Required | f.Type(int) | f.Min(1), 'transaction': f.Required | Trytes(TransactionHash), diff --git a/iota/commands/extended/send_transfer.py b/iota/commands/extended/send_transfer.py index 347148d..1d36fc7 100644 --- a/iota/commands/extended/send_transfer.py +++ b/iota/commands/extended/send_transfer.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Optional, Dict import filters as f @@ -28,15 +28,15 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request): - change_address = request['changeAddress'] # type: Optional[Address] - depth = request['depth'] # type: int - inputs = request['inputs'] # type: Optional[List[Address]] - min_weight_magnitude = request['minWeightMagnitude'] # type: int - seed = request['seed'] # type: Seed - transfers = request['transfers'] # type: List[ProposedTransaction] - reference = request['reference'] # type: Optional[TransactionHash] - security_level = request['securityLevel'] # int + async def _execute(self, request: Dict) -> Dict: + change_address: Optional[Address] = request['changeAddress'] + depth: int = request['depth'] + inputs: Optional[List[Address]] = request['inputs'] + min_weight_magnitude: int = request['minWeightMagnitude'] + seed: Seed = request['seed'] + transfers: List[ProposedTransaction] = request['transfers'] + reference: Optional[TransactionHash] = request['reference'] + security_level: int = request['securityLevel'] pt_response = await PrepareTransferCommand(self.adapter)( changeAddress=change_address, @@ -59,7 +59,7 @@ async def _execute(self, request): class SendTransferRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(SendTransferRequestFilter, self).__init__( { # Required parameters. diff --git a/iota/commands/extended/send_trytes.py b/iota/commands/extended/send_trytes.py index 0c2bbea..9783eab 100644 --- a/iota/commands/extended/send_trytes.py +++ b/iota/commands/extended/send_trytes.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Optional, Dict import filters as f @@ -29,11 +29,11 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request): - depth = request['depth'] # type: int - min_weight_magnitude = request['minWeightMagnitude'] # type: int - trytes = request['trytes'] # type: List[TryteString] - reference = request['reference'] # type: Optional[TransactionHash] + async def _execute(self, request: Dict) -> Dict: + depth: int = request['depth'] + min_weight_magnitude: int = request['minWeightMagnitude'] + trytes: List[TryteString] = request['trytes'] + reference: Optional[TransactionHash] = request['reference'] # Call ``getTransactionsToApprove`` to locate trunk and branch # transactions so that we can attach the bundle to the Tangle. @@ -61,7 +61,7 @@ async def _execute(self, request): class SendTrytesRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(SendTrytesRequestFilter, self).__init__({ 'depth': f.Required | f.Type(int) | f.Min(1), diff --git a/iota/commands/extended/traverse_bundle.py b/iota/commands/extended/traverse_bundle.py index 12f43d9..69ad9e5 100644 --- a/iota/commands/extended/traverse_bundle.py +++ b/iota/commands/extended/traverse_bundle.py @@ -1,8 +1,8 @@ -from typing import List, Optional +from typing import List, Optional, Dict import filters as f -from iota import BadApiResponse, BundleHash, Transaction, \ +from iota import BadApiResponse, Transaction, \ TransactionHash, TryteString, Bundle, TransactionTrytes from iota.commands import FilterCommand, RequestFilter from iota.commands.core.get_trytes import GetTrytesCommand @@ -28,18 +28,22 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request): - txn_hash = request['transaction'] # type: TransactionHash + async def _execute(self, request: Dict) -> Dict: + txn_hash: TransactionHash = request['transaction'] bundle = Bundle(await self._traverse_bundle(txn_hash, None)) # No bundle validation return { - 'bundles' : [bundle] + 'bundles': [bundle] } - async def _traverse_bundle(self, txn_hash, target_bundle_hash): + async def _traverse_bundle( + self, + txn_hash: TransactionHash, + target_bundle_hash: Optional[TransactionHash] + ) -> List[Transaction]: """ Recursively traverse the Tangle, collecting transactions until we hit a new bundle. @@ -47,9 +51,9 @@ async def _traverse_bundle(self, txn_hash, target_bundle_hash): This method is (usually) faster than ``findTransactions``, and it ensures we don't collect transactions from replayed bundles. """ - trytes =(await GetTrytesCommand(self.adapter)( + trytes: List[TryteString] = (await GetTrytesCommand(self.adapter)( hashes=[txn_hash]) - )['trytes'] # type: List[TryteString] + )['trytes'] # If no tx was found by the node for txn_hash, it returns 9s, # so we check here if it returned all 9s trytes. @@ -100,8 +104,9 @@ async def _traverse_bundle(self, txn_hash, target_bundle_hash): target_bundle_hash ) + class TraverseBundleRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(TraverseBundleRequestFilter, self).__init__({ 'transaction': f.Required | Trytes(TransactionHash), }) diff --git a/iota/commands/extended/utils.py b/iota/commands/extended/utils.py index 0380e7a..e68353b 100644 --- a/iota/commands/extended/utils.py +++ b/iota/commands/extended/utils.py @@ -1,4 +1,4 @@ -from typing import Generator, Iterable, List, Optional, Tuple +from typing import Iterable, List, Optional, Tuple from iota import Address, Bundle, Transaction, \ TransactionHash, TransactionTrytes, BadApiResponse @@ -17,12 +17,13 @@ async def iter_used_addresses( - adapter, # type: BaseAdapter - seed, # type: Seed - start, # type: int - security_level=None, # type: Optional[int] -): - # type: (...) -> Generator[Tuple[Address, List[TransactionHash]], None, None] + adapter: BaseAdapter, + seed: Seed, + start: int, + security_level: Optional[int] = None, + # 'typing' only supports AsyncGenerator from python 3.6.1, so put it + # as string literal here. +) -> 'AsyncGenerator[Tuple[Address, List[TransactionHash]], None]': """ Scans the Tangle for used addresses. A used address is an address that was spent from or has a transaction. @@ -58,11 +59,10 @@ async def iter_used_addresses( async def get_bundles_from_transaction_hashes( - adapter, - transaction_hashes, - inclusion_states, -): - # type: (BaseAdapter, Iterable[TransactionHash], bool) -> List[Bundle] + adapter: BaseAdapter, + transaction_hashes: Iterable[TransactionHash], + inclusion_states: bool, +) -> List[Bundle]: """ Given a set of transaction hashes, returns the corresponding bundles, sorted by tail transaction timestamp. @@ -91,10 +91,10 @@ async def get_bundles_from_transaction_hashes( 'returned_transaction_trytes': tx_trytes, }, ) - all_transactions = list(map( + all_transactions: List[Transaction] = list(map( Transaction.from_tryte_string, gt_response['trytes'], - )) # type: List[Transaction] + )) for txn in all_transactions: if txn.is_tail: @@ -131,9 +131,9 @@ async def get_bundles_from_transaction_hashes( txn.is_confirmed = gli_response['states'].get(txn.hash) # Find the bundles for each transaction. - txn_bundles = (await GetBundlesCommand(adapter)( + txn_bundles: List[Bundle] = (await GetBundlesCommand(adapter)( transactions=[txn.hash for txn in tail_transactions] - ))['bundles'] # type: List[Bundle] + ))['bundles'] if inclusion_states: for bundle, txn in zip(txn_bundles, tail_transactions): diff --git a/iota/crypto/addresses.py b/iota/crypto/addresses.py index e3703cd..f7bbf12 100644 --- a/iota/crypto/addresses.py +++ b/iota/crypto/addresses.py @@ -60,27 +60,29 @@ class AddressGenerator(Iterable[Address]): def __init__( self, - seed, - security_level=DEFAULT_SECURITY_LEVEL, - checksum=False, - ): - # type: (TrytesCompatible, int, bool) -> None + seed: TrytesCompatible, + security_level: int = DEFAULT_SECURITY_LEVEL, + checksum: bool = False, + ) -> None: super(AddressGenerator, self).__init__() self.security_level = security_level self.checksum = checksum self.seed = Seed(seed) - def __iter__(self): - # type: () -> Generator[Address, None, None] + def __iter__(self) -> Generator[Address, None, None]: """ Returns a generator for creating new addresses, starting at index 0 and potentially continuing on forever. """ return self.create_iterator() - def get_addresses(self, start, count=1, step=1): - # type: (int, int, int) -> List[Address] + def get_addresses( + self, + start: int, + count: int = 1, + step: int = 1 + ) -> List[Address]: """ Generates and returns one or more addresses at the specified index(es). @@ -154,8 +156,11 @@ def get_addresses(self, start, count=1, step=1): return addresses - def create_iterator(self, start=0, step=1): - # type: (int, int) -> Generator[Address, None, None] + def create_iterator( + self, + start: int = 0, + step: int = 1 + ) -> Generator[Address, None, None]: """ Creates an iterator that can be used to progressively generate new addresses. @@ -194,12 +199,11 @@ def create_iterator(self, start=0, step=1): yield self._generate_address(key_iterator) @staticmethod - def address_from_digest(digest): - # type: (Digest) -> Address + def address_from_digest(digest: Digest) -> Address: """ Generates an address from a private key digest. """ - address_trits = [0] * (Address.LEN * TRITS_PER_TRYTE) # type: List[int] + address_trits: List[int] = [0] * (Address.LEN * TRITS_PER_TRYTE) sponge = Kerl() sponge.absorb(digest.as_trits()) @@ -212,8 +216,7 @@ def address_from_digest(digest): security_level=digest.security_level, ) - def _generate_address(self, key_iterator): - # type: (KeyIterator) -> Address + def _generate_address(self, key_iterator: KeyIterator) -> Address: """ Generates a new address. @@ -229,13 +232,12 @@ def _generate_address(self, key_iterator): return self.address_from_digest(self._get_digest(key_iterator)) @staticmethod - def _get_digest(key_iterator): - # type: (KeyIterator) -> Digest + def _get_digest(key_iterator: KeyIterator) -> Digest: """ Extracts parameters for :py:meth:`address_from_digest`. Split into a separate method so that it can be mocked during unit tests. """ - private_key = next(key_iterator) # type: PrivateKey + private_key: PrivateKey = next(key_iterator) return private_key.get_digest() diff --git a/iota/crypto/kerl/conv.py b/iota/crypto/kerl/conv.py index 109969b..20d0a0c 100644 --- a/iota/crypto/kerl/conv.py +++ b/iota/crypto/kerl/conv.py @@ -1,7 +1,10 @@ +from typing import Dict, Text, List + + BYTE_HASH_LENGTH = 48 TRIT_HASH_LENGTH = 243 -tryte_table = { +tryte_table: Dict[Text, List[int]] = { '9': [0, 0, 0], # 0 'A': [1, 0, 0], # 1 'B': [-1, 1, 0], # 2 @@ -35,7 +38,7 @@ trit_table = {tuple(v): k for k, v in tryte_table.items()} -def trytes_to_trits(trytes): +def trytes_to_trits(trytes: Text) -> List[int]: trits = [] for tryte in trytes: trits.extend(tryte_table[tryte]) @@ -43,7 +46,7 @@ def trytes_to_trits(trytes): return trits -def trits_to_trytes(trits): +def trits_to_trytes(trits: List[int]) -> Text: trytes = [] trits_chunks = [trits[i:i + 3] for i in range(0, len(trits), 3)] @@ -53,19 +56,19 @@ def trits_to_trytes(trits): return ''.join(trytes) -def convertToTrits(bytes_k): +def convertToTrits(bytes_k: List[int]) -> List[int]: bigInt = convertBytesToBigInt(bytes_k) trits = convertBigintToBase(bigInt, 3, TRIT_HASH_LENGTH) return trits -def convertToBytes(trits): +def convertToBytes(trits: List[int]) -> List[int]: bigInt = convertBaseToBigint(trits, 3) bytes_k = convertBigintToBytes(bigInt) return bytes_k -def convertBytesToBigInt(ba): +def convertBytesToBigInt(ba: List[int]) -> int: # copy of array bytesArray = list(map(lambda x: x, ba)) @@ -88,7 +91,7 @@ def convertBytesToBigInt(ba): enumerate(reversed(bytesArray))) * signum -def convertBigintToBytes(big): +def convertBigintToBytes(big: int) -> List[int]: bytesArrayTemp = [(abs(big) >> pos * 8) % (1 << 8) for pos in range(48)] @@ -112,7 +115,7 @@ def convertBigintToBytes(big): return bytesArray -def convertBaseToBigint(array, base): +def convertBaseToBigint(array: List[int], base: int) -> int: bigint = 0 for i in range(len(array)): @@ -121,7 +124,7 @@ def convertBaseToBigint(array, base): return bigint -def convertBigintToBase(bigInt, base, length): +def convertBigintToBase(bigInt: int, base: int, length: int) -> List[int]: result = [] is_negative = bigInt < 0 @@ -147,7 +150,7 @@ def convertBigintToBase(bigInt, base, length): return result -def convert_sign(byte): +def convert_sign(byte: int) -> int: """ Convert between signed and unsigned bytes. """ diff --git a/iota/crypto/kerl/pykerl.py b/iota/crypto/kerl/pykerl.py index 365e022..d4bb8f1 100644 --- a/iota/crypto/kerl/pykerl.py +++ b/iota/crypto/kerl/pykerl.py @@ -14,13 +14,17 @@ class Kerl(object): - k = None # type: keccak_384 + k: keccak_384 = None - def __init__(self): + def __init__(self) -> None: self.reset() - def absorb(self, trits, offset=0, length=None): - # type: (MutableSequence[int], int, Optional[int]) -> None + def absorb( + self, + trits: MutableSequence[int], + offset: int = 0, + length: Optional[int] = None + ) -> None: """ Absorb trits into the sponge from a buffer. @@ -74,8 +78,12 @@ def absorb(self, trits, offset=0, length=None): offset += TRIT_HASH_LENGTH - def squeeze(self, trits, offset=0, length=None): - # type: (MutableSequence[int], int, Optional[int]) -> None + def squeeze( + self, + trits: MutableSequence[int], + offset: int = 0, + length: Optional[int] = None + ) -> None: """ Squeeze trits from the sponge into a buffer. @@ -135,5 +143,5 @@ def squeeze(self, trits, offset=0, length=None): offset += TRIT_HASH_LENGTH - def reset(self): + def reset(self) -> None: self.k = keccak_384() diff --git a/iota/crypto/pycurl.py b/iota/crypto/pycurl.py index 639f7c2..0c301b2 100644 --- a/iota/crypto/pycurl.py +++ b/iota/crypto/pycurl.py @@ -45,19 +45,21 @@ class Curl(object): **IMPORTANT: Not thread-safe!** """ - def __init__(self): - # type: (Optional[Sequence[int]]) -> None + def __init__(self) -> None: self.reset() - def reset(self): - # type: () -> None + def reset(self) -> None: """ Resets internal state. """ - self._state = [0] * STATE_LENGTH # type: List[int] - - def absorb(self, trits, offset=0, length=None): - # type: (Sequence[int], Optional[int], Optional[int]) -> None + self._state: List[int] = [0] * STATE_LENGTH + + def absorb( + self, + trits: Sequence[int], + offset: Optional[int] = 0, + length: Optional[int] = None + ) -> None: """ Absorb trits into the sponge. @@ -107,8 +109,12 @@ def absorb(self, trits, offset=0, length=None): # Move on to the next hash. offset += HASH_LENGTH - def squeeze(self, trits, offset=0, length=HASH_LENGTH): - # type: (MutableSequence[int], Optional[int], Optional[int]) -> None + def squeeze( + self, + trits: MutableSequence[int], + offset: Optional[int] = 0, + length: Optional[int] = HASH_LENGTH + ) -> None: """ Squeeze trits from the sponge. @@ -166,8 +172,7 @@ def squeeze(self, trits, offset=0, length=HASH_LENGTH): offset += HASH_LENGTH length -= HASH_LENGTH - def _transform(self): - # type: () -> None + def _transform(self) -> None: """ Transforms internal state. """ diff --git a/iota/crypto/signing.py b/iota/crypto/signing.py index f735047..0edbe5b 100644 --- a/iota/crypto/signing.py +++ b/iota/crypto/signing.py @@ -1,6 +1,6 @@ from typing import Iterator, List, Sequence -from iota import Hash, TRITS_PER_TRYTE, TryteString, TrytesCompatible +from iota import Hash, TRITS_PER_TRYTE, TryteString, TrytesCompatible, Address from iota.crypto import FRAGMENT_LENGTH, HASH_LENGTH from iota.crypto.kerl import Kerl from iota.crypto.types import PrivateKey, Seed @@ -16,8 +16,7 @@ ] -def normalize(hash_): - # type: (Hash) -> List[List[int]] +def normalize(hash_: Hash) -> List[List[int]]: """ "Normalizes" a hash, converting it into a sequence of integers (not trits!) suitable for use in signature generation/validation. @@ -61,14 +60,12 @@ class KeyGenerator(object): Generates signing keys for messages. """ - def __init__(self, seed): - # type: (TrytesCompatible) -> None + def __init__(self, seed: TrytesCompatible) -> None: super(KeyGenerator, self).__init__() self.seed = Seed(seed) - def get_key(self, index, iterations): - # type: (int, int) -> PrivateKey + def get_key(self, index: int, iterations: int) -> PrivateKey: """ Generates a single key. @@ -85,14 +82,14 @@ def get_key(self, index, iterations): """ return ( self.get_keys( - start=index, - count=1, - step=1, - iterations=iterations, + start=index, + count=1, + step=1, + iterations=iterations, )[0] ) - def get_key_for(self, address): + def get_key_for(self, address: Address): """ Generates the key associated with the specified address. @@ -100,12 +97,17 @@ def get_key_for(self, address): address was generated from a different key! """ return self.get_key( - index=address.key_index, - iterations=address.security_level, + index=address.key_index, + iterations=address.security_level, ) - def get_keys(self, start, count=1, step=1, iterations=1): - # type: (int, int, int, int) -> List[PrivateKey] + def get_keys( + self, + start: int, + count: int = 1, + step: int = 1, + iterations: int = 1 + ) -> List[PrivateKey]: """ Generates and returns one or more keys at the specified index(es). @@ -147,26 +149,26 @@ def get_keys(self, start, count=1, step=1, iterations=1): """ if count < 1: raise with_context( - exc=ValueError('``count`` must be positive.'), - - context={ - 'start': start, - 'count': count, - 'step': step, - 'iterations': iterations, - }, + exc=ValueError('``count`` must be positive.'), + + context={ + 'start': start, + 'count': count, + 'step': step, + 'iterations': iterations, + }, ) if not step: raise with_context( - exc=ValueError('``step`` must not be zero.'), - - context={ - 'start': start, - 'count': count, - 'step': step, - 'iterations': iterations, - }, + exc=ValueError('``step`` must not be zero.'), + + context={ + 'start': start, + 'count': count, + 'step': step, + 'iterations': iterations, + }, ) iterator = self.create_iterator(start, step, iterations) @@ -182,8 +184,12 @@ def get_keys(self, start, count=1, step=1, iterations=1): return keys - def create_iterator(self, start=0, step=1, security_level=1): - # type: (int, int, int) -> KeyIterator + def create_iterator( + self, + start: int = 0, + step: int = 1, + security_level: int = 1 + ) -> 'KeyIterator': """ Creates a generator that can be used to progressively generate new keys. @@ -218,34 +224,39 @@ class KeyIterator(Iterator[PrivateKey]): Creates PrivateKeys from a set of iteration parameters. """ - def __init__(self, seed, start, step, security_level): - # type: (Seed, int, int, int) -> None + def __init__( + self, + seed: Seed, + start: int, + step: int, + security_level: int + ) -> None: super(KeyIterator, self).__init__() if start < 0: raise with_context( - exc=ValueError('``start`` cannot be negative.'), + exc=ValueError('``start`` cannot be negative.'), - context={ - 'start': start, - 'step': step, - 'security_level': security_level, - }, + context={ + 'start': start, + 'step': step, + 'security_level': security_level, + }, ) if security_level < 1: raise with_context( - exc=ValueError('``security_level`` must be >= 1.'), + exc=ValueError('``security_level`` must be >= 1.'), - context={ - 'start': start, - 'step': step, - 'security_level': security_level, - }, + context={ + 'start': start, + 'step': step, + 'security_level': security_level, + }, ) # In order to work correctly, the seed must be padded so that it - # is a multiple of 81 trytes. + # is a multiple of 81 trytes. seed += b'9' * (Hash.LEN - ((len(seed) % Hash.LEN) or Hash.LEN)) self.security_level = security_level @@ -258,12 +269,10 @@ def __init__(self, seed, start, step, security_level): self.fragment_length = FRAGMENT_LENGTH * TRITS_PER_TRYTE self.hashes_per_fragment = FRAGMENT_LENGTH // Hash.LEN - def __iter__(self): - # type: () -> KeyIterator + def __iter__(self) -> 'KeyIterator': return self - def __next__(self): - # type: () -> PrivateKey + def __next__(self) -> PrivateKey: while self.current >= 0: sponge = self._create_sponge(self.current) @@ -277,8 +286,8 @@ def __next__(self): sponge.squeeze(buffer) key_start = ( - (fragment_seq * self.fragment_length) + - (hash_seq * HASH_LENGTH) + (fragment_seq * self.fragment_length) + + (hash_seq * HASH_LENGTH) ) key_stop = key_start + HASH_LENGTH @@ -289,23 +298,22 @@ def __next__(self): key[key_start:key_stop] = buffer[0:HASH_LENGTH] private_key = PrivateKey.from_trits( - key_index=self.current, - security_level=self.security_level, - trits=key, + key_index=self.current, + security_level=self.security_level, + trits=key, ) self.advance() return private_key - def advance(self): + def advance(self) -> None: """ Advances the generator without creating a key. """ self.current += self.step - def _create_sponge(self, index): - # type: (int) -> Kerl + def _create_sponge(self, index: int) -> Kerl: """ Prepares the hash sponge for the generator. """ @@ -333,8 +341,7 @@ class SignatureFragmentGenerator(Iterator[TryteString]): key. """ - def __init__(self, private_key, hash_): - # type: (PrivateKey, Hash) -> None + def __init__(self, private_key: PrivateKey, hash_: Hash) -> None: super(SignatureFragmentGenerator, self).__init__() self._key_chunks = private_key.iter_chunks(FRAGMENT_LENGTH) @@ -342,12 +349,10 @@ def __init__(self, private_key, hash_): self._normalized_hash = normalize(hash_) self._sponge = Kerl() - def __iter__(self): - # type: () -> SignatureFragmentGenerator + def __iter__(self) -> 'SignatureFragmentGenerator': return self - def __len__(self): - # type: () -> int + def __len__(self) -> int: """ Returns the number of fragments this generator can create. @@ -356,12 +361,11 @@ def __len__(self): """ return len(self._key_chunks) - def __next__(self): - # type: () -> TryteString + def __next__(self) -> TryteString: """ Returns the next signature fragment. """ - key_trytes = next(self._key_chunks) # type: TryteString + key_trytes: TryteString = next(self._key_chunks) self._iteration += 1 # If the key is long enough, loop back around to the start. @@ -376,7 +380,7 @@ def __next__(self): hash_start = i * HASH_LENGTH hash_end = hash_start + HASH_LENGTH - buffer = signature_fragment[hash_start:hash_end] # type: List[int] + buffer: List[int] = signature_fragment[hash_start:hash_end] for _ in range(13 - normalized_chunk[i]): self._sponge.reset() @@ -387,13 +391,13 @@ def __next__(self): return TryteString.from_trits(signature_fragment) + def validate_signature_fragments( - fragments, - hash_, - public_key, - sponge_type=Kerl, -): - # type: (Sequence[TryteString], Hash, TryteString, type) -> bool + fragments: Sequence[TryteString], + hash_: Hash, + public_key: TryteString, + sponge_type: type = Kerl, +) -> bool: """ Returns whether a sequence of signature fragments is valid. @@ -424,7 +428,7 @@ def validate_signature_fragments( buffer = [] for j, hash_trytes in enumerate(fragment.iter_chunks(Hash.LEN)): - buffer = hash_trytes.as_trits() # type: List[int] + buffer: List[int] = hash_trytes.as_trits() inner_sponge = sponge_type() # Note the sign flip compared to diff --git a/iota/crypto/types.py b/iota/crypto/types.py index 64e9963..99b4869 100644 --- a/iota/crypto/types.py +++ b/iota/crypto/types.py @@ -1,5 +1,5 @@ import warnings -from typing import Optional +from typing import Optional, Dict from iota.crypto import FRAGMENT_LENGTH, HASH_LENGTH, SeedWarning from iota.crypto.kerl import Kerl @@ -33,8 +33,11 @@ class Digest(TryteString): if length of ``trytes`` is not multiple of :py:attr:`iota.Hash.LEN`. """ - def __init__(self, trytes, key_index=None): - # type: (TrytesCompatible, Optional[int]) -> None + def __init__( + self, + trytes: TrytesCompatible, + key_index: Optional[int] = None + ) -> None: super(Digest, self).__init__(trytes) # A digest is a series of hashes; its length should reflect @@ -57,8 +60,7 @@ def __init__(self, trytes, key_index=None): self.key_index = key_index @property - def security_level(self): - # type: () -> int + def security_level(self) -> int: """ Returns the number of iterations that were used to generate this digest (also known as "security level"). @@ -68,8 +70,7 @@ def security_level(self): """ return len(self) // Hash.LEN - def as_json_compatible(self): - # type: () -> dict + def as_json_compatible(self) -> Dict: """ Returns a JSON-compatible representation of the digest. @@ -113,8 +114,7 @@ class Seed(TryteString): Length of a Seed. """ - def __init__(self, trytes=None): - # type: (Optional[TrytesCompatible]) -> None + def __init__(self, trytes: Optional[TrytesCompatible] = None) -> None: if trytes and len(trytes) > Hash.LEN: warnings.warn( message=( @@ -128,7 +128,7 @@ def __init__(self, trytes=None): super(Seed, self).__init__(trytes) @classmethod - def random(cls, length=Hash.LEN): + def random(cls, length: int = Hash.LEN) -> 'Seed': """ Generates a random seed using a CSPRNG. @@ -175,8 +175,12 @@ class PrivateKey(TryteString): :py:attr:`iota.transaction.Fragement.LEN`. """ - def __init__(self, trytes, key_index=None, security_level=None): - # type: (TrytesCompatible, Optional[int], Optional[int]) -> None + def __init__( + self, + trytes: TrytesCompatible, + key_index: Optional[int] = None, + security_level: Optional[int] = None + ) -> None: super(PrivateKey, self).__init__(trytes) if len(self._trytes) % FRAGMENT_LENGTH: @@ -197,8 +201,7 @@ def __init__(self, trytes, key_index=None, security_level=None): self.key_index = key_index self.security_level = security_level - def as_json_compatible(self): - # type: () -> dict + def as_json_compatible(self) -> Dict: """ Returns a JSON-compatible representation of the private key. @@ -218,8 +221,7 @@ def as_json_compatible(self): 'security_level': self.security_level, } - def get_digest(self): - # type: () -> Digest + def get_digest(self) -> Digest: """ Generates the digest used to do the actual signing. @@ -276,8 +278,11 @@ def get_digest(self): return Digest(TryteString.from_trits(digest), self.key_index) - def sign_input_transactions(self, bundle, start_index): - # type: (Bundle, int) -> None + def sign_input_transactions( + self, + bundle: Bundle, + start_index: int + ) -> None: """ Signs the inputs starting at the specified index. diff --git a/iota/exceptions.py b/iota/exceptions.py index c10e106..dbc5348 100644 --- a/iota/exceptions.py +++ b/iota/exceptions.py @@ -4,8 +4,7 @@ ] -def with_context(exc, context): - # type: (Exception, dict) -> Exception +def with_context(exc: Exception, context: dict) -> Exception: """ Attaches a ``context`` value to an Exception. diff --git a/iota/filters.py b/iota/filters.py index 66c4f72..80d0684 100644 --- a/iota/filters.py +++ b/iota/filters.py @@ -40,7 +40,7 @@ class GeneratedAddress(f.BaseFilter): } def _apply(self, value): - value = self._filter(value, f.Type(Address)) # type: Address + value: Address = self._filter(value, f.Type(Address)) if self._has_errors: return None @@ -77,7 +77,7 @@ class NodeUri(f.BaseFilter): } def _apply(self, value): - value = self._filter(value, f.Type(str)) # type: Text + value: Text = self._filter(value, f.Type(str)) if self._has_errors: return None @@ -91,7 +91,7 @@ def _apply(self, value): @filter_macro -def SecurityLevel(): +def SecurityLevel() -> f.FilterChain: """ Generates a filter chain for validating a security level. @@ -133,8 +133,7 @@ class Trytes(f.BaseFilter): CODE_WRONG_FORMAT: 'This value is not a valid {result_type}.', } - def __init__(self, result_type=TryteString): - # type: (type) -> None + def __init__(self, result_type: type = TryteString) -> None: super(Trytes, self).__init__() if not isinstance(result_type, type): @@ -159,13 +158,13 @@ def __init__(self, result_type=TryteString): self.result_type = result_type def _apply(self, value): - value = self._filter( + value: TrytesCompatible = self._filter( filter_chain=f.Type( (bytes, bytearray, str, TryteString) ), value=value, - ) # type: TrytesCompatible + ) if self._has_errors: return None @@ -206,8 +205,7 @@ def _apply(self, value): @filter_macro -def StringifiedTrytesArray(trytes_type=TryteString): - # type: (Type[TryteString]) -> f.FilterChain +def StringifiedTrytesArray(trytes_type: Type = TryteString) -> f.FilterChain: """ Validates that the incoming value is an array containing tryte strings corresponding to the specified type (e.g., @@ -216,7 +214,7 @@ def StringifiedTrytesArray(trytes_type=TryteString): When a value doesn't pass the filter, a ``ValueError`` is raised with lots of contextual info attached to it. - :param TryteString result_type: + :param TryteString trytes_type: Any subclass of :py:class:`~iota.TryteString` that you want the filter to validate. @@ -258,7 +256,7 @@ class AddressNoChecksum(Trytes): 'Checksum is {supplied_checksum}, should be {expected_checksum}?', } - def __init__(self): + def __init__(self) -> None: super(AddressNoChecksum, self).__init__(result_type=Address) def _apply(self, value): diff --git a/iota/multisig/api.py b/iota/multisig/api.py index a337973..d0abdd8 100644 --- a/iota/multisig/api.py +++ b/iota/multisig/api.py @@ -1,4 +1,4 @@ -from typing import Iterable, Optional +from typing import Iterable, Optional, Dict from iota import Address, Iota, AsyncIota, ProposedTransaction from iota.crypto.addresses import AddressGenerator @@ -47,8 +47,10 @@ class AsyncMultisigIota(AsyncIota): - https://github.com/iotaledger/wiki/blob/master/multisigs.md """ - async def create_multisig_address(self, digests): - # type: (Iterable[Digest]) -> dict + async def create_multisig_address( + self, + digests: Iterable[Digest] + ) -> Dict: """ Generates a multisig address from a collection of digests. @@ -74,11 +76,10 @@ async def create_multisig_address(self, digests): async def get_digests( self, - index=0, - count=1, - security_level=AddressGenerator.DEFAULT_SECURITY_LEVEL, - ): - # type: (int, int, int) -> dict + index: int = 0, + count: int = 1, + security_level: int = AddressGenerator.DEFAULT_SECURITY_LEVEL, + ) -> Dict: """ Generates one or more key digests from the seed. @@ -117,11 +118,10 @@ async def get_digests( async def get_private_keys( self, - index=0, - count=1, - security_level=AddressGenerator.DEFAULT_SECURITY_LEVEL, - ): - # type: (int, int, int) -> dict + index: int = 0, + count: int = 1, + security_level: int = AddressGenerator.DEFAULT_SECURITY_LEVEL, + ) -> Dict: """ Generates one or more private keys from the seed. @@ -166,11 +166,10 @@ async def get_private_keys( async def prepare_multisig_transfer( self, - transfers, # type: Iterable[ProposedTransaction] - multisig_input, # type: MultisigAddress - change_address=None, # type: Optional[Address] - ): - # type: (...) -> dict + transfers: Iterable[ProposedTransaction], + multisig_input: MultisigAddress, + change_address: Optional[Address] = None, + ) -> Dict: """ Prepares a bundle that authorizes the spending of IOTAs from a multisig address. @@ -291,8 +290,10 @@ class MultisigIota(Iota, AsyncMultisigIota): - https://github.com/iotaledger/wiki/blob/master/multisigs.md """ - def create_multisig_address(self, digests): - # type: (Iterable[Digest]) -> dict + def create_multisig_address( + self, + digests: Iterable[Digest] + ) -> Dict: """ Generates a multisig address from a collection of digests. @@ -318,11 +319,10 @@ def create_multisig_address(self, digests): def get_digests( self, - index=0, - count=1, - security_level=AddressGenerator.DEFAULT_SECURITY_LEVEL, - ): - # type: (int, int, int) -> dict + index: int = 0, + count: int = 1, + security_level: int = AddressGenerator.DEFAULT_SECURITY_LEVEL, + ) -> Dict: """ Generates one or more key digests from the seed. @@ -362,11 +362,10 @@ def get_digests( def get_private_keys( self, - index=0, - count=1, - security_level=AddressGenerator.DEFAULT_SECURITY_LEVEL, - ): - # type: (int, int, int) -> dict + index: int = 0, + count: int = 1, + security_level: int = AddressGenerator.DEFAULT_SECURITY_LEVEL, + ) -> Dict: """ Generates one or more private keys from the seed. @@ -412,11 +411,10 @@ def get_private_keys( def prepare_multisig_transfer( self, - transfers, # type: Iterable[ProposedTransaction] - multisig_input, # type: MultisigAddress - change_address=None, # type: Optional[Address] - ): - # type: (...) -> dict + transfers: Iterable[ProposedTransaction], + multisig_input: MultisigAddress, + change_address: Optional[Address] = None, + ) -> Dict: """ Prepares a bundle that authorizes the spending of IOTAs from a multisig address. diff --git a/iota/multisig/commands/create_multisig_address.py b/iota/multisig/commands/create_multisig_address.py index 91fbb5a..142160f 100644 --- a/iota/multisig/commands/create_multisig_address.py +++ b/iota/multisig/commands/create_multisig_address.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Dict import filters as f @@ -22,7 +22,7 @@ class CreateMultisigAddressCommand(FilterCommand): """ command = 'createMultisigAddress' - def get_request_filter(self): + def get_request_filter(self) -> 'CreateMultisigAddressRequestFilter': return CreateMultisigAddressRequestFilter() def get_response_filter(self): @@ -30,8 +30,8 @@ def get_response_filter(self): # There is no async operation going on here, but the base class is async, # so from the outside, we have to act like we are doing async. - async def _execute(self, request): - digests = request['digests'] # type: List[Digest] + async def _execute(self, request: Dict): + digests: List[Digest] = request['digests'] builder = MultisigAddressBuilder() @@ -44,7 +44,7 @@ async def _execute(self, request): class CreateMultisigAddressRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(CreateMultisigAddressRequestFilter, self).__init__({ 'digests': f.Required | f.Array | f.FilterRepeater( diff --git a/iota/multisig/commands/get_digests.py b/iota/multisig/commands/get_digests.py index ff3bc49..9ced79a 100644 --- a/iota/multisig/commands/get_digests.py +++ b/iota/multisig/commands/get_digests.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Dict import filters as f @@ -22,7 +22,7 @@ class GetDigestsCommand(FilterCommand): """ command = 'getDigests' - def get_request_filter(self): + def get_request_filter(self) -> 'GetDigestsRequestFilter': return GetDigestsRequestFilter() def get_response_filter(self): @@ -30,11 +30,11 @@ def get_response_filter(self): # There is no async operation going on here, but the base class is async, # so from the outside, we have to act like we are doing async. - async def _execute(self, request): - count = request['count'] # type: Optional[int] - index = request['index'] # type: int - seed = request['seed'] # type: Seed - security_level = request['securityLevel'] # type: int + async def _execute(self, request: Dict): + count: Optional[int] = request['count'] + index: int = request['index'] + seed: Seed = request['seed'] + security_level: int = request['securityLevel'] gpk_result = await GetPrivateKeysCommand(self.adapter)( seed=seed, @@ -49,7 +49,7 @@ async def _execute(self, request): class GetDigestsRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(GetDigestsRequestFilter, self).__init__( { # Optional Parameters diff --git a/iota/multisig/commands/get_private_keys.py b/iota/multisig/commands/get_private_keys.py index d1affc9..0dcfadf 100644 --- a/iota/multisig/commands/get_private_keys.py +++ b/iota/multisig/commands/get_private_keys.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Dict import filters as f @@ -23,7 +23,7 @@ class GetPrivateKeysCommand(FilterCommand): """ command = 'getPrivateKeys' - def get_request_filter(self): + def get_request_filter(self) -> 'GetPrivateKeysRequestFilter': return GetPrivateKeysRequestFilter() def get_response_filter(self): @@ -31,11 +31,11 @@ def get_response_filter(self): # There is no async operation going on here, but the base class is async, # so from the outside, we have to act like we are doing async. - async def _execute(self, request): - count = request['count'] # type: Optional[int] - index = request['index'] # type: int - seed = request['seed'] # type: Seed - security_level = request['securityLevel'] # type: int + async def _execute(self, request: Dict): + count: Optional[int] = request['count'] + index: int = request['index'] + seed: Seed = request['seed'] + security_level: int = request['securityLevel'] generator = KeyGenerator(seed) @@ -49,7 +49,7 @@ async def _execute(self, request): class GetPrivateKeysRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(GetPrivateKeysRequestFilter, self).__init__( { # Optional Parameters diff --git a/iota/multisig/commands/prepare_multisig_transfer.py b/iota/multisig/commands/prepare_multisig_transfer.py index b7dd663..7ae2453 100644 --- a/iota/multisig/commands/prepare_multisig_transfer.py +++ b/iota/multisig/commands/prepare_multisig_transfer.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Optional, Dict import filters as f @@ -25,45 +25,45 @@ class PrepareMultisigTransferCommand(FilterCommand): """ command = 'prepareMultisigTransfer' - def get_request_filter(self): + def get_request_filter(self) -> 'PrepareMultisigTransferRequestFilter': return PrepareMultisigTransferRequestFilter() def get_response_filter(self): pass - async def _execute(self, request): - change_address = request['changeAddress'] # type: Optional[Address] - multisig_input = request['multisigInput'] # type: MultisigAddress - transfers = request['transfers'] # type: List[ProposedTransaction] + async def _execute(self, request: Dict): + change_address: Optional[Address] = request['changeAddress'] + multisig_input: MultisigAddress = request['multisigInput'] + transfers: List[ProposedTransaction] = request['transfers'] bundle = ProposedMultisigBundle(transfers) want_to_spend = bundle.balance if want_to_spend > 0: gb_response = await GetBalancesCommand(self.adapter)( - addresses=[multisig_input], + addresses=[multisig_input], ) multisig_input.balance = gb_response['balances'][0] if multisig_input.balance < want_to_spend: raise with_context( - exc=ValueError( - 'Insufficient balance; found {found}, need {need} ' - '(``exc.context`` has more info).'.format( - found=multisig_input.balance, - need=want_to_spend, + exc=ValueError( + 'Insufficient balance; found {found}, need {need} ' + '(``exc.context`` has more info).'.format( + found=multisig_input.balance, + need=want_to_spend, + ), ), - ), - # The structure of this context object is intended - # to match the one from ``PrepareTransferCommand``. - context={ - 'available_to_spend': multisig_input.balance, - 'confirmed_inputs': [multisig_input], - 'request': request, - 'want_to_spend': want_to_spend, - }, + # The structure of this context object is intended + # to match the one from ``PrepareTransferCommand``. + context={ + 'available_to_spend': multisig_input.balance, + 'confirmed_inputs': [multisig_input], + 'request': request, + 'want_to_spend': want_to_spend, + }, ) bundle.add_inputs([multisig_input]) @@ -86,29 +86,29 @@ async def _execute(self, request): # method! # raise with_context( - exc=ValueError( - 'Bundle has unspent inputs, ' - 'but no change address specified.', - ), - - context={ - 'available_to_spend': multisig_input.balance, - 'balance': bundle.balance, - 'confirmed_inputs': [multisig_input], - 'request': request, - 'want_to_spend': want_to_spend, - }, + exc=ValueError( + 'Bundle has unspent inputs, ' + 'but no change address specified.', + ), + + context={ + 'available_to_spend': multisig_input.balance, + 'balance': bundle.balance, + 'confirmed_inputs': [multisig_input], + 'request': request, + 'want_to_spend': want_to_spend, + }, ) else: raise with_context( - exc=ValueError( - 'Use ``prepare_transfer`` ' - 'to create a bundle without spending IOTAs.', - ), + exc=ValueError( + 'Use ``prepare_transfer`` ' + 'to create a bundle without spending IOTAs.', + ), - context={ - 'request': request, - }, + context={ + 'request': request, + }, ) bundle.finalize() @@ -120,19 +120,19 @@ async def _execute(self, request): class PrepareMultisigTransferRequestFilter(RequestFilter): - def __init__(self): + def __init__(self) -> None: super(PrepareMultisigTransferRequestFilter, self).__init__( - { - 'changeAddress': Trytes(Address), - 'multisigInput': f.Required | f.Type(MultisigAddress), + { + 'changeAddress': Trytes(Address), + 'multisigInput': f.Required | f.Type(MultisigAddress), - 'transfers': - f.Required | f.Array | f.FilterRepeater( - f.Required | f.Type(ProposedTransaction), - ), - }, + 'transfers': + f.Required | f.Array | f.FilterRepeater( + f.Required | f.Type(ProposedTransaction), + ), + }, - allow_missing_keys={ - 'changeAddress', - }, + allow_missing_keys={ + 'changeAddress', + }, ) diff --git a/iota/multisig/crypto/addresses.py b/iota/multisig/crypto/addresses.py index 24fb1ce..abf5473 100644 --- a/iota/multisig/crypto/addresses.py +++ b/iota/multisig/crypto/addresses.py @@ -20,16 +20,16 @@ class MultisigAddressBuilder(object): multiple addresses from a single input (seed). """ - def __init__(self): + def __init__(self) -> None: super(MultisigAddressBuilder, self).__init__() - self._digests = [] # type: List[Digest] + self._digests: List[Digest] = [] """ Keeps track of digests that were added, so that we can attach them to the final :py:class:`MultisigAddress` object. """ - self._address = None # type: Optional[MultisigAddress] + self._address: Optional[MultisigAddress] = None """ Caches the generated address. @@ -40,8 +40,7 @@ def __init__(self): self._sponge = Kerl() - def add_digest(self, digest): - # type: (Digest) -> None + def add_digest(self, digest: Digest) -> None: """ Absorbs a digest into the sponge. @@ -61,8 +60,7 @@ def add_digest(self, digest): self._sponge.absorb(digest.as_trits()) self._digests.append(digest) - def get_address(self): - # type: () -> MultisigAddress + def get_address(self) -> MultisigAddress: """ Returns the new multisig address. diff --git a/iota/multisig/transaction.py b/iota/multisig/transaction.py index 53ba855..cce6ca3 100644 --- a/iota/multisig/transaction.py +++ b/iota/multisig/transaction.py @@ -36,8 +36,7 @@ class ProposedMultisigBundle(ProposedBundle): :return: :py:class:`ProposedMultisigBundle` object. """ - def add_inputs(self, inputs): - # type: (Iterable[MultisigAddress]) -> None + def add_inputs(self, inputs: Iterable[MultisigAddress]) -> None: """ Adds inputs to spend in the bundle. diff --git a/iota/multisig/types.py b/iota/multisig/types.py index e9623aa..c0691b5 100644 --- a/iota/multisig/types.py +++ b/iota/multisig/types.py @@ -1,5 +1,5 @@ from operator import attrgetter -from typing import Iterable, Optional +from typing import Iterable, Optional, Dict from iota import Address, TrytesCompatible from iota.crypto.types import Digest @@ -42,8 +42,12 @@ class MultisigAddress(Address): :py:class:`MultisigAddress` object. """ - def __init__(self, trytes, digests, balance=None): - # type: (TrytesCompatible, Iterable[Digest], Optional[int]) -> None + def __init__( + self, + trytes: TrytesCompatible, + digests: Iterable[Digest], + balance: Optional[int] = None + ) -> None: # Key index is meaningless for multisig addresses. super(MultisigAddress, self).__init__(trytes, balance, key_index=None) @@ -53,7 +57,7 @@ def __init__(self, trytes, digests, balance=None): map(attrgetter('security_level'), self.digests) ) - def as_json_compatible(self): + def as_json_compatible(self) -> Dict: """ Get a JSON represenation of the :py:class:`MultisigAddress` object. @@ -70,7 +74,6 @@ def as_json_compatible(self): } """ - # type: () -> dict return { 'trytes': self._trytes.decode('ascii'), 'balance': self.balance, diff --git a/iota/transaction/base.py b/iota/transaction/base.py index b46fe3a..b971352 100644 --- a/iota/transaction/base.py +++ b/iota/transaction/base.py @@ -1,6 +1,6 @@ from operator import attrgetter from typing import Iterable, Iterator, List, MutableSequence, \ - Optional, Sequence, Text + Optional, Sequence, Text, TypeVar, Type, Dict from iota.codecs import TrytesDecodeError from iota.crypto import Curl, HASH_LENGTH @@ -15,6 +15,8 @@ 'Transaction', ] +T = TypeVar('T', bound='Transaction') + class Transaction(JsonSerializable): """ @@ -76,8 +78,11 @@ class Transaction(JsonSerializable): """ @classmethod - def from_tryte_string(cls, trytes, hash_=None): - # type: (TrytesCompatible, Optional[TransactionHash]) -> Transaction + def from_tryte_string( + cls: Type[T], + trytes: TrytesCompatible, + hash_: Optional[TransactionHash] = None + ) -> T: """ Creates a Transaction object from a sequence of trytes. @@ -146,7 +151,7 @@ def from_tryte_string(cls, trytes, hash_=None): tryte_string = TransactionTrytes(trytes) if not hash_: - hash_trits = [0] * HASH_LENGTH # type: MutableSequence[int] + hash_trits: MutableSequence[int] = [0] * HASH_LENGTH sponge = Curl() sponge.absorb(tryte_string.as_trits()) @@ -155,51 +160,51 @@ def from_tryte_string(cls, trytes, hash_=None): hash_ = TransactionHash.from_trits(hash_trits) return cls( - hash_=hash_, - signature_message_fragment=Fragment(tryte_string[0:2187]), - address=Address(tryte_string[2187:2268]), - value=int_from_trits(tryte_string[2268:2295].as_trits()), - legacy_tag=Tag(tryte_string[2295:2322]), - timestamp=int_from_trits(tryte_string[2322:2331].as_trits()), - current_index=int_from_trits(tryte_string[2331:2340].as_trits()), - last_index=int_from_trits(tryte_string[2340:2349].as_trits()), - bundle_hash=BundleHash(tryte_string[2349:2430]), - trunk_transaction_hash=TransactionHash(tryte_string[2430:2511]), - branch_transaction_hash=TransactionHash(tryte_string[2511:2592]), - tag=Tag(tryte_string[2592:2619]), - - attachment_timestamp=int_from_trits( - tryte_string[2619:2628].as_trits()), - - attachment_timestamp_lower_bound=int_from_trits( - tryte_string[2628:2637].as_trits()), - - attachment_timestamp_upper_bound=int_from_trits( - tryte_string[2637:2646].as_trits()), - - nonce=Nonce(tryte_string[2646:2673]), + hash_=hash_, + signature_message_fragment=Fragment(tryte_string[0:2187]), + address=Address(tryte_string[2187:2268]), + value=int_from_trits(tryte_string[2268:2295].as_trits()), + legacy_tag=Tag(tryte_string[2295:2322]), + timestamp=int_from_trits(tryte_string[2322:2331].as_trits()), + current_index=int_from_trits(tryte_string[2331:2340].as_trits()), + last_index=int_from_trits(tryte_string[2340:2349].as_trits()), + bundle_hash=BundleHash(tryte_string[2349:2430]), + trunk_transaction_hash=TransactionHash(tryte_string[2430:2511]), + branch_transaction_hash=TransactionHash(tryte_string[2511:2592]), + tag=Tag(tryte_string[2592:2619]), + + attachment_timestamp=int_from_trits( + tryte_string[2619:2628].as_trits()), + + attachment_timestamp_lower_bound=int_from_trits( + tryte_string[2628:2637].as_trits()), + + attachment_timestamp_upper_bound=int_from_trits( + tryte_string[2637:2646].as_trits()), + + nonce=Nonce(tryte_string[2646:2673]), ) def __init__( self, - hash_, # type: Optional[TransactionHash] - signature_message_fragment, # type: Optional[Fragment] - address, # type: Address - value, # type: int - timestamp, # type: int - current_index, # type: Optional[int] - last_index, # type: Optional[int] - bundle_hash, # type: Optional[BundleHash] - trunk_transaction_hash, # type: Optional[TransactionHash] - branch_transaction_hash, # type: Optional[TransactionHash] - tag, # type: Optional[Tag] - attachment_timestamp, # type: Optional[int] - attachment_timestamp_lower_bound, # type: Optional[int] - attachment_timestamp_upper_bound, # type: Optional[int] - nonce, # type: Optional[Nonce] - legacy_tag=None # type: Optional[Tag] - ): - self.hash = hash_ + hash_: Optional[TransactionHash], + signature_message_fragment: Optional[Fragment], + address: Address, + value: int, + timestamp: int, + current_index: Optional[int], + last_index: Optional[int], + bundle_hash: Optional[BundleHash], + trunk_transaction_hash: Optional[TransactionHash], + branch_transaction_hash: Optional[TransactionHash], + tag: Optional[Tag], + attachment_timestamp: Optional[int], + attachment_timestamp_lower_bound: Optional[int], + attachment_timestamp_upper_bound: Optional[int], + nonce: Optional[Nonce], + legacy_tag: Optional[Tag] = None + ) -> None: + self.hash: TransactionHash = hash_ """ The transaction hash, used to uniquely identify the transaction on the Tangle. @@ -209,7 +214,7 @@ def __init__( :type: :py:class:`TransactionHash` """ - self.bundle_hash = bundle_hash + self.bundle_hash: BundleHash = bundle_hash """ The bundle hash, used to identify transactions that are part of the same bundle. @@ -220,7 +225,7 @@ def __init__( :type: :py:class:`BundleHash` """ - self.address = address + self.address: Address = address """ The address associated with this transaction. @@ -231,7 +236,7 @@ def __init__( :type: :py:class:`Address` """ - self.value = value + self.value: int = value """ The number of iotas being transferred in this transaction: @@ -244,7 +249,7 @@ def __init__( :type: ``int`` """ - self._legacy_tag = legacy_tag + self._legacy_tag: Tag = legacy_tag """ A short message attached to the transaction. @@ -254,7 +259,7 @@ def __init__( :type: :py:class:`Tag` """ - self.nonce = nonce + self.nonce: Nonce = nonce """ Unique value used to increase security of the transaction hash. @@ -263,7 +268,7 @@ def __init__( :type: :py:class:`Nonce` """ - self.timestamp = timestamp + self.timestamp: int = timestamp """ Timestamp used to increase the security of the transaction hash. @@ -276,7 +281,7 @@ def __init__( :type: ``int``, unix timestamp in seconds. """ - self.current_index = current_index + self.current_index: int = current_index """ The position of the transaction inside the bundle. @@ -290,7 +295,7 @@ def __init__( :type: ``int`` """ - self.last_index = last_index + self.last_index: int = last_index """ The index of the final transaction in the bundle. @@ -300,7 +305,7 @@ def __init__( :type: ``int`` """ - self.trunk_transaction_hash = trunk_transaction_hash + self.trunk_transaction_hash: TransactionHash = trunk_transaction_hash """ The transaction hash of the next transaction in the bundle. @@ -314,7 +319,7 @@ def __init__( :type: :py:class:`TransactionHash` """ - self.branch_transaction_hash = branch_transaction_hash + self.branch_transaction_hash: TransactionHash = branch_transaction_hash """ An unrelated transaction that this transaction "approves". @@ -329,7 +334,7 @@ def __init__( :type: :py:class:`TransactionHash` """ - self.tag = tag + self.tag: Tag = tag """ Optional classification tag applied to this transaction. @@ -338,7 +343,7 @@ def __init__( :type: :py:class:`Tag` """ - self.attachment_timestamp = attachment_timestamp + self.attachment_timestamp: int = attachment_timestamp """ Estimated epoch time of the attachment to the tangle. @@ -347,21 +352,21 @@ def __init__( :type: ``int``, unix timestamp in milliseconds, """ - self.attachment_timestamp_lower_bound = attachment_timestamp_lower_bound + self.attachment_timestamp_lower_bound: int = attachment_timestamp_lower_bound """ The lowest possible epoch time of the attachment to the tangle. :type: ``int``, unix timestamp in milliseconds. """ - self.attachment_timestamp_upper_bound = attachment_timestamp_upper_bound + self.attachment_timestamp_upper_bound: int = attachment_timestamp_upper_bound """ The highest possible epoch time of the attachment to the tangle. :type: ``int``, unix timestamp in milliseconds. """ - self.signature_message_fragment = signature_message_fragment + self.signature_message_fragment: Fragment = signature_message_fragment """ "Signature/Message Fragment" (note the slash): @@ -381,7 +386,7 @@ def __init__( :type: :py:class:`Fragment` """ - self.is_confirmed = None # type: Optional[bool] + self.is_confirmed: bool = None """ Whether this transaction has been confirmed by neighbor nodes. Must be set manually via the ``getInclusionStates`` API command. @@ -395,8 +400,7 @@ def __init__( """ @property - def is_tail(self): - # type: () -> bool + def is_tail(self) -> bool: """ Returns whether this transaction is a tail (first one in the bundle). @@ -408,8 +412,7 @@ def is_tail(self): return self.current_index == 0 @property - def value_as_trytes(self): - # type: () -> TryteString + def value_as_trytes(self) -> TryteString: """ Returns a TryteString representation of the transaction's :py:attr:`value`. @@ -418,8 +421,7 @@ def value_as_trytes(self): return TryteString.from_trits(trits_from_int(self.value, pad=81)) @property - def timestamp_as_trytes(self): - # type: () -> TryteString + def timestamp_as_trytes(self) -> TryteString: """ Returns a TryteString representation of the transaction's :py:attr:`timestamp`. @@ -428,20 +430,18 @@ def timestamp_as_trytes(self): return TryteString.from_trits(trits_from_int(self.timestamp, pad=27)) @property - def current_index_as_trytes(self): - # type: () -> TryteString + def current_index_as_trytes(self) -> TryteString: """ Returns a TryteString representation of the transaction's :py:attr:`current_index`. """ # Note that we are padding to 27 *trits*. return TryteString.from_trits( - trits_from_int(self.current_index, pad=27), + trits_from_int(self.current_index, pad=27), ) @property - def last_index_as_trytes(self): - # type: () -> TryteString + def last_index_as_trytes(self) -> TryteString: """ Returns a TryteString representation of the transaction's :py:attr:`last_index`. @@ -450,43 +450,39 @@ def last_index_as_trytes(self): return TryteString.from_trits(trits_from_int(self.last_index, pad=27)) @property - def attachment_timestamp_as_trytes(self): - # type: () -> TryteString + def attachment_timestamp_as_trytes(self) -> TryteString: """ Returns a TryteString representation of the transaction's :py:attr:`attachment_timestamp`. """ # Note that we are padding to 27 *trits*. return TryteString.from_trits( - trits_from_int(self.attachment_timestamp, pad=27), + trits_from_int(self.attachment_timestamp, pad=27), ) @property - def attachment_timestamp_lower_bound_as_trytes(self): - # type: () -> TryteString + def attachment_timestamp_lower_bound_as_trytes(self) -> TryteString: """ Returns a TryteString representation of the transaction's :py:attr:`attachment_timestamp_lower_bound`. """ # Note that we are padding to 27 *trits*. return TryteString.from_trits( - trits_from_int(self.attachment_timestamp_lower_bound, pad=27), + trits_from_int(self.attachment_timestamp_lower_bound, pad=27), ) @property - def attachment_timestamp_upper_bound_as_trytes(self): - # type: () -> TryteString + def attachment_timestamp_upper_bound_as_trytes(self) -> TryteString: """ Returns a TryteString representation of the transaction's :py:attr:`attachment_timestamp_upper_bound`. """ # Note that we are padding to 27 *trits*. return TryteString.from_trits( - trits_from_int(self.attachment_timestamp_upper_bound, pad=27), + trits_from_int(self.attachment_timestamp_upper_bound, pad=27), ) - def as_json_compatible(self): - # type: () -> dict + def as_json_compatible(self) -> Dict: """ Returns a JSON-compatible representation of the object. @@ -540,8 +536,7 @@ def as_json_compatible(self): 'nonce': self.nonce, } - def as_tryte_string(self): - # type: () -> TransactionTrytes + def as_tryte_string(self) -> TransactionTrytes: """ Returns a TryteString representation of the transaction. @@ -549,25 +544,24 @@ def as_tryte_string(self): :py:class:`TryteString` object. """ return TransactionTrytes( - self.signature_message_fragment - + self.address.address - + self.value_as_trytes - + self.legacy_tag - + self.timestamp_as_trytes - + self.current_index_as_trytes - + self.last_index_as_trytes - + self.bundle_hash - + self.trunk_transaction_hash - + self.branch_transaction_hash - + self.tag - + self.attachment_timestamp_as_trytes - + self.attachment_timestamp_lower_bound_as_trytes - + self.attachment_timestamp_upper_bound_as_trytes - + self.nonce + self.signature_message_fragment + + self.address.address + + self.value_as_trytes + + self.legacy_tag + + self.timestamp_as_trytes + + self.current_index_as_trytes + + self.last_index_as_trytes + + self.bundle_hash + + self.trunk_transaction_hash + + self.branch_transaction_hash + + self.tag + + self.attachment_timestamp_as_trytes + + self.attachment_timestamp_lower_bound_as_trytes + + self.attachment_timestamp_upper_bound_as_trytes + + self.nonce ) - def get_bundle_essence_trytes(self): - # type: () -> TryteString + def get_bundle_essence_trytes(self) -> TryteString: """ Returns the values needed for calculating bundle hash. The bundle hash is the hash of the bundle essence, which is itself @@ -597,8 +591,7 @@ def get_bundle_essence_trytes(self): ) @property - def legacy_tag(self): - # type: () -> Tag + def legacy_tag(self) -> Tag: """ Return the legacy tag of the transaction. If no legacy tag was set, returns the tag instead. @@ -606,6 +599,9 @@ def legacy_tag(self): return self._legacy_tag or self.tag +B = TypeVar('B', bound='Bundle') + + class Bundle(JsonSerializable, Sequence[Transaction]): """ A collection of transactions, treated as an atomic unit when @@ -630,8 +626,7 @@ class Bundle(JsonSerializable, Sequence[Transaction]): """ @classmethod - def from_tryte_strings(cls, trytes): - # type: (Iterable[TryteString]) -> Bundle + def from_tryte_strings(cls: Type[B], trytes: Iterable[TryteString]) -> B: """ Creates a Bundle object from a list of tryte values. @@ -657,20 +652,22 @@ def from_tryte_strings(cls, trytes): """ return cls(map(Transaction.from_tryte_string, trytes)) - def __init__(self, transactions=None): - # type: (Optional[Iterable[Transaction]]) -> None + def __init__( + self, + transactions: Optional[Iterable[Transaction]] = None + ) -> None: super(Bundle, self).__init__() - self.transactions = [] # type: List[Transaction] + self.transactions: List[Transaction] = [] """ List of :py:class:`Transaction` objects that are in the bundle. """ if transactions: self.transactions.extend( - sorted(transactions, key=attrgetter('current_index')), + sorted(transactions, key=attrgetter('current_index')), ) - self._is_confirmed = None # type: Optional[bool] + self._is_confirmed: Optional[bool] = None """ Whether this bundle has been confirmed by neighbor nodes. Must be set manually. @@ -680,25 +677,20 @@ def __init__(self, transactions=None): - :py:class:`Iota.get_transfers` """ - def __contains__(self, transaction): - # type: (Transaction) -> bool + def __contains__(self, transaction: Transaction) -> bool: return transaction in self.transactions - def __getitem__(self, index): - # type: (int) -> Transaction + def __getitem__(self, index: int) -> Transaction: return self.transactions[index] - def __iter__(self): - # type: () -> Iterator[Transaction] + def __iter__(self) -> Iterator[Transaction]: return iter(self.transactions) - def __len__(self): - # type: () -> int + def __len__(self) -> int: return len(self.transactions) @property - def is_confirmed(self): - # type: () -> Optional[bool] + def is_confirmed(self) -> Optional[bool]: """ Returns whether this bundle has been confirmed by neighbor nodes. @@ -714,8 +706,7 @@ def is_confirmed(self): return self._is_confirmed @is_confirmed.setter - def is_confirmed(self, new_is_confirmed): - # type: (bool) -> None + def is_confirmed(self, new_is_confirmed: bool) -> None: """ Sets the ``is_confirmed`` for the bundle. """ @@ -725,8 +716,7 @@ def is_confirmed(self, new_is_confirmed): txn.is_confirmed = new_is_confirmed @property - def hash(self): - # type: () -> Optional[BundleHash] + def hash(self) -> Optional[BundleHash]: """ Returns the hash of the bundle. @@ -743,8 +733,7 @@ def hash(self): return None @property - def tail_transaction(self): - # type: () -> Transaction + def tail_transaction(self) -> Transaction: """ Returns the tail transaction of the bundle. @@ -752,8 +741,7 @@ def tail_transaction(self): """ return self[0] - def get_messages(self, errors='drop'): - # type: (Text) -> List[Text] + def get_messages(self, errors: Text = 'drop') -> List[Text]: """ Attempts to decipher encoded messages from the transactions in the bundle. @@ -798,8 +786,7 @@ def get_messages(self, errors='drop'): return messages - def as_tryte_strings(self, head_to_tail=False): - # type: (bool) -> List[TransactionTrytes] + def as_tryte_strings(self, head_to_tail: bool = False) -> List[TransactionTrytes]: """ Returns TryteString representations of the transactions in this bundle. @@ -818,8 +805,7 @@ def as_tryte_strings(self, head_to_tail=False): transactions = self if head_to_tail else reversed(self) return [t.as_tryte_string() for t in transactions] - def as_json_compatible(self): - # type: () -> List[dict] + def as_json_compatible(self) -> List[Dict]: """ Returns a JSON-compatible representation of the object. @@ -833,8 +819,7 @@ def as_json_compatible(self): """ return [txn.as_json_compatible() for txn in self] - def group_transactions(self): - # type: () -> List[List[Transaction]] + def group_transactions(self) -> List[List[Transaction]]: """ Groups transactions in the bundle by address. diff --git a/iota/transaction/creation.py b/iota/transaction/creation.py index b798b2e..cb57286 100644 --- a/iota/transaction/creation.py +++ b/iota/transaction/creation.py @@ -1,4 +1,4 @@ -from typing import Iterable, Iterator, List, Optional, Sequence +from typing import Iterable, Iterator, List, Optional, Sequence, Dict from iota.crypto import HASH_LENGTH from iota.crypto.kerl import Kerl @@ -72,12 +72,12 @@ class ProposedTransaction(Transaction): def __init__( self, - address, # type: Address - value, # type: int - tag=None, # type: Optional[Tag] - message=None, # type: Optional[TryteString] - timestamp=None, # type: Optional[int] - ): + address: Address, + value: int, + tag: Optional[Tag] = None, + message: Optional[TryteString] = None, + timestamp: Optional[int] = None, + ) -> None: if not timestamp: timestamp = get_current_timestamp() @@ -107,8 +107,7 @@ def __init__( self.message = TryteString(b'') if message is None else message - def as_tryte_string(self): - # type: () -> TryteString + def as_tryte_string(self) -> TryteString: """ Returns a TryteString representation of the transaction. @@ -136,7 +135,7 @@ def as_tryte_string(self): return super(ProposedTransaction, self).as_tryte_string() - def increment_legacy_tag(self): + def increment_legacy_tag(self) -> None: """ Increments the transaction's legacy tag, used to fix insecure bundle hashes when finalizing a bundle. @@ -184,13 +183,13 @@ class ProposedBundle(Bundle, Sequence[ProposedTransaction]): def __init__( self, - transactions=None, # type: Optional[Iterable[ProposedTransaction]] - inputs=None, # type: Optional[Iterable[Address]] - change_address=None, # type: Optional[Address] - ): + transactions: Optional[Iterable[ProposedTransaction]] = None, + inputs: Optional[Iterable[Address]] = None, + change_address: Optional[Address] = None, + ) -> None: super(ProposedBundle, self).__init__() - self._transactions = [] # type: List[ProposedTransaction] + self._transactions: List[ProposedTransaction] = [] if transactions: for t in transactions: @@ -201,8 +200,7 @@ def __init__( self.change_address = change_address - def __bool__(self): - # type: () -> bool + def __bool__(self) -> bool: """ Returns whether this bundle has any transactions. @@ -210,34 +208,29 @@ def __bool__(self): """ return bool(self._transactions) - def __contains__(self, transaction): - # type: (ProposedTransaction) -> bool + def __contains__(self, transaction: ProposedTransaction) -> bool: return transaction in self._transactions - def __getitem__(self, index): - # type: (int) -> ProposedTransaction + def __getitem__(self, index: int) -> ProposedTransaction: """ Returns the transaction at the specified index. """ return self._transactions[index] - def __iter__(self): - # type: () -> Iterator[ProposedTransaction] + def __iter__(self) -> Iterator[ProposedTransaction]: """ Iterates over transactions in the bundle. """ return iter(self._transactions) - def __len__(self): - # type: () -> int + def __len__(self) -> int: """ Returns te number of transactions in the bundle. """ return len(self._transactions) @property - def balance(self): - # type: () -> int + def balance(self) -> int: """ Returns the bundle balance. In order for a bundle to be valid, its balance must be 0: @@ -255,8 +248,7 @@ def balance(self): return sum(t.value for t in self._transactions) @property - def tag(self): - # type: () -> Tag + def tag(self) -> Tag: """ Determines the most relevant tag for the bundle. @@ -268,8 +260,7 @@ def tag(self): return Tag(b'') - def as_json_compatible(self): - # type: () -> List[dict] + def as_json_compatible(self) -> List[Dict]: """ Returns a JSON-compatible representation of the object. @@ -283,8 +274,7 @@ def as_json_compatible(self): """ return [txn.as_json_compatible() for txn in self] - def add_transaction(self, transaction): - # type: (ProposedTransaction) -> None + def add_transaction(self, transaction: ProposedTransaction) -> None: """ Adds a transaction to the bundle. @@ -328,8 +318,7 @@ def add_transaction(self, transaction): fragment = fragment[Fragment.LEN:] - def add_inputs(self, inputs): - # type: (Iterable[Address]) -> None + def add_inputs(self, inputs: Iterable[Address]) -> None: """ Specifies inputs that can be used to fund transactions that spend iotas. @@ -386,8 +375,7 @@ def add_inputs(self, inputs): self._create_input_transactions(addy) - def send_unspent_inputs_to(self, address): - # type: (Address) -> None + def send_unspent_inputs_to(self, address: Address) -> None: """ Specifies the address that will receive unspent iotas. @@ -406,8 +394,7 @@ def send_unspent_inputs_to(self, address): self.change_address = address - def finalize(self): - # type: () -> None + def finalize(self) -> None: """ Finalizes the bundle, preparing it to be attached to the Tangle. @@ -477,9 +464,9 @@ def finalize(self): # https://github.com/iotaledger/iota.py/issues/84 if any(13 in part for part in normalize(bundle_hash)): # Increment the legacy tag and try again. - tail_transaction = ( + tail_transaction: ProposedTransaction = ( self.tail_transaction - ) # type: ProposedTransaction + ) tail_transaction.increment_legacy_tag() else: break @@ -491,8 +478,7 @@ def finalize(self): # Initialize signature/message fragment. txn.signature_message_fragment = Fragment(txn.message or b'') - def sign_inputs(self, key_generator): - # type: (KeyGenerator) -> None + def sign_inputs(self, key_generator: KeyGenerator) -> None: """ Sign inputs in a finalized bundle. @@ -562,8 +548,11 @@ def sign_inputs(self, key_generator): # cases); skip this transaction. i += 1 - def sign_input_at(self, start_index, private_key): - # type: (int, PrivateKey) -> None + def sign_input_at( + self, + start_index: int, + private_key: PrivateKey + ) -> None: """ Signs the input at the specified index. @@ -590,8 +579,7 @@ def sign_input_at(self, start_index, private_key): private_key.sign_input_transactions(self, start_index) - def _create_input_transactions(self, addy): - # type: (Address) -> None + def _create_input_transactions(self, addy: Address) -> None: """ Creates transactions for the specified input address. @@ -621,10 +609,9 @@ def _create_input_transactions(self, addy): def add_signature_or_message( self, - fragments, # type: Iterable[Fragment] - start_index=0 # type: Optional[int] - ): - # type: (...) -> None + fragments: Iterable[Fragment], + start_index: Optional[int] = 0 + ) -> None: """ Adds signature/message fragments to transactions in the bundle starting at start_index. If a transaction already has a fragment, diff --git a/iota/transaction/types.py b/iota/transaction/types.py index c888c3d..7e8f468 100644 --- a/iota/transaction/types.py +++ b/iota/transaction/types.py @@ -37,8 +37,7 @@ class Fragment(TryteString): Length of a fragment in trytes. """ - def __init__(self, trytes): - # type: (TrytesCompatible) -> None + def __init__(self, trytes: TrytesCompatible) -> None: super(Fragment, self).__init__(trytes, pad=self.LEN) if len(self._trytes) > self.LEN: @@ -65,8 +64,7 @@ class TransactionTrytes(TryteString): Length of a transaction in trytes. """ - def __init__(self, trytes): - # type: (TrytesCompatible) -> None + def __init__(self, trytes: TrytesCompatible) -> None: super(TransactionTrytes, self).__init__(trytes, pad=self.LEN) if len(self._trytes) > self.LEN: @@ -93,8 +91,7 @@ class Nonce(TryteString): Length of a nonce in trytes. """ - def __init__(self, trytes): - # type: (TrytesCompatible) -> None + def __init__(self, trytes: TrytesCompatible) -> None: super(Nonce, self).__init__(trytes, pad=self.LEN) if len(self._trytes) > self.LEN: diff --git a/iota/transaction/utils.py b/iota/transaction/utils.py index 8eacc3f..38e1617 100644 --- a/iota/transaction/utils.py +++ b/iota/transaction/utils.py @@ -11,8 +11,7 @@ ] -def convert_value_to_standard_unit(value, symbol='i'): - # type: (Text, Text) -> float +def convert_value_to_standard_unit(value: Text, symbol: Text = 'i') -> float: """ Converts between any two standard units of iota. @@ -57,8 +56,7 @@ def convert_value_to_standard_unit(value, symbol='i'): return amount * (unit_factor_from / unit_factor_to) -def get_current_timestamp(): - # type: () -> int +def get_current_timestamp() -> int: """ Returns the current timestamp, used to set ``timestamp`` for new :py:class:`ProposedTransaction` objects. diff --git a/iota/transaction/validator.py b/iota/transaction/validator.py index 77710dc..25b009b 100644 --- a/iota/transaction/validator.py +++ b/iota/transaction/validator.py @@ -1,4 +1,4 @@ -from typing import Generator, List, Optional, Text +from typing import Generator, List, Optional, Text, Type from iota.crypto.kerl import Kerl from iota.crypto.signing import validate_signature_fragments @@ -24,18 +24,16 @@ class BundleValidator(object): Checks a bundle and its transactions for problems. """ - def __init__(self, bundle): - # type: (Bundle) -> None + def __init__(self, bundle: Bundle) -> None: super(BundleValidator, self).__init__() self.bundle = bundle - self._errors = [] # type: Optional[List[Text]] + self._errors: Optional[List[Text]] = [] self._validator = self._create_validator() @property - def errors(self): - # type: () -> List[Text] + def errors(self) -> List[Text]: """ Returns all errors found with the bundle. """ @@ -46,8 +44,7 @@ def errors(self): return self._errors - def is_valid(self): - # type: () -> bool + def is_valid(self) -> bool: """ Returns whether the bundle is valid. """ @@ -61,8 +58,7 @@ def is_valid(self): return not self._errors - def _create_validator(self): - # type: () -> Generator[Text, None, None] + def _create_validator(self) -> Generator[Text, None, None]: """ Creates a generator that does all the work. """ @@ -124,7 +120,7 @@ def _create_validator(self): # Signature validation is only meaningful if the transactions # are otherwise valid. if not self._errors: - signature_validation_queue = [] # type: List[List[Transaction]] + signature_validation_queue: List[List[Transaction]] = [] for group in grouped_transactions: # Signature validation only applies to inputs. @@ -181,8 +177,10 @@ def _create_validator(self): ): yield error - def _get_bundle_signature_errors(self, groups): - # type: (List[List[Transaction]]) -> List[Text] + def _get_bundle_signature_errors( + self, + groups: List[List[Transaction]] + ) -> List[Text]: """ Validates the signature fragments in the bundle. @@ -230,8 +228,10 @@ def _get_bundle_signature_errors(self, groups): return current_errors @staticmethod - def _get_group_signature_error(group, sponge_type): - # type: (List[Transaction], type) -> Optional[Text] + def _get_group_signature_error( + group: List[Transaction], + sponge_type: Type + ) -> Optional[Text]: """ Validates the signature fragments for a group of transactions using the specified sponge type. diff --git a/iota/trits.py b/iota/trits.py index 6f45029..6f2fe60 100644 --- a/iota/trits.py +++ b/iota/trits.py @@ -14,8 +14,7 @@ ] -def add_trits(left, right): - # type: (Sequence[int], Sequence[int]) -> List[int] +def add_trits(left: Sequence[int], right: Sequence[int]) -> List[int]: """ Adds two sequences of trits together. @@ -40,8 +39,7 @@ def add_trits(left, right): return res -def int_from_trits(trits): - # type: (Iterable[int]) -> int +def int_from_trits(trits: Iterable[int]) -> int: """ Converts a sequence of trits into an integer value. """ @@ -50,8 +48,7 @@ def int_from_trits(trits): return sum(base * (3 ** power) for power, base in enumerate(trits)) -def trits_from_int(n, pad=1): - # type: (int, Optional[int]) -> List[int] +def trits_from_int(n: int, pad: Optional[int] = 1) -> List[int]: """ Returns a trit representation of an integer value. @@ -86,8 +83,7 @@ def trits_from_int(n, pad=1): return trits -def _cons_trits(left, right): - # type: (int, int) -> int +def _cons_trits(left: int, right: int) -> int: """ Compares two trits. If they have the same value, returns that value. Otherwise, returns 0. @@ -95,8 +91,7 @@ def _cons_trits(left, right): return left if left == right else 0 -def _add_trits(left, right): - # type: (int, int) -> int +def _add_trits(left: int, right: int) -> int: """ Adds two individual trits together. @@ -106,8 +101,7 @@ def _add_trits(left, right): return res if -2 < res < 2 else (res < 0) - (res > 0) -def _any_trits(left, right): - # type: (int, int) -> int +def _any_trits(left: int, right: int) -> int: """ Adds two individual trits together and returns a single trit indicating whether the result is positive or negative. @@ -116,8 +110,7 @@ def _any_trits(left, right): return (res > 0) - (res < 0) -def _full_add_trits(left, right, carry): - # type: (int, int, int) -> Tuple[int, int] +def _full_add_trits(left: int, right: int, carry: int) -> Tuple[int, int]: """ Adds two trits together, with support for a carry trit. """ diff --git a/iota/types.py b/iota/types.py index c01c12b..ac9abd0 100644 --- a/iota/types.py +++ b/iota/types.py @@ -4,7 +4,7 @@ from math import ceil from random import SystemRandom from typing import Any, AnyStr, Generator, Iterable, Iterator, List, \ - MutableSequence, Optional, Text, Type, TypeVar, Union + MutableSequence, Optional, Text, Type, TypeVar, Union, Dict from warnings import warn from iota import AsciiTrytesCodec, TRITS_PER_TRYTE @@ -61,8 +61,7 @@ class TryteString(JsonSerializable): """ @classmethod - def random(cls, length=None): - # type: (Optional[int]) -> TryteString + def random(cls: Type[T], length: Optional[int] = None) -> T: """ Generates a random sequence of trytes. @@ -95,8 +94,11 @@ def random(cls, length=None): ) @classmethod - def from_bytes(cls, bytes_, codec=AsciiTrytesCodec.name, *args, **kwargs): - # type: (Type[T], Union[bytes, bytearray], Text, *Any, **Any) -> T + def from_bytes(cls: Type[T], + bytes_: Union[bytes, bytearray], + codec: Text = AsciiTrytesCodec.name, + *args: Any, + **kwargs: Any) -> T: """ Creates a TryteString from a sequence of bytes. @@ -128,8 +130,10 @@ def from_bytes(cls, bytes_, codec=AsciiTrytesCodec.name, *args, **kwargs): return cls(encode(bytes_, codec), *args, **kwargs) @classmethod - def from_unicode(cls, string, *args, **kwargs): - # type: (Type[T], Text, *Any, **Any) -> T + def from_unicode(cls: Type[T], + string: Text, + *args: Any, + **kwargs: Any) -> T: """ Creates a TryteString from a Unicode string. @@ -159,7 +163,7 @@ def from_unicode(cls, string, *args, **kwargs): ) @classmethod - def from_string(cls, *args, **kwargs): + def from_string(cls: Type[T], *args: Any, **kwargs: Any) -> T: """ Deprecated; use :py:meth:`from_unicode` instead. @@ -175,8 +179,10 @@ def from_string(cls, *args, **kwargs): return cls.from_unicode(*args, **kwargs) @classmethod - def from_trytes(cls, trytes, *args, **kwargs): - # type: (Type[T], Iterable[Iterable[int]], *Any, **Any) -> T + def from_trytes(cls: Type[T], + trytes: Iterable[Iterable[int]], + *args: Any, + **kwargs: Any) -> T: """ Creates a TryteString from a sequence of trytes. @@ -229,8 +235,10 @@ def from_trytes(cls, trytes, *args, **kwargs): return cls(chars, *args, **kwargs) @classmethod - def from_trits(cls, trits, *args, **kwargs): - # type: (Type[T], Iterable[int], *Any, **Any) -> T + def from_trits(cls: Type[T], + trits: Iterable[int], + *args: Any, + **kwargs: Any) -> T: """ Creates a TryteString from a sequence of trits. @@ -274,8 +282,7 @@ def from_trits(cls, trits, *args, **kwargs): **kwargs ) - def __init__(self, trytes, pad=None): - # type: (TrytesCompatible, Optional[int]) -> None + def __init__(self, trytes: TrytesCompatible, pad: Optional[int] = None) -> None: """ :param TrytesCompatible trytes: Byte string or bytearray. @@ -358,20 +365,18 @@ def __init__(self, trytes, pad=None): if pad: trytes += b'9' * max(0, pad - len(trytes)) - self._trytes = trytes # type: bytearray + self._trytes: bytearray = trytes - def __hash__(self): - # type: () -> int + def __hash__(self) -> int: return hash(bytes(self._trytes)) - def __repr__(self): - # type: () -> Text + def __repr__(self) -> Text: return '{cls}({trytes!r})'.format( cls=type(self).__name__, trytes=bytes(self._trytes), ) - def __bytes__(self): + def __bytes__(self) -> bytes: """ Converts the TryteString into an ASCII representation. @@ -387,7 +392,7 @@ def __bytes__(self): """ return bytes(self._trytes) - def __str__(self): + def __str__(self) -> str: """ Same as :py:meth:`__bytes__`, except this method returns a unicode string. @@ -397,21 +402,17 @@ def __str__(self): return bytes(self._trytes).decode('ascii') - def __bool__(self): - # type: () -> bool + def __bool__(self) -> bool: return bool(self._trytes) and any(t != b'9' for t in self) - def __len__(self): - # type: () -> int + def __len__(self) -> int: return len(self._trytes) - def __iter__(self): - # type: () -> Generator[bytes, None, None] + def __iter__(self) -> Generator[bytes, None, None]: # :see: http://stackoverflow.com/a/14267935/ return (bytes(self._trytes[i:i + 1]) for i in range(len(self))) - def __contains__(self, other): - # type: (TrytesCompatible) -> bool + def __contains__(self, other: TrytesCompatible) -> bool: if isinstance(other, TryteString): return other._trytes in self._trytes elif isinstance(other, str): @@ -434,8 +435,7 @@ def __contains__(self, other): }, ) - def __getitem__(self, item): - # type: (Union[int, slice]) -> TryteString + def __getitem__(self, item: Union[int, slice]) -> T: new_trytes = bytearray() sliced = self._trytes[item] @@ -447,8 +447,9 @@ def __getitem__(self, item): return TryteString(new_trytes) - def __setitem__(self, item, trytes): - # type: (Union[int, slice], TrytesCompatible) -> None + def __setitem__(self, + item: Union[int, slice], + trytes: TrytesCompatible) -> None: new_trytes = TryteString(trytes) if isinstance(item, slice): @@ -469,8 +470,7 @@ def __setitem__(self, item, trytes): else: self._trytes[item] = new_trytes._trytes[0] - def __add__(self, other): - # type: (TrytesCompatible) -> TryteString + def __add__(self, other: TrytesCompatible) -> T: if isinstance(other, TryteString): return TryteString(self._trytes + other._trytes) elif isinstance(other, str): @@ -493,8 +493,7 @@ def __add__(self, other): }, ) - def __eq__(self, other): - # type: (TrytesCompatible) -> bool + def __eq__(self, other: TrytesCompatible) -> bool: if isinstance(other, TryteString): return self._trytes == other._trytes elif isinstance(other, str): @@ -517,13 +516,7 @@ def __eq__(self, other): }, ) - # :bc: In Python 2 this must be defined explicitly. - def __ne__(self, other): - # type: (TrytesCompatible) -> bool - return not (self == other) - - def count_chunks(self, chunk_size): - # type: (int) -> int + def count_chunks(self, chunk_size: int) -> int: """ Returns the number of constant-size chunks the TryteString can be divided into (rounded up). @@ -533,8 +526,9 @@ def count_chunks(self, chunk_size): """ return len(self.iter_chunks(chunk_size)) - def iter_chunks(self, chunk_size): - # type: (int) -> ChunkIterator + # Declare forward reference as string until + # https://www.python.org/dev/peps/pep-0563/ + def iter_chunks(self, chunk_size: int) -> 'ChunkIterator': """ Iterates over the TryteString, in chunks of constant size. @@ -545,8 +539,9 @@ def iter_chunks(self, chunk_size): """ return ChunkIterator(self, chunk_size) - def encode(self, errors='strict', codec=AsciiTrytesCodec.name): - # type: (Text, Text) -> bytes + def encode(self, + errors: Text = 'strict', + codec: Text = AsciiTrytesCodec.name) -> bytes: """ Encodes the TryteString into a lower-level primitive (usually bytes). @@ -619,8 +614,8 @@ def as_bytes(self, *args, **kwargs): ) return self.encode(*args, **kwargs) - def decode(self, errors='strict', strip_padding=True): - # type: (Text, bool) -> Text + def decode(self, errors: Text = 'strict', + strip_padding: bool = True) -> Text: """ Decodes the TryteString into a higher-level abstraction (usually Unicode characters). @@ -682,8 +677,7 @@ def as_string(self, *args, **kwargs): ) return self.decode(*args, **kwargs) - def as_json_compatible(self): - # type: () -> Text + def as_json_compatible(self) -> Text: """ Returns a JSON-compatible representation of the object. @@ -705,8 +699,7 @@ def as_json_compatible(self): """ return self._trytes.decode('ascii') - def as_integers(self): - # type: () -> List[int] + def as_integers(self) -> List[int]: """ Converts the TryteString into a sequence of integers. @@ -733,8 +726,7 @@ def as_integers(self): for c in self._trytes ] - def as_trytes(self): - # type: () -> List[List[int]] + def as_trytes(self) -> List[List[int]]: """ Converts the TryteString into a sequence of trytes. @@ -763,8 +755,7 @@ def as_trytes(self): for n in self.as_integers() ] - def as_trits(self): - # type: () -> List[int] + def as_trits(self) -> List[int]: """ Converts the TryteString into a sequence of trit values. @@ -810,8 +801,7 @@ def _repr_pretty_(self, p, cycle): return p.text(repr(self)) @staticmethod - def _normalize(n): - # type: (int) -> int + def _normalize(n: int) -> int: if n > 26: raise ValueError( '{n} cannot be represented by a single tryte.'.format( @@ -828,8 +818,7 @@ class ChunkIterator(Iterator[TryteString]): Iterates over a TryteString, in chunks of constant size. """ - def __init__(self, trytes, chunk_size): - # type: (TryteString, int) -> None + def __init__(self, trytes: TryteString, chunk_size: int) -> None: """ :param trytes: :py:class:`TryteString` to iterate over. @@ -846,12 +835,12 @@ def __init__(self, trytes, chunk_size): self._offset = 0 - def __iter__(self): - # type: () -> ChunkIterator + # ChunkIterator class is not defined yet here, so we can't use + # it as a type... Forward ref type annotation as available from PY3.7 + def __iter__(self) -> 'ChunkIterator': return self - def __len__(self): - # type: () -> int + def __len__(self) -> int: """ Returns how many chunks this iterator will return. @@ -861,8 +850,7 @@ def __len__(self): """ return int(ceil(len(self.trytes) / self.chunk_size)) - def __next__(self): - # type: () -> TryteString + def __next__(self) -> TryteString: """ Returns the next chunk in the iterator. @@ -897,8 +885,7 @@ class Hash(TryteString): Length is always 81 trytes long. """ - def __init__(self, trytes): - # type: (TrytesCompatible) -> None + def __init__(self, trytes: TrytesCompatible) -> None: super(Hash, self).__init__(trytes, pad=self.LEN) if len(self._trytes) > self.LEN: @@ -944,19 +931,17 @@ class Address(TryteString): def __init__( self, - trytes, # type: TrytesCompatible - balance=None, # type: Optional[int] - key_index=None, # type: Optional[int] - security_level=None, # type: Optional[int] - ): - # type: (...) -> None + trytes: TrytesCompatible, + balance: Optional[int] = None, + key_index: Optional[int] = None, + security_level: Optional[int] = None,) -> None: super(Address, self).__init__(trytes, pad=self.LEN) self.checksum = None if len(self._trytes) == (self.LEN + AddressChecksum.LEN): - self.checksum = AddressChecksum( + self.checksum: Optional[AddressChecksum] = AddressChecksum( self[self.LEN:] - ) # type: Optional[AddressChecksum] + ) elif len(self._trytes) > self.LEN: raise with_context( @@ -975,7 +960,7 @@ def __init__( ) # Make the address sans checksum accessible. - self.address = self[:self.LEN] # type: TryteString + self.address: TryteString = self[:self.LEN] """ Address trytes without the checksum. """ @@ -1007,7 +992,7 @@ def __init__( address. """ - def as_json_compatible(self): + def as_json_compatible(self) -> Dict[Text, Union[Text, int]]: """ Returns a JSON-compatible representation of the Address. @@ -1034,7 +1019,6 @@ def as_json_compatible(self): print(addy.as_json_compatible()) """ - # type: () -> dict return { 'trytes': self._trytes.decode('ascii'), 'balance': self.balance, @@ -1042,8 +1026,7 @@ def as_json_compatible(self): 'security_level': self.security_level, } - def is_checksum_valid(self): - # type: () -> bool + def is_checksum_valid(self) -> bool: """ Returns whether this address has a valid checksum. @@ -1074,8 +1057,7 @@ def is_checksum_valid(self): return False - def with_valid_checksum(self): - # type: () -> Address + def with_valid_checksum(self) -> 'Address': """ Returns the address with a valid checksum attached. @@ -1109,12 +1091,11 @@ def with_valid_checksum(self): security_level=self.security_level, ) - def _generate_checksum(self): - # type: () -> AddressChecksum + def _generate_checksum(self) -> 'AddressChecksum': """ Generates the correct checksum for this address. """ - checksum_trits = [] # type: MutableSequence[int] + checksum_trits: MutableSequence[int] = [] sponge = Kerl() sponge.absorb(self.address.as_trits()) @@ -1124,8 +1105,7 @@ def _generate_checksum(self): return AddressChecksum.from_trits(checksum_trits[-checksum_length:]) - def add_checksum(self): - # type: () -> None + def add_checksum(self) -> None: """ Adds checksum to :py:class:`Address` object. @@ -1164,8 +1144,7 @@ def add_checksum(self): # Add generated checksum to internal buffer. self._trytes = self._trytes + self.checksum._trytes - def remove_checksum(self): - # type: () -> None + def remove_checksum(self) -> None: """ Removes checksum from :py:class:`Address` object. @@ -1198,6 +1177,7 @@ def remove_checksum(self): self.checksum = None self._trytes = self._trytes[:self.LEN] + class AddressChecksum(TryteString): """ A :py:class:`TryteString` that acts as an address checksum. @@ -1212,8 +1192,7 @@ class AddressChecksum(TryteString): Length of an address checksum. """ - def __init__(self, trytes): - # type: (TrytesCompatible) -> None + def __init__(self, trytes: TrytesCompatible) -> None: super(AddressChecksum, self).__init__(trytes, pad=None) if len(self._trytes) != self.LEN: @@ -1245,8 +1224,7 @@ class Tag(TryteString): Length of a tag. """ - def __init__(self, trytes): - # type: (TrytesCompatible) -> None + def __init__(self, trytes: TrytesCompatible) -> None: super(Tag, self).__init__(trytes, pad=self.LEN) if len(self._trytes) > self.LEN: From 8010d66359328d5a6b05fd5fefeee9196bc7d5cb Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Thu, 19 Mar 2020 11:28:53 +0100 Subject: [PATCH 65/69] Update .github/ISSUE_TEMPLATE/bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 7a2541d..2607ec9 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -27,8 +27,6 @@ Which node are you connected to and which IOTA network is it in? What operating system are you using? - Operating system: - - ## Steps To reproduce the bug Explain how the maintainer can reproduce the bug. From 40c01300b6fa89d579b9ad711e6d6a539c0b77cf Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Thu, 19 Mar 2020 11:29:03 +0100 Subject: [PATCH 66/69] Update .github/pull_request_template.md --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3ced87e..d127d30 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -24,6 +24,6 @@ Add an `x` to the boxes that are relevant to your changes, and delete any items - [ ] My code follows the contribution guidelines for this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] I have made corresponding changes to the documentation +- [ ] I have made corresponding changes to the documentation (`docs/` directory and/or `docstring`s in source code) - [ ] I have followed [PEP-8](https://www.python.org/dev/peps/pep-0008/) Style Guide in my code. - [ ] New and existing unit tests pass locally with my changes From 9c95b9cd47baadbb40e99d7ba1eb521de735a584 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Fri, 20 Mar 2020 11:18:52 +0100 Subject: [PATCH 67/69] Apply suggestions from code review Co-Authored-By: Phoenix --- iota/adapter/__init__.py | 14 +++++------ iota/adapter/wrappers.py | 4 ++-- iota/codecs.py | 5 ++-- iota/commands/__init__.py | 6 ++--- iota/crypto/kerl/conv.py | 2 +- iota/crypto/signing.py | 2 +- iota/multisig/commands/get_digests.py | 2 +- iota/multisig/commands/get_private_keys.py | 2 +- .../commands/prepare_multisig_transfer.py | 2 +- iota/transaction/base.py | 24 +++++++++---------- 10 files changed, 32 insertions(+), 31 deletions(-) diff --git a/iota/adapter/__init__.py b/iota/adapter/__init__.py index f155310..78c5680 100644 --- a/iota/adapter/__init__.py +++ b/iota/adapter/__init__.py @@ -153,7 +153,7 @@ class BaseAdapter(object, metaclass=AdapterMeta): def __init__(self) -> None: super(BaseAdapter, self).__init__() - self._logger: Logger = None + self._logger: Optional[Logger] = None self.local_pow: bool = False @abstract_method @@ -166,7 +166,7 @@ def get_uri(self) -> Text: ) @abstract_method - def send_request(self, payload: Dict, **kwargs: Dict) -> Dict: + def send_request(self, payload: Dict, **kwargs: Any) -> Dict: """ Sends an API request to the node. @@ -200,7 +200,7 @@ def _log( self, level: int, message: Text, - context: Optional[int] = None + context: Optional[dict] = None ) -> None: """ Sends a message to the instance's logger, if configured. @@ -263,7 +263,7 @@ def __init__( self, uri: Union[Text, SplitResult], timeout: Optional[int] = None, - authentication: Optional[Dict] = None + authentication: Optional[Tuple[Text, Text]] = None ) -> None: super(HttpAdapter, self).__init__() @@ -325,7 +325,7 @@ def node_url(self) -> Text: def get_uri(self) -> Text: return self.uri.geturl() - async def send_request(self, payload: Dict, **kwargs: Dict) -> Dict: + async def send_request(self, payload: Dict, **kwargs: Any) -> Dict: kwargs.setdefault('headers', {}) for key, value in self.DEFAULT_HEADERS.items(): kwargs['headers'].setdefault(key, value) @@ -406,7 +406,7 @@ def _interpret_response( self, response: Response, payload: Dict, - expected_status: Container[Dict] + expected_status: Container[int] ) -> Dict: """ Interprets the HTTP response from the node. @@ -584,7 +584,7 @@ def seed_response(self, command: Text, response: Dict) -> 'MockAdapter': self.responses[command].append(response) return self - async def send_request(self, payload: Dict, **kwargs: Dict) -> Dict: + async def send_request(self, payload: Dict, **kwargs: Any) -> Dict: """ Mimic asynchronous behavior of `HttpAdapter.send_request`. """ diff --git a/iota/adapter/wrappers.py b/iota/adapter/wrappers.py index c984373..c8c06c9 100644 --- a/iota/adapter/wrappers.py +++ b/iota/adapter/wrappers.py @@ -26,7 +26,7 @@ def get_uri(self) -> Text: return self.adapter.get_uri() @abstract_method - def send_request(self, payload: Dict, **kwargs: Dict) -> Dict: + def send_request(self, payload: Dict, **kwargs: Any) -> Dict: raise NotImplementedError( 'Not implemented in {cls}.'.format(cls=type(self).__name__), ) @@ -131,7 +131,7 @@ def get_adapter(self, command: Text) -> BaseAdapter: """ return self.routes.get(command, self.adapter) - async def send_request(self, payload: Dict, **kwargs: Dict) -> Dict: + async def send_request(self, payload: Dict, **kwargs: Any) -> Dict: command = payload.get('command') return await self.get_adapter(command).send_request(payload, **kwargs) diff --git a/iota/codecs.py b/iota/codecs.py index 3d4a553..f54f785 100644 --- a/iota/codecs.py +++ b/iota/codecs.py @@ -59,11 +59,12 @@ def get_codec_info(cls) -> CodecInfo: """ codec = cls() - # In Python 2, all codecs are made equal. - # In Python 3, some codecs are more equal than others codec_info = { 'encode': codec.encode, 'decode': codec.decode, + + # In Python 2, all codecs are made equal. + # In Python 3, some codecs are more equal than others. '_is_text_encoding': False } diff --git a/iota/commands/__init__.py b/iota/commands/__init__.py index 56f8036..a9071cd 100644 --- a/iota/commands/__init__.py +++ b/iota/commands/__init__.py @@ -34,8 +34,8 @@ def __init__(self, adapter: BaseAdapter) -> None: self.adapter = adapter self.called: bool = False - self.request: Dict = None - self.response: Dict = None + self.request: Optional[Dict] = None + self.response: Optional[Dict] = None async def __call__(self, **kwargs: Any) -> Dict: """ @@ -262,4 +262,4 @@ def _apply_filter( }, ) - return value \ No newline at end of file + return value diff --git a/iota/crypto/kerl/conv.py b/iota/crypto/kerl/conv.py index 20d0a0c..6807f3c 100644 --- a/iota/crypto/kerl/conv.py +++ b/iota/crypto/kerl/conv.py @@ -91,7 +91,7 @@ def convertBytesToBigInt(ba: List[int]) -> int: enumerate(reversed(bytesArray))) * signum -def convertBigintToBytes(big: int) -> List[int]: +def convertBigIntToBytes(big: int) -> List[int]: bytesArrayTemp = [(abs(big) >> pos * 8) % (1 << 8) for pos in range(48)] diff --git a/iota/crypto/signing.py b/iota/crypto/signing.py index 0edbe5b..8649dc8 100644 --- a/iota/crypto/signing.py +++ b/iota/crypto/signing.py @@ -256,7 +256,7 @@ def __init__( ) # In order to work correctly, the seed must be padded so that it - # is a multiple of 81 trytes. + # is a multiple of 81 trytes. seed += b'9' * (Hash.LEN - ((len(seed) % Hash.LEN) or Hash.LEN)) self.security_level = security_level diff --git a/iota/multisig/commands/get_digests.py b/iota/multisig/commands/get_digests.py index 9ced79a..3d01841 100644 --- a/iota/multisig/commands/get_digests.py +++ b/iota/multisig/commands/get_digests.py @@ -30,7 +30,7 @@ def get_response_filter(self): # There is no async operation going on here, but the base class is async, # so from the outside, we have to act like we are doing async. - async def _execute(self, request: Dict): + async def _execute(self, request: Dict) -> Dict: count: Optional[int] = request['count'] index: int = request['index'] seed: Seed = request['seed'] diff --git a/iota/multisig/commands/get_private_keys.py b/iota/multisig/commands/get_private_keys.py index 0dcfadf..bb392b6 100644 --- a/iota/multisig/commands/get_private_keys.py +++ b/iota/multisig/commands/get_private_keys.py @@ -31,7 +31,7 @@ def get_response_filter(self): # There is no async operation going on here, but the base class is async, # so from the outside, we have to act like we are doing async. - async def _execute(self, request: Dict): + async def _execute(self, request: Dict) -> Dict: count: Optional[int] = request['count'] index: int = request['index'] seed: Seed = request['seed'] diff --git a/iota/multisig/commands/prepare_multisig_transfer.py b/iota/multisig/commands/prepare_multisig_transfer.py index 7ae2453..1585632 100644 --- a/iota/multisig/commands/prepare_multisig_transfer.py +++ b/iota/multisig/commands/prepare_multisig_transfer.py @@ -31,7 +31,7 @@ def get_request_filter(self) -> 'PrepareMultisigTransferRequestFilter': def get_response_filter(self): pass - async def _execute(self, request: Dict): + async def _execute(self, request: Dict) -> Dict: change_address: Optional[Address] = request['changeAddress'] multisig_input: MultisigAddress = request['multisigInput'] transfers: List[ProposedTransaction] = request['transfers'] diff --git a/iota/transaction/base.py b/iota/transaction/base.py index b971352..00d8965 100644 --- a/iota/transaction/base.py +++ b/iota/transaction/base.py @@ -214,7 +214,7 @@ def __init__( :type: :py:class:`TransactionHash` """ - self.bundle_hash: BundleHash = bundle_hash + self.bundle_hash: Optional[BundleHash] = bundle_hash """ The bundle hash, used to identify transactions that are part of the same bundle. @@ -249,7 +249,7 @@ def __init__( :type: ``int`` """ - self._legacy_tag: Tag = legacy_tag + self._legacy_tag: Optional[Tag] = legacy_tag """ A short message attached to the transaction. @@ -259,7 +259,7 @@ def __init__( :type: :py:class:`Tag` """ - self.nonce: Nonce = nonce + self.nonce: Optional[Nonce] = nonce """ Unique value used to increase security of the transaction hash. @@ -281,7 +281,7 @@ def __init__( :type: ``int``, unix timestamp in seconds. """ - self.current_index: int = current_index + self.current_index: Optional[int] = current_index """ The position of the transaction inside the bundle. @@ -295,7 +295,7 @@ def __init__( :type: ``int`` """ - self.last_index: int = last_index + self.last_index: Optional[int] = last_index """ The index of the final transaction in the bundle. @@ -305,7 +305,7 @@ def __init__( :type: ``int`` """ - self.trunk_transaction_hash: TransactionHash = trunk_transaction_hash + self.trunk_transaction_hash: Optional[TransactionHash] = trunk_transaction_hash """ The transaction hash of the next transaction in the bundle. @@ -319,7 +319,7 @@ def __init__( :type: :py:class:`TransactionHash` """ - self.branch_transaction_hash: TransactionHash = branch_transaction_hash + self.branch_transaction_hash: Optional[TransactionHash] = branch_transaction_hash """ An unrelated transaction that this transaction "approves". @@ -334,7 +334,7 @@ def __init__( :type: :py:class:`TransactionHash` """ - self.tag: Tag = tag + self.tag: Optional[Tag] = tag """ Optional classification tag applied to this transaction. @@ -343,7 +343,7 @@ def __init__( :type: :py:class:`Tag` """ - self.attachment_timestamp: int = attachment_timestamp + self.attachment_timestamp: Optional[int] = attachment_timestamp """ Estimated epoch time of the attachment to the tangle. @@ -352,21 +352,21 @@ def __init__( :type: ``int``, unix timestamp in milliseconds, """ - self.attachment_timestamp_lower_bound: int = attachment_timestamp_lower_bound + self.attachment_timestamp_lower_bound: Optional[int] = attachment_timestamp_lower_bound """ The lowest possible epoch time of the attachment to the tangle. :type: ``int``, unix timestamp in milliseconds. """ - self.attachment_timestamp_upper_bound: int = attachment_timestamp_upper_bound + self.attachment_timestamp_upper_bound: Optional[int] = attachment_timestamp_upper_bound """ The highest possible epoch time of the attachment to the tangle. :type: ``int``, unix timestamp in milliseconds. """ - self.signature_message_fragment: Fragment = signature_message_fragment + self.signature_message_fragment: Optional[Fragment] = signature_message_fragment """ "Signature/Message Fragment" (note the slash): From 0e609ad110a85a8901e5dee9d224bd44ff0983be Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Fri, 20 Mar 2020 11:23:26 +0100 Subject: [PATCH 68/69] PR review changes --- examples/send_transfer.py | 2 +- iota/__init__.py | 5 +- iota/adapter/__init__.py | 54 +++++------ iota/adapter/wrappers.py | 16 ++-- iota/api.py | 88 ++++++++--------- iota/api_async.py | 95 +++++++++---------- iota/bin/__init__.py | 6 +- iota/bin/repl.py | 3 +- iota/codecs.py | 6 +- iota/commands/__init__.py | 33 +++---- iota/commands/core/attach_to_tangle.py | 4 +- iota/commands/core/find_transactions.py | 4 +- iota/commands/core/get_balances.py | 4 +- .../core/get_transactions_to_approve.py | 4 +- iota/commands/extended/broadcast_and_store.py | 4 +- iota/commands/extended/broadcast_bundle.py | 4 +- .../extended/find_transaction_objects.py | 4 +- iota/commands/extended/get_account_data.py | 4 +- iota/commands/extended/get_bundles.py | 4 +- iota/commands/extended/get_inputs.py | 4 +- .../commands/extended/get_latest_inclusion.py | 4 +- iota/commands/extended/get_new_addresses.py | 4 +- .../extended/get_transaction_objects.py | 4 +- iota/commands/extended/get_transfers.py | 4 +- iota/commands/extended/is_promotable.py | 4 +- iota/commands/extended/is_reattachable.py | 4 +- iota/commands/extended/prepare_transfer.py | 4 +- iota/commands/extended/promote_transaction.py | 4 +- iota/commands/extended/replay_bundle.py | 4 +- iota/commands/extended/send_transfer.py | 4 +- iota/commands/extended/send_trytes.py | 4 +- iota/commands/extended/traverse_bundle.py | 4 +- iota/crypto/kerl/conv.py | 10 +- iota/crypto/types.py | 10 +- iota/filters.py | 4 +- iota/multisig/api.py | 34 +++---- .../commands/create_multisig_address.py | 4 +- iota/multisig/commands/get_digests.py | 4 +- iota/multisig/commands/get_private_keys.py | 4 +- .../commands/prepare_multisig_transfer.py | 4 +- iota/multisig/types.py | 6 +- iota/transaction/base.py | 12 +-- iota/transaction/creation.py | 4 +- iota/transaction/utils.py | 3 +- iota/transaction/validator.py | 16 ++-- iota/types.py | 32 +++---- 46 files changed, 259 insertions(+), 284 deletions(-) diff --git a/examples/send_transfer.py b/examples/send_transfer.py index ab8b372..34c0045 100644 --- a/examples/send_transfer.py +++ b/examples/send_transfer.py @@ -12,7 +12,7 @@ Tag, TryteString, ) -from address_generator import get_seed, output_seed +from .address_generator import get_seed, output_seed def main(address, depth, message, tag, uri, value): diff --git a/iota/__init__.py b/iota/__init__.py index e311054..970c1d6 100644 --- a/iota/__init__.py +++ b/iota/__init__.py @@ -1,7 +1,6 @@ +from typing import Dict # Define a few magic constants. -from typing import Dict, Text - DEFAULT_PORT: int = 14265 """ Default port to use when configuring an adapter, if the port is not @@ -16,7 +15,7 @@ In that way, it's kind of like toxic waste in a superhero story. """ -STANDARD_UNITS: Dict[Text, int] = { +STANDARD_UNITS: Dict[str, int] = { # Valid IOTA unit suffixes. Example value '-273.15 Ki' 'i': 1, 'Ki': 1000, diff --git a/iota/adapter/__init__.py b/iota/adapter/__init__.py index 78c5680..36fadc2 100644 --- a/iota/adapter/__init__.py +++ b/iota/adapter/__init__.py @@ -6,8 +6,8 @@ from inspect import isabstract as is_abstract from logging import DEBUG, Logger from socket import getdefaulttimeout as get_default_timeout -from typing import Container, Dict, List, Optional, Text, Tuple, Union, Any -from httpx import AsyncClient, Response, codes, auth +from typing import Container, List, Optional, Tuple, Union, Any, Dict +from httpx import AsyncClient, Response, codes, BasicAuth import asyncio from iota.exceptions import with_context @@ -32,7 +32,7 @@ """ # Custom types for type hints and docstrings. -AdapterSpec = Union[Text, 'BaseAdapter'] +AdapterSpec = Union[str, 'BaseAdapter'] """ Placeholder that means “URI or adapter instance”. @@ -69,7 +69,7 @@ class InvalidUri(ValueError): pass -adapter_registry: Dict[Text, 'AdapterMeta'] = {} +adapter_registry: Dict[str, 'AdapterMeta'] = {} """ Keeps track of available adapters and their supported protocols. """ @@ -127,7 +127,7 @@ def __init__(cls, what, bases=None, dict=None) -> None: # adapters. adapter_registry.setdefault(protocol, cls) - def configure(cls, parsed: Union[Text, SplitResult]) -> 'HttpAdapter': + def configure(cls, parsed: Union[str, SplitResult]) -> 'HttpAdapter': """ Creates a new instance using the specified URI. @@ -144,7 +144,7 @@ class BaseAdapter(object, metaclass=AdapterMeta): Adapters make it easy to customize the way an API instance communicates with a node. """ - supported_protocols: Tuple[Text] = () + supported_protocols: Tuple[str] = () """ Protocols that ``resolve_adapter`` can use to identify this adapter type. @@ -157,7 +157,7 @@ def __init__(self) -> None: self.local_pow: bool = False @abstract_method - def get_uri(self) -> Text: + def get_uri(self) -> str: """ Returns the URI that this adapter will use. """ @@ -166,7 +166,7 @@ def get_uri(self) -> Text: ) @abstract_method - def send_request(self, payload: Dict, **kwargs: Any) -> Dict: + def send_request(self, payload: dict, **kwargs: Any) -> dict: """ Sends an API request to the node. @@ -199,7 +199,7 @@ def set_logger(self, logger: Logger) -> 'BaseAdapter': def _log( self, level: int, - message: Text, + message: str, context: Optional[dict] = None ) -> None: """ @@ -231,7 +231,7 @@ class HttpAdapter(BaseAdapter): :param Optional[int] timeout: Connection timeout in seconds. - :param Optional[Tuple(Text,Text)] authentication: + :param Optional[Tuple(str,str)] authentication: Credetentials for basic authentication with the node. :return: @@ -261,9 +261,9 @@ class HttpAdapter(BaseAdapter): def __init__( self, - uri: Union[Text, SplitResult], + uri: Union[str, SplitResult], timeout: Optional[int] = None, - authentication: Optional[Tuple[Text, Text]] = None + authentication: Optional[Tuple[str, str]] = None ) -> None: super(HttpAdapter, self).__init__() @@ -316,16 +316,16 @@ def __init__( self.uri = uri @property - def node_url(self) -> Text: + def node_url(self) -> str: """ Returns the node URL. """ return self.uri.geturl() - def get_uri(self) -> Text: + def get_uri(self) -> str: return self.uri.geturl() - async def send_request(self, payload: Dict, **kwargs: Any) -> Dict: + async def send_request(self, payload: dict, **kwargs: Any) -> dict: kwargs.setdefault('headers', {}) for key, value in self.DEFAULT_HEADERS.items(): kwargs['headers'].setdefault(key, value) @@ -343,9 +343,9 @@ async def send_request(self, payload: Dict, **kwargs: Any) -> Dict: async def _send_http_request( self, - url: Text, - payload: Optional[Text], - method: Text = 'post', + url: str, + payload: Optional[str], + method: str = 'post', **kwargs: Any ) -> Response: """ @@ -360,7 +360,7 @@ async def _send_http_request( ) if self.authentication: - kwargs.setdefault('auth', auth.BasicAuth(*self.authentication)) + kwargs.setdefault('auth', BasicAuth(*self.authentication)) self._log( level=DEBUG, @@ -405,9 +405,9 @@ async def _send_http_request( def _interpret_response( self, response: Response, - payload: Dict, + payload: dict, expected_status: Container[int] - ) -> Dict: + ) -> dict: """ Interprets the HTTP response from the node. @@ -437,7 +437,7 @@ def _interpret_response( ) try: - decoded: Dict = json.loads(raw_content) + decoded: dict = json.loads(raw_content) # :bc: py2k doesn't have JSONDecodeError except ValueError: raise with_context( @@ -538,13 +538,13 @@ def configure(cls, uri): def __init__(self) -> None: super(MockAdapter, self).__init__() - self.responses: Dict[Text, deque] = {} + self.responses: Dict[str, deque] = {} self.requests: List[dict] = [] - def get_uri(self) -> Text: + def get_uri(self) -> str: return 'mock://' - def seed_response(self, command: Text, response: Dict) -> 'MockAdapter': + def seed_response(self, command: str, response: dict) -> 'MockAdapter': """ Sets the response that the adapter will return for the specified command. @@ -558,7 +558,7 @@ def seed_response(self, command: Text, response: Dict) -> 'MockAdapter': have a seeded response for a particular command, it will raise a ``BadApiResponse`` exception (simulates a 404 response). - :param Text command: + :param str command: The name of the command. Note that this is the camelCase version of the command name (e.g., ``getNodeInfo``, not ``get_node_info``). @@ -584,7 +584,7 @@ def seed_response(self, command: Text, response: Dict) -> 'MockAdapter': self.responses[command].append(response) return self - async def send_request(self, payload: Dict, **kwargs: Any) -> Dict: + async def send_request(self, payload: Dict, **kwargs: Any) -> dict: """ Mimic asynchronous behavior of `HttpAdapter.send_request`. """ diff --git a/iota/adapter/wrappers.py b/iota/adapter/wrappers.py index c8c06c9..3beb0b0 100644 --- a/iota/adapter/wrappers.py +++ b/iota/adapter/wrappers.py @@ -1,5 +1,5 @@ from abc import ABCMeta, abstractmethod as abstract_method -from typing import Dict, Text +from typing import Dict, Any from iota.adapter import AdapterSpec, BaseAdapter, resolve_adapter @@ -22,11 +22,11 @@ def __init__(self, adapter: AdapterSpec) -> None: self.adapter: BaseAdapter = adapter - def get_uri(self) -> Text: + def get_uri(self) -> str: return self.adapter.get_uri() @abstract_method - def send_request(self, payload: Dict, **kwargs: Any) -> Dict: + def send_request(self, payload: dict, **kwargs: Any) -> dict: raise NotImplementedError( 'Not implemented in {cls}.'.format(cls=type(self).__name__), ) @@ -94,13 +94,13 @@ def __init__(self, default_adapter: AdapterSpec) -> None: # when resolving URIs. self.adapter_aliases: Dict[AdapterSpec, BaseAdapter] = {} - self.routes: Dict[Text, BaseAdapter] = {} + self.routes: Dict[str, BaseAdapter] = {} - def add_route(self, command: Text, adapter: AdapterSpec) -> 'RoutingWrapper': + def add_route(self, command: str, adapter: AdapterSpec) -> 'RoutingWrapper': """ Adds a route to the wrapper. - :param Text command: + :param str command: The name of the command. Note that this is the camelCase version of the command name (e.g., ``attachToTangle``, not ``attach_to_tangle``). @@ -125,13 +125,13 @@ def add_route(self, command: Text, adapter: AdapterSpec) -> 'RoutingWrapper': return self - def get_adapter(self, command: Text) -> BaseAdapter: + def get_adapter(self, command: str) -> BaseAdapter: """ Return the adapter for the specified command. """ return self.routes.get(command, self.adapter) - async def send_request(self, payload: Dict, **kwargs: Any) -> Dict: + async def send_request(self, payload: dict, **kwargs: Any) -> dict: command = payload.get('command') return await self.get_adapter(command).send_request(payload, **kwargs) diff --git a/iota/api.py b/iota/api.py index 17ab405..1f42cec 100644 --- a/iota/api.py +++ b/iota/api.py @@ -1,4 +1,4 @@ -from typing import Dict, Iterable, Optional, Text +from typing import Dict, Iterable, Optional from iota import AdapterSpec, Address, BundleHash, ProposedTransaction, Tag, \ TransactionHash, TransactionTrytes, TryteString, TrytesCompatible @@ -98,12 +98,12 @@ def __init__( """ super().__init__(adapter, devnet, local_pow) - def add_neighbors(self, uris: Iterable[Text]) -> Dict: + def add_neighbors(self, uris: Iterable[str]) -> dict: """ Add one or more neighbors to the node. Lasts until the node is restarted. - :param Iterable[Text] uris: + :param Iterable[str] uris: Use format ``://:``. Example: ``add_neighbors(['udp://example.com:14265'])`` @@ -139,7 +139,7 @@ def attach_to_tangle( branch_transaction: TransactionHash, trytes: Iterable[TryteString], min_weight_magnitude: Optional[int] = None, - ) -> Dict: + ) -> dict: """ Attaches the specified transactions (trytes) to the Tangle by doing Proof of Work. You need to supply branchTransaction as @@ -188,7 +188,7 @@ def attach_to_tangle( ) ) - def broadcast_transactions(self, trytes: Iterable[TryteString]) -> Dict: + def broadcast_transactions(self, trytes: Iterable[TryteString]) -> dict: """ Broadcast a list of transactions to all neighbors. @@ -219,7 +219,7 @@ def broadcast_transactions(self, trytes: Iterable[TryteString]) -> Dict: ) ) - def check_consistency(self, tails: Iterable[TransactionHash]) -> Dict: + def check_consistency(self, tails: Iterable[TransactionHash]) -> dict: """ Used to ensure tail resolves to a consistent ledger which is necessary to validate before attempting promotion. Checks @@ -240,7 +240,7 @@ def check_consistency(self, tails: Iterable[TransactionHash]) -> Dict: { 'state': bool, Whether tails resolve to consistent ledger. - 'info': Text, + 'info': str, This field will only exist if 'state' is ``False``. } @@ -263,7 +263,7 @@ def find_transactions( addresses: Optional[Iterable[Address]] = None, tags: Optional[Iterable[Tag]] = None, approvees: Optional[Iterable[TransactionHash]] = None, - ) -> Dict: + ) -> dict: """ Find the transactions which match the specified input and return. @@ -316,7 +316,7 @@ def get_balances( addresses: Iterable[Address], threshold: int = 100, tips: Optional[Iterable[TransactionHash]] = None, - ) -> Dict: + ) -> dict: """ Similar to :py:meth:`get_inclusion_states`. Returns the confirmed balance which a list of addresses have at the latest @@ -373,7 +373,7 @@ def get_inclusion_states( self, transactions: Iterable[TransactionHash], tips: Iterable[TransactionHash] - ) -> Dict: + ) -> dict: """ Get the inclusion states of a set of transactions. This is for determining if a transaction was accepted and confirmed by the @@ -414,7 +414,7 @@ def get_inclusion_states( ) ) - def get_missing_transactions(self) -> Dict: + def get_missing_transactions(self) -> dict: """ Returns all transaction hashes that a node is currently requesting from its neighbors. @@ -440,7 +440,7 @@ def get_missing_transactions(self) -> Dict: super().get_missing_transactions() ) - def get_neighbors(self) -> Dict: + def get_neighbors(self) -> dict: """ Returns the set of neighbors the node is connected with, as well as their activity count. @@ -477,7 +477,7 @@ def get_neighbors(self) -> Dict: super().get_neighbors() ) - def get_node_api_configuration(self) -> Dict: + def get_node_api_configuration(self) -> dict: """ Returns a node's API configuration settings. @@ -505,7 +505,7 @@ def get_node_api_configuration(self) -> Dict: super().get_node_api_configuration() ) - def get_node_info(self) -> Dict: + def get_node_info(self) -> dict: """ Returns information about the node. @@ -513,9 +513,9 @@ def get_node_info(self) -> Dict: ``dict`` with the following structure:: { - 'appName': Text, + 'appName': str, Name of the IRI network. - 'appVersion': Text, + 'appVersion': str, Version of the IRI. 'jreAvailableProcessors': int, Available CPU cores on the node. @@ -526,7 +526,7 @@ def get_node_info(self) -> Dict: can use, 'jreTotalMemory': int, Total amount of memory in the Java virtual machine. - 'jreVersion': Text, + 'jreVersion': str, The version of the Java runtime environment. 'latestMilestone': TransactionHash Transaction hash of the latest milestone. @@ -549,7 +549,7 @@ def get_node_info(self) -> Dict: 'transactionsToRequest': int, Total number of transactions that the node is missing in its ledger. - 'features': List[Text], + 'features': List[str], Enabled configuration options. 'coordinatorAddress': Address, Address (Merkle root) of the Coordinator. @@ -568,7 +568,7 @@ def get_node_info(self) -> Dict: super().get_node_info() ) - def get_tips(self) -> Dict: + def get_tips(self) -> dict: """ Returns the list of tips (transactions which have no other transactions referencing them). @@ -599,7 +599,7 @@ def get_transactions_to_approve( self, depth: int, reference: Optional[TransactionHash] = None, - ) -> Dict: + ) -> dict: """ Tip selection which returns ``trunkTransaction`` and ``branchTransaction``. @@ -642,7 +642,7 @@ def get_transactions_to_approve( ) ) - def get_trytes(self, hashes: Iterable[TransactionHash]) -> Dict: + def get_trytes(self, hashes: Iterable[TransactionHash]) -> dict: """ Returns the raw transaction data (trytes) of one or more transactions. @@ -676,7 +676,7 @@ def get_trytes(self, hashes: Iterable[TransactionHash]) -> Dict: ) ) - def interrupt_attaching_to_tangle(self) -> Dict: + def interrupt_attaching_to_tangle(self) -> dict: """ Interrupts and completely aborts the :py:meth:`attach_to_tangle` process. @@ -700,12 +700,12 @@ def interrupt_attaching_to_tangle(self) -> Dict: super().interrupt_attaching_to_tangle() ) - def remove_neighbors(self, uris: Iterable[Text]) -> Dict: + def remove_neighbors(self, uris: Iterable[str]) -> dict: """ Removes one or more neighbors from the node. Lasts until the node is restarted. - :param Text uris: + :param str uris: Use format ``://:``. Example: `remove_neighbors(['udp://example.com:14265'])` @@ -730,7 +730,7 @@ def remove_neighbors(self, uris: Iterable[Text]) -> Dict: super().remove_neighbors(uris) ) - def store_transactions(self, trytes: Iterable[TryteString]) -> Dict: + def store_transactions(self, trytes: Iterable[TryteString]) -> dict: """ Store transactions into local storage of the node. @@ -764,7 +764,7 @@ def store_transactions(self, trytes: Iterable[TryteString]) -> Dict: def were_addresses_spent_from( self, addresses: Iterable[Address] - ) -> Dict: + ) -> dict: """ Check if a list of addresses was ever spent from, in the current epoch, or in previous epochs. @@ -856,7 +856,7 @@ def __init__( def broadcast_and_store( self, trytes: Iterable[TransactionTrytes] - ) -> Dict: + ) -> dict: """ Broadcasts and stores a set of transaction trytes. @@ -885,7 +885,7 @@ def broadcast_and_store( def broadcast_bundle( self, tail_transaction_hash: TransactionHash - ) -> Dict: + ) -> dict: """ Re-broadcasts all transactions in a bundle given the tail transaction hash. It might be useful when transactions did not properly propagate, @@ -919,7 +919,7 @@ def find_transaction_objects( addresses: Optional[Iterable[Address]] = None, tags: Optional[Iterable[Tag]] = None, approvees: Optional[Iterable[TransactionHash]] = None, - ) -> Dict: + ) -> dict: """ A more extensive version of :py:meth:`find_transactions` that returns transaction objects instead of hashes. @@ -974,7 +974,7 @@ def get_account_data( stop: Optional[int] = None, inclusion_states: bool = False, security_level: Optional[int] = None - ) -> Dict: + ) -> dict: """ More comprehensive version of :py:meth:`get_transfers` that returns addresses and account balance in addition to bundles. @@ -1056,7 +1056,7 @@ def get_account_data( def get_bundles( self, transactions: Iterable[TransactionHash] - ) -> Dict: + ) -> dict: """ Returns the bundle(s) associated with the specified transaction hashes. @@ -1093,7 +1093,7 @@ def get_inputs( stop: Optional[int] = None, threshold: Optional[int] = None, security_level: Optional[int] = None, - ) -> Dict: + ) -> dict: """ Gets all possible inputs of a seed and returns them, along with the total balance. @@ -1204,7 +1204,7 @@ def get_inputs( def get_latest_inclusion( self, hashes: Iterable[TransactionHash] - ) -> Dict[Text, Dict[TransactionHash, bool]]: + ) -> Dict[str, Dict[TransactionHash, bool]]: """ Fetches the inclusion state for the specified transaction hashes, as of the latest milestone that the node has processed. @@ -1305,7 +1305,7 @@ def get_new_addresses( def get_transaction_objects( self, hashes: [Iterable[TransactionHash]], - ) -> Dict: + ) -> dict: """ Fetches transaction objects from the Tangle given their transaction IDs (hashes). @@ -1338,7 +1338,7 @@ def get_transfers( start: int = 0, stop: Optional[int] = None, inclusion_states: bool = False - ) -> Dict: + ) -> dict: """ Returns all transfers associated with the seed. @@ -1403,7 +1403,7 @@ def get_transfers( def is_promotable( self, tails: Iterable[TransactionHash], - ) -> Dict: + ) -> dict: """ Checks if tail transaction(s) is promotable by calling :py:meth:`check_consistency` and verifying that ``attachmentTimestamp`` @@ -1423,7 +1423,7 @@ def is_promotable( If ``True``, all tails are promotable. If ``False``, see `info` field. - 'info': Optional(List[Text]) + 'info': Optional(List[str]) If `promotable` is ``False``, this contains info about what went wrong. Note that when 'promotable' is ``True``, 'info' does not @@ -1446,7 +1446,7 @@ def prepare_transfer( inputs: Optional[Iterable[Address]] = None, change_address: Optional[Address] = None, security_level: Optional[int] = None, - ) -> Dict: + ) -> dict: """ Prepares transactions to be broadcast to the Tangle, by generating the correct bundle, as well as choosing and signing @@ -1509,7 +1509,7 @@ def promote_transaction( transaction: TransactionHash, depth: int = 3, min_weight_magnitude: Optional[int] = None, - ) -> Dict: + ) -> dict: """ Promotes a transaction by adding spam on top of it. @@ -1549,7 +1549,7 @@ def replay_bundle( transaction: TransactionHash, depth: int = 3, min_weight_magnitude: Optional[int] = None, - ) -> Dict: + ) -> dict: """ Takes a tail transaction hash as input, gets the bundle associated with the transaction and then replays the bundle by @@ -1598,7 +1598,7 @@ def send_transfer( change_address: Optional[Address] = None, min_weight_magnitude: Optional[int] = None, security_level: Optional[int] = None, - ) -> Dict: + ) -> dict: """ Prepares a set of transfers and creates the bundle, then attaches the bundle to the Tangle, and broadcasts and stores the @@ -1667,7 +1667,7 @@ def send_trytes( trytes: Iterable[TransactionTrytes], depth: int = 3, min_weight_magnitude: Optional[int] = None - ) -> Dict: + ) -> dict: """ Attaches transaction trytes to the Tangle, then broadcasts and stores them. @@ -1707,7 +1707,7 @@ def send_trytes( ) ) - def is_reattachable(self, addresses: Iterable[Address]) -> Dict: + def is_reattachable(self, addresses: Iterable[Address]) -> dict: """ This API function helps you to determine whether you should replay a transaction or make a new one (either with the same @@ -1741,7 +1741,7 @@ def is_reattachable(self, addresses: Iterable[Address]) -> Dict: ) ) - def traverse_bundle(self, tail_hash: TransactionHash) -> Dict: + def traverse_bundle(self, tail_hash: TransactionHash) -> dict: """ Fetches and traverses a bundle from the Tangle given a tail transaction hash. diff --git a/iota/api_async.py b/iota/api_async.py index 9098461..7a85003 100644 --- a/iota/api_async.py +++ b/iota/api_async.py @@ -1,12 +1,11 @@ -from typing import Dict, Iterable, Optional, Text +from typing import Dict, Iterable, Optional from iota import AdapterSpec, Address, BundleHash, ProposedTransaction, Tag, \ TransactionHash, TransactionTrytes, TryteString, TrytesCompatible from iota.adapter import BaseAdapter, resolve_adapter -from iota.commands import BaseCommand, CustomCommand, core, extended +from iota.commands import CustomCommand, core, extended from iota.crypto.addresses import AddressGenerator from iota.crypto.types import Seed -import asyncio __all__ = [ 'AsyncIota', @@ -85,14 +84,14 @@ def __init__( self.adapter.set_local_pow(local_pow) self.devnet = devnet - def create_command(self, command: Text) -> CustomCommand: + def create_command(self, command: str) -> CustomCommand: """ Creates a pre-configured CustomCommand instance. This method is useful for invoking undocumented or experimental methods, or if you just want to troll your node for awhile. - :param Text command: + :param str command: The name of the command to create. """ @@ -125,12 +124,12 @@ def default_min_weight_magnitude(self) -> int: """ return 9 if self.devnet else 14 - async def add_neighbors(self, uris: Iterable[Text]) -> Dict: + async def add_neighbors(self, uris: Iterable[str]) -> dict: """ Add one or more neighbors to the node. Lasts until the node is restarted. - :param Iterable[Text] uris: + :param Iterable[str] uris: Use format ``://:``. Example: ``add_neighbors(['udp://example.com:14265'])`` @@ -161,7 +160,7 @@ async def attach_to_tangle( branch_transaction: TransactionHash, trytes: Iterable[TryteString], min_weight_magnitude: Optional[int] = None, - ) -> Dict: + ) -> dict: """ Attaches the specified transactions (trytes) to the Tangle by doing Proof of Work. You need to supply branchTransaction as @@ -208,7 +207,7 @@ async def attach_to_tangle( trytes=trytes, ) - async def broadcast_transactions(self, trytes: Iterable[TryteString]) -> Dict: + async def broadcast_transactions(self, trytes: Iterable[TryteString]) -> dict: """ Broadcast a list of transactions to all neighbors. @@ -232,7 +231,7 @@ async def broadcast_transactions(self, trytes: Iterable[TryteString]) -> Dict: """ return await core.BroadcastTransactionsCommand(self.adapter)(trytes=trytes) - async def check_consistency(self, tails: Iterable[TransactionHash]) -> Dict: + async def check_consistency(self, tails: Iterable[TransactionHash]) -> dict: """ Used to ensure tail resolves to a consistent ledger which is necessary to validate before attempting promotion. Checks @@ -253,7 +252,7 @@ async def check_consistency(self, tails: Iterable[TransactionHash]) -> Dict: { 'state': bool, Whether tails resolve to consistent ledger. - 'info': Text, + 'info': str, This field will only exist if 'state' is ``False``. } @@ -271,7 +270,7 @@ async def find_transactions( addresses: Optional[Iterable[Address]] = None, tags: Optional[Iterable[Tag]] = None, approvees: Optional[Iterable[TransactionHash]] = None, - ) -> Dict: + ) -> dict: """ Find the transactions which match the specified input and return. @@ -319,7 +318,7 @@ async def get_balances( addresses: Iterable[Address], threshold: int = 100, tips: Optional[Iterable[TransactionHash]] = None, - ) -> Dict: + ) -> dict: """ Similar to :py:meth:`get_inclusion_states`. Returns the confirmed balance which a list of addresses have at the latest @@ -371,7 +370,7 @@ async def get_inclusion_states( self, transactions: Iterable[TransactionHash], tips: Iterable[TransactionHash] - ) -> Dict: + ) -> dict: """ Get the inclusion states of a set of transactions. This is for determining if a transaction was accepted and confirmed by the @@ -407,7 +406,7 @@ async def get_inclusion_states( tips=tips, ) - async def get_missing_transactions(self) -> Dict: + async def get_missing_transactions(self) -> dict: """ Returns all transaction hashes that a node is currently requesting from its neighbors. @@ -428,7 +427,7 @@ async def get_missing_transactions(self) -> Dict: """ return await core.GetMissingTransactionsCommand(self.adapter)() - async def get_neighbors(self) -> Dict: + async def get_neighbors(self) -> dict: """ Returns the set of neighbors the node is connected with, as well as their activity count. @@ -460,7 +459,7 @@ async def get_neighbors(self) -> Dict: """ return await core.GetNeighborsCommand(self.adapter)() - async def get_node_api_configuration(self) -> Dict: + async def get_node_api_configuration(self) -> dict: """ Returns a node's API configuration settings. @@ -483,7 +482,7 @@ async def get_node_api_configuration(self) -> Dict: """ return await core.GetNodeAPIConfigurationCommand(self.adapter)() - async def get_node_info(self) -> Dict: + async def get_node_info(self) -> dict: """ Returns information about the node. @@ -491,9 +490,9 @@ async def get_node_info(self) -> Dict: ``dict`` with the following structure:: { - 'appName': Text, + 'appName': str, Name of the IRI network. - 'appVersion': Text, + 'appVersion': str, Version of the IRI. 'jreAvailableProcessors': int, Available CPU cores on the node. @@ -504,7 +503,7 @@ async def get_node_info(self) -> Dict: can use, 'jreTotalMemory': int, Total amount of memory in the Java virtual machine. - 'jreVersion': Text, + 'jreVersion': str, The version of the Java runtime environment. 'latestMilestone': TransactionHash Transaction hash of the latest milestone. @@ -527,7 +526,7 @@ async def get_node_info(self) -> Dict: 'transactionsToRequest': int, Total number of transactions that the node is missing in its ledger. - 'features': List[Text], + 'features': List[str], Enabled configuration options. 'coordinatorAddress': Address, Address (Merkle root) of the Coordinator. @@ -541,7 +540,7 @@ async def get_node_info(self) -> Dict: """ return await core.GetNodeInfoCommand(self.adapter)() - async def get_tips(self) -> Dict: + async def get_tips(self) -> dict: """ Returns the list of tips (transactions which have no other transactions referencing them). @@ -567,7 +566,7 @@ async def get_transactions_to_approve( self, depth: int, reference: Optional[TransactionHash] = None, - ) -> Dict: + ) -> dict: """ Tip selection which returns ``trunkTransaction`` and ``branchTransaction``. @@ -605,7 +604,7 @@ async def get_transactions_to_approve( reference=reference, ) - async def get_trytes(self, hashes: Iterable[TransactionHash]) -> Dict: + async def get_trytes(self, hashes: Iterable[TransactionHash]) -> dict: """ Returns the raw transaction data (trytes) of one or more transactions. @@ -632,7 +631,7 @@ async def get_trytes(self, hashes: Iterable[TransactionHash]) -> Dict: """ return await core.GetTrytesCommand(self.adapter)(hashes=hashes) - async def interrupt_attaching_to_tangle(self) -> Dict: + async def interrupt_attaching_to_tangle(self) -> dict: """ Interrupts and completely aborts the :py:meth:`attach_to_tangle` process. @@ -651,12 +650,12 @@ async def interrupt_attaching_to_tangle(self) -> Dict: """ return await core.InterruptAttachingToTangleCommand(self.adapter)() - async def remove_neighbors(self, uris: Iterable[Text]) -> Dict: + async def remove_neighbors(self, uris: Iterable[str]) -> dict: """ Removes one or more neighbors from the node. Lasts until the node is restarted. - :param Text uris: + :param str uris: Use format ``://:``. Example: `remove_neighbors(['udp://example.com:14265'])` @@ -676,7 +675,7 @@ async def remove_neighbors(self, uris: Iterable[Text]) -> Dict: """ return await core.RemoveNeighborsCommand(self.adapter)(uris=uris) - async def store_transactions(self, trytes: Iterable[TryteString]) -> Dict: + async def store_transactions(self, trytes: Iterable[TryteString]) -> dict: """ Store transactions into local storage of the node. @@ -705,7 +704,7 @@ async def store_transactions(self, trytes: Iterable[TryteString]) -> Dict: async def were_addresses_spent_from( self, addresses: Iterable[Address] - ) -> Dict: + ) -> dict: """ Check if a list of addresses was ever spent from, in the current epoch, or in previous epochs. @@ -795,7 +794,7 @@ def __init__( async def broadcast_and_store( self, trytes: Iterable[TransactionTrytes] - ) -> Dict: + ) -> dict: """ Broadcasts and stores a set of transaction trytes. @@ -822,7 +821,7 @@ async def broadcast_and_store( async def broadcast_bundle( self, tail_transaction_hash: TransactionHash - ) -> Dict: + ) -> dict: """ Re-broadcasts all transactions in a bundle given the tail transaction hash. It might be useful when transactions did not properly propagate, @@ -854,7 +853,7 @@ async def find_transaction_objects( addresses: Optional[Iterable[Address]] = None, tags: Optional[Iterable[Tag]] = None, approvees: Optional[Iterable[TransactionHash]] = None, - ) -> Dict: + ) -> dict: """ A more extensive version of :py:meth:`find_transactions` that returns transaction objects instead of hashes. @@ -905,7 +904,7 @@ async def get_account_data( stop: Optional[int] = None, inclusion_states: bool = False, security_level: Optional[int] = None - ) -> Dict: + ) -> dict: """ More comprehensive version of :py:meth:`get_transfers` that returns addresses and account balance in addition to bundles. @@ -984,7 +983,7 @@ async def get_account_data( async def get_bundles( self, transactions: Iterable[TransactionHash] - ) -> Dict: + ) -> dict: """ Returns the bundle(s) associated with the specified transaction hashes. @@ -1019,7 +1018,7 @@ async def get_inputs( stop: Optional[int] = None, threshold: Optional[int] = None, security_level: Optional[int] = None, - ) -> Dict: + ) -> dict: """ Gets all possible inputs of a seed and returns them, along with the total balance. @@ -1127,7 +1126,7 @@ async def get_inputs( async def get_latest_inclusion( self, hashes: Iterable[TransactionHash] - ) -> Dict[Text, Dict[TransactionHash, bool]]: + ) -> Dict[str, Dict[TransactionHash, bool]]: """ Fetches the inclusion state for the specified transaction hashes, as of the latest milestone that the node has processed. @@ -1221,7 +1220,7 @@ async def get_new_addresses( async def get_transaction_objects( self, hashes: [Iterable[TransactionHash]], - ) -> Dict: + ) -> dict: """ Fetches transaction objects from the Tangle given their transaction IDs (hashes). @@ -1252,7 +1251,7 @@ async def get_transfers( start: int = 0, stop: Optional[int] = None, inclusion_states: bool = False - ) -> Dict: + ) -> dict: """ Returns all transfers associated with the seed. @@ -1314,7 +1313,7 @@ async def get_transfers( async def is_promotable( self, tails: Iterable[TransactionHash], - ) -> Dict: + ) -> dict: """ Checks if tail transaction(s) is promotable by calling :py:meth:`check_consistency` and verifying that ``attachmentTimestamp`` @@ -1334,7 +1333,7 @@ async def is_promotable( If ``True``, all tails are promotable. If ``False``, see `info` field. - 'info': Optional(List[Text]) + 'info': Optional(List[str]) If `promotable` is ``False``, this contains info about what went wrong. Note that when 'promotable' is ``True``, 'info' does not @@ -1355,7 +1354,7 @@ async def prepare_transfer( inputs: Optional[Iterable[Address]] = None, change_address: Optional[Address] = None, security_level: Optional[int] = None, - ) -> Dict: + ) -> dict: """ Prepares transactions to be broadcast to the Tangle, by generating the correct bundle, as well as choosing and signing @@ -1415,7 +1414,7 @@ async def promote_transaction( transaction: TransactionHash, depth: int = 3, min_weight_magnitude: Optional[int] = None, - ) -> Dict: + ) -> dict: """ Promotes a transaction by adding spam on top of it. @@ -1454,7 +1453,7 @@ async def replay_bundle( transaction: TransactionHash, depth: int = 3, min_weight_magnitude: Optional[int] = None, - ) -> Dict: + ) -> dict: """ Takes a tail transaction hash as input, gets the bundle associated with the transaction and then replays the bundle by @@ -1502,7 +1501,7 @@ async def send_transfer( change_address: Optional[Address] = None, min_weight_magnitude: Optional[int] = None, security_level: Optional[int] = None, - ) -> Dict: + ) -> dict: """ Prepares a set of transfers and creates the bundle, then attaches the bundle to the Tangle, and broadcasts and stores the @@ -1571,7 +1570,7 @@ async def send_trytes( trytes: Iterable[TransactionTrytes], depth: int = 3, min_weight_magnitude: Optional[int] = None - ) -> Dict: + ) -> dict: """ Attaches transaction trytes to the Tangle, then broadcasts and stores them. @@ -1610,7 +1609,7 @@ async def send_trytes( minWeightMagnitude=min_weight_magnitude, ) - async def is_reattachable(self, addresses: Iterable[Address]) -> Dict: + async def is_reattachable(self, addresses: Iterable[Address]) -> dict: """ This API function helps you to determine whether you should replay a transaction or make a new one (either with the same @@ -1640,7 +1639,7 @@ async def is_reattachable(self, addresses: Iterable[Address]) -> Dict: addresses=addresses ) - async def traverse_bundle(self, tail_hash: TransactionHash) -> Dict: + async def traverse_bundle(self, tail_hash: TransactionHash) -> dict: """ Fetches and traverses a bundle from the Tangle given a tail transaction hash. diff --git a/iota/bin/__init__.py b/iota/bin/__init__.py index 917d2a7..4c035a6 100644 --- a/iota/bin/__init__.py +++ b/iota/bin/__init__.py @@ -4,7 +4,7 @@ from getpass import getpass as secure_input from io import StringIO from sys import exit -from typing import Any, Optional, Text, Dict +from typing import Any, Optional from iota import Iota, __version__ from iota.crypto.types import Seed @@ -73,7 +73,7 @@ def run_from_argv(self, argv: Optional[tuple] = None) -> int: return exit_code - def parse_argv(self, argv: Optional[tuple] = None) -> Dict: + def parse_argv(self, argv: Optional[tuple] = None) -> dict: """ Parses arguments for the command. @@ -145,7 +145,7 @@ def create_argument_parser(self) -> ArgumentParser: return parser @staticmethod - def seed_from_filepath(filepath: Text) -> Seed: + def seed_from_filepath(filepath: str) -> Seed: """ Reads a seed from the first line of a text file. diff --git a/iota/bin/repl.py b/iota/bin/repl.py index 25b36af..5e16163 100755 --- a/iota/bin/repl.py +++ b/iota/bin/repl.py @@ -6,10 +6,9 @@ from logging import basicConfig, getLogger, DEBUG from sys import stderr +from typing import Any # Import all IOTA symbols into module scope, so that it's more # convenient for the user. -from typing import Any - from iota import * from iota.adapter import resolve_adapter from iota.adapter.wrappers import RoutingWrapper diff --git a/iota/codecs.py b/iota/codecs.py index f54f785..cb3b533 100644 --- a/iota/codecs.py +++ b/iota/codecs.py @@ -1,5 +1,5 @@ from codecs import Codec, CodecInfo, register as lookup_function -from typing import Union, Text, Tuple +from typing import Union, Tuple from warnings import warn from iota.exceptions import with_context @@ -72,7 +72,7 @@ def get_codec_info(cls) -> CodecInfo: def encode(self, input: Union[memoryview, bytes, bytearray], - errors: Text = 'strict') -> Tuple[bytes, int]: + errors: str = 'strict') -> Tuple[bytes, int]: """ Encodes a byte string into trytes. """ @@ -108,7 +108,7 @@ def encode(self, def decode(self, input: Union[memoryview, bytes, bytearray], - errors: Text = 'strict') -> Tuple[bytes, int]: + errors: str = 'strict') -> Tuple[bytes, int]: """ Decodes a tryte string into bytes. """ diff --git a/iota/commands/__init__.py b/iota/commands/__init__.py index a9071cd..0f4c99a 100644 --- a/iota/commands/__init__.py +++ b/iota/commands/__init__.py @@ -1,10 +1,5 @@ from abc import ABCMeta, abstractmethod as abstract_method -from importlib import import_module -from inspect import getmembers as get_members, isabstract as is_abstract, \ - isclass as is_class -from pkgutil import walk_packages -from types import ModuleType -from typing import Any, Dict, Mapping, Optional, Text, Union +from typing import Any, Mapping, Optional import filters as f @@ -24,7 +19,7 @@ class BaseCommand(object, metaclass=ABCMeta): """ An API command ready to send to the node. """ - command: Text = None + command: str = None def __init__(self, adapter: BaseAdapter) -> None: """ @@ -34,10 +29,10 @@ def __init__(self, adapter: BaseAdapter) -> None: self.adapter = adapter self.called: bool = False - self.request: Optional[Dict] = None - self.response: Optional[Dict] = None + self.request: Optional[dict] = None + self.response: Optional[dict] = None - async def __call__(self, **kwargs: Any) -> Dict: + async def __call__(self, **kwargs: Any) -> dict: """ Sends the command to the node. """ @@ -75,7 +70,7 @@ def reset(self) -> None: self.request = None self.response = None - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: """ Sends the request object to the adapter and returns the response. @@ -86,7 +81,7 @@ async def _execute(self, request: Dict) -> Dict: return await self.adapter.send_request(request) @abstract_method - def _prepare_request(self, request: Dict) -> Optional[Dict]: + def _prepare_request(self, request: dict) -> Optional[dict]: """ Modifies the request before sending it to the node. @@ -104,7 +99,7 @@ def _prepare_request(self, request: Dict) -> Optional[Dict]: ) @abstract_method - def _prepare_response(self, response: Dict) -> Optional[Dict]: + def _prepare_response(self, response: dict) -> Optional[dict]: """ Modifies the response from the node. @@ -126,7 +121,7 @@ class CustomCommand(BaseCommand): Useful for executing experimental/undocumented commands. """ - def __init__(self, adapter: BaseAdapter, command: Text) -> None: + def __init__(self, adapter: BaseAdapter, command: str) -> None: super(CustomCommand, self).__init__(adapter) self.command = command @@ -216,14 +211,14 @@ def get_response_filter(self) -> Optional[ResponseFilter]: 'Not implemented in {cls}.'.format(cls=type(self).__name__), ) - def _prepare_request(self, request: Dict) -> Dict: + def _prepare_request(self, request: dict) -> dict: return self._apply_filter( value = request, filter_ = self.get_request_filter(), failure_message = 'Request failed validation', ) - def _prepare_response(self, response: Dict) -> Dict: + def _prepare_response(self, response: dict) -> dict: return self._apply_filter( value = response, filter_ = self.get_response_filter(), @@ -232,10 +227,10 @@ def _prepare_response(self, response: Dict) -> Dict: @staticmethod def _apply_filter( - value: Dict, + value: dict, filter_: Optional[f.BaseFilter], - failure_message: Text - ) -> Dict: + failure_message: str + ) -> dict: """ Applies a filter to a value. If the value does not pass the filter, an exception will be raised with lots of contextual info diff --git a/iota/commands/core/attach_to_tangle.py b/iota/commands/core/attach_to_tangle.py index 64715d0..a0f9749 100644 --- a/iota/commands/core/attach_to_tangle.py +++ b/iota/commands/core/attach_to_tangle.py @@ -1,5 +1,3 @@ -from typing import Dict - import filters as f from iota import TransactionHash, TransactionTrytes @@ -26,7 +24,7 @@ def get_request_filter(self): def get_response_filter(self): return AttachToTangleResponseFilter() - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: if self.adapter.local_pow is True: from pow import ccurl_interface powed_trytes = ccurl_interface.attach_to_tangle( diff --git a/iota/commands/core/find_transactions.py b/iota/commands/core/find_transactions.py index 6b2f701..5f70a49 100644 --- a/iota/commands/core/find_transactions.py +++ b/iota/commands/core/find_transactions.py @@ -1,5 +1,3 @@ -from typing import Dict - import filters as f from iota import BundleHash, Tag, TransactionHash @@ -55,7 +53,7 @@ def __init__(self) -> None: ) def _apply(self, value): - value: Dict = super(FindTransactionsRequestFilter, self)._apply( + value: dict = super(FindTransactionsRequestFilter, self)._apply( value ) diff --git a/iota/commands/core/get_balances.py b/iota/commands/core/get_balances.py index 661776c..5c54751 100644 --- a/iota/commands/core/get_balances.py +++ b/iota/commands/core/get_balances.py @@ -1,5 +1,3 @@ -from typing import Dict - import filters as f from iota import TransactionHash @@ -52,7 +50,7 @@ def __init__(self) -> None: ) def _apply(self, value): - value: Dict = super(GetBalancesRequestFilter, self)._apply( + value: dict = super(GetBalancesRequestFilter, self)._apply( value ) diff --git a/iota/commands/core/get_transactions_to_approve.py b/iota/commands/core/get_transactions_to_approve.py index 6533e9a..11a26d4 100644 --- a/iota/commands/core/get_transactions_to_approve.py +++ b/iota/commands/core/get_transactions_to_approve.py @@ -1,5 +1,3 @@ -from typing import Dict - import filters as f from iota import TransactionHash @@ -40,7 +38,7 @@ def __init__(self) -> None: }) def _apply(self, value): - value: Dict = super(GetTransactionsToApproveRequestFilter, self)._apply( + value: dict = super(GetTransactionsToApproveRequestFilter, self)._apply( value, ) diff --git a/iota/commands/extended/broadcast_and_store.py b/iota/commands/extended/broadcast_and_store.py index 07b4bce..7b58607 100644 --- a/iota/commands/extended/broadcast_and_store.py +++ b/iota/commands/extended/broadcast_and_store.py @@ -1,5 +1,3 @@ -from typing import Dict - from iota.commands import FilterCommand from iota.commands.core.broadcast_transactions import \ BroadcastTransactionsCommand @@ -25,7 +23,7 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: # Submit the two coroutines to the already running event loop await asyncio.gather( BroadcastTransactionsCommand(self.adapter)(**request), diff --git a/iota/commands/extended/broadcast_bundle.py b/iota/commands/extended/broadcast_bundle.py index e4de3d1..a17c0fa 100644 --- a/iota/commands/extended/broadcast_bundle.py +++ b/iota/commands/extended/broadcast_bundle.py @@ -1,5 +1,3 @@ -from typing import Dict - import filters as f from iota.filters import Trytes @@ -29,7 +27,7 @@ def get_response_filter(self): # Return value is filtered before hitting us. pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: # Given tail hash, fetches the bundle from the tangle # and validates it. tail_hash: TransactionHash = request['tail_hash'] diff --git a/iota/commands/extended/find_transaction_objects.py b/iota/commands/extended/find_transaction_objects.py index 24af87d..fbc3ef4 100644 --- a/iota/commands/extended/find_transaction_objects.py +++ b/iota/commands/extended/find_transaction_objects.py @@ -1,4 +1,4 @@ -from typing import Iterable, List, Optional, Dict +from typing import Iterable, List, Optional from iota import Address, BundleHash, Tag, Transaction, TransactionHash from iota.commands.core import GetTrytesCommand, FindTransactionsCommand @@ -19,7 +19,7 @@ class FindTransactionObjectsCommand(FindTransactionsCommand): def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: bundles: Optional[Iterable[BundleHash]] = request\ .get('bundles') addresses: Optional[Iterable[Address]] = request\ diff --git a/iota/commands/extended/get_account_data.py b/iota/commands/extended/get_account_data.py index 9aa07c7..ac97399 100644 --- a/iota/commands/extended/get_account_data.py +++ b/iota/commands/extended/get_account_data.py @@ -1,5 +1,5 @@ from operator import attrgetter -from typing import List, Optional, Dict +from typing import List, Optional import filters as f @@ -32,7 +32,7 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: inclusion_states: bool = request['inclusionStates'] seed: Seed = request['seed'] start: int = request['start'] diff --git a/iota/commands/extended/get_bundles.py b/iota/commands/extended/get_bundles.py index e1e65bc..5da2845 100644 --- a/iota/commands/extended/get_bundles.py +++ b/iota/commands/extended/get_bundles.py @@ -1,4 +1,4 @@ -from typing import Dict, Iterable +from typing import Iterable import filters as f @@ -29,7 +29,7 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: transaction_hashes: Iterable[TransactionHash] = request['transactions'] async def fetch_and_validate(tx_hash): diff --git a/iota/commands/extended/get_inputs.py b/iota/commands/extended/get_inputs.py index f36d785..4b50e3f 100644 --- a/iota/commands/extended/get_inputs.py +++ b/iota/commands/extended/get_inputs.py @@ -1,4 +1,4 @@ -from typing import Optional, Dict +from typing import Optional import filters as f @@ -30,7 +30,7 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: stop: Optional[int] = request['stop'] seed: Seed = request['seed'] start: int = request['start'] diff --git a/iota/commands/extended/get_latest_inclusion.py b/iota/commands/extended/get_latest_inclusion.py index 1e5db6f..afd5e3e 100644 --- a/iota/commands/extended/get_latest_inclusion.py +++ b/iota/commands/extended/get_latest_inclusion.py @@ -1,4 +1,4 @@ -from typing import List, Dict +from typing import List import filters as f @@ -27,7 +27,7 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: hashes: List[TransactionHash] = request['hashes'] gni_response = await GetNodeInfoCommand(self.adapter)() diff --git a/iota/commands/extended/get_new_addresses.py b/iota/commands/extended/get_new_addresses.py index 5b926a0..bc6a156 100644 --- a/iota/commands/extended/get_new_addresses.py +++ b/iota/commands/extended/get_new_addresses.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Dict +from typing import List, Optional import filters as f @@ -31,7 +31,7 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: checksum: bool = request['checksum'] count: Optional[int] = request['count'] index: int = request['index'] diff --git a/iota/commands/extended/get_transaction_objects.py b/iota/commands/extended/get_transaction_objects.py index be8412e..632aacf 100644 --- a/iota/commands/extended/get_transaction_objects.py +++ b/iota/commands/extended/get_transaction_objects.py @@ -1,4 +1,4 @@ -from typing import Iterable, List, Dict +from typing import Iterable, List import filters as f @@ -26,7 +26,7 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: hashes: Iterable[TransactionHash] = request\ .get('hashes') diff --git a/iota/commands/extended/get_transfers.py b/iota/commands/extended/get_transfers.py index ae36c04..a96bd7c 100644 --- a/iota/commands/extended/get_transfers.py +++ b/iota/commands/extended/get_transfers.py @@ -1,5 +1,5 @@ from itertools import chain -from typing import Optional, Dict +from typing import Optional import filters as f @@ -30,7 +30,7 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: inclusion_states: bool = request['inclusionStates'] seed: Seed = request['seed'] start: int = request['start'] diff --git a/iota/commands/extended/is_promotable.py b/iota/commands/extended/is_promotable.py index e401a66..d3f6da4 100644 --- a/iota/commands/extended/is_promotable.py +++ b/iota/commands/extended/is_promotable.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional +from typing import Optional from iota.commands import FilterCommand, RequestFilter from iota.commands.core import CheckConsistencyCommand, GetTrytesCommand @@ -50,7 +50,7 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: tails: TransactionHash = request['tails'] # First, check consistency diff --git a/iota/commands/extended/is_reattachable.py b/iota/commands/extended/is_reattachable.py index 95af4a5..97b3dea 100644 --- a/iota/commands/extended/is_reattachable.py +++ b/iota/commands/extended/is_reattachable.py @@ -1,4 +1,4 @@ -from typing import List, Dict +from typing import List import filters as f @@ -25,7 +25,7 @@ def get_request_filter(self): def get_response_filter(self): return IsReattachableResponseFilter() - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: addresses: List[Address] = request['addresses'] # fetch full transaction objects diff --git a/iota/commands/extended/prepare_transfer.py b/iota/commands/extended/prepare_transfer.py index d694712..259869e 100644 --- a/iota/commands/extended/prepare_transfer.py +++ b/iota/commands/extended/prepare_transfer.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Dict +from typing import List, Optional import filters as f @@ -32,7 +32,7 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: # Required parameters. seed: Seed = request['seed'] bundle: ProposedBundle = ProposedBundle(request['transfers']) diff --git a/iota/commands/extended/promote_transaction.py b/iota/commands/extended/promote_transaction.py index 210d3e7..26805b8 100644 --- a/iota/commands/extended/promote_transaction.py +++ b/iota/commands/extended/promote_transaction.py @@ -1,5 +1,3 @@ -from typing import Dict - import filters as f from iota import Address, BadApiResponse, ProposedTransaction, TransactionHash @@ -28,7 +26,7 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: depth: int = request['depth'] min_weight_magnitude: int = request['minWeightMagnitude'] transaction: TransactionHash = request['transaction'] diff --git a/iota/commands/extended/replay_bundle.py b/iota/commands/extended/replay_bundle.py index 254d22c..2f9a46b 100644 --- a/iota/commands/extended/replay_bundle.py +++ b/iota/commands/extended/replay_bundle.py @@ -1,5 +1,3 @@ -from typing import Dict - import filters as f from iota import Bundle, TransactionHash @@ -27,7 +25,7 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: depth: int = request['depth'] min_weight_magnitude: int = request['minWeightMagnitude'] transaction: TransactionHash = request['transaction'] diff --git a/iota/commands/extended/send_transfer.py b/iota/commands/extended/send_transfer.py index 1d36fc7..be1122e 100644 --- a/iota/commands/extended/send_transfer.py +++ b/iota/commands/extended/send_transfer.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Dict +from typing import List, Optional import filters as f @@ -28,7 +28,7 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: change_address: Optional[Address] = request['changeAddress'] depth: int = request['depth'] inputs: Optional[List[Address]] = request['inputs'] diff --git a/iota/commands/extended/send_trytes.py b/iota/commands/extended/send_trytes.py index 9783eab..40bfd51 100644 --- a/iota/commands/extended/send_trytes.py +++ b/iota/commands/extended/send_trytes.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Dict +from typing import List, Optional import filters as f @@ -29,7 +29,7 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: depth: int = request['depth'] min_weight_magnitude: int = request['minWeightMagnitude'] trytes: List[TryteString] = request['trytes'] diff --git a/iota/commands/extended/traverse_bundle.py b/iota/commands/extended/traverse_bundle.py index 69ad9e5..f819d11 100644 --- a/iota/commands/extended/traverse_bundle.py +++ b/iota/commands/extended/traverse_bundle.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Dict +from typing import List, Optional import filters as f @@ -28,7 +28,7 @@ def get_request_filter(self): def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: txn_hash: TransactionHash = request['transaction'] bundle = Bundle(await self._traverse_bundle(txn_hash, None)) diff --git a/iota/crypto/kerl/conv.py b/iota/crypto/kerl/conv.py index 6807f3c..8ba58e1 100644 --- a/iota/crypto/kerl/conv.py +++ b/iota/crypto/kerl/conv.py @@ -1,10 +1,10 @@ -from typing import Dict, Text, List +from typing import List, Dict BYTE_HASH_LENGTH = 48 TRIT_HASH_LENGTH = 243 -tryte_table: Dict[Text, List[int]] = { +tryte_table: Dict[str, List[int]] = { '9': [0, 0, 0], # 0 'A': [1, 0, 0], # 1 'B': [-1, 1, 0], # 2 @@ -38,7 +38,7 @@ trit_table = {tuple(v): k for k, v in tryte_table.items()} -def trytes_to_trits(trytes: Text) -> List[int]: +def trytes_to_trits(trytes: str) -> List[int]: trits = [] for tryte in trytes: trits.extend(tryte_table[tryte]) @@ -46,7 +46,7 @@ def trytes_to_trits(trytes: Text) -> List[int]: return trits -def trits_to_trytes(trits: List[int]) -> Text: +def trits_to_trytes(trits: List[int]) -> str: trytes = [] trits_chunks = [trits[i:i + 3] for i in range(0, len(trits), 3)] @@ -64,7 +64,7 @@ def convertToTrits(bytes_k: List[int]) -> List[int]: def convertToBytes(trits: List[int]) -> List[int]: bigInt = convertBaseToBigint(trits, 3) - bytes_k = convertBigintToBytes(bigInt) + bytes_k = convertBigIntToBytes(bigInt) return bytes_k diff --git a/iota/crypto/types.py b/iota/crypto/types.py index 99b4869..a33ab0c 100644 --- a/iota/crypto/types.py +++ b/iota/crypto/types.py @@ -1,5 +1,5 @@ import warnings -from typing import Optional, Dict +from typing import Optional from iota.crypto import FRAGMENT_LENGTH, HASH_LENGTH, SeedWarning from iota.crypto.kerl import Kerl @@ -70,7 +70,7 @@ def security_level(self) -> int: """ return len(self) // Hash.LEN - def as_json_compatible(self) -> Dict: + def as_json_compatible(self) -> dict: """ Returns a JSON-compatible representation of the digest. @@ -78,7 +78,7 @@ def as_json_compatible(self) -> Dict: ``dict`` with the following structure:: { - trytes': Text, + trytes': str, 'key_index': int, } @@ -201,7 +201,7 @@ def __init__( self.key_index = key_index self.security_level = security_level - def as_json_compatible(self) -> Dict: + def as_json_compatible(self) -> dict: """ Returns a JSON-compatible representation of the private key. @@ -209,7 +209,7 @@ def as_json_compatible(self) -> Dict: ``dict`` with the following structure:: { - trytes': Text, + trytes': str, 'key_index': int, 'security_level': int, } diff --git a/iota/filters.py b/iota/filters.py index 80d0684..d88b15b 100644 --- a/iota/filters.py +++ b/iota/filters.py @@ -1,4 +1,4 @@ -from typing import Text, Type +from typing import Type import filters as f from filters.macros import filter_macro @@ -77,7 +77,7 @@ class NodeUri(f.BaseFilter): } def _apply(self, value): - value: Text = self._filter(value, f.Type(str)) + value: str = self._filter(value, f.Type(str)) if self._has_errors: return None diff --git a/iota/multisig/api.py b/iota/multisig/api.py index d0abdd8..37695f6 100644 --- a/iota/multisig/api.py +++ b/iota/multisig/api.py @@ -1,4 +1,4 @@ -from typing import Iterable, Optional, Dict +from typing import Iterable, Optional from iota import Address, Iota, AsyncIota, ProposedTransaction from iota.crypto.addresses import AddressGenerator @@ -50,7 +50,7 @@ class AsyncMultisigIota(AsyncIota): async def create_multisig_address( self, digests: Iterable[Digest] - ) -> Dict: + ) -> dict: """ Generates a multisig address from a collection of digests. @@ -63,7 +63,7 @@ async def create_multisig_address( keys in the exact same order. :return: - Dict with the following items:: + ``dict`` with the following items:: { 'address': MultisigAddress, @@ -79,7 +79,7 @@ async def get_digests( index: int = 0, count: int = 1, security_level: int = AddressGenerator.DEFAULT_SECURITY_LEVEL, - ) -> Dict: + ) -> dict: """ Generates one or more key digests from the seed. @@ -101,7 +101,7 @@ async def get_digests( This value must be between 1 and 3, inclusive. :return: - Dict with the following items:: + ``dict`` with the following items:: { 'digests': List[Digest], @@ -121,7 +121,7 @@ async def get_private_keys( index: int = 0, count: int = 1, security_level: int = AddressGenerator.DEFAULT_SECURITY_LEVEL, - ) -> Dict: + ) -> dict: """ Generates one or more private keys from the seed. @@ -144,7 +144,7 @@ async def get_private_keys( This value must be between 1 and 3, inclusive. :return: - Dict with the following items:: + ``dict`` with the following items:: { 'keys': List[PrivateKey], @@ -169,7 +169,7 @@ async def prepare_multisig_transfer( transfers: Iterable[ProposedTransaction], multisig_input: MultisigAddress, change_address: Optional[Address] = None, - ) -> Dict: + ) -> dict: """ Prepares a bundle that authorizes the spending of IOTAs from a multisig address. @@ -233,7 +233,7 @@ async def prepare_multisig_transfer( signing the input(s)! :return: - Dict containing the following values:: + ``dict`` wontaining the following values:: { 'trytes': List[TransactionTrytes], @@ -293,7 +293,7 @@ class MultisigIota(Iota, AsyncMultisigIota): def create_multisig_address( self, digests: Iterable[Digest] - ) -> Dict: + ) -> dict: """ Generates a multisig address from a collection of digests. @@ -306,7 +306,7 @@ def create_multisig_address( keys in the exact same order. :return: - Dict with the following items:: + ``dict`` with the following items:: { 'address': MultisigAddress, @@ -322,7 +322,7 @@ def get_digests( index: int = 0, count: int = 1, security_level: int = AddressGenerator.DEFAULT_SECURITY_LEVEL, - ) -> Dict: + ) -> dict: """ Generates one or more key digests from the seed. @@ -344,7 +344,7 @@ def get_digests( This value must be between 1 and 3, inclusive. :return: - Dict with the following items:: + ``dict`` with the following items:: { 'digests': List[Digest], @@ -365,7 +365,7 @@ def get_private_keys( index: int = 0, count: int = 1, security_level: int = AddressGenerator.DEFAULT_SECURITY_LEVEL, - ) -> Dict: + ) -> dict: """ Generates one or more private keys from the seed. @@ -388,7 +388,7 @@ def get_private_keys( This value must be between 1 and 3, inclusive. :return: - Dict with the following items:: + ``dict`` with the following items:: { 'keys': List[PrivateKey], @@ -414,7 +414,7 @@ def prepare_multisig_transfer( transfers: Iterable[ProposedTransaction], multisig_input: MultisigAddress, change_address: Optional[Address] = None, - ) -> Dict: + ) -> dict: """ Prepares a bundle that authorizes the spending of IOTAs from a multisig address. @@ -478,7 +478,7 @@ def prepare_multisig_transfer( signing the input(s)! :return: - Dict containing the following values:: + ``dict`` containing the following values:: { 'trytes': List[TransactionTrytes], diff --git a/iota/multisig/commands/create_multisig_address.py b/iota/multisig/commands/create_multisig_address.py index 142160f..55c6ed4 100644 --- a/iota/multisig/commands/create_multisig_address.py +++ b/iota/multisig/commands/create_multisig_address.py @@ -1,4 +1,4 @@ -from typing import List, Dict +from typing import List import filters as f @@ -30,7 +30,7 @@ def get_response_filter(self): # There is no async operation going on here, but the base class is async, # so from the outside, we have to act like we are doing async. - async def _execute(self, request: Dict): + async def _execute(self, request: dict) -> dict: digests: List[Digest] = request['digests'] builder = MultisigAddressBuilder() diff --git a/iota/multisig/commands/get_digests.py b/iota/multisig/commands/get_digests.py index 3d01841..bb35125 100644 --- a/iota/multisig/commands/get_digests.py +++ b/iota/multisig/commands/get_digests.py @@ -1,4 +1,4 @@ -from typing import Optional, Dict +from typing import Optional import filters as f @@ -30,7 +30,7 @@ def get_response_filter(self): # There is no async operation going on here, but the base class is async, # so from the outside, we have to act like we are doing async. - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: count: Optional[int] = request['count'] index: int = request['index'] seed: Seed = request['seed'] diff --git a/iota/multisig/commands/get_private_keys.py b/iota/multisig/commands/get_private_keys.py index bb392b6..8e82476 100644 --- a/iota/multisig/commands/get_private_keys.py +++ b/iota/multisig/commands/get_private_keys.py @@ -1,4 +1,4 @@ -from typing import Optional, Dict +from typing import Optional import filters as f @@ -31,7 +31,7 @@ def get_response_filter(self): # There is no async operation going on here, but the base class is async, # so from the outside, we have to act like we are doing async. - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: count: Optional[int] = request['count'] index: int = request['index'] seed: Seed = request['seed'] diff --git a/iota/multisig/commands/prepare_multisig_transfer.py b/iota/multisig/commands/prepare_multisig_transfer.py index 1585632..cafa296 100644 --- a/iota/multisig/commands/prepare_multisig_transfer.py +++ b/iota/multisig/commands/prepare_multisig_transfer.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Dict +from typing import List, Optional import filters as f @@ -31,7 +31,7 @@ def get_request_filter(self) -> 'PrepareMultisigTransferRequestFilter': def get_response_filter(self): pass - async def _execute(self, request: Dict) -> Dict: + async def _execute(self, request: dict) -> dict: change_address: Optional[Address] = request['changeAddress'] multisig_input: MultisigAddress = request['multisigInput'] transfers: List[ProposedTransaction] = request['transfers'] diff --git a/iota/multisig/types.py b/iota/multisig/types.py index c0691b5..f8fdb33 100644 --- a/iota/multisig/types.py +++ b/iota/multisig/types.py @@ -1,5 +1,5 @@ from operator import attrgetter -from typing import Iterable, Optional, Dict +from typing import Iterable, Optional from iota import Address, TrytesCompatible from iota.crypto.types import Digest @@ -57,7 +57,7 @@ def __init__( map(attrgetter('security_level'), self.digests) ) - def as_json_compatible(self) -> Dict: + def as_json_compatible(self) -> dict: """ Get a JSON represenation of the :py:class:`MultisigAddress` object. @@ -65,7 +65,7 @@ def as_json_compatible(self) -> Dict: ``dict`` with the following structure:: { - 'trytes': Text, + 'trytes': str, String representation of the address trytes. 'balance': int, Balance of the address. diff --git a/iota/transaction/base.py b/iota/transaction/base.py index 00d8965..b1a3a5a 100644 --- a/iota/transaction/base.py +++ b/iota/transaction/base.py @@ -1,6 +1,6 @@ from operator import attrgetter from typing import Iterable, Iterator, List, MutableSequence, \ - Optional, Sequence, Text, TypeVar, Type, Dict + Optional, Sequence, TypeVar, Type from iota.codecs import TrytesDecodeError from iota.crypto import Curl, HASH_LENGTH @@ -482,7 +482,7 @@ def attachment_timestamp_upper_bound_as_trytes(self) -> TryteString: trits_from_int(self.attachment_timestamp_upper_bound, pad=27), ) - def as_json_compatible(self) -> Dict: + def as_json_compatible(self) -> dict: """ Returns a JSON-compatible representation of the object. @@ -741,12 +741,12 @@ def tail_transaction(self) -> Transaction: """ return self[0] - def get_messages(self, errors: Text = 'drop') -> List[Text]: + def get_messages(self, errors: str = 'drop') -> List[str]: """ Attempts to decipher encoded messages from the transactions in the bundle. - :param Text errors: + :param str errors: How to handle trytes that can't be converted, or bytes that can't be decoded using UTF-8: @@ -762,7 +762,7 @@ def get_messages(self, errors: Text = 'drop') -> List[Text]: 'ignore' Omit the invalid tryte/byte sequence. - :return: ``List[Text]`` + :return: ``List[str]`` """ decode_errors = 'strict' if errors == 'drop' else errors @@ -805,7 +805,7 @@ def as_tryte_strings(self, head_to_tail: bool = False) -> List[TransactionTrytes transactions = self if head_to_tail else reversed(self) return [t.as_tryte_string() for t in transactions] - def as_json_compatible(self) -> List[Dict]: + def as_json_compatible(self) -> List[dict]: """ Returns a JSON-compatible representation of the object. diff --git a/iota/transaction/creation.py b/iota/transaction/creation.py index cb57286..dcc69a4 100644 --- a/iota/transaction/creation.py +++ b/iota/transaction/creation.py @@ -1,4 +1,4 @@ -from typing import Iterable, Iterator, List, Optional, Sequence, Dict +from typing import Iterable, Iterator, List, Optional, Sequence from iota.crypto import HASH_LENGTH from iota.crypto.kerl import Kerl @@ -260,7 +260,7 @@ def tag(self) -> Tag: return Tag(b'') - def as_json_compatible(self) -> List[Dict]: + def as_json_compatible(self) -> List[dict]: """ Returns a JSON-compatible representation of the object. diff --git a/iota/transaction/utils.py b/iota/transaction/utils.py index 38e1617..1e1cee2 100644 --- a/iota/transaction/utils.py +++ b/iota/transaction/utils.py @@ -1,6 +1,5 @@ from calendar import timegm as unix_timestamp from datetime import datetime -from typing import Text from iota import STANDARD_UNITS from iota.exceptions import with_context @@ -11,7 +10,7 @@ ] -def convert_value_to_standard_unit(value: Text, symbol: Text = 'i') -> float: +def convert_value_to_standard_unit(value: str, symbol: str = 'i') -> float: """ Converts between any two standard units of iota. diff --git a/iota/transaction/validator.py b/iota/transaction/validator.py index 25b009b..6b64261 100644 --- a/iota/transaction/validator.py +++ b/iota/transaction/validator.py @@ -1,4 +1,4 @@ -from typing import Generator, List, Optional, Text, Type +from typing import Generator, List, Optional, Type from iota.crypto.kerl import Kerl from iota.crypto.signing import validate_signature_fragments @@ -29,16 +29,16 @@ def __init__(self, bundle: Bundle) -> None: self.bundle = bundle - self._errors: Optional[List[Text]] = [] + self._errors: Optional[List[str]] = [] self._validator = self._create_validator() @property - def errors(self) -> List[Text]: + def errors(self) -> List[str]: """ Returns all errors found with the bundle. """ try: - self._errors.extend(self._validator) # type: List[Text] + self._errors.extend(self._validator) # type: List[str] except StopIteration: pass @@ -58,7 +58,7 @@ def is_valid(self) -> bool: return not self._errors - def _create_validator(self) -> Generator[Text, None, None]: + def _create_validator(self) -> Generator[str, None, None]: """ Creates a generator that does all the work. """ @@ -180,7 +180,7 @@ def _create_validator(self) -> Generator[Text, None, None]: def _get_bundle_signature_errors( self, groups: List[List[Transaction]] - ) -> List[Text]: + ) -> List[str]: """ Validates the signature fragments in the bundle. @@ -231,7 +231,7 @@ def _get_bundle_signature_errors( def _get_group_signature_error( group: List[Transaction], sponge_type: Type - ) -> Optional[Text]: + ) -> Optional[str]: """ Validates the signature fragments for a group of transactions using the specified sponge type. @@ -242,7 +242,7 @@ def _get_group_signature_error( :return: - ``None``: Indicates that the signature fragments are valid. - - ``Text``: Error message indicating the fragments are invalid. + - ``str``: Error message indicating the fragments are invalid. """ validate_group_signature = validate_signature_fragments( fragments=[txn.signature_message_fragment for txn in group], diff --git a/iota/types.py b/iota/types.py index ac9abd0..a4c3cf1 100644 --- a/iota/types.py +++ b/iota/types.py @@ -4,7 +4,7 @@ from math import ceil from random import SystemRandom from typing import Any, AnyStr, Generator, Iterable, Iterator, List, \ - MutableSequence, Optional, Text, Type, TypeVar, Union, Dict + MutableSequence, Optional, Type, TypeVar, Union, Dict from warnings import warn from iota import AsciiTrytesCodec, TRITS_PER_TRYTE @@ -96,7 +96,7 @@ def random(cls: Type[T], length: Optional[int] = None) -> T: @classmethod def from_bytes(cls: Type[T], bytes_: Union[bytes, bytearray], - codec: Text = AsciiTrytesCodec.name, + codec: str = AsciiTrytesCodec.name, *args: Any, **kwargs: Any) -> T: """ @@ -106,7 +106,7 @@ def from_bytes(cls: Type[T], Source bytes. ASCII representation of a sequence of bytes. Note that only tryte alphabet supported! - :param Text codec: + :param str codec: Reserved for future use. Currently supports only the 'trytes_ascii' codec. See https://github.com/iotaledger/iota.py/issues/62 for @@ -131,13 +131,13 @@ def from_bytes(cls: Type[T], @classmethod def from_unicode(cls: Type[T], - string: Text, + string: str, *args: Any, **kwargs: Any) -> T: """ Creates a TryteString from a Unicode string. - :param Text string: + :param str string: Source Unicode string. :param args: @@ -370,7 +370,7 @@ def __init__(self, trytes: TrytesCompatible, pad: Optional[int] = None) -> None: def __hash__(self) -> int: return hash(bytes(self._trytes)) - def __repr__(self) -> Text: + def __repr__(self) -> str: return '{cls}({trytes!r})'.format( cls=type(self).__name__, trytes=bytes(self._trytes), @@ -540,13 +540,13 @@ def iter_chunks(self, chunk_size: int) -> 'ChunkIterator': return ChunkIterator(self, chunk_size) def encode(self, - errors: Text = 'strict', - codec: Text = AsciiTrytesCodec.name) -> bytes: + errors: str = 'strict', + codec: str = AsciiTrytesCodec.name) -> bytes: """ Encodes the TryteString into a lower-level primitive (usually bytes). - :param Text errors: + :param str errors: How to handle trytes that can't be converted: 'strict' @@ -558,7 +558,7 @@ def encode(self, 'ignore' omit the tryte from the result. - :param Text codec: + :param str codec: Reserved for future use. See https://github.com/iotaledger/iota.py/issues/62 for @@ -614,13 +614,13 @@ def as_bytes(self, *args, **kwargs): ) return self.encode(*args, **kwargs) - def decode(self, errors: Text = 'strict', - strip_padding: bool = True) -> Text: + def decode(self, errors: str = 'strict', + strip_padding: bool = True) -> str: """ Decodes the TryteString into a higher-level abstraction (usually Unicode characters). - :param Text errors: + :param str errors: How to handle trytes that can't be converted, or bytes that can't be decoded using UTF-8: @@ -677,7 +677,7 @@ def as_string(self, *args, **kwargs): ) return self.decode(*args, **kwargs) - def as_json_compatible(self) -> Text: + def as_json_compatible(self) -> str: """ Returns a JSON-compatible representation of the object. @@ -992,7 +992,7 @@ def __init__( address. """ - def as_json_compatible(self) -> Dict[Text, Union[Text, int]]: + def as_json_compatible(self) -> Dict[str, Union[str, int]]: """ Returns a JSON-compatible representation of the Address. @@ -1000,7 +1000,7 @@ def as_json_compatible(self) -> Dict[Text, Union[Text, int]]: ``dict`` with the following structure:: { - 'trytes': Text, + 'trytes': str, 'balance': int, 'key_index': int, 'security_level': int, From 078feb8547d863ea1f1fa46efd40b8db04666f0d Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Fri, 20 Mar 2020 12:23:23 +0100 Subject: [PATCH 69/69] Bump version number to 3.0.0b1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ee9c74a..07d228f 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ name='PyOTA', description='IOTA API library for Python', url='https://github.com/iotaledger/iota.py', - version='2.3.0b1', + version='3.0.0b1', long_description=long_description,