├── .coveragerc ├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── README.rst ├── docs ├── .gitignore ├── Makefile ├── authors.rst ├── conf.py ├── contributing.rst ├── index.rst ├── installation.rst ├── make.bat ├── readme.rst └── usage.rst ├── neorpc ├── Client.py ├── Settings.py └── __init__.py ├── requirements_dev.txt ├── setup.cfg ├── setup.py └── tests ├── __init__.py └── test_client.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = neorpc 3 | 4 | [report] 5 | omit = 6 | venv* 7 | tests -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | __pycache__ 4 | *.pyc 5 | neo_python_rpc.egg-info 6 | .eggs 7 | 8 | venv 9 | build 10 | dist 11 | htmlcov 12 | .tox 13 | .coverage -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Config file for automatic testing at travis-ci.org 2 | # This file will be regenerated if you run travis_pypi_setup.py 3 | 4 | language: python 5 | python: 6 | - 3.6 7 | - 3.5 8 | - 3.4 9 | 10 | # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors 11 | install: 12 | - pip install -r requirements_dev.txt 13 | - pip install coveralls 14 | 15 | # command to run tests, e.g. python setup.py test 16 | script: 17 | - coverage run setup.py test 18 | - pycodestyle neorpc 19 | 20 | 21 | after_success: 22 | coveralls 23 | 24 | 25 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Thomas Saunders 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every 8 | little bit helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/CityOfZion/neo-python-rpc/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" 30 | and "help wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | neo-python-rpc could always use more documentation, whether as part of the 42 | official neo-python-rpc docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/CityOfZion/neo-python-rpc/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `neo-python-rpc` for local development. 61 | 62 | 1. Fork the `neo-python-rpc` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/neo-python-rpc.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 68 | 69 | $ mkvirtualenv neo-python-rpc 70 | $ cd neo-python-rpc/ 71 | $ python setup.py develop 72 | 73 | 4. Create a branch for local development:: 74 | 75 | $ git checkout -b name-of-your-bugfix-or-feature 76 | 77 | Now you can make your changes locally. 78 | 79 | 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: 80 | 81 | $ flake8 neo-python-rpc tests 82 | $ python setup.py test or py.test 83 | $ tox 84 | 85 | To get flake8 and tox, just pip install them into your virtualenv. 86 | 87 | 6. Commit your changes and push your branch to GitHub:: 88 | 89 | $ git add . 90 | $ git commit -m "Your detailed description of your changes." 91 | $ git push origin name-of-your-bugfix-or-feature 92 | 93 | 7. Submit a pull request through the GitHub website. 94 | 95 | Pull Request Guidelines 96 | ----------------------- 97 | 98 | Before you submit a pull request, check that it meets these guidelines: 99 | 100 | 1. The pull request should include tests. 101 | 2. If the pull request adds functionality, the docs should be updated. Put 102 | your new functionality into a function with a docstring, and add the 103 | feature to the list in README.rst. 104 | 3. The pull request should work for Python 2.6, 2.7, 3.3, 3.4 and 3.5, and for PyPy. Check 105 | https://travis-ci.org/CityOfZion/neo-python-rpc/pull_requests 106 | and make sure that the tests pass for all supported Python versions. 107 | 108 | Tips 109 | ---- 110 | 111 | To run a subset of tests:: 112 | 113 | 114 | $ python -m unittest tests.test_client.py 115 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Thomas Saunders 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | 7 | recursive-include tests * 8 | recursive-exclude * __pycache__ 9 | recursive-exclude * *.py[co] 10 | 11 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | define BROWSER_PYSCRIPT 4 | import os, webbrowser, sys 5 | try: 6 | from urllib import pathname2url 7 | except: 8 | from urllib.request import pathname2url 9 | 10 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 11 | endef 12 | export BROWSER_PYSCRIPT 13 | 14 | define PRINT_HELP_PYSCRIPT 15 | import re, sys 16 | 17 | for line in sys.stdin: 18 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 19 | if match: 20 | target, help = match.groups() 21 | print("%-20s %s" % (target, help)) 22 | endef 23 | export PRINT_HELP_PYSCRIPT 24 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 25 | 26 | help: 27 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 28 | 29 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 30 | 31 | 32 | clean-build: ## remove build artifacts 33 | rm -fr build/ 34 | rm -fr dist/ 35 | rm -fr .eggs/ 36 | find . -name '*.egg-info' -exec rm -fr {} + 37 | find . -name '*.egg' -exec rm -f {} + 38 | 39 | clean-pyc: ## remove Python file artifacts 40 | find . -name '*.pyc' -exec rm -f {} + 41 | find . -name '*.pyo' -exec rm -f {} + 42 | find . -name '*~' -exec rm -f {} + 43 | find . -name '__pycache__' -exec rm -fr {} + 44 | 45 | clean-test: ## remove test and coverage artifacts 46 | rm -fr .tox/ 47 | rm -f .coverage 48 | rm -fr htmlcov/ 49 | 50 | lint: ## check style with flake8 51 | flake8 neorpc tests 52 | 53 | test: ## run tests quickly with the default Python 54 | 55 | python setup.py test 56 | 57 | test-all: ## run tests on every Python version with tox 58 | tox 59 | 60 | coverage: ## check code coverage quickly with the default Python 61 | coverage run --source neorpc setup.py test 62 | coverage report -m 63 | coverage html 64 | $(BROWSER) htmlcov/index.html 65 | 66 | docs: ## generate Sphinx HTML documentation, including API docs 67 | rm -f docs/neo-python-rpc.rst 68 | rm -f docs/modules.rst 69 | sphinx-apidoc -o docs/ neo-python-rpc 70 | $(MAKE) -C docs clean 71 | $(MAKE) -C docs html 72 | $(BROWSER) docs/_build/html/index.html 73 | 74 | servedocs: docs ## compile the docs watching for changes 75 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 76 | 77 | release: clean ## package and upload a release 78 | python setup.py sdist upload 79 | python setup.py bdist_wheel upload 80 | 81 | dist: clean ## builds source and wheel package 82 | python setup.py sdist 83 | python setup.py bdist_wheel 84 | ls -l dist 85 | 86 | install: clean ## install the package to the active Python's site-packages 87 | python setup.py install 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 5 |

6 | 7 |

neo-python-rpc

8 | 9 |

A lightweight Python RPC Client for the NEO Blockchain

10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 24 | 25 | 26 | ## Install 27 | 28 | `pip install neo-python-rpc` 29 | 30 | 31 | 32 | ### Basic Usage 33 | 34 | - Get Height of blockchain 35 | ``` 36 | >>> from neorpc.Client import RPCClient 37 | >>> client = RPCClient() 38 | >>> blockchain_height = client.get_height() 39 | >>> blockchain_height 40 | 769332 41 | ``` 42 | 43 | - Get a block 44 | ``` 45 | >>> block = client.get_block(123456) 46 | >>> block 47 | { 48 | "nextconsensus": "AdyQbbn6ENjqWDa5JNYMwN3ikNcA4JeZdk", 49 | "size": 686, 50 | "previousblockhash": "0xdbee7804eb1f648e640401a864ed29bbc787d7f9d36b6f4be0a3ffd693723d5e", 51 | "script": { 52 | "invocation": "4012bbee0bac21b6ec5c4b0cda3aa28f3aa51e3b4a6c230908370adc8758fc3b051a865539bdca0cc6b450ff4fa18d3295f5c5a05a862b8aa9baff3e69011cb0f640ca5e3e5b3399a2014aa96575 53 | e2b0c3d1239d41ac05a92ec430ab0e0388f5e76e30062dd9aecca3007993e8bde7a00b70cdbef2fbb641164b9fba5f6ed724dc2e40458bcdf880cfef8d94aef714274ff4c258a13f541d2f8f678520bb5414704662cc5b9e714 54 | f2e42c94c2a61f8e0b3fb2ec8cc6e9ef2e0670056082370e9331e0f40f0a005fbb338e69465c6e0760ac04c2d61bdbec9922f10b8936d277609dd0f6cab1d7ee944902e0af2ba4ac6183a30158c1aa96c77262070161d24e1c5 55 | 530842403cc25ba0ce9ca460d3bf6108a48c676f131076c1542173278cace43a69b7038d4f2f5b401e8bdea2e10c9c68598d9f530b6618b3165a7c791442cd6d093b6aa2", 56 | "verification": "55210209e7fd41dfb5c2f8dc72eb30358ac100ea8c72da18847befe06eade68cebfcb9210327da12b5c40200e9f65569476bbff2218da4f32548ff43b6387ec1416a231ee821034ff5ceeac41a 57 | cf22cd5ed2da17a6df4dd8358fcb2bfb1a43208ad0feaab2746b21026ce35b29147ad09e4afe4ec4a7319095f08198fa8babbe3c56e970b143528d2221038dddc06ce687677a53d54f096d2591ba2302068cf123c1f2d75c2dd 58 | dc542557921039dafd8571a641058ccc832c5e2111ea39b09c0bde36050914384f7a48bce9bf92102d02b1873a0863cd042cc717da31cea0d7cf9db32b74d4c72c01b0011503e2e2257ae" 59 | }, 60 | "tx": [ 61 | { 62 | "vin": [], 63 | "size": 10, 64 | "type": "MinerTransaction", 65 | "txid": "0x0981c5e57997af0ce18d032bc9969220308dce8ec643844398649b6732ad91fe", 66 | "attributes": [], 67 | "net_fee": "0", 68 | "vout": [], 69 | "nonce": 1808064137, 70 | "version": 0, 71 | "scripts": [], 72 | "sys_fee": "0" 73 | } 74 | ], 75 | "nextblockhash": "0xed2cf18a0b15bb3b6a244abd2de1227afde61d1b0a9cbc582e587c18b3c3a00e", 76 | "confirmations": 647426, 77 | "merkleroot": "0x0981c5e57997af0ce18d032bc9969220308dce8ec643844398649b6732ad91fe", 78 | "hash": "0x10d1473c9fa133b28ea4f470e58b97bfe1222e102b14fbc50d3ade59eb8e3b74", 79 | "version": 0, 80 | "time": 1496944720, 81 | "nonce": "c8f9672b6bc4de89", 82 | "index": 123456 83 | } 84 | ``` 85 | 86 | 87 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | neo-python-rpc 3 | ============== 4 | 5 | 6 | .. image:: https://img.shields.io/pypi/v/neo-python-rpc.svg 7 | :target: https://pypi.python.org/pypi/neo-python-rpc 8 | 9 | .. image:: https://img.shields.io/travis/CityOfZion/neo-python-rpc.svg 10 | :target: https://travis-ci.org/CityOfZion/neo-python-rpc 11 | 12 | .. image:: https://readthedocs.org/projects/neo-python-rpc/badge/?version=latest 13 | :target: https://neo-python-rpc.readthedocs.io/en/latest/?badge=latest 14 | :alt: Documentation Status 15 | 16 | .. image:: https://coveralls.io/repos/github/CityOfZion/neo-python-rpc/badge.svg?branch=master 17 | :target: https://coveralls.io/github/CityOfZion/neo-python-rpc?branch=master 18 | :alt: Coverage Status 19 | 20 | A Python RPC Client for the NEO Blockchain 21 | 22 | 23 | * Free software: MIT license 24 | * Documentation: https://neo-python-rpc.readthedocs.io. 25 | 26 | 27 | Features 28 | -------- 29 | 30 | A lightweight RPC Client for the NEO Blockchain 31 | 32 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | /neo-python-rpc.rst 2 | /neo-python-rpc.*.rst 3 | /modules.rst 4 | _build -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/neo-python-rpc.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/neo-python-rpc.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/neo-python-rpc" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/neo-python-rpc" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # neo-python-rpc documentation build configuration file, created by 5 | # sphinx-quickstart on Tue Jul 9 22:26:36 2013. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | 19 | # If extensions (or modules to document with autodoc) are in another 20 | # directory, add these directories to sys.path here. If the directory is 21 | # relative to the documentation root, use os.path.abspath to make it 22 | # absolute, like shown here. 23 | #sys.path.insert(0, os.path.abspath('.')) 24 | 25 | # Get the project root dir, which is the parent dir of this 26 | cwd = os.getcwd() 27 | project_root = os.path.dirname(cwd) 28 | 29 | # Insert the project root dir as the first element in the PYTHONPATH. 30 | # This lets us ensure that the source package is imported, and that its 31 | # version is used. 32 | sys.path.insert(0, project_root) 33 | 34 | import neorpc 35 | 36 | # -- General configuration --------------------------------------------- 37 | 38 | # If your documentation needs a minimal Sphinx version, state it here. 39 | #needs_sphinx = '1.0' 40 | 41 | # Add any Sphinx extension module names here, as strings. They can be 42 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 43 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 44 | 45 | # Add any paths that contain templates here, relative to this directory. 46 | templates_path = ['_templates'] 47 | 48 | # The suffix of source filenames. 49 | source_suffix = '.rst' 50 | 51 | # The encoding of source files. 52 | #source_encoding = 'utf-8-sig' 53 | 54 | # The master toctree document. 55 | master_doc = 'index' 56 | 57 | # General information about the project. 58 | project = u'neo-python-rpc' 59 | copyright = u"2017, Thomas Saunders" 60 | 61 | # The version info for the project you're documenting, acts as replacement 62 | # for |version| and |release|, also used in various other places throughout 63 | # the built documents. 64 | # 65 | # The short X.Y version. 66 | version = neorpc.__version__ 67 | # The full version, including alpha/beta/rc tags. 68 | release = neorpc.__version__ 69 | 70 | # The language for content autogenerated by Sphinx. Refer to documentation 71 | # for a list of supported languages. 72 | #language = None 73 | 74 | # There are two options for replacing |today|: either, you set today to 75 | # some non-false value, then it is used: 76 | #today = '' 77 | # Else, today_fmt is used as the format for a strftime call. 78 | #today_fmt = '%B %d, %Y' 79 | 80 | # List of patterns, relative to source directory, that match files and 81 | # directories to ignore when looking for source files. 82 | exclude_patterns = ['_build'] 83 | 84 | # The reST default role (used for this markup: `text`) to use for all 85 | # documents. 86 | #default_role = None 87 | 88 | # If true, '()' will be appended to :func: etc. cross-reference text. 89 | #add_function_parentheses = True 90 | 91 | # If true, the current module name will be prepended to all description 92 | # unit titles (such as .. function::). 93 | #add_module_names = True 94 | 95 | # If true, sectionauthor and moduleauthor directives will be shown in the 96 | # output. They are ignored by default. 97 | #show_authors = False 98 | 99 | # The name of the Pygments (syntax highlighting) style to use. 100 | pygments_style = 'sphinx' 101 | 102 | # A list of ignored prefixes for module index sorting. 103 | #modindex_common_prefix = [] 104 | 105 | # If true, keep warnings as "system message" paragraphs in the built 106 | # documents. 107 | #keep_warnings = False 108 | 109 | 110 | # -- Options for HTML output ------------------------------------------- 111 | 112 | # The theme to use for HTML and HTML Help pages. See the documentation for 113 | # a list of builtin themes. 114 | html_theme = 'default' 115 | 116 | # Theme options are theme-specific and customize the look and feel of a 117 | # theme further. For a list of options available for each theme, see the 118 | # documentation. 119 | #html_theme_options = {} 120 | 121 | # Add any paths that contain custom themes here, relative to this directory. 122 | #html_theme_path = [] 123 | 124 | # The name for this set of Sphinx documents. If None, it defaults to 125 | # " v documentation". 126 | #html_title = None 127 | 128 | # A shorter title for the navigation bar. Default is the same as 129 | # html_title. 130 | #html_short_title = None 131 | 132 | # The name of an image file (relative to this directory) to place at the 133 | # top of the sidebar. 134 | #html_logo = None 135 | 136 | # The name of an image file (within the static path) to use as favicon 137 | # of the docs. This file should be a Windows icon file (.ico) being 138 | # 16x16 or 32x32 pixels large. 139 | #html_favicon = None 140 | 141 | # Add any paths that contain custom static files (such as style sheets) 142 | # here, relative to this directory. They are copied after the builtin 143 | # static files, so a file named "default.css" will overwrite the builtin 144 | # "default.css". 145 | html_static_path = ['_static'] 146 | 147 | # If not '', a 'Last updated on:' timestamp is inserted at every page 148 | # bottom, using the given strftime format. 149 | #html_last_updated_fmt = '%b %d, %Y' 150 | 151 | # If true, SmartyPants will be used to convert quotes and dashes to 152 | # typographically correct entities. 153 | #html_use_smartypants = True 154 | 155 | # Custom sidebar templates, maps document names to template names. 156 | #html_sidebars = {} 157 | 158 | # Additional templates that should be rendered to pages, maps page names 159 | # to template names. 160 | #html_additional_pages = {} 161 | 162 | # If false, no module index is generated. 163 | #html_domain_indices = True 164 | 165 | # If false, no index is generated. 166 | #html_use_index = True 167 | 168 | # If true, the index is split into individual pages for each letter. 169 | #html_split_index = False 170 | 171 | # If true, links to the reST sources are added to the pages. 172 | #html_show_sourcelink = True 173 | 174 | # If true, "Created using Sphinx" is shown in the HTML footer. 175 | # Default is True. 176 | #html_show_sphinx = True 177 | 178 | # If true, "(C) Copyright ..." is shown in the HTML footer. 179 | # Default is True. 180 | #html_show_copyright = True 181 | 182 | # If true, an OpenSearch description file will be output, and all pages 183 | # will contain a tag referring to it. The value of this option 184 | # must be the base URL from which the finished HTML is served. 185 | #html_use_opensearch = '' 186 | 187 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 188 | #html_file_suffix = None 189 | 190 | # Output file base name for HTML help builder. 191 | htmlhelp_basename = 'neo-python-rpcdoc' 192 | 193 | 194 | # -- Options for LaTeX output ------------------------------------------ 195 | 196 | latex_elements = { 197 | # The paper size ('letterpaper' or 'a4paper'). 198 | #'papersize': 'letterpaper', 199 | 200 | # The font size ('10pt', '11pt' or '12pt'). 201 | #'pointsize': '10pt', 202 | 203 | # Additional stuff for the LaTeX preamble. 204 | #'preamble': '', 205 | } 206 | 207 | # Grouping the document tree into LaTeX files. List of tuples 208 | # (source start file, target name, title, author, documentclass 209 | # [howto/manual]). 210 | latex_documents = [ 211 | ('index', 'neo-python-rpc.tex', 212 | u'neorpc-python-rpc Documentation', 213 | u'Thomas Saunders', 'manual'), 214 | ] 215 | 216 | # The name of an image file (relative to this directory) to place at 217 | # the top of the title page. 218 | #latex_logo = None 219 | 220 | # For "manual" documents, if this is true, then toplevel headings 221 | # are parts, not chapters. 222 | #latex_use_parts = False 223 | 224 | # If true, show page references after internal links. 225 | #latex_show_pagerefs = False 226 | 227 | # If true, show URL addresses after external links. 228 | #latex_show_urls = False 229 | 230 | # Documents to append as an appendix to all manuals. 231 | #latex_appendices = [] 232 | 233 | # If false, no module index is generated. 234 | #latex_domain_indices = True 235 | 236 | 237 | # -- Options for manual page output ------------------------------------ 238 | 239 | # One entry per manual page. List of tuples 240 | # (source start file, name, description, authors, manual section). 241 | man_pages = [ 242 | ('index', 'neo-python-rpc', 243 | u'neo-python-rpc Documentation', 244 | [u'Thomas Saunders'], 1) 245 | ] 246 | 247 | # If true, show URL addresses after external links. 248 | #man_show_urls = False 249 | 250 | 251 | # -- Options for Texinfo output ---------------------------------------- 252 | 253 | # Grouping the document tree into Texinfo files. List of tuples 254 | # (source start file, target name, title, author, 255 | # dir menu entry, description, category) 256 | texinfo_documents = [ 257 | ('index', 'neo-python-rpc', 258 | u'neo-python-rpc Documentation', 259 | u'Thomas Saunders', 260 | 'neo-python-rpc', 261 | 'One line description of project.', 262 | 'Miscellaneous'), 263 | ] 264 | 265 | # Documents to append as an appendix to all manuals. 266 | #texinfo_appendices = [] 267 | 268 | # If false, no module index is generated. 269 | #texinfo_domain_indices = True 270 | 271 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 272 | #texinfo_show_urls = 'footnote' 273 | 274 | # If true, do not generate a @detailmenu in the "Top" node's menu. 275 | #texinfo_no_detailmenu = False 276 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to neo-python-rpc's documentation! 2 | ========================================== 3 | 4 | Contents: 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | readme 10 | installation 11 | usage 12 | modules 13 | contributing 14 | authors 15 | 16 | Indices and tables 17 | ================== 18 | 19 | * :ref:`genindex` 20 | * :ref:`modindex` 21 | * :ref:`search` 22 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | 8 | Stable release 9 | -------------- 10 | 11 | To install neo-python-rpc, run this command in your terminal: 12 | 13 | .. code-block:: console 14 | 15 | $ pip install neo-python-rpc 16 | 17 | This is the preferred method to install neo-python-rpc, as it will always install the most recent stable release. 18 | 19 | If you don't have `pip`_ installed, this `Python installation guide`_ can guide 20 | you through the process. 21 | 22 | .. _pip: https://pip.pypa.io 23 | .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ 24 | 25 | 26 | From sources 27 | ------------ 28 | 29 | The sources for neo-python-rpc can be downloaded from the `Github repo`_. 30 | 31 | You can either clone the public repository: 32 | 33 | .. code-block:: console 34 | 35 | $ git clone git://github.com/CityOfZion/neo-python-rpc 36 | 37 | Or download the `tarball`_: 38 | 39 | .. code-block:: console 40 | 41 | $ curl -OL https://github.com/CityOfZion/neo-python-rpc/tarball/master 42 | 43 | Once you have a copy of the source, you can install it with: 44 | 45 | .. code-block:: console 46 | 47 | $ python setup.py install 48 | 49 | 50 | .. _Github repo: https://github.com/CityOfZion/neo-python-rpc 51 | .. _tarball: https://github.com/CityOfZion/neo-python-rpc/tarball/master 52 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\neo-python-rpc.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\neo-python-rpc.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Usage 3 | ===== 4 | 5 | To use neo-python-rpc in a project:: 6 | 7 | >>> from neorpc.Client import RPCClient 8 | >>> client = RPCClient() 9 | >>> blockchain_height = client.get_height() 10 | >>> blockchain_height 11 | 769332 12 | 13 | 14 | Get a block 15 | 16 | >>> block = client.get_block(123456) 17 | >>> block 18 | { 19 | "nextconsensus": "AdyQbbn6ENjqWDa5JNYMwN3ikNcA4JeZdk", 20 | "size": 686, 21 | "previousblockhash": "0xdbee7804eb1f648e640401a864ed29bbc787d7f9d36b6f4be0a3ffd693723d5e", 22 | "script": { 23 | "invocation": "4012bbee0bac21b6ec5c4b0cda3aa28f3aa51e3b4a6c230908370adc8758fc3b051a865539bdca0cc6b450ff4fa18d3295f5c5a05a862b8aa9baff3e69011cb0f640ca5e3e5b3399a2014aa96575 24 | e2b0c3d1239d41ac05a92ec430ab0e0388f5e76e30062dd9aecca3007993e8bde7a00b70cdbef2fbb641164b9fba5f6ed724dc2e40458bcdf880cfef8d94aef714274ff4c258a13f541d2f8f678520bb5414704662cc5b9e714 25 | f2e42c94c2a61f8e0b3fb2ec8cc6e9ef2e0670056082370e9331e0f40f0a005fbb338e69465c6e0760ac04c2d61bdbec9922f10b8936d277609dd0f6cab1d7ee944902e0af2ba4ac6183a30158c1aa96c77262070161d24e1c5 26 | 530842403cc25ba0ce9ca460d3bf6108a48c676f131076c1542173278cace43a69b7038d4f2f5b401e8bdea2e10c9c68598d9f530b6618b3165a7c791442cd6d093b6aa2", 27 | "verification": "55210209e7fd41dfb5c2f8dc72eb30358ac100ea8c72da18847befe06eade68cebfcb9210327da12b5c40200e9f65569476bbff2218da4f32548ff43b6387ec1416a231ee821034ff5ceeac41a 28 | cf22cd5ed2da17a6df4dd8358fcb2bfb1a43208ad0feaab2746b21026ce35b29147ad09e4afe4ec4a7319095f08198fa8babbe3c56e970b143528d2221038dddc06ce687677a53d54f096d2591ba2302068cf123c1f2d75c2dd 29 | dc542557921039dafd8571a641058ccc832c5e2111ea39b09c0bde36050914384f7a48bce9bf92102d02b1873a0863cd042cc717da31cea0d7cf9db32b74d4c72c01b0011503e2e2257ae" 30 | }, 31 | "tx": [ 32 | { 33 | "vin": [], 34 | "size": 10, 35 | "type": "MinerTransaction", 36 | "txid": "0x0981c5e57997af0ce18d032bc9969220308dce8ec643844398649b6732ad91fe", 37 | "attributes": [], 38 | "net_fee": "0", 39 | "vout": [], 40 | "nonce": 1808064137, 41 | "version": 0, 42 | "scripts": [], 43 | "sys_fee": "0" 44 | } 45 | ], 46 | "nextblockhash": "0xed2cf18a0b15bb3b6a244abd2de1227afde61d1b0a9cbc582e587c18b3c3a00e", 47 | "confirmations": 647426, 48 | "merkleroot": "0x0981c5e57997af0ce18d032bc9969220308dce8ec643844398649b6732ad91fe", 49 | "hash": "0x10d1473c9fa133b28ea4f470e58b97bfe1222e102b14fbc50d3ade59eb8e3b74", 50 | "version": 0, 51 | "time": 1496944720, 52 | "nonce": "c8f9672b6bc4de89", 53 | "index": 123456 54 | } -------------------------------------------------------------------------------- /neorpc/Client.py: -------------------------------------------------------------------------------- 1 | from neorpc.Settings import settings as rpc_settings 2 | import requests 3 | import binascii 4 | 5 | 6 | class NEORPCException(Exception): 7 | pass 8 | 9 | 10 | class RPCClient(): 11 | 12 | id_counter = 0 13 | 14 | _settings = rpc_settings 15 | _addr_list = None 16 | 17 | @property 18 | def endpoints(self): 19 | return self._addr_list 20 | 21 | @property 22 | def default_endpoint(self): 23 | self._addr_list.sort() 24 | return self._addr_list[0] 25 | 26 | def get_account(self, address, id=None, endpoint=None): 27 | """ 28 | Look up an account on the blockchain. Sample output: 29 | 30 | Args: 31 | address: (str) address to lookup ( in format 'AXjaFSP23Jkbe6Pk9pPGT6NBDs1HVdqaXK') 32 | id: (int, optional) id to use for response tracking 33 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 34 | 35 | Returns: 36 | json object of the result or the error encountered in the RPC call 37 | 38 | """ 39 | return self._call_endpoint(GET_ACCOUNT_STATE, params=[address], id=id, endpoint=endpoint) 40 | 41 | def get_height(self, id=None, endpoint=None): 42 | """ 43 | Get the current height of the blockchain 44 | Args: 45 | id: (int, optional) id to use for response tracking 46 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 47 | 48 | Returns: 49 | json object of the result or the error encountered in the RPC call 50 | """ 51 | return self._call_endpoint(GET_BLOCK_COUNT, id=id, endpoint=endpoint) 52 | 53 | def get_asset(self, asset_hash, id=None, endpoint=None): 54 | """ 55 | Get an asset by its hash 56 | Args: 57 | asset_hash: (str) asset to lookup, example would be 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b' 58 | id: (int, optional) id to use for response tracking 59 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 60 | 61 | Returns: 62 | json object of the result or the error encountered in the RPC call 63 | """ 64 | return self._call_endpoint(GET_ASSET_STATE, params=[asset_hash], id=id, endpoint=endpoint) 65 | 66 | def get_balance(self, asset_hash, id=None, endpoint=None): 67 | """ 68 | Get balance by asset hash 69 | Args: 70 | asset_hash: (str) asset to lookup, example would be 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b' 71 | id: (int, optional) id to use for response tracking 72 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 73 | 74 | Returns: 75 | json object of the result or the error encountered in the RPC call 76 | """ 77 | return self._call_endpoint(GET_BALANCE, params=[asset_hash], id=id, endpoint=endpoint) 78 | 79 | def get_best_blockhash(self, id=None, endpoint=None): 80 | """ 81 | Get the hash of the highest block 82 | Args: 83 | id: (int, optional) id to use for response tracking 84 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 85 | Returns: 86 | json object of the result or the error encountered in the RPC call 87 | """ 88 | return self._call_endpoint(GET_BEST_BLOCK_HASH, id=id, endpoint=endpoint) 89 | 90 | def get_block(self, height_or_hash, id=None, endpoint=None): 91 | """ 92 | Look up a block by the height or hash of the block. 93 | Args: 94 | height_or_hash: (int or str) either the height of the desired block or its hash in the form '1e67372c158a4cfbb17b9ad3aaae77001a4247a00318e354c62e53b56af4006f' 95 | id: (int, optional) id to use for response tracking 96 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 97 | 98 | Returns: 99 | block: a json object or the ``neorpc.Core.Block.Block`` object 100 | """ 101 | return self._call_endpoint(GET_BLOCK, params=[height_or_hash, 1], id=id, endpoint=endpoint) 102 | 103 | def get_block_hash(self, height, id=None, endpoint=None): 104 | """ 105 | Get hash of a block by its height 106 | Args: 107 | height: (int) height of the block to lookup 108 | id: (int, optional) id to use for response tracking 109 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 110 | 111 | Returns: 112 | json object of the result or the error encountered in the RPC call 113 | """ 114 | return self._call_endpoint(GET_BLOCK_HASH, params=[height], id=id, endpoint=endpoint) 115 | 116 | def get_block_header(self, block_hash, id=None, endpoint=None): 117 | """ 118 | Get the corresponding block header information according to the specified script hash. 119 | Args: 120 | block_hash: (str) the block scripthash (e.g. 'a5508c9b6ed0fc09a531a62bc0b3efcb6b8a9250abaf72ab8e9591294c1f6957') 121 | id: (int, optional) id to use for response tracking 122 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 123 | 124 | Returns: 125 | json object of the result or the error encountered in the RPC call 126 | """ 127 | return self._call_endpoint(GET_BLOCK_HEADER, params=[block_hash, 1], id=id, endpoint=endpoint) 128 | 129 | def get_block_sysfee(self, height, id=None, endpoint=None): 130 | """ 131 | Get the system fee of a block by height. This is used in calculating gas claims 132 | Args: 133 | height: (int) height of the block to lookup 134 | id: (int, optional) id to use for response tracking 135 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 136 | 137 | Returns: 138 | json object of the result or the error encountered in the RPC call 139 | """ 140 | return self._call_endpoint(GET_BLOCK_SYS_FEE, params=[height], id=id, endpoint=endpoint) 141 | 142 | def get_connection_count(self, id=None, endpoint=None): 143 | """ 144 | Gets the number of nodes connected to the endpoint 145 | Args: 146 | id: (int, optional) id to use for response tracking 147 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 148 | Returns: 149 | json object of the result or the error encountered in the RPC call 150 | """ 151 | return self._call_endpoint(GET_CONNECTION_COUNT, id=id, endpoint=endpoint) 152 | 153 | def get_contract_state(self, contract_hash, id=None, endpoint=None): 154 | """ 155 | Get a contract state object by its hash 156 | Args: 157 | contract_hash: (str) the hash of the contract to lookup, for example 'd7678dd97c000be3f33e9362e673101bac4ca654' 158 | id: (int, optional) id to use for response tracking 159 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 160 | Returns: 161 | json object of the result or the error encountered in the RPC call 162 | """ 163 | return self._call_endpoint(GET_CONTRACT_STATE, params=[contract_hash], id=id, endpoint=endpoint) 164 | 165 | def get_raw_mempool(self, id=None, endpoint=None): 166 | """ 167 | Returns the tx that are in the memorypool of the endpoint 168 | Args: 169 | id: (int, optional) id to use for response tracking 170 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 171 | Returns: 172 | json object of the result or the error encountered in the RPC call 173 | """ 174 | return self._call_endpoint(GET_RAW_MEMPOOL, id=id, endpoint=endpoint) 175 | 176 | def get_transaction(self, tx_hash, id=None, endpoint=None): 177 | """ 178 | Look up a transaction by hash. 179 | Args: 180 | tx_hash: (str) hash in the form '58c634f81fbd4ae2733d7e3930a9849021840fc19dc6af064d6f2812a333f91d' 181 | id: (int, optional) id to use for response tracking 182 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 183 | 184 | Returns: 185 | json: the transaction as a json object 186 | """ 187 | return self._call_endpoint(GET_RAW_TRANSACTION, params=[tx_hash, 1], id=id, endpoint=endpoint) 188 | 189 | def get_storage(self, contract_hash, storage_key, id=None, endpoint=None): 190 | """ 191 | Returns a storage item of a specified contract 192 | Args: 193 | contract_hash: (str) hash of the contract to lookup, for example 'd7678dd97c000be3f33e9362e673101bac4ca654' 194 | storage_key: (str) storage key to lookup, for example 'totalSupply' 195 | id: (int, optional) id to use for response tracking 196 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 197 | Returns: 198 | bytearray: bytearray value of the storage item 199 | """ 200 | result = self._call_endpoint(GET_STORAGE, params=[contract_hash, binascii.hexlify(storage_key.encode('utf-8')).decode('utf-8')], id=id, endpoint=endpoint) 201 | try: 202 | 203 | return bytearray(binascii.unhexlify(result.encode('utf-8'))) 204 | except Exception as e: 205 | raise NEORPCException("could not decode result %s " % e) 206 | 207 | def get_tx_out(self, tx_hash, vout_id, id=None, endpoint=None): 208 | """ 209 | Gets a transaction output by specified transaction hash and output index 210 | Args: 211 | tx_hash: (str) hash in the form '58c634f81fbd4ae2733d7e3930a9849021840fc19dc6af064d6f2812a333f91d' 212 | vout_id: (int) index of the transaction output in the transaction 213 | id: (int, optional) id to use for response tracking 214 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 215 | Returns: 216 | json object of the result or the error encountered in the RPC call 217 | """ 218 | return self._call_endpoint(GET_TX_OUT, params=[tx_hash, vout_id], id=id, endpoint=endpoint) 219 | 220 | def invoke_contract(self, contract_hash, params, id=None, endpoint=None): 221 | """ 222 | Invokes a contract 223 | Args: 224 | contract_hash: (str) hash of the contract, for example 'd7678dd97c000be3f33e9362e673101bac4ca654' 225 | params: (list) a list of json ContractParameters to pass along with the invocation, example [{'type':7,'value':'symbol'},{'type':16, 'value':[]}] 226 | id: (int, optional) id to use for response tracking 227 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 228 | Returns: 229 | json object of the result or the error encountered in the RPC call 230 | """ 231 | return self._call_endpoint(INVOKE, params=[contract_hash, params], id=id, endpoint=endpoint) 232 | 233 | def invoke_contract_fn(self, contract_hash, operation, params=None, id=None, endpoint=None): 234 | """ 235 | Invokes a contract 236 | Args: 237 | contract_hash: (str) hash of the contract, for example 'd7678dd97c000be3f33e9362e673101bac4ca654' 238 | operation: (str) the operation to call on the contract 239 | params: (list) a list of json ContractParameters to pass along with the invocation, example [{'type':7,'value':'symbol'},{'type':16, 'value':[]}] 240 | id: (int, optional) id to use for response tracking 241 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 242 | Returns: 243 | json object of the result or the error encountered in the RPC call 244 | """ 245 | return self._call_endpoint(INVOKE_FUNCTION, params=[contract_hash, operation, params if params else []], id=id, endpoint=endpoint) 246 | 247 | def invoke_script(self, script, id=None, endpoint=None): 248 | """ 249 | Invokes a script that has been assembled 250 | Args: 251 | script: (str) a hexlified string of a contract invocation script, example '00c10b746f74616c537570706c796754a64cac1b1073e662933ef3e30b007cd98d67d7' 252 | id: (int, optional) id to use for response tracking 253 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 254 | Returns: 255 | json object of the result or the error encountered in the RPC call 256 | """ 257 | return self._call_endpoint(INVOKE_SCRIPT, params=[script], id=id, endpoint=endpoint) 258 | 259 | def send_raw_tx(self, serialized_tx, id=None, endpoint=None): 260 | """ 261 | Submits a serialized tx to the network 262 | Args: 263 | serialized_tx: (str) a hexlified string of a transaction 264 | id: (int, optional) id to use for response tracking 265 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 266 | Returns: 267 | bool: whether the tx was accepted or not 268 | """ 269 | return self._call_endpoint(SEND_TX, params=[serialized_tx], id=id, endpoint=endpoint) 270 | 271 | def validate_addr(self, address, id=None, endpoint=None): 272 | """ 273 | returns whether or not addr string is valid 274 | 275 | Args: 276 | address: (str) address to lookup ( in format 'AXjaFSP23Jkbe6Pk9pPGT6NBDs1HVdqaXK') 277 | id: (int, optional) id to use for response tracking 278 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 279 | 280 | Returns: 281 | json object of the result or the error encountered in the RPC call 282 | 283 | """ 284 | return self._call_endpoint(VALIDATE_ADDR, params=[address], id=id, endpoint=endpoint) 285 | 286 | def get_peers(self, id=None, endpoint=None): 287 | """ 288 | Get the current peers of a remote node 289 | Args: 290 | id: (int, optional) id to use for response tracking 291 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 292 | 293 | Returns: 294 | json object of the result or the error encountered in the RPC call 295 | """ 296 | return self._call_endpoint(GET_PEERS, id=id, endpoint=endpoint) 297 | 298 | def get_validators(self, id=None, endpoint=None): 299 | """ 300 | Returns the current NEO consensus nodes information and voting status. 301 | Args: 302 | id: (int, optional) id to use for response tracking 303 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 304 | 305 | Returns: 306 | json object of the result or the error encountered in the RPC call 307 | """ 308 | return self._call_endpoint(GET_VALIDATORS, id=id, endpoint=endpoint) 309 | 310 | def get_version(self, id=None, endpoint=None): 311 | """ 312 | Get the current version of the endpoint. 313 | Note: Not all endpoints currently implement this method 314 | 315 | Args: 316 | id: (int, optional) id to use for response tracking 317 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 318 | Returns: 319 | json object of the result or the error encountered in the RPC call 320 | """ 321 | return self._call_endpoint(GET_VERSION, id=id, endpoint=endpoint) 322 | 323 | def get_new_address(self, id=None, endpoint=None): 324 | """ 325 | Create new address 326 | Args: 327 | id: (int, optional) id to use for response tracking 328 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 329 | Returns: 330 | json object of the result or the error encountered in the RPC call 331 | """ 332 | return self._call_endpoint(GET_NEW_ADDRESS, id=id, endpoint=endpoint) 333 | 334 | def get_wallet_height(self, id=None, endpoint=None): 335 | """ 336 | Get the current wallet index height. 337 | Args: 338 | id: (int, optional) id to use for response tracking 339 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 340 | Returns: 341 | json object of the result or the error encountered in the RPC call 342 | """ 343 | return self._call_endpoint(GET_WALLET_HEIGHT, id=id, endpoint=endpoint) 344 | 345 | def list_address(self, id=None, endpoint=None): 346 | """ 347 | Lists all the addresses in the current wallet. 348 | Args: 349 | id: (int, optional) id to use for response tracking 350 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 351 | Returns: 352 | json object of the result or the error encountered in the RPC call 353 | """ 354 | return self._call_endpoint(LIST_ADDRESS, id=id, endpoint=endpoint) 355 | 356 | def send_from(self, asset_id, addr_from, to_addr, value, fee=None, change_addr=None, id=None, endpoint=None): 357 | """ 358 | Transfer from the specified address to the destination address. 359 | Args: 360 | asset_id: (str) asset identifier (for NEO: 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', for GAS: '602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7') 361 | addr_from: (str) transfering address 362 | to_addr: (str) destination address 363 | value: (int/decimal) transfer amount 364 | fee: (decimal, optional) Paying the handling fee helps elevate the priority of the network to process the transfer. It defaults to 0, and can be set to a minimum of 0.00000001. The low priority threshold is 0.001. 365 | change_addr: (str, optional) Change address, default is the first standard address in the wallet. 366 | id: (int, optional) id to use for response tracking 367 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 368 | Returns: 369 | json object of the result or the error encountered in the RPC call 370 | """ 371 | params = [asset_id, addr_from, to_addr, value] 372 | if fee: 373 | params.append(fee) 374 | if fee and change_addr: 375 | params.append(change_addr) 376 | elif not fee and change_addr: 377 | params.append(0) 378 | params.append(change_addr) 379 | return self._call_endpoint(SEND_FROM, params=params, id=id, endpoint=endpoint) 380 | 381 | def send_to_address(self, asset_id, to_addr, value, fee=None, change_addr=None, id=None, endpoint=None): 382 | """ 383 | Args: 384 | asset_id: (str) asset identifier (for NEO: 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', for GAS: '602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7') 385 | to_addr: (str) destination address 386 | value: (int/decimal) transfer amount 387 | fee: (decimal, optional) Paying the handling fee helps elevate the priority of the network to process the transfer. It defaults to 0, and can be set to a minimum of 0.00000001. The low priority threshold is 0.001. 388 | change_addr: (str, optional) Change address, default is the first standard address in the wallet. 389 | id: (int, optional) id to use for response tracking 390 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 391 | Returns: 392 | json object of the result or the error encountered in the RPC call 393 | """ 394 | params = [asset_id, to_addr, value] 395 | if fee: 396 | params.append(fee) 397 | if fee and change_addr: 398 | params.append(change_addr) 399 | elif not fee and change_addr: 400 | params.append(0) 401 | params.append(change_addr) 402 | return self._call_endpoint(SEND_TO_ADDRESS, params=params, id=id, endpoint=endpoint) 403 | 404 | def send_many(self, outputs_array, fee=None, change_addr=None, id=None, endpoint=None): 405 | """ 406 | Args: 407 | outputs_array: (dict) array, the data structure of each element in the array is as follows: 408 | {"asset": ,"value": ,"address":
} 409 | asset: (str) asset identifier (for NEO: 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', for GAS: '602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7') 410 | value: (int/decimal) transfer amount 411 | address: (str) destination address 412 | fee: (decimal, optional) Paying the handling fee helps elevate the priority of the network to process the transfer. It defaults to 0, and can be set to a minimum of 0.00000001. The low priority threshold is 0.001. 413 | change_addr: (str, optional) Change address, default is the first standard address in the wallet. 414 | id: (int, optional) id to use for response tracking 415 | endpoint: (RPCEndpoint, optional) endpoint to specify to use 416 | """ 417 | params = [outputs_array] 418 | if fee: 419 | params.append(fee) 420 | if fee and change_addr: 421 | params.append(change_addr) 422 | elif not fee and change_addr: 423 | params.append(0) 424 | params.append(change_addr) 425 | return self._call_endpoint(SEND_MANY, params=params, id=id, endpoint=endpoint) 426 | 427 | def __init__(self, config=None, setup=False): 428 | 429 | if config: 430 | self._settings = config 431 | 432 | self._build_addr() 433 | 434 | if setup: 435 | self.setup_endpoints() 436 | 437 | def setup_endpoints(self): 438 | self._build_addr() 439 | [endpoint.setup() for endpoint in self._addr_list] 440 | 441 | def _call_endpoint(self, method, params=None, id=None, endpoint=None): 442 | payload = self._build_payload(method, params, id) 443 | endpoint = self.default_endpoint if endpoint is None else endpoint 444 | try: 445 | response = requests.post(endpoint.addr, json=payload, timeout=TIMEOUT) 446 | response.raise_for_status() 447 | if response.status_code == 200: 448 | if 'result' in response.json(): 449 | return response.json()['result'] 450 | return response.json() 451 | except Exception as e: 452 | raise NEORPCException("Could not call method %s with endpoint: %s : %s " % (method, endpoint.addr, e)) 453 | 454 | def _build_addr(self): 455 | self._addr_list = [RPCEndpoint(self, addr) for addr in self._settings.RPC_LIST] 456 | 457 | def _build_payload(self, method, params, id): 458 | 459 | id = self.id_counter if id is None else id 460 | self.id_counter += 1 461 | 462 | params = [] if params is None else params 463 | 464 | rpc_version = "2.0" 465 | 466 | return {'jsonrpc': rpc_version, 'method': method, 'params': params, 'id': id} 467 | 468 | 469 | # methods that read data 470 | GET_ACCOUNT_STATE = 'getaccountstate' 471 | GET_ASSET_STATE = 'getassetstate' 472 | GET_BEST_BLOCK_HASH = 'getbestblockhash' 473 | GET_BLOCK = 'getblock' 474 | GET_BLOCK_COUNT = 'getblockcount' 475 | GET_BLOCK_HASH = 'getblockhash' 476 | GET_BLOCK_HEADER = 'getblockheader' 477 | GET_BLOCK_SYS_FEE = 'getblocksysfee' 478 | GET_CONNECTION_COUNT = 'getconnectioncount' 479 | GET_CONTRACT_STATE = 'getcontractstate' 480 | GET_PEERS = 'getpeers' 481 | GET_RAW_MEMPOOL = 'getrawmempool' 482 | GET_RAW_TRANSACTION = 'getrawtransaction' 483 | GET_STORAGE = 'getstorage' 484 | GET_TX_OUT = 'gettxout' 485 | GET_VALIDATORS = 'getvalidators' 486 | GET_VERSION = 'getversion' 487 | 488 | # wallet method 489 | GET_BALANCE = 'getbalance' 490 | GET_NEW_ADDRESS = 'getnewaddress' 491 | GET_WALLET_HEIGHT = 'getwalletheight' 492 | LIST_ADDRESS = 'listaddress' 493 | SEND_FROM = 'sendfrom' 494 | SEND_TO_ADDRESS = 'sendtoaddress' 495 | SEND_MANY = 'sendmany' 496 | 497 | # invocation related methods 498 | INVOKE = 'invoke' 499 | INVOKE_FUNCTION = 'invokefunction' 500 | INVOKE_SCRIPT = 'invokescript' 501 | 502 | # send 503 | SEND_TX = 'sendrawtransaction' 504 | SUBMIT_BLOCK = 'submitblock' 505 | 506 | # validate 507 | VALIDATE_ADDR = 'validateaddress' 508 | 509 | 510 | TIMEOUT = 10 511 | 512 | 513 | class RPCEndpoint(): 514 | addr = None 515 | height = None 516 | client = None 517 | status = None 518 | elapsed = None 519 | 520 | def __init__(self, client, address): 521 | self.client = client 522 | self.addr = address 523 | 524 | def setup(self): 525 | try: 526 | response = requests.post(self.addr, json={'jsonrpc': '2.0', 'method': GET_BLOCK_COUNT, 'params': [], 'id': 1}, timeout=TIMEOUT) 527 | self.update_endpoint_details(response) 528 | 529 | if response.status_code == 200: 530 | json = response.json() 531 | self.height = int(json['result']) 532 | except Exception as e: 533 | raise NEORPCException("Could not setup %s : %s " % (self.addr, e)) 534 | 535 | def update_endpoint_details(self, response): 536 | 537 | self.status = response.status_code 538 | self.elapsed = response.elapsed.microseconds 539 | 540 | def _compare(self, other): 541 | if self.status != 200: 542 | return -1 543 | elif other.status != 200: 544 | return 1 545 | 546 | if self.height == other.height: 547 | if self.elapsed == other.elapsed: 548 | return 0 549 | 550 | if other.elapsed > 0 and self.elapsed > 0: 551 | 552 | if self.elapsed > other.elapsed: 553 | return 1 554 | else: 555 | return -1 556 | else: 557 | if self.height < other.height: 558 | return 1 559 | else: 560 | return -1 561 | 562 | def __eq__(self, other): 563 | return self.addr == other.addr 564 | 565 | def __lt__(self, other): 566 | return self._compare(other) < 0 567 | 568 | def __gt__(self, other): 569 | return self._compare(other) > 0 570 | 571 | def __le__(self, other): 572 | return self._compare(other) <= 0 573 | 574 | def __ge__(self, other): 575 | return self._compare(other) >= 0 576 | 577 | def __str__(self): 578 | return "[%s] %s %s %s" % (self.addr, self.status, self.height, self.elapsed) 579 | -------------------------------------------------------------------------------- /neorpc/Settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Sets up your RPC endpoints 4 | 5 | 6 | """ 7 | 8 | 9 | class SettingsHolder: 10 | """ 11 | This class holds all the settings. Needs to be setup with one of the 12 | `setup` methods before using it. 13 | """ 14 | RPC_LIST = None 15 | 16 | # Setup methods 17 | def setup(self, addr_list): 18 | """ Load settings from a JSON config file """ 19 | self.RPC_LIST = addr_list 20 | 21 | def setup_mainnet(self): 22 | """ Load settings from the mainnet JSON config file """ 23 | self.setup( 24 | [ 25 | "https://seed1.cityofzion.io:443", 26 | "https://seed2.cityofzion.io:443", 27 | "https://seed3.cityofzion.io:443", 28 | "https://seed4.cityofzion.io:443", 29 | "https://seed5.cityofzion.io:443" 30 | ] 31 | ) 32 | 33 | def setup_testnet(self): 34 | self.setup( 35 | [ 36 | "https://test1.cityofzion.io:443", 37 | "https://test2.cityofzion.io:443", 38 | "https://test3.cityofzion.io:443", 39 | "https://test4.cityofzion.io:443", 40 | "https://test5.cityofzion.io:443" 41 | ] 42 | ) 43 | 44 | def setup_privnet(self): 45 | """ Load settings from the privnet JSON config file """ 46 | self.setup( 47 | [ 48 | "http://127.0.0.1:30333" 49 | ] 50 | ) 51 | 52 | 53 | # Settings instance used by external modules 54 | settings = SettingsHolder() 55 | 56 | # Load testnet settings as default 57 | settings.setup_testnet() 58 | -------------------------------------------------------------------------------- /neorpc/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.2.2' 2 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.10 2 | argh==0.26.2 3 | Babel==2.5.1 4 | bumpversion==0.5.3 5 | certifi>=2017.11.5 6 | chardet==3.0.4 7 | coverage==4.1 8 | docutils==0.12 9 | idna==2.6 10 | imagesize==0.7.1 11 | Jinja2==2.10 12 | MarkupSafe==1.0 13 | mccabe==0.5.3 14 | pathtools==0.1.2 15 | pkginfo==1.4.1 16 | pluggy==0.3.1 17 | py==1.4.34 18 | pycodestyle==2.3.1 19 | pyflakes==1.2.3 20 | Pygments==2.2.0 21 | pytz==2017.3 22 | PyYAML==3.12 23 | requests==2.18.4 24 | requests-toolbelt==0.8.0 25 | responses==0.9.0 26 | six==1.11.0 27 | snowballstemmer==1.2.1 28 | Sphinx==1.4.8 29 | tox==2.3.1 30 | tqdm==4.19.4 31 | twine==1.9.1 32 | urllib3==1.22 33 | virtualenv==15.1.0 34 | watchdog==0.8.3 35 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.2.2 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:neorpc/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [aliases] 15 | 16 | [pycodestyle] 17 | ignore = E721,E402,W291,E501,E704 18 | 19 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """The setup script.""" 5 | 6 | from setuptools import setup, find_packages 7 | try: # pip version >= 10.0 8 | from pip._internal.req import parse_requirements 9 | from pip._internal.download import PipSession 10 | except ImportError: # pip version < 10.0 11 | from pip.req import parse_requirements 12 | from pip.download import PipSession 13 | 14 | with open('README.rst') as readme_file: 15 | readme = readme_file.read() 16 | 17 | 18 | # parse_requirements() returns generator of pip.req.InstallRequirement objects 19 | install_reqs = parse_requirements('requirements_dev.txt', session=PipSession()) 20 | 21 | dev_reqs = [str(ir.req) for ir in install_reqs] 22 | 23 | requirements = [ 24 | 'requests', 25 | ] 26 | 27 | setup_requirements = dev_reqs 28 | 29 | test_requirements = dev_reqs 30 | 31 | 32 | setup( 33 | name='neo-python-rpc', 34 | version='0.2.2', 35 | description="A Python RPC Client for the NEO Blockchain", 36 | long_description=readme, 37 | author="Thomas Saunders", 38 | author_email='tom@cityofzion.io', 39 | url='https://github.com/CityOfZion/neo-python-rpc', 40 | packages=find_packages(include=['neorpc']), 41 | include_package_data=True, 42 | install_requires=requirements, 43 | license="MIT license", 44 | zip_safe=False, 45 | keywords='neo, python, rpc, client', 46 | classifiers=[ 47 | 'Development Status :: 2 - Pre-Alpha', 48 | 'Intended Audience :: Developers', 49 | 'License :: OSI Approved :: MIT License', 50 | 'Natural Language :: English', 51 | 'Programming Language :: Python :: 3', 52 | 'Programming Language :: Python :: 3.3', 53 | 'Programming Language :: Python :: 3.4', 54 | 'Programming Language :: Python :: 3.5', 55 | 'Programming Language :: Python :: 3.6', 56 | 57 | ], 58 | test_suite='tests', 59 | tests_require=test_requirements, 60 | setup_requires=setup_requirements, 61 | ) 62 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Unit test package for neorpc-python-rpc.""" 4 | -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from neorpc.Settings import SettingsHolder 3 | from neorpc.Client import RPCClient, RPCEndpoint, NEORPCException 4 | import binascii 5 | import responses 6 | 7 | 8 | class RPCClientTestCase(TestCase): 9 | 10 | sample_addr = 'AXjaFSP23Jkbe6Pk9pPGT6NBDs1HVdqaXK' 11 | 12 | sample_asset = 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b' 13 | 14 | def test_client(self): 15 | 16 | client = RPCClient() 17 | 18 | self.assertIsNotNone(client.endpoints) 19 | 20 | self.assertGreater(len(client.endpoints), 0) 21 | 22 | self.assertIsInstance(client.default_endpoint, RPCEndpoint) 23 | 24 | self.assertEqual(client.default_endpoint.height, None) 25 | 26 | def test_settings(self): 27 | settings = SettingsHolder() 28 | 29 | settings.setup_mainnet() 30 | client = RPCClient(config=settings) 31 | self.assertIsNotNone(client.endpoints) 32 | 33 | settings.setup_testnet() 34 | client = RPCClient(config=settings) 35 | self.assertIsNotNone(client.endpoints) 36 | 37 | settings.setup_privnet() 38 | client = RPCClient(config=settings) 39 | self.assertIsNotNone(client.endpoints) 40 | 41 | def test_client_setup(self): 42 | 43 | client = RPCClient(setup=True) 44 | 45 | self.assertIsNotNone(client.endpoints) 46 | 47 | self.assertGreater(len(client.endpoints), 0) 48 | 49 | self.assertIsInstance(client.default_endpoint, RPCEndpoint) 50 | 51 | self.assertIsNotNone(client.default_endpoint.height) 52 | 53 | self.assertEqual(client.default_endpoint.status, 200) 54 | 55 | def test_call_endpoint_exception(self): 56 | settings = SettingsHolder() 57 | 58 | settings.setup_privnet() 59 | client = RPCClient(config=settings) 60 | 61 | # Assumes no privnet is running (which always holds true on Travis-CI) 62 | with self.assertRaises(NEORPCException) as context: 63 | client.get_height() 64 | self.assertTrue("Could not call method" in str(context.exception)) 65 | 66 | @responses.activate 67 | def test_call_endpoint_status_moved(self): # , mocked_post): 68 | client = RPCClient() 69 | 70 | responses.add(responses.POST, 'https://test5.cityofzion.io:443/', 71 | json={'Found': 'Moved'}, status=302) 72 | 73 | response = client.get_height() 74 | self.assertTrue('Found' in response) 75 | 76 | def test_height(self): 77 | client = RPCClient() 78 | 79 | response = client.get_height() 80 | 81 | height = int(response) 82 | 83 | self.assertGreaterEqual(height, 0) 84 | 85 | def test_account_state(self): 86 | 87 | client = RPCClient() 88 | 89 | account = client.get_account(self.sample_addr) 90 | 91 | script_hash = bytearray(binascii.unhexlify(account['script_hash'][2:])) 92 | script_hash.reverse() 93 | 94 | self.assertEqual(len(script_hash), 20) 95 | 96 | balances = account['balances'] 97 | 98 | self.assertIsNotNone(balances) 99 | self.assertGreater(len(balances), 0) 100 | 101 | def test_asset_state(self): 102 | 103 | client = RPCClient() 104 | 105 | asset = client.get_asset(self.sample_asset) 106 | 107 | self.assertEqual(asset['type'], 'GoverningToken') 108 | 109 | self.assertEqual(int(asset['available']), 100000000) 110 | 111 | self.assertEqual(int(asset['precision']), 0) 112 | 113 | def test_best_blockhash(self): 114 | 115 | client = RPCClient() 116 | 117 | hash = bytearray(binascii.unhexlify(client.get_best_blockhash()[2:])) 118 | hash.reverse() 119 | 120 | self.assertEqual(len(hash), 32) 121 | 122 | def test_get_block_json(self): 123 | 124 | client = RPCClient() 125 | 126 | height = 12344 127 | blockjson1 = client.get_block(height) 128 | 129 | blockhash = blockjson1['hash'][2:] 130 | 131 | self.assertEqual(blockhash, '1e67372c158a4cfbb17b9ad3aaae77001a4247a00318e354c62e53b56af4006f') 132 | 133 | # blockjson2 = client.get_block(blockhash) 134 | 135 | # self.assertEqual(blockjson1, blockjson2) 136 | 137 | self.assertEqual(height, blockjson1['index']) 138 | 139 | def test_getblockhash(self): 140 | 141 | client = RPCClient() 142 | 143 | height = 12344 144 | 145 | hash = client.get_block_hash(height) 146 | 147 | self.assertEqual(hash[2:], '1e67372c158a4cfbb17b9ad3aaae77001a4247a00318e354c62e53b56af4006f') 148 | 149 | def test_getblockheader(self): 150 | 151 | client = RPCClient() 152 | 153 | hash = '1e67372c158a4cfbb17b9ad3aaae77001a4247a00318e354c62e53b56af4006f' 154 | 155 | header = client.get_block_header(hash) 156 | 157 | self.assertEqual(header['hash'][2:], '1e67372c158a4cfbb17b9ad3aaae77001a4247a00318e354c62e53b56af4006f') 158 | self.assertEqual(header['index'], 12344) 159 | self.assertNotIn('tx', header) 160 | 161 | def test_sysfee(self): 162 | 163 | client = RPCClient() 164 | 165 | height = 740324 166 | 167 | fee = int(client.get_block_sysfee(height)) 168 | 169 | self.assertEqual(fee, 469014) 170 | 171 | def test_contract_state(self): 172 | 173 | client = RPCClient() 174 | 175 | contract_hash = 'f8d448b227991cf07cb96a6f9c0322437f1599b9' 176 | 177 | contract = client.get_contract_state(contract_hash) 178 | 179 | hash = contract['hash'][2:] 180 | 181 | self.assertEqual(hash, contract_hash) 182 | 183 | def test_connection_count(self): 184 | 185 | client = RPCClient() 186 | 187 | connection_count = int(client.get_connection_count()) 188 | 189 | self.assertGreater(connection_count, 0) 190 | 191 | def test_mempool(self): 192 | 193 | client = RPCClient() 194 | 195 | mempool = client.get_raw_mempool() 196 | 197 | self.assertIsInstance(mempool, list) 198 | 199 | def test_tx_json(self): 200 | 201 | client = RPCClient() 202 | 203 | hash = '58c634f81fbd4ae2733d7e3930a9849021840fc19dc6af064d6f2812a333f91d' 204 | tx = client.get_transaction(hash) 205 | 206 | self.assertEqual(tx['blocktime'], 1510283768) 207 | 208 | self.assertEqual(tx['txid'][2:], hash) 209 | 210 | def test_get_storage(self): 211 | 212 | client = RPCClient() 213 | 214 | contract_hash = 'd7678dd97c000be3f33e9362e673101bac4ca654' 215 | key = 'totalSupply' 216 | 217 | storage = client.get_storage(contract_hash, key) 218 | 219 | self.assertIsInstance(storage, bytearray) 220 | 221 | intval = int.from_bytes(storage, 'little') 222 | 223 | self.assertEqual(intval, 227000000000000) 224 | 225 | # now also test for failure 226 | key = 'invalidkey' 227 | with self.assertRaises(NEORPCException) as context: 228 | storage = client.get_storage(contract_hash, key) 229 | self.assertTrue("could not decode result" in str(context.exception)) 230 | 231 | def test_tx_out(self): 232 | 233 | client = RPCClient() 234 | 235 | tx_hash = '58c634f81fbd4ae2733d7e3930a9849021840fc19dc6af064d6f2812a333f91d' 236 | index = 0 237 | txout = client.get_tx_out(tx_hash, index) 238 | 239 | # this tests fails since this tx out has been spent on the blockchain? 240 | # self.assertEqual(txout['asset'][2:], '602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7') 241 | # self.assertEqual(txout['n'], index) 242 | # self.assertEqual(txout['address'], self.sample_addr) 243 | 244 | self.assertIsNone(txout) 245 | 246 | def test_invoke(self): 247 | 248 | client = RPCClient() 249 | 250 | contract_hash = 'd7678dd97c000be3f33e9362e673101bac4ca654' 251 | params = [{'type': 7, 'value': 'symbol'}, {'type': 16, 'value': []}] 252 | 253 | result = client.invoke_contract(contract_hash, params) 254 | 255 | self.assertEqual(result['state'], 'HALT, BREAK') 256 | invoke_result = result['stack'] 257 | self.assertEqual(len(invoke_result), 1) 258 | 259 | stack_item = invoke_result[0] 260 | 261 | self.assertEqual(stack_item['type'], 'ByteArray') 262 | self.assertEqual(binascii.hexlify('LWTF'.encode('utf-8')).decode('utf-8'), stack_item['value']) 263 | 264 | def test_invoke_fn(self): 265 | 266 | client = RPCClient() 267 | 268 | contract_hash = 'd7678dd97c000be3f33e9362e673101bac4ca654' 269 | 270 | result = client.invoke_contract_fn(contract_hash, 'name') 271 | 272 | self.assertEqual(result['state'], 'HALT, BREAK') 273 | invoke_result = result['stack'] 274 | self.assertEqual(len(invoke_result), 1) 275 | 276 | invoke_result = result['stack'] 277 | self.assertEqual(len(invoke_result), 1) 278 | 279 | stack_item = invoke_result[0] 280 | 281 | self.assertEqual(stack_item['type'], 'ByteArray') 282 | 283 | val = binascii.unhexlify(stack_item['value'].encode('utf-8')).decode('utf-8') 284 | 285 | self.assertEqual(val, 'LOCALTOKEN') 286 | 287 | def test_invoke_script(self): 288 | 289 | client = RPCClient() 290 | 291 | script = '00c10b746f74616c537570706c796754a64cac1b1073e662933ef3e30b007cd98d67d7' 292 | 293 | result = client.invoke_script(script) 294 | 295 | self.assertEqual(result['state'], 'HALT, BREAK') 296 | invoke_result = result['stack'] 297 | self.assertEqual(len(invoke_result), 1) 298 | 299 | invoke_result = result['stack'] 300 | self.assertEqual(len(invoke_result), 1) 301 | 302 | stack_item = invoke_result[0] 303 | 304 | self.assertEqual(stack_item['type'], 'ByteArray') 305 | 306 | val = int.from_bytes(binascii.unhexlify(stack_item['value'].encode('utf-8')), 'little') 307 | 308 | self.assertEqual(val, 227000000000000) 309 | 310 | def test_send_raw_tx(self): 311 | 312 | client = RPCClient() 313 | 314 | raw_tx = '80000120d8edd2df8d6907caacd4af8872a596cb75c5829d015ce72895ce376d12def9a780ba502ae28ad7a4b7fbcf6baa4856edb537417d2a0000029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500a3e11100000000d8edd2df8d6907caacd4af8872a596cb75c5829d9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500bbeea000000000d8edd2df8d6907caacd4af8872a596cb75c5829d01414044dfd2b360e548607ece3d453173079233040c2484a99671a7346a8ca16969245b946bfa4c13125f4c931b0cbab216e0d241d908f37ad96abb776890832a3a4b2321025de86902ed42aca7246207a70869b22253aeb7cc84a4cb5eee3773fd78b3f339ac' 315 | 316 | # this will result in a false, since this tx has already been made 317 | # but, if it were badly formmated, it would be an error 318 | # so we are testing if we get back a false 319 | result = client.send_raw_tx(raw_tx) 320 | 321 | self.assertIn('error', result) 322 | self.assertIn('Block or transaction already exists', result['error']['message']) 323 | 324 | def test_validate_addr(self): 325 | 326 | client = RPCClient() 327 | 328 | result = client.validate_addr(self.sample_addr) 329 | 330 | self.assertEqual(result['isvalid'], True) 331 | self.assertEqual(result['address'], self.sample_addr) 332 | 333 | bad_addr = 'BXjaFSP23Jkbe6Pk9pPGT6NBDs1HVdqaXK' 334 | 335 | result = client.validate_addr(bad_addr) 336 | 337 | self.assertEqual(result['isvalid'], False) 338 | 339 | def test_get_peers(self): 340 | 341 | client = RPCClient() 342 | 343 | result = client.get_peers() 344 | 345 | self.assertIsNotNone(result) 346 | # self.assertIn('connected', result) 347 | # self.assertIn('unconnected', result) 348 | 349 | def test_get_validators(self): 350 | 351 | client = RPCClient() 352 | 353 | result = client.get_validators() 354 | 355 | self.assertIn('publickey', result[0]) 356 | self.assertIn('votes', result[0]) 357 | self.assertIn('active', result[0]) 358 | 359 | def test_get_version(self): 360 | client = RPCClient() 361 | version = client.get_version() 362 | self.assertIn("port", version) 363 | self.assertIn("nonce", version) 364 | self.assertIn("useragent", version) 365 | 366 | 367 | class RPCEndPointTestCase(TestCase): 368 | 369 | def setUp(self): 370 | self.ep1 = RPCEndpoint(client=None, address='addr1') 371 | self.ep2 = RPCEndpoint(client=None, address='addr1') 372 | self.ep1.status = 200 373 | self.ep2.status = 200 374 | self.ep1.elapsed = 1 375 | self.ep2.elapsed = 1 376 | self.ep1.height = 1 377 | self.ep2.height = 1 378 | 379 | def test_eq(self): 380 | self.assertEqual(self.ep1, self.ep2) 381 | 382 | def test_gt_and_ge(self): 383 | self.ep2.height = 2 384 | self.assertGreater(self.ep1, self.ep2) 385 | self.ep2.height = 1 386 | self.assertGreaterEqual(self.ep1, self.ep2) 387 | 388 | self.ep1.status = 404 389 | self.assertGreater(self.ep2, self.ep1) 390 | 391 | def test_lt_and_le(self): 392 | self.ep2.height = 2 393 | self.assertLess(self.ep2, self.ep1) 394 | self.ep2.height = 1 395 | self.assertLessEqual(self.ep2, self.ep1) 396 | 397 | def test_str(self): 398 | self.assertEqual("[addr1] 200 1 1", str(self.ep1)) 399 | --------------------------------------------------------------------------------