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..30b2aba
--- /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 approve 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 `develop` branch, and be sure to consider the following:
+
+- 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.
+
+
+
+
+
+
+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 include comprehensive unit tests.
+
+### 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 `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.
+
+**Note:** Reviewers may request changes before your pull request can be approved and merged.
+
+
+
+
+
+Code of Conduct :clipboard:
+
+
+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
new file mode 100644
index 0000000..2607ec9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,48 @@
+---
+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?
+
+- PyOTA Version:
+- Python Version:
+
+## IOTA network
+
+Which node are you connected to and which IOTA network is it in?
+
+- Node URL:
+- Network:
+
+## Environment
+
+What operating system are you using?
+
+- Operating system:
+## 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 here any error messages and/or stacktraces 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..a272f5d
--- /dev/null
+++ b/.github/SECURITY.md
@@ -0,0 +1,22 @@
+
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 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 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:
+
+
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..d127d30
--- /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 (`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
diff --git a/.travis.yml b/.travis.yml
index 33763b0..916012f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,11 +1,9 @@
language: python
python:
- - '2.7'
- - '3.5'
- '3.6'
- '3.7'
install:
- - pip install .[docs-builder]
+ - pip install .[docs-builder,test-runner]
- pip install docutils pygments # Used to check package metadata.
script:
- python setup.py check --strict --metadata --restructuredtext
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..e1cfa1c 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
@@ -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.
@@ -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/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/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/commands.rst b/docs/commands.rst
index a860a44..cd32985 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...
@@ -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
diff --git a/docs/conf.py b/docs/conf.py
index 782a2b3..be5b331 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -31,7 +31,13 @@
# 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',
+]
+
+# 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']
@@ -47,7 +53,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/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/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/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 @@
+
+
+
\ 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 @@
+
+
+
\ 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/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/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..b975dc7
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,2 @@
+sphinx==2.4.2
+sphinx_rtd_theme==0.4.3
\ No newline at end of file
diff --git a/docs/transfers.rst b/docs/transfers.rst
new file mode 100644
index 0000000..26d6361
--- /dev/null
+++ b/docs/transfers.rst
@@ -0,0 +1,322 @@
+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 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
+`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 unconfirmed transactions ("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. 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 (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 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
+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 iotas being transferred.
+
+.. 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:`transfers:Use the Library` below.
+
+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`.
+
+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.
+
+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, the transaction is valid.
+
+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'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.
+
+.. 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
+---------------
+
+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.
+
+.. 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
+
+ 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!
+
+.. 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
+
+ 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 would also need to:
+ # bundle.add_inputs()
+ # bundle.send_unspent_inputs_to()
+
+ bundle.finalize()
+
+ # Again, for value transfers, we would need to:
+ # 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
+.. _cumulative weight: https://blog.iota.org/the-tangle-an-illustrated-introduction-f359b8b2ec80
diff --git a/docs/tutorials.rst b/docs/tutorials.rst
index b46f00e..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.
@@ -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
@@ -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
@@ -716,6 +716,177 @@ 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
+in 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.
+
+.. 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 ``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
+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 +894,19 @@ 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
+.. _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
+.. _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
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/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 bf24d34..9139546 100644
--- a/examples/hello_world.py
+++ b/examples/hello_world.py
@@ -1,19 +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 requests.exceptions import ConnectionError
-from six import text_type
+from httpx.exceptions import NetworkError
from iota import BadApiResponse, StrictIota, __version__
@@ -24,7 +18,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)
)
@@ -45,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..34c0045 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,8 +12,7 @@
Tag,
TryteString,
)
-from six import text_type
-from address_generator import get_seed, output_seed
+from .address_generator import get_seed, output_seed
def main(address, depth, message, tag, uri, value):
@@ -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/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..3ca2a99 100644
--- a/examples/tutorials/07_fetch_encrypted.py
+++ b/examples/tutorials/07_fetch_encrypted.py
@@ -11,14 +11,14 @@
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: ')
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
diff --git a/examples/tutorials/08_async_send_monitor.py b/examples/tutorials/08_async_send_monitor.py
new file mode 100644
index 0000000..09687c6
--- /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
+ gis_response = await api.get_inclusion_states(sent_tx_hashes, None)
+
+ 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 gis_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())
diff --git a/iota/__init__.py b/iota/__init__.py
index 8ab602b..970c1d6 100644
--- a/iota/__init__.py
+++ b/iota/__init__.py
@@ -1,15 +1,13 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
+from typing import Dict
# Define a few magic constants.
-DEFAULT_PORT = 14265
+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
@@ -17,7 +15,7 @@
In that way, it's kind of like toxic waste in a superhero story.
"""
-STANDARD_UNITS = {
+STANDARD_UNITS: Dict[str, int] = {
# Valid IOTA unit suffixes. Example value '-273.15 Ki'
'i': 1,
'Ki': 1000,
@@ -35,6 +33,7 @@
from .types import *
from .transaction import *
from .adapter import *
+from .api_async import *
from .api import *
from .trits import *
diff --git a/iota/adapter/__init__.py b/iota/adapter/__init__.py
index 4586200..36fadc2 100644
--- a/iota/adapter/__init__.py
+++ b/iota/adapter/__init__.py
@@ -1,18 +1,14 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
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 requests import Response, auth, codes, request
-from six import PY2, binary_type, iteritems, moves as compat, text_type, \
- add_metaclass
+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
from iota.json import JsonEncoder
@@ -28,13 +24,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'
"""
@@ -43,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”.
@@ -52,12 +41,18 @@
"""
# 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: Any) -> Future:
+ """
+ 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):
@@ -74,21 +69,20 @@ class InvalidUri(ValueError):
pass
-adapter_registry = {} # type: Dict[Text, AdapterMeta]
+adapter_registry: Dict[str, '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 = compat.urllib_parse.urlsplit(uri) # type: SplitResult
+ parsed: SplitResult = urlsplit(uri)
if not parsed.scheme:
raise with_context(
@@ -124,8 +118,7 @@ class AdapterMeta(ABCMeta):
Automatically registers new adapter classes in ``adapter_registry``.
"""
- # noinspection PyShadowingBuiltins
- 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):
@@ -134,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[str, SplitResult]) -> 'HttpAdapter':
"""
Creates a new instance using the specified URI.
@@ -145,29 +137,27 @@ def configure(cls, parsed):
return cls(parsed)
-@add_metaclass(AdapterMeta)
-class BaseAdapter(object):
+class BaseAdapter(object, metaclass=AdapterMeta):
"""
Interface for IOTA API adapters.
Adapters make it easy to customize the way an API instance
communicates with a node.
"""
- supported_protocols = () # type: Tuple[Text]
+ supported_protocols: Tuple[str] = ()
"""
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: Optional[Logger] = None
+ self.local_pow: bool = False
@abstract_method
- def get_uri(self):
- # type: () -> Text
+ def get_uri(self) -> str:
"""
Returns the URI that this adapter will use.
"""
@@ -176,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: Any) -> dict:
"""
Sends an API request to the node.
@@ -198,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
@@ -208,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: str,
+ context: Optional[dict] = 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
@@ -226,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.
@@ -233,13 +225,13 @@ 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:
Connection timeout in seconds.
- :param Optional[Tuple(Text,Text)] authentication:
+ :param Optional[Tuple(str,str)] authentication:
Credetentials for basic authentication with the node.
:return:
@@ -267,15 +259,20 @@ 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[str, SplitResult],
+ timeout: Optional[int] = None,
+ authentication: Optional[Tuple[str, str]] = None
+ ) -> None:
super(HttpAdapter, self).__init__()
+ self.client = AsyncClient()
self.timeout = timeout
self.authentication = authentication
- if isinstance(uri, text_type):
- uri = compat.urllib_parse.urlsplit(uri) # type: SplitResult
+ if isinstance(uri, str):
+ uri: SplitResult = urlsplit(uri)
if uri.scheme not in self.supported_protocols:
raise with_context(
@@ -302,7 +299,6 @@ def __init__(self, uri, timeout=None, authentication=None):
)
try:
- # noinspection PyStatementEffect
uri.port
except ValueError:
raise with_context(
@@ -320,24 +316,21 @@ def __init__(self, uri, timeout=None, authentication=None):
self.uri = uri
@property
- def node_url(self):
- # type: () -> Text
+ def node_url(self) -> str:
"""
Returns the node URL.
"""
return self.uri.geturl()
- def get_uri(self):
- # type: () -> Text
+ def get_uri(self) -> str:
return self.uri.geturl()
- def send_request(self, payload, **kwargs):
- # type: (dict, dict) -> dict
+ async def send_request(self, payload: dict, **kwargs: Any) -> 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 = 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,10 +339,15 @@ 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):
- # type: (Text, Optional[Text], Text, dict) -> Response
+ async def _send_http_request(
+ self,
+ url: str,
+ payload: Optional[str],
+ method: str = 'post',
+ **kwargs: Any
+ ) -> Response:
"""
Sends the actual HTTP request.
@@ -362,7 +360,7 @@ def _send_http_request(self, url, payload, method='post', **kwargs):
)
if self.authentication:
- kwargs.setdefault('auth', auth.HTTPBasicAuth(*self.authentication))
+ kwargs.setdefault('auth', BasicAuth(*self.authentication))
self._log(
level=DEBUG,
@@ -380,8 +378,7 @@ def _send_http_request(self, url, payload, method='post', **kwargs):
'request_url': url,
},
)
-
- response = request(method=method, url=url, data=payload, **kwargs)
+ response = await self.client.request(method=method, url=url, data=payload, **kwargs)
self._log(
level=DEBUG,
@@ -405,8 +402,12 @@ 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[int]
+ ) -> dict:
"""
Interprets the HTTP response from the node.
@@ -436,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(
@@ -474,9 +475,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
@@ -530,22 +531,20 @@ class MockAdapter(BaseAdapter):
"""
supported_protocols = ('mock',)
- # noinspection PyUnusedLocal
@classmethod
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[str, deque] = {}
+ self.requests: List[dict] = []
- def get_uri(self):
+ def get_uri(self) -> str:
return 'mock://'
- def seed_response(self, command, response):
- # type: (Text, dict) -> MockAdapter
+ def seed_response(self, command: str, response: dict) -> 'MockAdapter':
"""
Sets the response that the adapter will return for the specified
command.
@@ -559,7 +558,7 @@ def seed_response(self, command, response):
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``).
@@ -585,8 +584,10 @@ def seed_response(self, command, response):
self.responses[command].append(response)
return self
- def send_request(self, payload, **kwargs):
- # type: (dict, dict) -> dict
+ async def send_request(self, payload: Dict, **kwargs: Any) -> dict:
+ """
+ Mimic asynchronous behavior of `HttpAdapter.send_request`.
+ """
# Store a snapshot so that we can inspect the request later.
self.requests.append(dict(payload))
@@ -627,4 +628,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..3beb0b0 100644
--- a/iota/adapter/wrappers.py
+++ b/iota/adapter/wrappers.py
@@ -1,11 +1,5 @@
-# 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 typing import Dict, Any
from iota.adapter import AdapterSpec, BaseAdapter, resolve_adapter
@@ -14,29 +8,25 @@
]
-@add_metaclass(ABCMeta)
-class BaseWrapper(BaseAdapter):
+class BaseWrapper(BaseAdapter, metaclass=ABCMeta):
"""
Base functionality for "adapter wrappers", used to extend the
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) -> str:
return self.adapter.get_uri()
@abstract_method
- def send_request(self, payload, **kwargs):
- # type: (dict, dict) -> dict
+ def send_request(self, payload: dict, **kwargs: Any) -> dict:
raise NotImplementedError(
'Not implemented in {cls}.'.format(cls=type(self).__name__),
)
@@ -93,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``.
@@ -103,16 +92,15 @@ 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[str, BaseAdapter] = {}
- def add_route(self, command, adapter):
- # type: (Text, 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``).
@@ -137,15 +125,13 @@ def add_route(self, command, adapter):
return self
- def get_adapter(self, command):
- # type: (Text) -> BaseAdapter
+ def get_adapter(self, command: str) -> BaseAdapter:
"""
Return the adapter for the specified command.
"""
return self.routes.get(command, self.adapter)
- def send_request(self, payload, **kwargs):
- # type: (dict, dict) -> dict
+ async def send_request(self, payload: dict, **kwargs: Any) -> 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/iota/api.py b/iota/api.py
index 19481c5..1f42cec 100644
--- a/iota/api.py
+++ b/iota/api.py
@@ -1,16 +1,10 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
-
-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.extended.helpers import Helpers
from iota.crypto.addresses import AddressGenerator
-from iota.crypto.types import Seed
+from iota.api_async import AsyncStrictIota, AsyncIota
+import asyncio
__all__ = [
'InvalidCommand',
@@ -25,9 +19,34 @@ class InvalidCommand(ValueError):
"""
pass
-class StrictIota(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):
"""
- 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.
@@ -39,9 +58,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:
@@ -49,20 +68,24 @@ class StrictIota(object):
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.
"""
- def __init__(self, adapter, testnet=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.
- :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:
@@ -70,79 +93,17 @@ def __init__(self, adapter, testnet=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(StrictIota, self).__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.testnet = testnet
-
- 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.
+ super().__init__(adapter, devnet, local_pow)
- """
- 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.testnet else 14
-
- def add_neighbors(self, uris):
- # type: (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'])``
@@ -165,16 +126,20 @@ 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)
+
+ # 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
+ 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
@@ -197,7 +162,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::
@@ -211,18 +176,19 @@ 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 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,
+ )
)
- 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.
@@ -244,10 +210,16 @@ 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)
- def check_consistency(self, tails):
- # type: (Iterable[TransactionHash]) -> dict
+ # 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: Iterable[TransactionHash]) -> dict:
"""
Used to ensure tail resolves to a consistent ledger which is
necessary to validate before attempting promotion. Checks
@@ -268,7 +240,7 @@ def check_consistency(self, tails):
{
'state': bool,
Whether tails resolve to consistent ledger.
- 'info': Text,
+ 'info': str,
This field will only exist if 'state' is ``False``.
}
@@ -276,18 +248,22 @@ def check_consistency(self, tails):
- https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#checkconsistency
"""
- return 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,
+ )
)
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.
@@ -323,20 +299,24 @@ def find_transactions(
- https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#findtransactions
"""
- return 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,
+ )
)
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
@@ -378,14 +358,22 @@ def get_balances(
- https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getbalances
"""
- return 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,
+ )
)
- 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
@@ -416,13 +404,17 @@ 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)(
- 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,
+ )
)
- 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.
@@ -441,10 +433,14 @@ def get_missing_transactions(self):
- https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getmissingtransactions
"""
- return core.GetMissingTransactionsCommand(self.adapter)()
- def get_neighbors(self):
- # type: () -> dict
+ # 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) -> dict:
"""
Returns the set of neighbors the node is connected with, as well
as their activity count.
@@ -474,10 +470,14 @@ def get_neighbors(self):
- https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getneighbors
"""
- return core.GetNeighborsCommand(self.adapter)()
- def get_node_api_configuration(self):
- # type: () -> dict
+ # 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) -> dict:
"""
Returns a node's API configuration settings.
@@ -498,10 +498,14 @@ 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)()
- def get_node_info(self):
- # type: () -> dict
+ # 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) -> dict:
"""
Returns information about the node.
@@ -509,9 +513,9 @@ def get_node_info(self):
``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.
@@ -522,7 +526,7 @@ def get_node_info(self):
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.
@@ -545,7 +549,7 @@ def get_node_info(self):
'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.
@@ -557,10 +561,14 @@ def get_node_info(self):
- https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getnodeinfo
"""
- return core.GetNodeInfoCommand(self.adapter)()
- def get_tips(self):
- # type: () -> dict
+ # 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) -> dict:
"""
Returns the list of tips (transactions which have no other
transactions referencing them).
@@ -580,10 +588,18 @@ 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)()
- def get_transactions_to_approve(self, depth, reference=None):
- # type: (int, Optional[TransactionHash]) -> dict
+ # 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: int,
+ reference: Optional[TransactionHash] = None,
+ ) -> dict:
"""
Tip selection which returns ``trunkTransaction`` and
``branchTransaction``.
@@ -616,13 +632,17 @@ 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)(
- 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,
+ )
)
- 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.
@@ -647,10 +667,16 @@ 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)
- def interrupt_attaching_to_tangle(self):
- # type: () -> dict
+ # 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) -> dict:
"""
Interrupts and completely aborts the :py:meth:`attach_to_tangle`
process.
@@ -667,15 +693,19 @@ 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)()
- def remove_neighbors(self, uris):
- # type: (Iterable[Text]) -> dict
+ # 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: 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'])`
@@ -693,10 +723,14 @@ 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)
- def store_transactions(self, trytes):
- # type: (Iterable[TryteString]) -> dict
+ # 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: Iterable[TryteString]) -> dict:
"""
Store transactions into local storage of the node.
@@ -720,10 +754,17 @@ 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)
- def were_addresses_spent_from(self, addresses):
- # type: (Iterable[Address]) -> dict
+ # 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: Iterable[Address]
+ ) -> dict:
"""
Check if a list of addresses was ever spent from, in the current
epoch, or in previous epochs.
@@ -749,15 +790,18 @@ 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)(
- addresses=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)
)
-class Iota(StrictIota):
+class Iota(StrictIota, AsyncIota):
"""
- Implements the core API, plus additional wrapper methods for common
- operations.
+ Implements the synchronous core API, plus additional synchronous wrapper
+ methods for common operations.
:param AdapterSpec adapter:
URI string or BaseAdapter instance.
@@ -769,9 +813,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
@@ -782,7 +826,7 @@ class Iota(StrictIota):
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:
@@ -791,8 +835,13 @@ class Iota(StrictIota):
- https://github.com/iotaledger/wiki/blob/master/api-proposal.md
"""
- def __init__(self, adapter, seed=None, testnet=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.
@@ -801,13 +850,13 @@ 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)
+ # Explicitly call AsyncIota's init, as we need the seed
+ AsyncIota.__init__(self, adapter, seed, devnet, local_pow)
- self.seed = Seed(seed) if seed else Seed.random()
- self.helpers = Helpers(self)
-
- 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.
@@ -827,10 +876,16 @@ def broadcast_and_store(self, trytes):
- https://github.com/iotaledger/wiki/blob/master/api-proposal.md#broadcastandstore
"""
- return extended.BroadcastAndStoreCommand(self.adapter)(trytes=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)
+ )
- 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,
@@ -852,21 +907,24 @@ 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)
+ # 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)
+ )
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.
- 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,15 +957,24 @@ def find_transaction_objects(
}
"""
- return extended.FindTransactionObjectsCommand(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_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.
@@ -975,16 +1042,21 @@ def get_account_data(self, start=0, stop=None, inclusion_states=False, security_
}
"""
- return extended.GetAccountDataCommand(self.adapter)(
- seed=self.seed,
- start=start,
- stop=stop,
- inclusionStates=inclusion_states,
- security_level=security_level
+ # 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,
+ )
)
- 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.
@@ -1009,16 +1081,19 @@ def get_bundles(self, transactions):
- https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getbundle
"""
- return extended.GetBundlesCommand(self.adapter)(transactions=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)
+ )
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.
@@ -1115,16 +1190,21 @@ def get_inputs(
- https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getinputs
"""
- return extended.GetInputsCommand(self.adapter)(
- seed=self.seed,
- start=start,
- stop=stop,
- threshold=threshold,
- securityLevel=security_level
+ # 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,
+ )
)
- def get_latest_inclusion(self, hashes):
- # type: (Iterable[TransactionHash]) -> Dict[TransactionHash, bool]
+ def get_latest_inclusion(
+ self,
+ hashes: Iterable[TransactionHash]
+ ) -> Dict[str, Dict[TransactionHash, bool]]:
"""
Fetches the inclusion state for the specified transaction
hashes, as of the latest milestone that the node has processed.
@@ -1145,16 +1225,19 @@ def get_latest_inclusion(self, hashes):
}
"""
- return extended.GetLatestInclusionCommand(self.adapter)(hashes=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)
+ )
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.
@@ -1208,19 +1291,21 @@ def get_new_addresses(
- https://github.com/iotaledger/wiki/blob/master/api-proposal.md#getnewaddress
"""
- return extended.GetNewAddressesCommand(self.adapter)(
- count=count,
- index=index,
- securityLevel=security_level,
- checksum=checksum,
- seed=self.seed,
+ # 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,
+ )
)
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).
@@ -1242,12 +1327,18 @@ def get_transaction_objects(
List of Transaction objects that match the input.
}
"""
- return extended.GetTransactionObjectsCommand(self.adapter)(
- hashes=hashes,
+ # 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)
)
- 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.
@@ -1299,18 +1390,20 @@ 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)(
- seed=self.seed,
- start=start,
- stop=stop,
- inclusionStates=inclusion_states,
+ # 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,
+ )
)
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``
@@ -1330,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
@@ -1341,18 +1434,19 @@ def is_promotable(
References:
- https://github.com/iotaledger/iota.js/blob/next/api_reference.md#module_core.isPromotable
"""
- return extended.IsPromotableCommand(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().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
@@ -1399,21 +1493,23 @@ def prepare_transfer(
- https://github.com/iotaledger/wiki/blob/master/api-proposal.md#preparetransfers
"""
- return extended.PrepareTransferCommand(self.adapter)(
- seed=self.seed,
- transfers=transfers,
- inputs=inputs,
- changeAddress=change_address,
- securityLevel=security_level,
+ # 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,
+ )
)
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.
@@ -1438,22 +1534,22 @@ def promote_transaction(
The newly-published bundle.
}
"""
- if min_weight_magnitude is None:
- min_weight_magnitude = self.default_min_weight_magnitude
-
- return extended.PromoteTransactionCommand(self.adapter)(
- transaction=transaction,
- depth=depth,
- minWeightMagnitude=min_weight_magnitude,
+ # 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,
+ )
)
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
@@ -1484,25 +1580,25 @@ def replay_bundle(
- 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 extended.ReplayBundleCommand(self.adapter)(
- transaction=transaction,
- depth=depth,
- minWeightMagnitude=min_weight_magnitude,
+ # 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,
+ )
)
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
@@ -1553,21 +1649,25 @@ def send_transfer(
- 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 extended.SendTransferCommand(self.adapter)(
- seed=self.seed,
- depth=depth,
- transfers=transfers,
- inputs=inputs,
- changeAddress=change_address,
- minWeightMagnitude=min_weight_magnitude,
- securityLevel=security_level,
+ # 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,
+ )
)
- 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.
@@ -1597,17 +1697,17 @@ def send_trytes(self, trytes, depth=3, min_weight_magnitude=None):
- 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 extended.SendTrytesCommand(self.adapter)(
- trytes=trytes,
- depth=depth,
- minWeightMagnitude=min_weight_magnitude,
+ # 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,
+ )
)
- 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
@@ -1633,12 +1733,15 @@ def is_reattachable(self, addresses):
}
"""
- return extended.IsReattachableCommand(self.adapter)(
- addresses=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,
+ )
)
- 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.
@@ -1661,6 +1764,10 @@ def traverse_bundle(self, tail_hash):
}
"""
- return extended.TraverseBundleCommand(self.adapter)(
- transaction=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,
+ )
)
diff --git a/iota/api_async.py b/iota/api_async.py
new file mode 100644
index 0000000..7a85003
--- /dev/null
+++ b/iota/api_async.py
@@ -0,0 +1,1667 @@
+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 CustomCommand, core, extended
+from iota.crypto.addresses import AddressGenerator
+from iota.crypto.types import Seed
+
+__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:`AsyncIota` 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:`README:Optional Local Pow` for more info and
+ :ref:`find out` how to use it.
+
+ """
+
+ def __init__(
+ self,
+ adapter: AdapterSpec,
+ devnet: bool = False,
+ local_pow: bool = False
+ ) -> 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:`README: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: 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,
+ # 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: 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 str command:
+ The name of the command to create.
+
+ """
+ return CustomCommand(self.adapter, command)
+
+ 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
+ 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) -> 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: Iterable[str]) -> dict:
+ """
+ Add one or more neighbors to the node. Lasts until the node is
+ restarted.
+
+ :param Iterable[str] 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: 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
+ 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: 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: 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': str,
+ 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: 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.
+
+ 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: 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
+ 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: 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
+ 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) -> 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) -> 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) -> 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) -> dict:
+ """
+ Returns information about the node.
+
+ :return:
+ ``dict`` with the following structure::
+
+ {
+ 'appName': str,
+ Name of the IRI network.
+ 'appVersion': str,
+ 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': str,
+ 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[str],
+ 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) -> 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: int,
+ reference: Optional[TransactionHash] = None,
+ ) -> 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: 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) -> 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: Iterable[str]) -> dict:
+ """
+ Removes one or more neighbors from the node. Lasts until the
+ node is restarted.
+
+ :param str 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: 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: 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:`README: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: AdapterSpec,
+ seed: Optional[TrytesCompatible] = None,
+ devnet: bool = False,
+ local_pow: bool = False
+ ) -> 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: 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: 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: 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.
+
+ 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: 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.
+
+ 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: 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: 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.
+
+ 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: Iterable[TransactionHash]
+ ) -> Dict[str, 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: int = 0,
+ count: int = 1,
+ security_level: int = AddressGenerator.DEFAULT_SECURITY_LEVEL,
+ checksum: bool = False,
+ ):
+ """
+ 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: [Iterable[TransactionHash]],
+ ) -> 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: int = 0,
+ stop: Optional[int] = None,
+ inclusion_states: bool = False
+ ) -> 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: 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[str])
+ 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: 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
+ 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: TransactionHash,
+ depth: int = 3,
+ min_weight_magnitude: Optional[int] = None,
+ ) -> 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: 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
+ 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: 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
+ 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: Iterable[TransactionTrytes],
+ depth: int = 3,
+ min_weight_magnitude: Optional[int] = None
+ ) -> 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: 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: 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
+ )
diff --git a/iota/bin/__init__.py b/iota/bin/__init__.py
index eb97e20..4c035a6 100644
--- a/iota/bin/__init__.py
+++ b/iota/bin/__init__.py
@@ -1,16 +1,10 @@
-# 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
from getpass import getpass as secure_input
from io import StringIO
from sys import exit
-from typing import Any, Optional, Text
-
-from six import text_type, add_metaclass
+from typing import Any, Optional
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.
"""
@@ -30,8 +23,12 @@ class IotaCommandLineApp(object):
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
@@ -39,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).
@@ -55,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.
@@ -78,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.
@@ -102,13 +96,12 @@ 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
- 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.
@@ -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=(
@@ -143,17 +136,16 @@ 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
@staticmethod
- def seed_from_filepath(filepath):
- # type: (Text) -> Seed
+ def seed_from_filepath(filepath: str) -> Seed:
"""
Reads a seed from the first line of a text file.
@@ -163,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.
"""
@@ -173,7 +164,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 bfae94b..5e16163 100755
--- a/iota/bin/repl.py
+++ b/iota/bin/repl.py
@@ -1,18 +1,12 @@
#!/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
-
+from typing import Any
# Import all IOTA symbols into module scope, so that it's more
# convenient for the user.
from iota import *
@@ -26,8 +20,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']
@@ -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:
@@ -60,13 +51,12 @@ 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(
'--pow-uri',
- type=text_type,
+ type=str,
default=None,
dest='pow_uri',
help='URI of node to send POW requests to.'
@@ -83,16 +73,15 @@ def create_argument_parser(self):
return parser
@staticmethod
- def _start_repl(api):
- # type: (Iota) -> None
+ def _start_repl(api: Iota) -> None:
"""
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(),
)
)
@@ -100,7 +89,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..cb3b533 100644
--- a/iota/codecs.py
+++ b/iota/codecs.py
@@ -1,12 +1,7 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
-
from codecs import Codec, CodecInfo, register as lookup_function
+from typing import Union, Tuple
from warnings import warn
-from six import PY3, binary_type
-
from iota.exceptions import with_context
__all__ = [
@@ -46,7 +41,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.
@@ -58,7 +52,7 @@ 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.
@@ -68,24 +62,24 @@ def get_codec_info(cls):
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
}
- # 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
-
return CodecInfo(**codec_info)
- # noinspection PyShadowingBuiltins
- def encode(self, input, errors='strict'):
+ def encode(self,
+ input: Union[memoryview, bytes, bytearray],
+ errors: str = 'strict') -> Tuple[bytes, int]:
"""
Encodes a byte string into trytes.
"""
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,17 +104,18 @@ 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'):
+ def decode(self,
+ input: Union[memoryview, bytes, bytearray],
+ errors: str = 'strict') -> Tuple[bytes, int]:
"""
Decodes a tryte string into bytes.
"""
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 +185,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 39695bc..0f4c99a 100644
--- a/iota/commands/__init__.py
+++ b/iota/commands/__init__.py
@@ -1,17 +1,7 @@
-# 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, \
- 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
-import six
from iota.adapter import BaseAdapter
from iota.exceptions import with_context
@@ -24,27 +14,25 @@
'ResponseFilter',
]
-@six.add_metaclass(ABCMeta)
-class BaseCommand(object):
+
+class BaseCommand(object, metaclass=ABCMeta):
"""
An API command ready to send to the node.
"""
- command = None # Text
+ command: str = 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: Optional[dict] = None
+ self.response: Optional[dict] = None
- def __call__(self, **kwargs):
- # type: (**Any) -> dict
+ async def __call__(self, **kwargs: Any) -> dict:
"""
Sends the command to the node.
"""
@@ -64,7 +52,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:
@@ -74,17 +62,15 @@ 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
- 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.
@@ -92,11 +78,10 @@ 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):
- # type: (dict) -> Optional[dict]
+ def _prepare_request(self, request: dict) -> Optional[dict]:
"""
Modifies the request before sending it to the node.
@@ -114,8 +99,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.
@@ -137,8 +121,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: str) -> None:
super(CustomCommand, self).__init__(adapter)
self.command = command
@@ -197,15 +180,13 @@ 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.
"""
@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).
@@ -218,8 +199,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).
@@ -231,14 +211,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(),
@@ -246,8 +226,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: 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
@@ -274,4 +257,4 @@ def _apply_filter(value, filter_, failure_message):
},
)
- return value
\ No newline at end of file
+ return value
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..d9634c5 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
@@ -28,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 8c2644f..a0f9749 100644
--- a/iota/commands/core/attach_to_tangle.py
+++ b/iota/commands/core/attach_to_tangle.py
@@ -1,12 +1,9 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
-
import filters as f
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 +24,7 @@ def get_request_filter(self):
def get_response_filter(self):
return AttachToTangleResponseFilter()
- 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(
@@ -36,12 +33,13 @@ 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):
+ def __init__(self) -> None:
super(AttachToTangleRequestFilter, self).__init__({
'branchTransaction': f.Required | Trytes(TransactionHash),
'trunkTransaction': f.Required | Trytes(TransactionHash),
@@ -53,14 +51,14 @@ 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),
})
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 a8227b5..fae4607 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
@@ -29,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 598322e..ca0da90 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
@@ -29,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 8c53114..5f70a49 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
@@ -36,7 +31,7 @@ class FindTransactionsRequestFilter(RequestFilter):
CODE_NO_SEARCH_VALUES: 'No search values specified.',
}
- def __init__(self):
+ def __init__(self) -> None:
super(FindTransactionsRequestFilter, self).__init__(
{
'addresses':
@@ -58,9 +53,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
@@ -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
}
@@ -83,7 +78,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 5d55fd3..5c54751 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
@@ -30,7 +25,7 @@ def get_response_filter(self):
class GetBalancesRequestFilter(RequestFilter):
- def __init__(self):
+ def __init__(self) -> None:
super(GetBalancesRequestFilter, self).__init__(
{
'addresses':
@@ -55,9 +50,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
@@ -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
}
@@ -74,7 +69,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 cfcd602..c7f70ea 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
@@ -29,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 c0d3ee4..820a0bf 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
@@ -29,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 9d074f4..bda4172 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__ = [
@@ -25,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 acaf9a0..1b36e2d 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__ = [
@@ -25,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 85d4386..14f9471 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
@@ -29,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 f4ab15a..77d7375 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
@@ -29,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 d9f9ac5..11a26d4 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
@@ -29,7 +25,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),
@@ -42,9 +38,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
@@ -57,7 +53,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 f1a1851..d714b2f 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
@@ -29,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,
@@ -37,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 837e87e..e6cfb4f 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__ = [
@@ -25,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 6e0896c..ecd6e72 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
@@ -28,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 3171584..4bab99d 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
@@ -29,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 0d41e17..618ae0d 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
@@ -28,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/__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 c28526b..7b58607 100644
--- a/iota/commands/extended/broadcast_and_store.py
+++ b/iota/commands/extended/broadcast_and_store.py
@@ -1,11 +1,8 @@
-# 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
from iota.commands.core.store_transactions import StoreTransactionsCommand
+import asyncio
__all__ = [
'BroadcastAndStoreCommand',
@@ -26,9 +23,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: dict) -> dict:
+ # 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/iota/commands/extended/broadcast_bundle.py b/iota/commands/extended/broadcast_bundle.py
index b7d84b3..a17c0fa 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
@@ -31,19 +27,19 @@ def get_response_filter(self):
# Return value is filtered before hitting us.
pass
- 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 = GetBundlesCommand(self.adapter)(transactions=[request['tail_hash']])
- BroadcastTransactionsCommand(self.adapter)(trytes=bundle[0])
+ 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 e0dd096..fbc3ef4 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
@@ -23,17 +19,17 @@ class FindTransactionObjectsCommand(FindTransactionsCommand):
def get_response_filter(self):
pass
- 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]]
-
- ft_response = FindTransactionsCommand(adapter=self.adapter)(
+ 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,
addresses=addresses,
tags=tags,
@@ -43,12 +39,12 @@ 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(
+ 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 46dd8d1..ac97399 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
@@ -36,18 +32,18 @@ def get_request_filter(self):
def get_response_filter(self):
pass
- 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] = []
- 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 +52,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 +72,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,
@@ -95,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.
@@ -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 cb1acb3..5da2845 100644
--- a/iota/commands/extended/get_bundles.py
+++ b/iota/commands/extended/get_bundles.py
@@ -1,6 +1,4 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
+from typing import Iterable
import filters as f
@@ -10,6 +8,7 @@
from iota.exceptions import with_context
from iota.transaction.validator import BundleValidator
from iota.filters import Trytes
+import asyncio
__all__ = [
'GetBundlesCommand',
@@ -30,16 +29,13 @@ def get_request_filter(self):
def get_response_filter(self):
pass
- def _execute(self, request):
- transaction_hashes = request['transactions'] # type: Iterable[TransactionHash]
+ async def _execute(self, request: dict) -> dict:
+ transaction_hashes: Iterable[TransactionHash] = request['transactions']
- 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,14 +51,20 @@ 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,
}
+
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 0349c0c..4b50e3f 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
@@ -34,16 +30,16 @@ def get_request_filter(self):
def get_response_filter(self):
pass
- 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:
- 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 +55,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': []}
@@ -119,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 cf96bbf..afd5e3e 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
@@ -31,12 +27,12 @@ def get_request_filter(self):
def get_response_filter(self):
pass
- def _execute(self, request):
- hashes = request['hashes'] # type: List[TransactionHash]
+ async def _execute(self, request: dict) -> dict:
+ hashes: List[TransactionHash] = request['hashes']
- 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']],
)
@@ -47,7 +43,7 @@ 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 65f3c57..bc6a156 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
@@ -14,6 +10,7 @@
from iota.crypto.addresses import AddressGenerator
from iota.crypto.types import Seed
from iota.filters import SecurityLevel, Trytes
+import asyncio
__all__ = [
'GetNewAddressesCommand',
@@ -34,16 +31,16 @@ def get_request_filter(self):
def get_response_filter(self):
pass
- 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':
- self._find_addresses(
+ await self._find_addresses(
seed,
index,
count,
@@ -52,8 +49,14 @@ def _execute(self, request):
),
}
- 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.
"""
@@ -64,16 +67,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],
- )
- if response['states'][0]:
- continue
-
- response = FindTransactionsCommand(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.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]
@@ -82,7 +87,7 @@ 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 4f47564..632aacf 100644
--- a/iota/commands/extended/get_transaction_objects.py
+++ b/iota/commands/extended/get_transaction_objects.py
@@ -1,8 +1,4 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
-
-from typing import Iterable, List, Optional
+from typing import Iterable, List
import filters as f
@@ -30,26 +26,27 @@ def get_request_filter(self):
def get_response_filter(self):
pass
- 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 = GetTrytesCommand(adapter=self.adapter)(hashes=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 a8e5e9a..a96bd7c 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
@@ -34,22 +30,24 @@ def get_request_filter(self):
def get_response_filter(self):
pass
- 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.
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 +56,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,
@@ -77,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.
@@ -98,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/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/iota/commands/extended/is_promotable.py b/iota/commands/extended/is_promotable.py
index cda9769..d3f6da4 100644
--- a/iota/commands/extended/is_promotable.py
+++ b/iota/commands/extended/is_promotable.py
@@ -1,6 +1,4 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
+from typing import Optional
from iota.commands import FilterCommand, RequestFilter
from iota.commands.core import CheckConsistencyCommand, GetTrytesCommand
@@ -37,6 +35,7 @@
Calculate current time in milliseconds.
"""
+
class IsPromotableCommand(FilterCommand):
"""
Determines if a tail transaction is promotable.
@@ -51,33 +50,33 @@ def get_request_filter(self):
def get_response_filter(self):
pass
- 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:
# - 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,
)
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
- GetTrytesCommand(self.adapter)(hashes=tails)['trytes']
+ (await GetTrytesCommand(self.adapter)(hashes=tails))['trytes']
]
response = {
- 'promotable' : True,
- 'info' : [],
+ 'promotable': True,
+ 'info': [],
}
# Check timestamps
@@ -93,14 +92,15 @@ 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 |
@@ -108,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 3b5e83a..97b3dea 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
@@ -10,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',
@@ -29,13 +25,13 @@ def get_request_filter(self):
def get_response_filter(self):
return IsReattachableResponseFilter()
- 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 = 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 +48,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']
@@ -66,7 +62,7 @@ def _execute(self, request):
class IsReattachableRequestFilter(RequestFilter):
- def __init__(self):
+ def __init__(self) -> None:
super(IsReattachableRequestFilter, self).__init__(
{
'addresses': StringifiedTrytesArray(Address) | f.Required,
@@ -75,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 dd725b8..259869e 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
@@ -36,15 +32,15 @@ def get_request_filter(self):
def get_response_filter(self):
pass
- 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:
@@ -53,7 +49,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,
@@ -64,9 +60,9 @@ 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 = GetBalancesCommand(self.adapter)(
+ gb_response = await GetBalancesCommand(self.adapter)(
addresses=[i.address for i in proposed_inputs],
)
@@ -105,10 +101,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)
@@ -125,7 +121,7 @@ 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 3bcbd1a..26805b8 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
@@ -30,16 +26,16 @@ def get_request_filter(self):
def get_response_filter(self):
pass
- 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 = 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. '
- 'You should reattach first.'.format(transaction=transaction)
+ 'Info: {reason}'.format(transaction=transaction, reason=cc_response['info'])
)
spam_transfer = ProposedTransaction(
@@ -47,7 +43,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],
@@ -57,12 +53,12 @@ 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),
- # 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..2f9a46b 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
@@ -29,18 +25,18 @@ def get_request_filter(self):
def get_response_filter(self):
pass
- 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 = 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
+ bundle: Bundle = gb_response['bundles'][0]
- return SendTrytesCommand(self.adapter)(
+ return await SendTrytesCommand(self.adapter)(
depth=depth,
minWeightMagnitude=min_weight_magnitude,
trytes=bundle.as_tryte_strings(),
@@ -48,12 +44,12 @@ 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),
- # 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..be1122e 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
@@ -32,17 +28,17 @@ def get_request_filter(self):
def get_response_filter(self):
pass
- 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
-
- pt_response = PrepareTransferCommand(self.adapter)(
+ 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,
inputs=inputs,
seed=seed,
@@ -50,7 +46,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'],
@@ -63,14 +59,14 @@ def _execute(self, request):
class SendTransferRequestFilter(RequestFilter):
- def __init__(self):
+ def __init__(self) -> None:
super(SendTransferRequestFilter, self).__init__(
{
# Required parameters.
'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..40bfd51 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
@@ -33,20 +29,20 @@ def get_request_filter(self):
def get_response_filter(self):
pass
- 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.
- 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 +53,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,
@@ -65,7 +61,7 @@ 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),
@@ -74,7 +70,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),
diff --git a/iota/commands/extended/traverse_bundle.py b/iota/commands/extended/traverse_bundle.py
index d1176db..f819d11 100644
--- a/iota/commands/extended/traverse_bundle.py
+++ b/iota/commands/extended/traverse_bundle.py
@@ -1,12 +1,8 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
-
from typing import List, Optional
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
@@ -32,18 +28,22 @@ def get_request_filter(self):
def get_response_filter(self):
pass
- def _execute(self, request):
- txn_hash = request['transaction'] # type: TransactionHash
+ async def _execute(self, request: dict) -> dict:
+ txn_hash: TransactionHash = request['transaction']
- bundle = Bundle(self._traverse_bundle(txn_hash, None))
+ bundle = Bundle(await self._traverse_bundle(txn_hash, None))
# No bundle validation
return {
- 'bundles' : [bundle]
+ 'bundles': [bundle]
}
- 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.
@@ -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: List[TryteString] = (await GetTrytesCommand(self.adapter)(
+ hashes=[txn_hash])
+ )['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.
@@ -99,13 +99,14 @@ 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
)
+
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 3292a16..e68353b 100644
--- a/iota/commands/extended/utils.py
+++ b/iota/commands/extended/utils.py
@@ -1,12 +1,9 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
-
-from typing import Generator, Iterable, List, Optional, Tuple
+from typing import 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 \
@@ -19,19 +16,24 @@
from iota.crypto.types import Seed
-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]
+async def iter_used_addresses(
+ 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.
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
@@ -40,12 +42,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,12 +58,11 @@ def iter_used_addresses(
wasf_command.reset()
-def get_bundles_from_transaction_hashes(
- adapter,
- transaction_hashes,
- inclusion_states,
-):
- # type: (BaseAdapter, Iterable[TransactionHash], bool) -> List[Bundle]
+async def get_bundles_from_transaction_hashes(
+ 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.
@@ -70,17 +71,30 @@ 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)
- all_transactions = list(map(
+ 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[Transaction] = list(map(
Transaction.from_tryte_string,
gt_response['trytes'],
- )) # type: List[Transaction]
+ ))
for txn in all_transactions:
if txn.is_tail:
@@ -92,9 +106,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 +123,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 +131,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: List[Bundle] = (await GetBundlesCommand(adapter)(
+ transactions=[txn.hash for txn in tail_transactions]
+ ))['bundles']
- 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/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..f7bbf12 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
@@ -64,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).
@@ -158,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.
@@ -198,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())
@@ -216,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.
@@ -233,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/__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..8ba58e1 100644
--- a/iota/crypto/kerl/conv.py
+++ b/iota/crypto/kerl/conv.py
@@ -1,11 +1,10 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
+from typing import List, Dict
+
BYTE_HASH_LENGTH = 48
TRIT_HASH_LENGTH = 243
-tryte_table = {
+tryte_table: Dict[str, List[int]] = {
'9': [0, 0, 0], # 0
'A': [1, 0, 0], # 1
'B': [-1, 1, 0], # 2
@@ -39,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: str) -> List[int]:
trits = []
for tryte in trytes:
trits.extend(tryte_table[tryte])
@@ -47,7 +46,7 @@ def trytes_to_trits(trytes):
return trits
-def trits_to_trytes(trits):
+def trits_to_trytes(trits: List[int]) -> str:
trytes = []
trits_chunks = [trits[i:i + 3] for i in range(0, len(trits), 3)]
@@ -57,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)
+ 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))
@@ -92,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)]
@@ -116,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)):
@@ -125,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
@@ -151,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 86edcb5..d4bb8f1 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
@@ -19,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.
@@ -79,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.
@@ -122,9 +125,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)
@@ -143,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 48074c2..0c301b2 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
@@ -49,20 +45,21 @@ class Curl(object):
**IMPORTANT: Not thread-safe!**
"""
- def __init__(self):
- # type: (Optional[Sequence[int]]) -> None
+ def __init__(self) -> None:
self.reset()
- # noinspection PyAttributeOutsideInit
- 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.
@@ -112,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.
@@ -171,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 8c2b2a7..8649dc8 100644
--- a/iota/crypto/signing.py
+++ b/iota/crypto/signing.py
@@ -1,12 +1,6 @@
-# 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 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
@@ -22,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.
@@ -67,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.
@@ -91,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.
@@ -106,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).
@@ -153,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)
@@ -188,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.
@@ -224,30 +224,35 @@ 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
@@ -264,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)
@@ -283,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
@@ -295,26 +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
- if PY2:
- next = __next__
-
- 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.
"""
@@ -342,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)
@@ -351,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.
@@ -365,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.
@@ -385,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()
@@ -396,17 +391,13 @@ def __next__(self):
return TryteString.from_trits(signature_fragment)
- if PY2:
- next = __next__
-
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.
@@ -437,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 c868940..a33ab0c 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
@@ -37,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
@@ -61,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").
@@ -72,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.
@@ -81,7 +78,7 @@ def as_json_compatible(self):
``dict`` with the following structure::
{
- trytes': Text,
+ trytes': str,
'key_index': int,
}
@@ -117,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=(
@@ -132,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.
@@ -179,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:
@@ -201,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.
@@ -210,7 +209,7 @@ def as_json_compatible(self):
``dict`` with the following structure::
{
- trytes': Text,
+ trytes': str,
'key_index': int,
'security_level': int,
}
@@ -222,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.
@@ -280,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 31129c7..dbc5348 100644
--- a/iota/exceptions.py
+++ b/iota/exceptions.py
@@ -1,14 +1,10 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
__all__ = [
'with_context',
]
-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 9025a81..d88b15b 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
+from typing import 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
@@ -44,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
@@ -81,12 +77,12 @@ class NodeUri(f.BaseFilter):
}
def _apply(self, value):
- value = self._filter(value, f.Type(text_type)) # type: Text
+ value: str = self._filter(value, f.Type(str))
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,9 +90,8 @@ def _apply(self, value):
return value
-# noinspection PyPep8Naming
@filter_macro
-def SecurityLevel():
+def SecurityLevel() -> f.FilterChain:
"""
Generates a filter chain for validating a security level.
@@ -138,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):
@@ -164,14 +158,13 @@ def __init__(self, result_type=TryteString):
self.result_type = result_type
def _apply(self, value):
- # noinspection PyTypeChecker
- value = self._filter(
+ value: TrytesCompatible = self._filter(
filter_chain=f.Type(
- (binary_type, bytearray, text_type, TryteString)
+ (bytes, bytearray, str, TryteString)
),
value=value,
- ) # type: TrytesCompatible
+ )
if self._has_errors:
return None
@@ -211,10 +204,8 @@ def _apply(self, value):
)
-# noinspection PyPep8Naming
@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.,
@@ -223,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.
@@ -265,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/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 0ad14b8..37695f6 100644
--- a/iota/multisig/api.py
+++ b/iota/multisig/api.py
@@ -1,24 +1,22 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
-
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
@@ -26,17 +24,37 @@ class MultisigIota(Iota):
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
"""
- 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.
- :param digests:
+ :param Iterable[Digest] digests:
Digests to use to create the multisig address.
.. important::
@@ -45,37 +63,36 @@ def create_multisig_address(self, digests):
keys in the exact same order.
:return:
- Dict with the following items::
+ ``dict`` with the following items::
{
'address': MultisigAddress,
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,
- 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.
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
@@ -84,7 +101,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],
@@ -92,20 +109,19 @@ 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,
- 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.
@@ -113,13 +129,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
@@ -128,7 +144,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],
@@ -141,20 +157,19 @@ 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
- 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.
@@ -166,18 +181,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::
@@ -186,10 +201,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.
@@ -197,8 +212,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.
@@ -217,7 +233,7 @@ def prepare_multisig_transfer(
signing the input(s)!
:return:
- Dict containing the following values::
+ ``dict`` wontaining the following values::
{
'trytes': List[TransactionTrytes],
@@ -232,10 +248,257 @@ 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 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.
+
+ 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
+ """
+
+ def create_multisig_address(
+ self,
+ digests: Iterable[Digest]
+ ) -> dict:
+ """
+ Generates a multisig address from a collection of digests.
+
+ :param Iterable[Digest] 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: int = 0,
+ count: int = 1,
+ security_level: int = AddressGenerator.DEFAULT_SECURITY_LEVEL,
+ ) -> dict:
+ """
+ Generates one or more key digests from the seed.
+
+ Digests are safe to share; use them to generate multisig
+ addresses.
+
+ :param int index:
+ The starting key index.
+
+ :param int count:
+ Number of digests to generate.
+
+ :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.
+
+ :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: int = 0,
+ count: int = 1,
+ security_level: int = AddressGenerator.DEFAULT_SECURITY_LEVEL,
+ ) -> 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 int index:
+ The starting key index.
+
+ :param int count:
+ Number of keys to generate.
+
+ :param int 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: Iterable[ProposedTransaction],
+ multisig_input: MultisigAddress,
+ change_address: Optional[Address] = None,
+ ) -> 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.Iota.prepare_transfer` instead.
+
+ :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.Iota.prepare_transfer` instead.
+
+ :param MultisigAddress 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 Optional[Address] 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.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.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/__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 06ff70c..55c6ed4 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
@@ -26,14 +22,16 @@ class CreateMultisigAddressCommand(FilterCommand):
"""
command = 'createMultisigAddress'
- def get_request_filter(self):
+ def get_request_filter(self) -> 'CreateMultisigAddressRequestFilter':
return CreateMultisigAddressRequestFilter()
def get_response_filter(self):
pass
- def _execute(self, request):
- digests = request['digests'] # type: List[Digest]
+ # 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:
+ digests: List[Digest] = request['digests']
builder = MultisigAddressBuilder()
@@ -46,7 +44,7 @@ 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 91256b9..bb35125 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
@@ -26,19 +22,21 @@ class GetDigestsCommand(FilterCommand):
"""
command = 'getDigests'
- def get_request_filter(self):
+ def get_request_filter(self) -> 'GetDigestsRequestFilter':
return GetDigestsRequestFilter()
def get_response_filter(self):
pass
- 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
+ # 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:
+ count: Optional[int] = request['count']
+ index: int = request['index']
+ seed: Seed = request['seed']
+ security_level: int = request['securityLevel']
- gpk_result = GetPrivateKeysCommand(self.adapter)(
+ gpk_result = await GetPrivateKeysCommand(self.adapter)(
seed=seed,
count=count,
index=index,
@@ -51,7 +49,7 @@ 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 2a58abf..8e82476 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
@@ -27,17 +23,19 @@ class GetPrivateKeysCommand(FilterCommand):
"""
command = 'getPrivateKeys'
- def get_request_filter(self):
+ def get_request_filter(self) -> 'GetPrivateKeysRequestFilter':
return GetPrivateKeysRequestFilter()
def get_response_filter(self):
pass
- 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
+ # 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:
+ count: Optional[int] = request['count']
+ index: int = request['index']
+ seed: Seed = request['seed']
+ security_level: int = request['securityLevel']
generator = KeyGenerator(seed)
@@ -51,7 +49,7 @@ 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 7d43c2c..cafa296 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
@@ -29,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
- 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) -> 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 = GetBalancesCommand(self.adapter)(
- addresses=[multisig_input],
+ gb_response = await GetBalancesCommand(self.adapter)(
+ 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])
@@ -90,29 +86,29 @@ 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()
@@ -124,19 +120,19 @@ 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/__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..abf5473 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
@@ -24,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.
@@ -44,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.
@@ -65,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 42e3d9d..cce6ca3 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
@@ -12,22 +8,42 @@
'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
+ def add_inputs(self, inputs: Iterable[MultisigAddress]) -> None:
"""
Adds inputs to spend in the bundle.
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..f8fdb33 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
@@ -21,10 +17,37 @@ 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):
- # 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)
@@ -34,8 +57,23 @@ def __init__(self, trytes, digests, balance=None):
map(attrgetter('security_level'), self.digests)
)
- def as_json_compatible(self):
- # type: () -> dict
+ def as_json_compatible(self) -> dict:
+ """
+ Get a JSON represenation of the :py:class:`MultisigAddress` object.
+
+ :return:
+ ``dict`` with the following structure::
+
+ {
+ 'trytes': str,
+ String representation of the address trytes.
+ 'balance': int,
+ Balance of the address.
+ 'digests': Iterable[Digest]
+ Digests that were used to create the address.
+ }
+
+ """
return {
'trytes': self._trytes.decode('ascii'),
'balance': self.balance,
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 c904c70..b1a3a5a 100644
--- a/iota/transaction/base.py
+++ b/iota/transaction/base.py
@@ -1,10 +1,6 @@
-# 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
+ Optional, Sequence, TypeVar, Type
from iota.codecs import TrytesDecodeError
from iota.crypto import Curl, HASH_LENGTH
@@ -19,6 +15,8 @@
'Transaction',
]
+T = TypeVar('T', bound='Transaction')
+
class Transaction(JsonSerializable):
"""
@@ -80,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.
@@ -150,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())
@@ -159,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.
@@ -213,7 +214,7 @@ def __init__(
:type: :py:class:`TransactionHash`
"""
- self.bundle_hash = bundle_hash
+ self.bundle_hash: Optional[BundleHash] = bundle_hash
"""
The bundle hash, used to identify transactions that are part of the same
bundle.
@@ -224,7 +225,7 @@ def __init__(
:type: :py:class:`BundleHash`
"""
- self.address = address
+ self.address: Address = address
"""
The address associated with this transaction.
@@ -235,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:
@@ -248,7 +249,7 @@ def __init__(
:type: ``int``
"""
- self._legacy_tag = legacy_tag
+ self._legacy_tag: Optional[Tag] = legacy_tag
"""
A short message attached to the transaction.
@@ -258,7 +259,7 @@ def __init__(
:type: :py:class:`Tag`
"""
- self.nonce = nonce
+ self.nonce: Optional[Nonce] = nonce
"""
Unique value used to increase security of the transaction hash.
@@ -267,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.
@@ -280,7 +281,7 @@ def __init__(
:type: ``int``, unix timestamp in seconds.
"""
- self.current_index = current_index
+ self.current_index: Optional[int] = current_index
"""
The position of the transaction inside the bundle.
@@ -294,7 +295,7 @@ def __init__(
:type: ``int``
"""
- self.last_index = last_index
+ self.last_index: Optional[int] = last_index
"""
The index of the final transaction in the bundle.
@@ -304,7 +305,7 @@ def __init__(
:type: ``int``
"""
- self.trunk_transaction_hash = trunk_transaction_hash
+ self.trunk_transaction_hash: Optional[TransactionHash] = trunk_transaction_hash
"""
The transaction hash of the next transaction in the bundle.
@@ -318,7 +319,7 @@ def __init__(
:type: :py:class:`TransactionHash`
"""
- self.branch_transaction_hash = branch_transaction_hash
+ self.branch_transaction_hash: Optional[TransactionHash] = branch_transaction_hash
"""
An unrelated transaction that this transaction "approves".
@@ -333,7 +334,7 @@ def __init__(
:type: :py:class:`TransactionHash`
"""
- self.tag = tag
+ self.tag: Optional[Tag] = tag
"""
Optional classification tag applied to this transaction.
@@ -342,7 +343,7 @@ def __init__(
:type: :py:class:`Tag`
"""
- self.attachment_timestamp = attachment_timestamp
+ self.attachment_timestamp: Optional[int] = attachment_timestamp
"""
Estimated epoch time of the attachment to the tangle.
@@ -351,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: 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 = 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 = signature_message_fragment
+ self.signature_message_fragment: Optional[Fragment] = signature_message_fragment
"""
"Signature/Message Fragment" (note the slash):
@@ -385,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.
@@ -399,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).
@@ -412,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`.
@@ -422,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`.
@@ -432,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`.
@@ -454,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.
@@ -544,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.
@@ -553,28 +544,39 @@ 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_signature_validation_trytes(self):
- # type: () -> TryteString
+ def get_bundle_essence_trytes(self) -> 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.
@@ -589,8 +591,7 @@ def get_signature_validation_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.
@@ -598,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
@@ -622,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.
@@ -649,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.
@@ -672,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.
@@ -706,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.
"""
@@ -717,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.
@@ -735,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.
@@ -744,13 +741,12 @@ def tail_transaction(self):
"""
return self[0]
- def get_messages(self, errors='drop'):
- # type: (Text) -> 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:
@@ -766,7 +762,7 @@ def get_messages(self, errors='drop'):
'ignore'
Omit the invalid tryte/byte sequence.
- :return: ``List[Text]``
+ :return: ``List[str]``
"""
decode_errors = 'strict' if errors == 'drop' else errors
@@ -790,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.
@@ -810,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.
@@ -825,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 302e5f8..dcc69a4 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
@@ -78,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()
@@ -113,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.
@@ -142,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.
@@ -190,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:
@@ -207,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.
@@ -216,38 +208,29 @@ 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
+ 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:
@@ -265,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.
@@ -278,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.
@@ -293,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.
@@ -338,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.
@@ -396,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.
@@ -416,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.
@@ -476,7 +453,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)
@@ -487,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
@@ -501,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.
@@ -572,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.
@@ -600,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.
@@ -631,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 caba973..7e8f468 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
@@ -41,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:
@@ -69,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:
@@ -97,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 b9505f8..1e1cee2 100644
--- a/iota/transaction/utils.py
+++ b/iota/transaction/utils.py
@@ -1,10 +1,5 @@
-# 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
from iota import STANDARD_UNITS
from iota.exceptions import with_context
@@ -15,8 +10,7 @@
]
-def convert_value_to_standard_unit(value, symbol='i'):
- # type: (Text, Text) -> float
+def convert_value_to_standard_unit(value: str, symbol: str = 'i') -> float:
"""
Converts between any two standard units of iota.
@@ -61,8 +55,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 e9308cd..6b64261 100644
--- a/iota/transaction/validator.py
+++ b/iota/transaction/validator.py
@@ -1,8 +1,4 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
-
-from typing import Generator, List, Optional, Text
+from typing import Generator, List, Optional, Type
from iota.crypto.kerl import Kerl
from iota.crypto.signing import validate_signature_fragments
@@ -28,30 +24,27 @@ 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[str]] = []
self._validator = self._create_validator()
@property
- def errors(self):
- # type: () -> 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
return self._errors
- def is_valid(self):
- # type: () -> bool
+ def is_valid(self) -> bool:
"""
Returns whether the bundle is valid.
"""
@@ -65,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[str, None, None]:
"""
Creates a generator that does all the work.
"""
@@ -128,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.
@@ -185,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[str]:
"""
Validates the signature fragments in the bundle.
@@ -210,7 +204,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.
@@ -235,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[str]:
"""
Validates the signature fragments for a group of transactions
using the specified sponge type.
@@ -247,7 +242,7 @@ def _get_group_signature_error(group, sponge_type):
: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/trits.py b/iota/trits.py
index 189af50..6f2fe60 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__ = [
@@ -18,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.
@@ -44,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.
"""
@@ -54,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.
@@ -90,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.
@@ -99,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.
@@ -110,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.
@@ -120,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 abc7089..a4c3cf1 100644
--- a/iota/types.py
+++ b/iota/types.py
@@ -1,18 +1,12 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
from codecs import decode, encode
from itertools import chain
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, Type, TypeVar, Union, Dict
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.
@@ -68,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.
@@ -83,7 +75,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,27 +87,26 @@ 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
+ def from_bytes(cls: Type[T],
+ bytes_: Union[bytes, bytearray],
+ codec: str = AsciiTrytesCodec.name,
+ *args: Any,
+ **kwargs: 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!
- :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
@@ -139,12 +130,14 @@ 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: str,
+ *args: Any,
+ **kwargs: Any) -> T:
"""
Creates a TryteString from a Unicode string.
- :param Text string:
+ :param str string:
Source Unicode string.
:param args:
@@ -170,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.
@@ -186,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.
@@ -240,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.
@@ -285,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.
@@ -344,7 +340,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):
@@ -369,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
- return hash(binary_type(self._trytes))
+ def __hash__(self) -> int:
+ return hash(bytes(self._trytes))
- def __repr__(self):
- # type: () -> Text
+ def __repr__(self) -> str:
return '{cls}({trytes!r})'.format(
cls=type(self).__name__,
- trytes=binary_type(self._trytes),
+ trytes=bytes(self._trytes),
)
- def __bytes__(self):
+ def __bytes__(self) -> bytes:
"""
Converts the TryteString into an ASCII representation.
@@ -396,9 +390,9 @@ 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):
+ def __str__(self) -> str:
"""
Same as :py:meth:`__bytes__`, except this method returns a
unicode string.
@@ -406,40 +400,32 @@ 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
+ def __bool__(self) -> 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
+ def __len__(self) -> int:
return len(self._trytes)
- def __iter__(self):
- # type: () -> Generator[binary_type, None, None]
+ def __iter__(self) -> 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
+ def __contains__(self, other: 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__,
),
),
@@ -449,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]
@@ -462,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):
@@ -484,21 +470,20 @@ 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, 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__,
),
),
@@ -508,21 +493,20 @@ 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, 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__,
),
),
@@ -532,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).
@@ -548,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.
@@ -560,13 +539,14 @@ def iter_chunks(self, chunk_size):
"""
return ChunkIterator(self, chunk_size)
- def encode(self, errors='strict', codec=AsciiTrytesCodec.name):
- # type: (Text, Text) -> binary_type
+ def encode(self,
+ 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'
@@ -578,7 +558,7 @@ def encode(self, errors='strict', codec=AsciiTrytesCodec.name):
'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
@@ -634,13 +614,13 @@ 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: 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:
@@ -697,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) -> str:
"""
Returns a JSON-compatible representation of the object.
@@ -720,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.
@@ -748,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.
@@ -778,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.
@@ -825,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(
@@ -843,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.
@@ -861,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.
@@ -876,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.
@@ -895,10 +868,6 @@ def __next__(self):
return chunk
- if PY2:
- # In Python 2, iterator methods are named a little differently.
- next = __next__
-
class Hash(TryteString):
"""
@@ -916,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:
@@ -963,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(
@@ -994,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.
"""
@@ -1026,7 +992,7 @@ def __init__(
address.
"""
- def as_json_compatible(self):
+ def as_json_compatible(self) -> Dict[str, Union[str, int]]:
"""
Returns a JSON-compatible representation of the Address.
@@ -1034,7 +1000,7 @@ def as_json_compatible(self):
``dict`` with the following structure::
{
- 'trytes': Text,
+ 'trytes': str,
'balance': int,
'key_index': int,
'security_level': int,
@@ -1053,7 +1019,6 @@ def as_json_compatible(self):
print(addy.as_json_compatible())
"""
- # type: () -> dict
return {
'trytes': self._trytes.decode('ascii'),
'balance': self.balance,
@@ -1061,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.
@@ -1093,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.
@@ -1128,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())
@@ -1143,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.
@@ -1183,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.
@@ -1217,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.
@@ -1231,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:
@@ -1264,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:
diff --git a/setup.py b/setup.py
index fca22f6..07d228f 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.
@@ -28,18 +24,17 @@
# either automatically (``python setup.py test``) or manually
# (``pip install -e .[test-runner]``).
tests_require = [
- 'mock; python_version < "3.0"',
+ 'aiounittest',
'nose',
]
##
# Off we go!
-# noinspection SpellCheckingInspection
setuptools.setup(
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,
@@ -57,26 +52,27 @@
],
},
+ # 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"',
- 'phx-filters; python_version >= "3.5"',
+ 'httpx',
+ 'phx-filters',
'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"',
],
extras_require={
'ccurl': ['pyota-ccurl'],
+ 'docs-builder': ['sphinx >= 2.4.2', 'sphinx_rtd_theme >= 0.4.3'],
'pow': ['pyota-pow >= 1.0.2'],
- 'docs-builder': ['sphinx', 'sphinx_rtd_theme'],
# tox is able to run the tests in parallel since version 3.7
'test-runner': ['tox >= 3.7'] + tests_require,
},
@@ -91,10 +87,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',
diff --git a/test/__init__.py b/test/__init__.py
index 0b9065d..ef2f268 100644
--- a/test/__init__.py
+++ b/test/__init__.py
@@ -1,16 +1,5 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
+from unittest import mock
+from unittest.mock import MagicMock, patch
-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
+# 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 f8ec7e5..1b3b138 100644
--- a/test/adapter/wrappers_test.py
+++ b/test/adapter/wrappers_test.py
@@ -1,15 +1,13 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
-
from unittest import TestCase
from iota.adapter import HttpAdapter, MockAdapter
from iota.adapter.wrappers import RoutingWrapper
+from test import async_test
class RoutingWrapperTestCase(TestCase):
- def test_routing(self):
+ @async_test
+ async def test_routing(self):
"""
Routing commands to different adapters.
"""
@@ -27,18 +25,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 7ea8ca4..58977c5 100644
--- a/test/adapter_test.py
+++ b/test/adapter_test.py
@@ -1,18 +1,13 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
-
import json
import socket
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
-
+from iota.adapter import API_VERSION, HttpAdapter, MockAdapter, \
+ resolve_adapter, async_return
+from test import mock, async_test
class ResolveAdapterTestCase(TestCase):
"""
@@ -55,20 +50,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 +124,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 +136,10 @@ 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 +154,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 +173,19 @@ 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 +203,19 @@ 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),
+ str(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 +229,19 @@ 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),
+ str(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 +249,19 @@ 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),
+ str(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 +270,19 @@ 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),
+ str(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,39 +291,49 @@ 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),
+ str(context.exception),
'Malformed 200 response from node: {response!r}'.format(
response = invalid_response,
),
)
- @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)
+ )
+ )
+
+ 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 +341,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 +361,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 +381,13 @@ 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 +395,10 @@ 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'),
diff --git a/test/api_test.py b/test/api_test.py
index 95084f4..ec4ee13 100644
--- a/test/api_test.py
+++ b/test/api_test.py
@@ -1,16 +1,11 @@
-# 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
from iota.commands.core.get_node_info import GetNodeInfoCommand
+from test import async_test
class CustomCommandTestCase(TestCase):
@@ -21,7 +16,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 +25,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 +35,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 +44,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 +54,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 +83,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)
@@ -128,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 985c3ac..0cf64e1 100644
--- a/test/commands/core/add_neighbors_test.py
+++ b/test/commands/core/add_neighbors_test.py
@@ -1,16 +1,12 @@
-# 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 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 +147,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..176f3a3 100644
--- a/test/commands/core/attach_to_tangle_test.py
+++ b/test/commands/core/attach_to_tangle_test.py
@@ -1,24 +1,19 @@
-# 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 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):
filter_type = AttachToTangleCommand(MockAdapter()).get_request_filter
skip_value_check = True
- # noinspection SpellCheckingInspection
def setUp(self):
super(AttachToTangleRequestFilterTestCase, self).setUp()
@@ -38,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)),
],
}
@@ -54,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.
@@ -68,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.
@@ -93,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',
)),
@@ -385,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()
@@ -432,17 +425,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 +443,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..f185529 100644
--- a/test/commands/core/broadcast_transactions_test.py
+++ b/test/commands/core/broadcast_transactions_test.py
@@ -1,25 +1,19 @@
-# 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
-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
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),
],
},
)
@@ -186,17 +180,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 +198,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..9f971f7 100644
--- a/test/commands/core/check_consistency_test.py
+++ b/test/commands/core/check_consistency_test.py
@@ -1,24 +1,19 @@
-# 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 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):
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()
@@ -200,17 +194,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 +213,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 +245,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 +255,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 +266,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 +275,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..f8e106e 100644
--- a/test/commands/core/find_transactions_test.py
+++ b/test/commands/core/find_transactions_test.py
@@ -1,26 +1,21 @@
-# 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
-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):
filter_type = FindTransactionsCommand(MockAdapter()).get_request_filter
skip_value_check = True
- # noinspection SpellCheckingInspection
def setUp(self):
super(FindTransactionsRequestFilterTestCase, self).setUp()
@@ -37,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)),
],
}
@@ -96,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)),
],
},
)
@@ -136,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.
@@ -167,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.
@@ -198,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.
@@ -229,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.
@@ -490,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()
@@ -514,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.
@@ -561,18 +554,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..acbe919 100644
--- a/test/commands/core/get_balances_test.py
+++ b/test/commands/core/get_balances_test.py
@@ -1,24 +1,19 @@
-# 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 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):
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
@@ -351,17 +345,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 +363,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..dd49efc 100644
--- a/test/commands/core/get_inclusion_states_test.py
+++ b/test/commands/core/get_inclusion_states_test.py
@@ -1,24 +1,18 @@
-# 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 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):
filter_type = GetInclusionStatesCommand(MockAdapter()).get_request_filter
skip_value_check = True
- # noinspection SpellCheckingInspection
def setUp(self):
super(GetInclusionStatesRequestFilterTestCase, self).setUp()
@@ -259,17 +253,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 +271,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..68a2faf 100644
--- a/test/commands/core/get_missing_transactions_test.py
+++ b/test/commands/core/get_missing_transactions_test.py
@@ -1,16 +1,12 @@
-# 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 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):
@@ -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.
@@ -109,17 +103,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 +121,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..7554e60 100644
--- a/test/commands/core/get_neighbors_test.py
+++ b/test/commands/core/get_neighbors_test.py
@@ -1,15 +1,11 @@
-# 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 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 +45,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 +63,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..a0a83ee 100644
--- a/test/commands/core/get_node_api_configuration_test.py
+++ b/test/commands/core/get_node_api_configuration_test.py
@@ -1,16 +1,12 @@
-# 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 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 +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_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 +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_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..1b3249a 100644
--- a/test/commands/core/get_node_info_test.py
+++ b/test/commands/core/get_node_info_test.py
@@ -1,15 +1,11 @@
-# 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 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):
@@ -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.
@@ -122,17 +117,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 +135,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..8894baf 100644
--- a/test/commands/core/get_tips_test.py
+++ b/test/commands/core/get_tips_test.py
@@ -1,17 +1,13 @@
-# 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 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):
@@ -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.
@@ -120,17 +115,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,13 +134,34 @@ 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.
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 b515800..ab8c321 100644
--- a/test/commands/core/get_transactions_to_approve_test.py
+++ b/test/commands/core/get_transactions_to_approve_test.py
@@ -1,17 +1,13 @@
-# 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 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):
@@ -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.
@@ -227,17 +222,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 +240,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..7d15dee 100644
--- a/test/commands/core/get_trytes_test.py
+++ b/test/commands/core/get_trytes_test.py
@@ -1,24 +1,19 @@
-# 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 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):
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()
@@ -242,17 +236,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 +254,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..b4b6fa2 100644
--- a/test/commands/core/interrupt_attaching_to_tangle_test.py
+++ b/test/commands/core/interrupt_attaching_to_tangle_test.py
@@ -1,16 +1,12 @@
-# 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 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 +44,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 +62,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..75de850 100644
--- a/test/commands/core/remove_neighbors_test.py
+++ b/test/commands/core/remove_neighbors_test.py
@@ -1,16 +1,12 @@
-# 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 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 +149,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 +167,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..6d98652 100644
--- a/test/commands/core/store_transactions_test.py
+++ b/test/commands/core/store_transactions_test.py
@@ -1,25 +1,19 @@
-# 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
-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):
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)),
],
},
)
@@ -187,17 +181,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 +199,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..b6ae7f8 100644
--- a/test/commands/core/were_addresses_spent_from_test.py
+++ b/test/commands/core/were_addresses_spent_from_test.py
@@ -1,17 +1,13 @@
-# 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 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):
@@ -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()
@@ -169,17 +164,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 +182,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
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 9354784..52626dc 100644
--- a/test/commands/extended/broadcast_and_store_test.py
+++ b/test/commands/extended/broadcast_and_store_test.py
@@ -1,19 +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, 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
def setUp(self):
super(BroadcastAndStoreCommandTestCase, self).setUp()
@@ -27,12 +19,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,14 +39,38 @@ 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``.
"""
self.adapter.seed_response('broadcastTransactions', {
'trytes': [
- text_type(self.trytes1, 'ascii'),
- text_type(self.trytes2, 'ascii'),
+ str(self.trytes1, 'ascii'),
+ str(self.trytes2, 'ascii'),
],
})
@@ -65,6 +81,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})
diff --git a/test/commands/extended/broadcast_bundle_test.py b/test/commands/extended/broadcast_bundle_test.py
index 3d04780..e7a7821 100644
--- a/test/commands/extended/broadcast_bundle_test.py
+++ b/test/commands/extended/broadcast_bundle_test.py
@@ -1,18 +1,14 @@
-# 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 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
@@ -23,7 +19,6 @@ class BroadcastBundleRequestFilterTestCase(BaseFilterTestCase):
def setUp(self):
super(BroadcastBundleRequestFilterTestCase, self).setUp()
- # noinspection SpellCheckingInspection
self.transaction = (
'TESTVALUE9DONTUSEINPRODUCTION99999KPZOTR'
'VDB9GZDJGZSSDCBIX9QOK9PAV9RMDBGDXLDTIZTWQ'
@@ -142,12 +137,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 +156,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 +193,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 +219,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/find_transaction_objects.py b/test/commands/extended/find_transaction_objects_test.py
similarity index 71%
rename from test/commands/extended/find_transaction_objects.py
rename to test/commands/extended/find_transaction_objects_test.py
index e87b380..670742b 100644
--- a/test/commands/extended/find_transaction_objects.py
+++ b/test/commands/extended/find_transaction_objects_test.py
@@ -1,16 +1,12 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
-
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):
- # noinspection SpellCheckingInspection
def setUp(self):
super(FindTransactionObjectsCommandTestCase, self).setUp()
@@ -70,12 +66,34 @@ 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=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!'
+ )
+
+ 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='You found me!')
+ MagicMock(return_value=async_return('You found me!'))
) as mocked_command:
api = Iota(self.adapter)
@@ -90,7 +108,31 @@ 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.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 +140,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,
diff --git a/test/commands/extended/get_account_data_test.py b/test/commands/extended/get_account_data_test.py
index 1a0e524..82af9c4 100644
--- a/test/commands/extended/get_account_data_test.py
+++ b/test/commands/extended/get_account_data_test.py
@@ -1,28 +1,21 @@
-# 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, 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):
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,
@@ -322,9 +315,20 @@ 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
def setUp(self):
super(GetAccountDataCommandTestCase, self).setUp()
@@ -362,12 +366,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 +386,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.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 +426,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 +445,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 +457,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 +478,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/get_bundles_test.py b/test/commands/extended/get_bundles_test.py
index 4c91e85..769d106 100644
--- a/test/commands/extended/get_bundles_test.py
+++ b/test/commands/extended/get_bundles_test.py
@@ -1,18 +1,14 @@
-# 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 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):
@@ -22,7 +18,6 @@ class GetBundlesRequestFilterTestCase(BaseFilterTestCase):
def setUp(self):
super(GetBundlesRequestFilterTestCase, self).setUp()
- # noinspection SpellCheckingInspection
self.transactions = [
(
'TESTVALUE9DONTUSEINPRODUCTION99999KPZOTR'
@@ -330,12 +325,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 +345,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 +382,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 +391,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 +407,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 +422,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 +441,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/get_inputs_test.py b/test/commands/extended/get_inputs_test.py
index 439a687..c05a711 100644
--- a/test/commands/extended/get_inputs_test.py
+++ b/test/commands/extended/get_inputs_test.py
@@ -1,27 +1,22 @@
-# 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 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):
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()
@@ -441,12 +435,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 +455,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 +496,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 +519,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 +538,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 +563,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 +580,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 +598,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,14 +621,14 @@ 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``.
"""
# ``getInputs`` uses ``findTransactions`` and
# ``wereAddressesSpentFrom`` to identify unused addresses.
- # noinspection SpellCheckingInspection
self.adapter.seed_response('findTransactions', {
'hashes': [
TransactionHash(
@@ -617,7 +638,6 @@ def test_no_stop_threshold_met(self):
],
})
- # noinspection SpellCheckingInspection
self.adapter.seed_response('findTransactions', {
'hashes': [
TransactionHash(
@@ -642,7 +662,6 @@ 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
@@ -652,7 +671,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 +693,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``.
"""
@@ -685,7 +705,6 @@ 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
@@ -696,12 +715,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.
"""
@@ -709,7 +729,6 @@ 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(
@@ -719,7 +738,6 @@ def test_no_stop_threshold_zero(self):
],
})
- # noinspection SpellCheckingInspection
self.adapter.seed_response('findTransactions', {
'hashes': [
TransactionHash(
@@ -745,7 +763,6 @@ 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
@@ -755,7 +772,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,14 +788,14 @@ 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``.
"""
# ``getInputs`` uses ``findTransactions`` and
# ``wereAddressesSpentFrom`` to identify unused addresses.
- # noinspection SpellCheckingInspection
self.adapter.seed_response('findTransactions', {
'hashes': [
TransactionHash(
@@ -788,7 +805,6 @@ def test_no_stop_no_threshold(self):
],
})
- # noinspection SpellCheckingInspection
self.adapter.seed_response('findTransactions', {
'hashes': [
TransactionHash(
@@ -813,7 +829,6 @@ 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
@@ -823,7 +838,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,14 +859,14 @@ 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.
"""
# ``getInputs`` uses ``findTransactions`` and
# ``wereAddressesSpentFrom`` to identify unused addresses.
- # noinspection SpellCheckingInspection
self.adapter.seed_response('findTransactions', {
'hashes': [
TransactionHash(
@@ -876,7 +891,6 @@ 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.
@@ -888,7 +902,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 +917,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
@@ -912,7 +927,6 @@ 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
@@ -927,7 +941,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 +963,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)
@@ -963,7 +978,6 @@ 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(
@@ -984,7 +998,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 +1012,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)
@@ -1015,7 +1030,6 @@ def test_security_level_1_with_stop(self):
})
# ``getInputs`` uses ``findTransactions`` to identify unused
# addresses.
- # noinspection SpellCheckingInspection
self.adapter.seed_response('findTransactions', {
'hashes': [
TransactionHash(
@@ -1028,7 +1042,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
diff --git a/test/commands/extended/get_latest_inclusion_test.py b/test/commands/extended/get_latest_inclusion_test.py
index 4680693..130fd40 100644
--- a/test/commands/extended/get_latest_inclusion_test.py
+++ b/test/commands/extended/get_latest_inclusion_test.py
@@ -1,25 +1,20 @@
-# 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 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):
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()
@@ -204,12 +198,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 +218,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_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 +257,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,
diff --git a/test/commands/extended/get_new_addresses_test.py b/test/commands/extended/get_new_addresses_test.py
index bf14b0a..9969774 100644
--- a/test/commands/extended/get_new_addresses_test.py
+++ b/test/commands/extended/get_new_addresses_test.py
@@ -1,26 +1,21 @@
-# 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 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):
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()
@@ -365,12 +359,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 +379,37 @@ def test_wireup(self):
'You found me!'
)
- def test_get_addresses_offline(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_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,19 +423,19 @@ 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,
seed = self.seed,
)
- # noinspection SpellCheckingInspection
self.assertDictEqual(
response,
@@ -437,7 +455,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 +468,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 +483,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 +496,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 +523,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.
@@ -505,7 +534,6 @@ 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': [
@@ -521,13 +549,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 +577,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,
diff --git a/test/commands/extended/get_transaction_objects_test.py b/test/commands/extended/get_transaction_objects_test.py
index 552889a..4879f5b 100644
--- a/test/commands/extended/get_transaction_objects_test.py
+++ b/test/commands/extended/get_transaction_objects_test.py
@@ -1,16 +1,12 @@
-# coding=utf-8
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
-
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):
- # noinspection SpellCheckingInspection
def setUp(self):
super(GetTransactionObjectsCommandTestCase, self).setUp()
@@ -69,12 +65,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 +85,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,
diff --git a/test/commands/extended/get_transfers_test.py b/test/commands/extended/get_transfers_test.py
index 7373daf..4b1cd48 100644
--- a/test/commands/extended/get_transfers_test.py
+++ b/test/commands/extended/get_transfers_test.py
@@ -1,28 +1,21 @@
-# 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, 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):
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()
@@ -339,12 +331,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 +350,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.
"""
@@ -366,7 +383,6 @@ 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
@@ -437,9 +453,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 +465,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 +475,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.
"""
@@ -467,7 +484,6 @@ 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
@@ -491,15 +507,15 @@ 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.
"""
- # noinspection PyUnusedLocal
def create_generator(ag, start, step=1):
# Inject an invalid value into the generator, to ensure it is
# skipped.
@@ -568,9 +584,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 +596,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,11 +606,11 @@ 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.
"""
- # noinspection PyUnusedLocal
def create_generator(ag, start, step=1):
# Inject an invalid value into the generator, to ensure it is
# skipped.
@@ -646,9 +662,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 +674,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,11 +684,11 @@ 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.
"""
- # noinspection PyUnusedLocal
def create_generator(ag, start, step=1):
for addy in [self.addy1][start::step]:
yield addy
@@ -742,21 +758,21 @@ def create_generator(ag, start, step=1):
{
'duration': 99,
- 'trytes': [binary_type(transaction_trytes)],
+ 'trytes': [bytes(transaction_trytes)],
},
)
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 +786,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,
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))
diff --git a/test/commands/extended/is_promotable_test.py b/test/commands/extended/is_promotable_test.py
index cc1506a..3d81964 100644
--- a/test/commands/extended/is_promotable_test.py
+++ b/test/commands/extended/is_promotable_test.py
@@ -1,26 +1,21 @@
-# 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 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
skip_value_check = True
- # noinspection SpellCheckingInspection
def setUp(self):
super(IsPromotableRequestFilterTestCase, self).setUp()
@@ -290,12 +285,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 +305,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 +343,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 +353,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 +367,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 +378,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 +399,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,
diff --git a/test/commands/extended/is_reattachable_test.py b/test/commands/extended/is_reattachable_test.py
index 2800b06..965d597 100644
--- a/test/commands/extended/is_reattachable_test.py
+++ b/test/commands/extended/is_reattachable_test.py
@@ -1,24 +1,17 @@
-# 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
-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):
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()
@@ -199,12 +190,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 +209,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
diff --git a/test/commands/extended/prepare_transfer_test.py b/test/commands/extended/prepare_transfer_test.py
index 0a6c46b..3ce6bea 100644
--- a/test/commands/extended/prepare_transfer_test.py
+++ b/test/commands/extended/prepare_transfer_test.py
@@ -1,29 +1,23 @@
-# 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
-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):
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:
@@ -576,12 +569,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 +589,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'
@@ -632,7 +649,7 @@ 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.
@@ -647,7 +664,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 +678,7 @@ def test_pass_inputs_explicit_no_change(self):
'FIVFBBYQHFYZYIEEWZL9VPMMKIIYTEZRRHXJXKIKF',
})
- response = self.command(
+ response = await self.command(
seed =
Seed(
'TESTVALUEONE9DONTUSEINPRODUCTION99999C9V'
@@ -706,7 +724,7 @@ 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.
@@ -741,7 +759,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 +773,7 @@ def test_pass_inputs_explicit_with_change(self):
'FIVFBBYQHFYZYIEEWZL9VPMMKIIYTEZRRHXJXKIKF',
})
- response = self.command(
+ response = await self.command(
seed =
Seed(
'TESTVALUEONE9DONTUSEINPRODUCTION99999C9V'
@@ -791,7 +810,7 @@ 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.
@@ -820,7 +839,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 +854,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 +882,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 +896,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 +920,7 @@ def test_pass_inputs_implicit_no_change(self):
],
'totalBalance': 42,
- },
+ }),
)
with mock.patch(
@@ -907,7 +928,7 @@ def test_pass_inputs_implicit_no_change(self):
mock_get_inputs,
):
response =\
- self.command(
+ await self.command(
seed =
Seed(
'TESTVALUEONE9DONTUSEINPRODUCTION99999C9V'
@@ -927,7 +948,7 @@ 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.
@@ -962,7 +983,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 +997,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 +1011,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'
@@ -1022,7 +1044,7 @@ 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.
@@ -1051,7 +1073,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 +1091,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 +1108,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 +1121,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 +1132,7 @@ def test_pass_change_address_auto_generated(self):
security_level = 2,
),
],
- },
+ }),
)
self.adapter.seed_response('getBalances', {
@@ -1125,7 +1149,7 @@ def test_pass_change_address_auto_generated(self):
mock_get_new_addresses_command,
):
response = \
- self.command(
+ await self.command(
seed =
Seed(
b'TESTVALUEONE9DONTUSEINPRODUCTION99999C9V'
@@ -1156,7 +1180,7 @@ 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.
@@ -1185,11 +1209,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'
@@ -1211,7 +1236,7 @@ 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(
@@ -1219,12 +1244,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'
@@ -1270,7 +1296,7 @@ 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.
@@ -1289,7 +1315,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 +1343,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 +1364,7 @@ def mock_get_balances_execute(adapter, request):
mock_get_balances_execute,
):
response = \
- self.command(
+ await self.command(
seed=seed,
transfers=[
ProposedTransaction(
@@ -1355,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
@@ -1366,7 +1393,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 +1418,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 +1457,7 @@ def mock_get_balances_execute(adapter, request):
mock_get_balances_execute,
):
response = \
- self.command(
+ await self.command(
seed=seed,
transfers=[
ProposedTransaction(
@@ -1444,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 e732ae4..7b46509 100644
--- a/test/commands/extended/promote_transaction_test.py
+++ b/test/commands/extended/promote_transaction_test.py
@@ -1,26 +1,20 @@
-# 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
-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):
filter_type = PromoteTransactionCommand(MockAdapter()).get_request_filter
skip_value_check = True
- # noinspection SpellCheckingInspection
def setUp(self):
super(PromoteTransactionRequestFilterTestCase, self).setUp()
@@ -51,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,
@@ -312,12 +306,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 +326,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 +363,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,17 +386,19 @@ def test_happy_path(self):
}
)
- def test_not_promotable(self):
+ @async_test
+ async def test_not_promotable(self):
"""
Bundle isn't promotable.
"""
self.adapter.seed_response('checkConsistency', {
'state': False,
+ 'info': 'Something went terribly wrong.',
})
with self.assertRaises(BadApiResponse):
- response = self.command(
+ response = await self.command(
transaction=self.hash1,
depth=3,
minWeightMagnitude=16,
diff --git a/test/commands/extended/replay_bundle_test.py b/test/commands/extended/replay_bundle_test.py
index 858e038..43b1d05 100644
--- a/test/commands/extended/replay_bundle_test.py
+++ b/test/commands/extended/replay_bundle_test.py
@@ -1,27 +1,20 @@
-# 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
-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):
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,
@@ -304,12 +297,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,11 +317,34 @@ 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.
"""
- # noinspection SpellCheckingInspection
bundle = Bundle([
# "Spend" transaction, Part 1 of 1
Transaction(
@@ -602,9 +618,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 +635,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 +645,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,
diff --git a/test/commands/extended/send_transfer_test.py b/test/commands/extended/send_transfer_test.py
index 8a277c0..7b6b570 100644
--- a/test/commands/extended/send_transfer_test.py
+++ b/test/commands/extended/send_transfer_test.py
@@ -1,29 +1,22 @@
-# 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
-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):
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),
],
@@ -672,12 +665,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,11 +685,34 @@ 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.
"""
- # noinspection SpellCheckingInspection
transaction1 =\
TransactionTrytes(
b'GYPRVHBEZOOFXSHQBLCYW9ICTCISLHDBNMMVYD9JJHQMPQCTIQAQTJNNNJ9IDXLRCC'
@@ -743,14 +759,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 +776,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(),
diff --git a/test/commands/extended/send_trytes_test.py b/test/commands/extended/send_trytes_test.py
index 7030180..e3129fe 100644
--- a/test/commands/extended/send_trytes_test.py
+++ b/test/commands/extended/send_trytes_test.py
@@ -1,25 +1,19 @@
-# 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
-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):
filter_type = SendTrytesCommand(MockAdapter()).get_request_filter
skip_value_check = True
- # noinspection SpellCheckingInspection
def setUp(self):
super(SendTrytesRequestFilterTestCase, self).setUp()
@@ -58,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,
@@ -347,7 +341,6 @@ def test_fail_trytes_contents_invalid(self):
class SendTrytesCommandTestCase(TestCase):
- # noinspection SpellCheckingInspection
def setUp(self):
super(SendTrytesCommandTestCase, self).setUp()
@@ -376,7 +369,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,19 +384,43 @@ 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``.
"""
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'),
],
})
@@ -415,7 +432,7 @@ def test_happy_path(self):
TransactionTrytes(self.trytes2),
]
- response = self.command(
+ response = await self.command(
trytes = trytes,
depth = 100,
minWeightMagnitude = 18,
diff --git a/test/commands/extended/traverse_bundle_test.py b/test/commands/extended/traverse_bundle_test.py
index b869b36..2b77f6a 100644
--- a/test/commands/extended/traverse_bundle_test.py
+++ b/test/commands/extended/traverse_bundle_test.py
@@ -1,18 +1,14 @@
-# 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 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):
@@ -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()
@@ -129,12 +123,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 +143,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 +222,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 +233,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 +382,7 @@ def test_multiple_transactions(self):
],
})
- response = self.command(
+ response = await self.command(
transaction =
TransactionHash(
b'TOYJPHKMLQNDVLDHDILARUJCCIUMQBLUSWPCTIVA'
@@ -376,7 +395,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 +449,7 @@ def test_non_tail_transaction(self):
})
with self.assertRaises(BadApiResponse):
- self.command(
+ await self.command(
transaction =
TransactionHash(
b'FSEWUNJOEGNUI9QOCRFMYSIFAZLJHKZBPQZZYFG9'
@@ -437,14 +457,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 +473,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 +483,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'
diff --git a/test/commands/extended/utils_test.py b/test/commands/extended/utils_test.py
new file mode 100644
index 0000000..23c87c4
--- /dev/null
+++ b/test/commands/extended/utils_test.py
@@ -0,0 +1,799 @@
+from unittest import TestCase
+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, MagicMock
+from iota import TransactionTrytes, TransactionHash, Bundle, BadApiResponse
+
+
+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],
+ })
+
+ 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)]
+
+ @async_test
+ async def test_first_address_is_not_used(self):
+ """
+ The very first 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([], await self.get_all_used_addresses())
+
+ self.assertListEqual(
+ self.adapter.requests,
+ [
+ {
+ 'command': 'findTransactions',
+ 'addresses': [self.address0],
+ },
+ {
+ 'command': 'wereAddressesSpentFrom',
+ 'addresses': [self.address0],
+ },
+ ]
+ )
+
+ @async_test
+ async 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], await 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],
+ },
+ ]
+ )
+
+ @async_test
+ async 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], await 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],
+ },
+ ]
+ )
+
+ @async_test
+ async 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],
+ await 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],
+ },
+ ]
+ )
+
+ @async_test
+ async 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],
+ await 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],
+ },
+ ]
+ )
+
+
+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
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 be7f3ad..76d8127 100644
--- a/test/multisig/commands/create_multisig_address_test.py
+++ b/test/multisig/commands/create_multisig_address_test.py
@@ -1,25 +1,17 @@
-# 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
+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):
- # noinspection SpellCheckingInspection
def setUp(self):
super(CreateMultisigAddressCommandTestCase, self).setUp()
@@ -49,12 +41,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,13 +61,36 @@ 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(
result,
@@ -96,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()
@@ -142,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 daa88c2..b75c2ad 100644
--- a/test/multisig/commands/get_digests_test.py
+++ b/test/multisig/commands/get_digests_test.py
@@ -1,26 +1,19 @@
-# 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
+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):
- # noinspection SpellCheckingInspection
def setUp(self):
super(GetDigestsCommandTestCase, self).setUp()
@@ -36,12 +29,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,23 +49,46 @@ 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',
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
- 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,28 +99,27 @@ 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',
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
- 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]})
@@ -124,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):
@@ -170,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 a6dc7d5..3f84b77 100644
--- a/test/multisig/commands/get_private_keys_test.py
+++ b/test/multisig/commands/get_private_keys_test.py
@@ -1,26 +1,18 @@
-# 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
+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):
- # noinspection SpellCheckingInspection
def setUp(self):
super(GetPrivateKeysCommandTestCase, self).setUp()
@@ -40,12 +32,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 +52,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 +84,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 +93,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 +103,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,
@@ -154,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 bbf4ac6..6fc4484 100644
--- a/test/multisig/commands/prepare_multisig_transfer_test.py
+++ b/test/multisig/commands/prepare_multisig_transfer_test.py
@@ -1,20 +1,16 @@
-# 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 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):
@@ -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()
@@ -527,12 +521,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 +541,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 +581,7 @@ def test_happy_path(self):
)
pmt_result =\
- self.command(
+ await self.command(
transfers = [
ProposedTransaction(
address = Address(self.trytes_1),
@@ -624,7 +642,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 +658,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 +713,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 +725,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 +740,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 +756,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 +771,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 +795,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),
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):
"""
diff --git a/tox.ini b/tox.ini
index 3220f26..821cac8 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 =
+ aiounittest
mock
nose