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

Commit 0d21e45

Browse files
committed
Merge remote-tracking branch 'upstream/develop' into develop
2 parents b342e6f + d66776d commit 0d21e45

File tree

11 files changed

+165
-27
lines changed

11 files changed

+165
-27
lines changed

MANIFEST.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
include docs/conf.py
2+
include docs/make.bat
3+
include docs/Makefile
14
include LICENSE
25
include README.rst
6+
recursive-include docs *.rst
37
recursive-include examples *.py
48
recursive-include test *.py *.csv

README.rst

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,14 @@ To install this extension, use the following command::
4545
pip install pyota[ccurl]
4646

4747

48+
.. _readme-installing-from-source:
49+
4850
Installing from Source
4951
======================
5052

51-
1. `Create virtualenv`_ (recommended, but not required).
52-
2. ``git clone https://github.com/iotaledger/iota.lib.py.git``
53-
3. ``pip install -e .``
53+
#. `Create virtualenv`_ (recommended, but not required).
54+
#. ``git clone https://github.com/iotaledger/iota.lib.py.git``
55+
#. ``pip install -e .``
5456

5557
Running Unit Tests
5658
------------------
@@ -66,12 +68,33 @@ PyOTA is also compatible with `tox`_::
6668
=============
6769
Documentation
6870
=============
69-
For the full documentation of this library, please refer to the
70-
`official API`_
71+
PyOTA's documentation is available on `ReadTheDocs`_.
72+
73+
If you are :ref:`installing from source <readme-installing-from-source>`, you
74+
can also build the documentation locally:
75+
76+
#. Install extra dependencies (you only have to do this once)::
77+
78+
pip install '.[docs-builder]'
79+
80+
.. tip::
81+
82+
To install the CCurl extension and the documentation builder tools
83+
together, use the following command::
84+
85+
pip install '.[ccurl,docs-builder]'
86+
87+
#. Switch to the ``docs`` directory::
88+
89+
cd docs
90+
91+
#. Build the documentation::
7192

93+
make html
7294

7395
.. _Create virtualenv: https://realpython.com/blog/python/python-virtual-environments-a-primer/
7496
.. _PyOTA Bug Tracker: https://github.com/iotaledger/iota.lib.py/issues
97+
.. _ReadTheDocs: https://pyota.readthedocs.io/
7598
.. _Slack: https://slack.iota.org/
7699
.. _dedicated forum: https://forum.iota.org/
77100
.. _official API: https://iota.readme.io/

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
# The theme to use for HTML and HTML Help pages. See the documentation for
8484
# a list of builtin themes.
8585
#
86-
# html_theme = 'alabaster'
86+
html_theme = 'sphinx_rtd_theme'
8787

8888
# Theme options are theme-specific and customize the look and feel of a theme
8989
# further. For a list of options available for each theme, see the

docs/index.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,4 @@
99
api
1010
multisig
1111

12-
.. note::
13-
**🚧 PyOTA documentation is still under construction. 🚧**
14-
15-
Follow https://github.com/iotaledger/iota.lib.py/issues/78 for updates.
16-
1712
.. include:: ../README.rst

examples/address_generator.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@
1212
from typing import Optional, Text
1313

1414
from iota import __version__, Iota
15+
from iota.crypto.addresses import AddressGenerator
1516
from iota.crypto.types import Seed
1617
from six import binary_type, moves as compat, text_type
1718

1819

19-
def main(uri, index, count):
20-
# type: (Text, int, Optional[int], bool) -> None
20+
def main(uri, index, count, security, checksum):
21+
# type: (Text, int, Optional[int], Optional[int], bool) -> None
2122
seed = get_seed()
2223

2324
# Create the API instance.
@@ -34,7 +35,7 @@ def main(uri, index, count):
3435
print('')
3536

3637
# Here's where all the magic happens!
37-
api_response = api.get_new_addresses(index, count)
38+
api_response = api.get_new_addresses(index, count, security, checksum)
3839
for addy in api_response['addresses']:
3940
print(binary_type(addy).decode('ascii'))
4041

@@ -111,4 +112,20 @@ def output_seed(seed):
111112
'If not specified, the first unused address will be returned.'
112113
)
113114

115+
parser.add_argument(
116+
'--security',
117+
type = int,
118+
default = AddressGenerator.DEFAULT_SECURITY_LEVEL,
119+
help = 'Security level to be used for the private key / address. '
120+
'Can be 1, 2 or 3',
121+
)
122+
123+
parser.add_argument(
124+
'--with-checksum',
125+
action = 'store_true',
126+
default = False,
127+
dest = 'checksum',
128+
help = 'List the address with the checksum.',
129+
)
130+
114131
main(**vars(parser.parse_args(argv[1:])))

iota/api.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,8 +611,9 @@ def get_new_addresses(
611611
index = 0,
612612
count = 1,
613613
security_level = AddressGenerator.DEFAULT_SECURITY_LEVEL,
614+
checksum = False,
614615
):
615-
# type: (int, Optional[int], int) -> dict
616+
# type: (int, Optional[int], int, bool) -> dict
616617
"""
617618
Generates one or more new addresses from the seed.
618619
@@ -636,6 +637,10 @@ def get_new_addresses(
636637
637638
This value must be between 1 and 3, inclusive.
638639
640+
:param checksum:
641+
Specify whether to return the address with the checksum.
642+
Defaults to False.
643+
639644
:return:
640645
Dict with the following items::
641646
@@ -651,6 +656,7 @@ def get_new_addresses(
651656
count = count,
652657
index = index,
653658
securityLevel = security_level,
659+
checksum = checksum,
654660
seed = self.seed,
655661
)
656662

iota/commands/extended/get_new_addresses.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import filters as f
88

9-
from iota import Address
9+
from iota import Address, AddressChecksum
1010
from iota.commands import FilterCommand, RequestFilter
1111
from iota.commands.core.find_transactions import FindTransactionsCommand
1212
from iota.crypto.addresses import AddressGenerator
@@ -33,28 +33,33 @@ def get_response_filter(self):
3333
pass
3434

3535
def _execute(self, request):
36+
checksum = request['checksum'] # type: bool
3637
count = request['count'] # type: Optional[int]
3738
index = request['index'] # type: int
3839
security_level = request['securityLevel'] # type: int
3940
seed = request['seed'] # type: Seed
4041

4142
return {
42-
'addresses': self._find_addresses(seed, index, count, security_level),
43+
'addresses':
44+
self._find_addresses(seed, index, count, security_level, checksum),
4345
}
4446

45-
def _find_addresses(self, seed, index, count, security_level):
46-
# type: (Seed, int, Optional[int], int) -> List[Address]
47+
def _find_addresses(self, seed, index, count, security_level, checksum):
48+
# type: (Seed, int, Optional[int], int, bool) -> List[Address]
4749
"""
4850
Find addresses matching the command parameters.
4951
"""
50-
# type: (Seed, int, Optional[int]) -> List[Address]
51-
generator = AddressGenerator(seed, security_level)
52+
generator = AddressGenerator(seed, security_level, checksum)
5253

5354
if count is None:
5455
# Connect to Tangle and find the first address without any
5556
# transactions.
5657
for addy in generator.create_iterator(start=index):
57-
response = FindTransactionsCommand(self.adapter)(addresses=[addy])
58+
# We use addy.address here because FindTransactions does
59+
# not work on an address with a checksum
60+
response = FindTransactionsCommand(self.adapter)(
61+
addresses=[addy.address]
62+
)
5863

5964
if not response.get('hashes'):
6065
return [addy]
@@ -75,8 +80,10 @@ def __init__(self):
7580
super(GetNewAddressesRequestFilter, self).__init__(
7681
{
7782
# Everything except ``seed`` is optional.
78-
'count': f.Type(int) | f.Min(1),
79-
'index': f.Type(int) | f.Min(0) | f.Optional(default=0),
83+
84+
'checksum': f.Type(bool) | f.Optional(default=False),
85+
'count': f.Type(int) | f.Min(1),
86+
'index': f.Type(int) | f.Min(0) | f.Optional(default=0),
8087

8188
'securityLevel':
8289
f.Type(int)
@@ -88,6 +95,7 @@ def __init__(self):
8895
},
8996

9097
allow_missing_keys = {
98+
'checksum',
9199
'count',
92100
'index',
93101
'securityLevel',

iota/crypto/addresses.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@ class AddressGenerator(Iterable[Address]):
4040
- :py:class:`iota.transaction.BundleValidator`
4141
"""
4242

43-
def __init__(self, seed, security_level=DEFAULT_SECURITY_LEVEL):
44-
# type: (TrytesCompatible, int) -> None
43+
def __init__(self, seed, security_level=DEFAULT_SECURITY_LEVEL, checksum=False):
44+
# type: (TrytesCompatible, int, bool) -> None
4545
super(AddressGenerator, self).__init__()
4646

4747
self.security_level = security_level
48+
self.checksum = checksum
4849
self.seed = Seed(seed)
4950

5051
def __iter__(self):
@@ -175,7 +176,10 @@ def _generate_address(self, key_iterator):
175176
176177
Used in the event of a cache miss.
177178
"""
178-
return self.address_from_digest(self._get_digest(key_iterator))
179+
if self.checksum:
180+
return self.address_from_digest(self._get_digest(key_iterator)).with_valid_checksum()
181+
else:
182+
return self.address_from_digest(self._get_digest(key_iterator))
179183

180184
@staticmethod
181185
def _get_digest(key_iterator):

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060

6161
extras_require = {
6262
'ccurl': ['pyota-ccurl'],
63+
'docs-builder': ['sphinx', 'sphinx_rtd_theme'],
6364
},
6465

6566
test_suite = 'test',

test/commands/extended/get_new_addresses_test.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def test_pass_happy_path(self):
3535
'index': 1,
3636
'count': 1,
3737
'securityLevel': 2,
38+
'checksum': False,
3839
}
3940

4041
filter_ = self._filter(request)
@@ -59,6 +60,7 @@ def test_pass_optional_parameters_excluded(self):
5960
'index': 0,
6061
'count': None,
6162
'securityLevel': AddressGenerator.DEFAULT_SECURITY_LEVEL,
63+
'checksum': False,
6264
},
6365
)
6466

@@ -75,6 +77,9 @@ def test_pass_compatible_types(self):
7577
'index': 100,
7678
'count': 8,
7779
'securityLevel': 2,
80+
81+
# ``checksum`` must be boolean.
82+
'checksum': False,
7883
})
7984

8085
self.assertFilterPasses(filter_)
@@ -86,6 +91,7 @@ def test_pass_compatible_types(self):
8691
'index': 100,
8792
'count': 8,
8893
'securityLevel': 2,
94+
'checksum': False,
8995
},
9096
)
9197

@@ -111,6 +117,7 @@ def test_fail_unexpected_parameters(self):
111117
'index': None,
112118
'count': 1,
113119
'securityLevel': 2,
120+
'checksum': False,
114121

115122
# Some men just want to watch the world burn.
116123
'foo': 'bar',
@@ -306,6 +313,21 @@ def test_fail_security_level_wrong_type(self):
306313
},
307314
)
308315

316+
def test_fail_checksum_wrong_type(self):
317+
"""
318+
``checksum`` is not a boolean.
319+
"""
320+
self.assertFilterErrors(
321+
{
322+
'checksum': '2',
323+
'seed': Seed(self.seed),
324+
},
325+
326+
{
327+
'checksum': [f.Type.CODE_WRONG_TYPE],
328+
},
329+
)
330+
309331

310332
class GetNewAddressesCommandTestCase(TestCase):
311333
# noinspection SpellCheckingInspection
@@ -333,6 +355,13 @@ def setUp(self):
333355
b'IWYTLQUUHDWSOVXLIKVJTYZBFKLABWRBFYVSMD9NB',
334356
)
335357

358+
self.addy_1_checksum =\
359+
Address(
360+
b'NYMWLBUJEISSACZZBRENC9HEHYQXHCGQHSNHVCEA'
361+
b'ZDCTEVNGSDUEKTSYBSQGMVJRIEDHWDYSEYCFAZAH'
362+
b'9T9FPJROTW',
363+
)
364+
336365
def test_wireup(self):
337366
"""
338367
Verify that the command is wired up correctly.
@@ -446,3 +475,20 @@ def test_get_addresses_online(self):
446475
},
447476
],
448477
)
478+
479+
def test_new_address_checksum(self):
480+
"""
481+
Generate address with a checksum.
482+
"""
483+
response =\
484+
self.command(
485+
checksum = True,
486+
count = 1,
487+
index = 0,
488+
seed = self.seed,
489+
)
490+
491+
self.assertDictEqual(
492+
response,
493+
{'addresses': [self.addy_1_checksum]},
494+
)

0 commit comments

Comments
 (0)