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

1.0 #17

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 4 additions & 12 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@ This is the official Python library for the IOTA Core.
It implements both the `official API`_, as well as newly-proposed functionality
(such as signing, bundles, utilities and conversion).

.. warning::
This is pre-release software!
There may be performance and stability issues.

Please report any issues using the `PyOTA Bug Tracker`_.

Join the Discussion
===================
If you want to get involved in the community, need help with getting setup,
Expand All @@ -23,6 +17,9 @@ Distributed Ledgers and IoT with other people, feel free to join our `Slack`_.

You can also ask questions on our `dedicated forum`_.

If you encounter any issues while using PyOTA, please report them using the
`PyOTA Bug Tracker`_.

============
Dependencies
============
Expand All @@ -33,12 +30,7 @@ Installation
============
To install the latest version::

pip install --pre pyota

**Important:** PyOTA is currently pre-release software.
There may be performance and stability issues.

Please report any issues using the `PyOTA Bug Tracker`_.
pip install pyota

Installing from Source
======================
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
name = 'PyOTA',
description = 'IOTA API library for Python',
url = 'https://github.com/iotaledger/iota.lib.py',
version = '1.0.0b7',
version = '1.0.0',

packages = find_packages('src'),
include_package_data = True,
Expand All @@ -52,7 +52,7 @@
license = 'MIT',

classifiers = [
'Development Status :: 4 - Beta',
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 2',
Expand Down
7 changes: 4 additions & 3 deletions src/iota/adapter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import json
from abc import ABCMeta, abstractmethod as abstract_method
from collections import deque
from inspect import isabstract as is_abstract
from socket import getdefaulttimeout as get_default_timeout
from typing import Dict, List, Text, Tuple, Union
Expand Down Expand Up @@ -312,7 +313,7 @@ def configure(cls, uri):
def __init__(self):
super(MockAdapter, self).__init__()

self.responses = {} # type: Dict[Text, List[dict]]
self.responses = {} # type: Dict[Text, deque]
self.requests = [] # type: List[dict]

def seed_response(self, command, response):
Expand All @@ -337,7 +338,7 @@ def seed_response(self, command, response):
# {'message': 'Hello!'}
"""
if command not in self.responses:
self.responses[command] = []
self.responses[command] = deque()

self.responses[command].append(response)
return self
Expand All @@ -350,7 +351,7 @@ def send_request(self, payload, **kwargs):
command = payload['command']

try:
response = self.responses[command].pop(0)
response = self.responses[command].popleft()
except KeyError:
raise with_context(
exc = BadApiResponse(
Expand Down
55 changes: 34 additions & 21 deletions src/iota/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,63 +385,76 @@ def get_bundles(self, transaction):
"""
return self.getBundles(transaction=transaction)

def get_inputs(self, start=None, end=None, threshold=None):
# type: (Optional[int], Optional[int], Optional[int]) -> dict
def get_inputs(self, start=0, stop=None, threshold=None):
# type: (int, Optional[int], Optional[int]) -> dict
"""
Gets all possible inputs of a seed and returns them with the total
balance.

This is either done deterministically (by generating all addresses
until :py:meth:`find_transactions` returns an empty
result and then doing :py:meth:`get_balances`), or by providing a
key range to search.
until :py:meth:`find_transactions` returns an empty result), or by
providing a key range to search.

:param start:
Starting key index.
Defaults to 0.

:param end:
:param stop:
Stop before this index.
Note that this parameter behaves like the ``stop`` attribute in a
:py:class:`slice` object; the end index is _not_ included in the
:py:class:`slice` object; the stop index is *not* included in the
result.

If not specified, then this method will not stop until it finds
an unused address.
If ``None`` (default), then this method will not stop until it
finds an unused address.

:param threshold:
Determines the minimum threshold for a successful result.
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 that 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.

:return:
Dict with the following structure::

{
'inputs': [
{
'address': <Address object>,
'balance': <address balance>,
'keyIndex`: <index of the address>,
},
... <one object per input found>
]

'inputs': <list of Address objects>
'totalBalance': <aggregate balance of all inputs>,
}

Note that each Address in the result has its ``balance``
attribute set.

Example::

response = iota.get_inputs(...)

input0 = response['inputs'][0] # type: Address
input0.balance # 42

:raise:
- :py:class:`iota.adapter.BadApiResponse` if ``threshold`` is not
met.
met. Not applicable if ``threshold`` is ``None``.

References:
- https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getinputs
"""
return self.getInputs(
seed = self.seed,
start = start,
end = end,
stop = stop,
threshold = threshold,
)

Expand Down
37 changes: 16 additions & 21 deletions src/iota/commands/extended/get_inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ def get_response_filter(self):
pass

def _execute(self, request):
end = request['end'] # type: Optional[int]
stop = request['stop'] # type: Optional[int]
seed = request['seed'] # type: Seed
start = request['start'] # type: int
threshold = request['threshold'] # type: Optional[int]

generator = AddressGenerator(seed)

# Determine the addresses we will be scanning.
if end is None:
if stop is None:
# This is similar to the ``getNewAddresses`` command, except it
# is interested in all the addresses that `getNewAddresses`
# skips.
Expand All @@ -55,7 +55,7 @@ def _execute(self, request):
else:
break
else:
addresses = generator.get_addresses(start, end - start)
addresses = generator.get_addresses(start, stop)

# Load balances for the addresses that we generated.
gb_response = GetBalancesCommand(self.adapter)(addresses=addresses)
Expand All @@ -71,12 +71,7 @@ def _execute(self, request):
addresses[i].balance = balance

if balance:
result['inputs'].append({
'address': addresses[i],
'balance': balance,
'keyIndex': addresses[i].key_index,
})

result['inputs'].append(addresses[i])
result['totalBalance'] += balance

if (threshold is not None) and (result['totalBalance'] >= threshold):
Expand Down Expand Up @@ -113,15 +108,15 @@ class GetInputsRequestFilter(RequestFilter):
CODE_INTERVAL_TOO_BIG = 'interval_too_big'

templates = {
CODE_INTERVAL_INVALID: '``start`` must be <= ``end``',
CODE_INTERVAL_TOO_BIG: '``end`` - ``start`` must be <= {max_interval}',
CODE_INTERVAL_INVALID: '``start`` must be <= ``stop``',
CODE_INTERVAL_TOO_BIG: '``stop`` - ``start`` must be <= {max_interval}',
}

def __init__(self):
super(GetInputsRequestFilter, self).__init__(
{
# These arguments are optional.
'end': f.Type(int) | f.Min(0),
'stop': f.Type(int) | f.Min(0),
'start': f.Type(int) | f.Min(0) | f.Optional(0),
'threshold': f.Type(int) | f.Min(0),

Expand All @@ -130,7 +125,7 @@ def __init__(self):
},

allow_missing_keys = {
'end',
'stop',
'start',
'threshold',
}
Expand All @@ -143,27 +138,27 @@ def _apply(self, value):
if self._has_errors:
return filtered

if filtered['end'] is not None:
if filtered['start'] > filtered['end']:
if filtered['stop'] is not None:
if filtered['start'] > filtered['stop']:
filtered['start'] = self._invalid_value(
value = filtered['start'],
reason = self.CODE_INTERVAL_INVALID,
sub_key = 'start',

context = {
'start': filtered['start'],
'end': filtered['end'],
'stop': filtered['stop'],
},
)
elif (filtered['end'] - filtered['start']) > self.MAX_INTERVAL:
filtered['end'] = self._invalid_value(
value = filtered['end'],
elif (filtered['stop'] - filtered['start']) > self.MAX_INTERVAL:
filtered['stop'] = self._invalid_value(
value = filtered['stop'],
reason = self.CODE_INTERVAL_TOO_BIG,
sub_key = 'end',
sub_key = 'stop',

context = {
'start': filtered['start'],
'end': filtered['end'],
'stop': filtered['stop'],
},

template_vars = {
Expand Down
13 changes: 6 additions & 7 deletions src/iota/commands/extended/get_new_addresses.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from __future__ import absolute_import, division, print_function, \
unicode_literals

from typing import Optional

import filters as f

from iota.commands import FilterCommand, RequestFilter
Expand Down Expand Up @@ -30,12 +32,9 @@ def get_response_filter(self):
pass

def _execute(self, request):
# Optional parameters.
count = request.get('count')
index = request.get('index')

# Required parameters.
seed = request['seed']
count = request['count'] # type: Optional[int]
index = request['index'] # type: int
seed = request['seed'] # type: Seed

generator = AddressGenerator(seed)

Expand All @@ -57,7 +56,7 @@ def __init__(self):
{
# ``count`` and ``index`` are optional.
'count': f.Type(int) | f.Min(1),
'index': f.Type(int) | f.Min(0),
'index': f.Type(int) | f.Min(0) | f.Optional(default=0),

'seed': f.Required | Trytes(result_type=Seed),
},
Expand Down
35 changes: 22 additions & 13 deletions src/iota/commands/extended/get_transfers.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,40 +71,49 @@ def _execute(self, request):

if hashes:
# Sort transactions into tail and non-tail.
tails = set()
non_tails = set()
tail_transaction_hashes = set()
non_tail_bundle_hashes = set()

gt_response = GetTrytesCommand(self.adapter)(hashes=hashes)
transactions = list(map(
all_transactions = list(map(
Transaction.from_tryte_string,
gt_response['trytes'],
))
)) # type: List[Transaction]

for txn in transactions:
for txn in all_transactions:
if txn.is_tail:
tails.add(txn.hash)
tail_transaction_hashes.add(txn.hash)
else:
# Capture the bundle ID instead of the transaction hash so that
# we can query the node to find the tail transaction for that
# bundle.
non_tails.add(txn.bundle_hash)
non_tail_bundle_hashes.add(txn.bundle_hash)

if non_tails:
for txn in self._find_transactions(bundles=non_tails):
if non_tail_bundle_hashes:
for txn in self._find_transactions(bundles=list(non_tail_bundle_hashes)):
if txn.is_tail:
tails.add(txn.hash)
if txn.hash not in tail_transaction_hashes:
all_transactions.append(txn)
tail_transaction_hashes.add(txn.hash)

# Filter out all non-tail transactions.
tail_transactions = [
txn
for txn in all_transactions
if txn.hash in tail_transaction_hashes
]

# Attach inclusion states, if requested.
if inclusion_states:
gli_response = GetLatestInclusionCommand(self.adapter)(
hashes = list(tails),
hashes = list(tail_transaction_hashes),
)

for txn in transactions:
for txn in tail_transactions:
txn.is_confirmed = gli_response['states'].get(txn.hash)

# Find the bundles for each transaction.
for txn in transactions:
for txn in tail_transactions:
gb_response = GetBundlesCommand(self.adapter)(transaction=txn.hash)
txn_bundles = gb_response['bundles'] # type: List[Bundle]

Expand Down
Loading