From 6e1cfe2d38798e28d8e490e8123b79f2085db330 Mon Sep 17 00:00:00 2001 From: John White Date: Wed, 24 Jan 2018 16:31:13 -0600 Subject: [PATCH 001/166] Remove existing build config files. --- MANIFEST.in | 3 - Makefile | 64 ------- Pipfile | 40 ----- Pipfile.lock | 296 ------------------------------- docs/cli.rst | 2 +- package_meta.py | 87 --------- pylintrc | 450 ----------------------------------------------- requirements.txt | 62 ------- setup.cfg | 64 ------- setup.py | 15 -- steem/steemd.py | 4 +- 11 files changed, 3 insertions(+), 1084 deletions(-) delete mode 100644 MANIFEST.in delete mode 100644 Makefile delete mode 100644 Pipfile delete mode 100644 Pipfile.lock delete mode 100644 package_meta.py delete mode 100644 pylintrc delete mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 1cb0cd7..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include package_meta.py -include Pipfile -include Pipfile.lock diff --git a/Makefile b/Makefile deleted file mode 100644 index aa5ff10..0000000 --- a/Makefile +++ /dev/null @@ -1,64 +0,0 @@ -ROOT_DIR := . -DOCS_DIR := $(ROOT_DIR)/docs -DOCS_BUILD_DIR := $(DOCS_DIR)/_build - -PROJECT_NAME := steem-python -PROJECT_DOCKER_TAG := steemit/$(PROJECT_NAME) - - -default: install-global - -.PHONY: docker-image build-without-docker test-without-lint test-pylint test-without-build install-pipenv install-global clean clean-build - -docker-image: clean - docker build -t $(PROJECT_DOCKER_TAG) . - -Pipfile.lock: Pipfile - pipenv lock --three --hashes - -requirements.txt: Pipfile.lock - pipenv lock -r >requirements.txt - -build-without-docker: requirements.txt Pipfile.lock - mkdir -p build/wheel - pipenv install --three --dev - pipenv run python3.6 scripts/doc_rst_convert.py - pipenv run python3.6 setup.py build - rm README.rst - -dockerised-test: docker-image - docker run -ti $(PROJECT_DOCKER_TAG) make -C /buildroot/src build-without-docker install-pipenv test-without-build - -test: build-without-docker test-without-build - -test-without-build: test-without-lint test-pylint - -test-without-lint: - pipenv run pytest -v - -test-pylint: - pipenv run pytest -v --pylint - -clean: clean-build clean-pyc - rm -rf requirements.txt - -clean-build: - rm -fr build/ dist/ *.egg-info .eggs/ .tox/ __pycache__/ .cache/ .coverage htmlcov src - -clean-pyc: - find . -name '*.pyc' -exec rm -f {} + - find . -name '*.pyo' -exec rm -f {} + - find . -name '*~' -exec rm -f {} + - -install-pipenv: clean - pipenv run pip3.6 install -e . - -install-global: clean - python3.6 scripts/doc_rst_convert.py - pip3.6 install -e . - -pypi: - python3.6 scripts/doc_rst_convert.py - python3.6 setup.py bdist_wheel --universal - python3.6 setup.py sdist bdist_wheel upload - rm README.rst diff --git a/Pipfile b/Pipfile deleted file mode 100644 index e1ffe1e..0000000 --- a/Pipfile +++ /dev/null @@ -1,40 +0,0 @@ -[[source]] -verify_ssl = true -url = "https://pypi.python.org/simple" - -[requires] -python_version = "3.6" - -[packages] -appdirs = "*" -ecdsa = "*" -pylibscrypt = "*" -scrypt = "*" -pycrypto = "*" -requests = "*" -urllib3 = "*" -certifi = "*" -ujson = "*" -w3lib = "*" -maya = "*" -toolz = "*" -funcy = "*" -langdetect = "*" -diff-match-patch = "*" -voluptuous = "*" -PrettyTable = "*" -pipfile = "*" - -[dev-packages] -pep8 = "*" -pytest = "*" -pytest-pylint = "*" -pypandoc = "*" -recommonmark = "*" -sphinxcontrib-programoutput = "*" -sphinxcontrib-restbuilder = "*" -yapf = "*" -autopep8 = "*" -setuptools = "*" -wheel = "*" -Sphinx = "*" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index ce80ccb..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,296 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "ac976db966433db818770e2f7ae10be65b8e28806d9e2e2ce43ec7dadf30d256" - }, - "requires": { - "python_version": "3.6" - }, - "sources": [ - { - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "appdirs": { - "hash": "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e", - "version": "==1.4.3" - }, - "certifi": { - "hash": "sha256:54a07c09c586b0e4c619f02a5e94e36619da8e2b053e20f594348c0611803704", - "version": "==2017.7.27.1" - }, - "chardet": { - "hash": "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", - "version": "==3.0.4" - }, - "dateparser": { - "hash": "sha256:e2629d2f8361722c6047138ca085256c9f2cf5cc657fd66122aa0564afa4dc33", - "version": "==0.6.0" - }, - "diff-match-patch": { - "hash": "sha256:9dba5611fbf27893347349fd51cc1911cb403682a7163373adacc565d11e2e4c", - "version": "==20121119" - }, - "ecdsa": { - "hash": "sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c", - "version": "==0.13" - }, - "funcy": { - "hash": "sha256:a33ea9ccdc5d81ad68caff20d3b0cc232b15de5574d22c239784facaf567a9bc", - "version": "==1.9.1" - }, - "humanize": { - "hash": "sha256:a43f57115831ac7c70de098e6ac46ac13be00d69abbf60bdcac251344785bb19", - "version": "==0.5.1" - }, - "idna": { - "hash": "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", - "version": "==2.6" - }, - "langdetect": { - "hash": "sha256:91a170d5f0ade380db809b3ba67f08e95fe6c6c8641f96d67a51ff7e98a9bf30", - "version": "==1.0.7" - }, - "maya": { - "hash": "sha256:b22d22837f921b8cbe884b6a288d9f795c58d9d9165e4a9ff80df102914e2e49", - "version": "==0.3.3" - }, - "pendulum": { - "hash": "sha256:ea71a4a8f667a986aeb7621f6d240c81d16680e87df324a5aed6cb33e16e55ec", - "version": "==1.3.0" - }, - "pipfile": { - "hash": "sha256:f7d9f15de8b660986557eb3cc5391aa1a16207ac41bc378d03f414762d36c984", - "version": "==0.0.2" - }, - "prettytable": { - "hash": "sha256:a53da3b43d7a5c229b5e3ca2892ef982c46b7923b51e98f0db49956531211c4f", - "version": "==0.7.2" - }, - "pycrypto": { - "hash": "sha256:f2ce1e989b272cfcb677616763e0a2e7ec659effa67a88aa92b3a65528f60a3c", - "version": "==2.6.1" - }, - "pylibscrypt": { - "hash": "sha256:b389df5760df03fa5fdda3be7738cc66f29e4edaaba695f1f44e5815f9f4b484", - "version": "==1.6.1" - }, - "python-dateutil": { - "hash": "sha256:95511bae634d69bc7329ba55e646499a842bc4ec342ad54a8cdb65645a0aad3c", - "version": "==2.6.1" - }, - "pytz": { - "hash": "sha256:d1d6729c85acea5423671382868627129432fba9a89ecbb248d8d1c7a9f01c67", - "version": "==2017.2" - }, - "pytzdata": { - "hash": "sha256:bd19fd653f89e498f1d4f9390d96456ce26ecd293a5e7405120a5de875d3314c", - "version": "==2017.2.2" - }, - "regex": { - "hash": "sha256:80166c9e21c0171c7b502035f3ba25f43b5122def387ca6ba9706b6892fed7aa", - "version": "==2017.09.23" - }, - "requests": { - "hash": "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", - "version": "==2.18.4" - }, - "ruamel.yaml": { - "hash": "sha256:d92d90c9bc0945223e47223a67808dd97ac9390ed914cc6871479b7ba489e607", - "version": "==0.15.34" - }, - "scrypt": { - "hash": "sha256:d4a5a4f53450b8ef629bbf1ee4be6105c69936e49b3d8bc621ac2287f0c86020", - "version": "==0.8.0" - }, - "six": { - "hash": "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", - "version": "==1.11.0" - }, - "toml": { - "hash": "sha256:e1e8c220046889234df5ec688d6f97b734fc4a08a6d8edfc176f4e6abf90cfb5", - "version": "==0.9.3.1" - }, - "toolz": { - "hash": "sha256:4a13c90c426001d6299c5568cf5b98e095df9c985df194008a67f84ef4fc6c50", - "version": "==0.8.2" - }, - "tzlocal": { - "hash": "sha256:05a2908f7fb1ba8843f03b2360d6ad314dbf2bce4644feb702ccd38527e13059", - "version": "==1.4" - }, - "ujson": { - "hash": "sha256:f66073e5506e91d204ab0c614a148d5aa938bdbf104751be66f8ad7a222f5f86", - "version": "==1.35" - }, - "urllib3": { - "hash": "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", - "version": "==1.22" - }, - "voluptuous": { - "hash": "sha256:7a7466f8dc3666a292d186d1d871a47bf2120836ccb900d5ba904674957a2396", - "version": "==0.10.5" - }, - "w3lib": { - "hash": "sha256:7b661935805b7d39afe90bb32dec8e4d20b377e74c66597eb1ddfad10071938e", - "version": "==1.18.0" - } - }, - "develop": { - "alabaster": { - "hash": "sha256:2eef172f44e8d301d25aff8068fddd65f767a3f04b5f15b0f4922f113aa1c732", - "version": "==0.7.10" - }, - "astroid": { - "hash": "sha256:39a21dd2b5d81a6731dc0ac2884fa419532dffd465cdd43ea6c168d36b76efb3", - "version": "==1.5.3" - }, - "autopep8": { - "hash": "sha256:eb1685527355809967a0363572289303dc05f4b05edbeee4c9051762103e0ee6", - "version": "==1.3.2" - }, - "babel": { - "hash": "sha256:f20b2acd44f587988ff185d8949c3e208b4b3d5d20fcab7d91fe481ffa435528", - "version": "==2.5.1" - }, - "certifi": { - "hash": "sha256:54a07c09c586b0e4c619f02a5e94e36619da8e2b053e20f594348c0611803704", - "version": "==2017.7.27.1" - }, - "chardet": { - "hash": "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", - "version": "==3.0.4" - }, - "commonmark": { - "hash": "sha256:34d73ec8085923c023930dfc0bcd1c4286e28a2a82de094bb72fabcc0281cbe5", - "version": "==0.5.4" - }, - "docutils": { - "hash": "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", - "version": "==0.14" - }, - "idna": { - "hash": "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", - "version": "==2.6" - }, - "imagesize": { - "hash": "sha256:6ebdc9e0ad188f9d1b2cdd9bc59cbe42bf931875e829e7a595e6b3abdc05cdfb", - "version": "==0.7.1" - }, - "isort": { - "hash": "sha256:cd5d3fc2c16006b567a17193edf4ed9830d9454cbeb5a42ac80b36ea00c23db4", - "version": "==4.2.15" - }, - "jinja2": { - "hash": "sha256:2231bace0dfd8d2bf1e5d7e41239c06c9e0ded46e70cc1094a0aa64b0afeb054", - "version": "==2.9.6" - }, - "lazy-object-proxy": { - "hash": "sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d", - "version": "==1.3.1" - }, - "markupsafe": { - "hash": "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665", - "version": "==1.0" - }, - "mccabe": { - "hash": "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "version": "==0.6.1" - }, - "pep8": { - "hash": "sha256:4fc2e478addcf17016657dff30b2d8d611e8341fac19ccf2768802f6635d7b8a", - "version": "==1.7.0" - }, - "pip": { - "hash": "sha256:690b762c0a8460c303c089d5d0be034fb15a5ea2b75bdf565f40421f542fefb0", - "version": "==9.0.1" - }, - "py": { - "hash": "sha256:2ccb79b01769d99115aa600d7eed99f524bf752bba8f041dc1c184853514655a", - "version": "==1.4.34" - }, - "pycodestyle": { - "hash": "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9", - "version": "==2.3.1" - }, - "pygments": { - "hash": "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", - "version": "==2.2.0" - }, - "pylint": { - "hash": "sha256:948679535a28afc54afb9210dabc6973305409042ece8e5768ca1409910c1ed8", - "version": "==1.7.4" - }, - "pypandoc": { - "hash": "sha256:e914e6d5f84a76764887e4d909b09d63308725f0cbb5293872c2c92f07c11a5b", - "version": "==1.4" - }, - "pytest": { - "hash": "sha256:81a25f36a97da3313e1125fce9e7bbbba565bc7fec3c5beb14c262ddab238ac1", - "version": "==3.2.3" - }, - "pytest-pylint": { - "hash": "sha256:ec63f7c4c05331654ab54fda8e68b8a11512009d506a8e35ee9b6d40a359356d", - "version": "==0.7.1" - }, - "pytz": { - "hash": "sha256:d1d6729c85acea5423671382868627129432fba9a89ecbb248d8d1c7a9f01c67", - "version": "==2017.2" - }, - "recommonmark": { - "hash": "sha256:cd8bf902e469dae94d00367a8197fb7b81fcabc9cfb79d520e0d22d0fbeaa8b7", - "version": "==0.4.0" - }, - "requests": { - "hash": "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", - "version": "==2.18.4" - }, - "setuptools": { - "hash": "sha256:ef824aefbd20dc364891836b75a19386dcf2f4235bf7d80531a8517ab29d0602", - "version": "==36.5.0" - }, - "six": { - "hash": "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", - "version": "==1.11.0" - }, - "snowballstemmer": { - "hash": "sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89", - "version": "==1.2.1" - }, - "sphinx": { - "hash": "sha256:3e70eb94f7e81b47e0545ebc26b758193b6c8b222e152ded99b9c972e971c731", - "version": "==1.6.4" - }, - "sphinxcontrib-programoutput": { - "hash": "sha256:bd47ff0e1cddec82e1d4501f6f0fa3f77481765fcc7c58ec685ef05b44386c40", - "version": "==0.11" - }, - "sphinxcontrib-restbuilder": { - "hash": "sha256:8f2d7d73930fdedc3571adab32fbe843b4716829a291dbb27bab56b7c8d1e23d", - "version": "==0.1" - }, - "sphinxcontrib-websupport": { - "hash": "sha256:f4932e95869599b89bf4f80fc3989132d83c9faa5bf633e7b5e0c25dffb75da2", - "version": "==1.0.1" - }, - "urllib3": { - "hash": "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", - "version": "==1.22" - }, - "wheel": { - "hash": "sha256:e721e53864f084f956f40f96124a74da0631ac13fbbd1ba99e8e2b5e9cafdf64", - "version": "==0.30.0" - }, - "wrapt": { - "hash": "sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6", - "version": "==1.10.11" - }, - "yapf": { - "hash": "sha256:b6a47545511839861ae92108476c119de27a4b137f380f2fd452bcc39bcd6c31", - "version": "==0.18.0" - } - } -} diff --git a/docs/cli.rst b/docs/cli.rst index 33666c4..a4c32b3 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -17,7 +17,7 @@ You can change the password via `changewalletpassphrase` command. steempy changewalletpassphrase -From this point on, every time an action requires your private keys, you will be prompted ot enter +From this point on, every time an action requires your private keys, you will be prompted to enter this password (from CLI as well as while using `steem` library). To bypass password entry, you can set an environmnet variable ``UNLOCK``. diff --git a/package_meta.py b/package_meta.py deleted file mode 100644 index 2a548de..0000000 --- a/package_meta.py +++ /dev/null @@ -1,87 +0,0 @@ -""" Some magic to grab requirements and stuff from Pipfile.lock (if it exists) - If not found, grabs from requirements.txt, if not found there grabs from Pipfile - Pipfile is loaded first, then Pipfile.lock or requirements.txt can override - - Sticking it all here means end users don't need to have Pipfile and pipenv installed -""" -import json -import os - -from setuptools.config import read_configuration - -import configparser - -from pip.req import parse_requirements -from pip.download import PipSession - -global required_python_ver -global default_requirements -global dev_requirements -global source_code_path -global pipfile_lock_data -global pipfile_path -global lockfile_path -global requires_file_path -global pipfile_data -global setup_cfg_path -global setup_cfg_data -global package_name -global package_ver - -source_code_path = os.path.dirname(os.path.realpath(__file__)) -pipfile_path = '%s/Pipfile' % source_code_path -lockfile_path = '%s/Pipfile.lock' % source_code_path -requires_file_path = '%s/requirements.txt' % source_code_path -setup_cfg_path = '%s/setup.cfg' % source_code_path - -setup_cfg_data = read_configuration(setup_cfg_path,ignore_option_errors=True) - -pipfile_data = configparser.ConfigParser() - -pipfile_data.read(pipfile_path) - -required_python_ver = pipfile_data['requires'].get('python_version','3.5') -default_requirements = [] -dev_requirements = [] - -for package_name in pipfile_data['packages']: - package_ver = pipfile_data['packages'].get(package_name) - if package_ver=='*': - default_requirements.append(package_name) - else: - default_requirements.append('%s==%s' % (package_name,str(package_ver))) - -for package_name in pipfile_data['dev-packages']: - package_ver = pipfile_data['packages'].get(package_name) - if package_ver==None: - dev_requirements.append(package_name) - else: - dev_requirements.append('%s==%s' % (package_name,str(package_ver))) - -if os.path.exists(lockfile_path): - with open(lockfile_path) as lockfile: - lockfile_data = json.load(lockfile) - - required_python_ver = lockfile_data['_meta']['requires']['python_version'] - default_requirements = [] - for k,v in lockfile_data['default'].items(): - default_requirements.append('%s%s' % (k,v['version'])) -else: - required_python_ver = '3.6' - default_requirements = [str(ir.req) for ir in parse_requirements(requires_file_path,session=PipSession())] - -required_python_major,required_python_minor = [int(x) for x in required_python_ver.split('.')[:2]] - - -package_name = setup_cfg_data['metadata']['name'] -package_ver = setup_cfg_data['metadata']['version'] - - -if __name__=='__main__': - print('Required python version: %s' % required_python_ver) - print('Required default packages:') - for package_name in default_requirements: - print('\t%s' % package_name) - print('Required development packages:') - for package_name in dev_requirements: - print('\t%s' % package_name) diff --git a/pylintrc b/pylintrc deleted file mode 100644 index ab7e73b..0000000 --- a/pylintrc +++ /dev/null @@ -1,450 +0,0 @@ -[MASTER] - -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS,deploy,setup.py,docs - - -# Pickle collected data for later comparisons. -persistent=yes - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Use multiple processes to speed up Pylint. -jobs=1 - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - -# Allow optimization of some AST trees. This will activate a peephole AST -# optimizer, which will apply various small optimizations. For instance, it can -# be used to obtain the result of joining multiple strings with the addition -# operator. Joining a lot of strings can lead to a maximum recursion error in -# Pylint and this flag can prevent that. It has one side effect, the resulting -# AST will be different than the one from reality. -optimize-ast=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -disable= - # disabled by me, - broad-except, - locally-disabled, - missing-docstring, - line-too-long, - pointless-string-statement, - bad-continuation, - cyclic-import, - duplicate-code, - fixme, - # disabled by default, - import-star-module-level, - old-octal-literal, - oct-method, - print-statement, - unpacking-in-except, - parameter-unpacking, - backtick, - old-raise-syntax, - old-ne-operator, - long-suffix, - dict-view-method, - dict-iter-method, - metaclass-assignment, - next-method-called, - raising-string, - indexing-exception, - raw_input-builtin, - long-builtin, - file-builtin, - execfile-builtin, - coerce-builtin, - cmp-builtin, - buffer-builtin, - basestring-builtin, - apply-builtin, - filter-builtin-not-iterating, - using-cmp-argument, - useless-suppression, - range-builtin-not-iterating, - suppressed-message, - no-absolute-import, - old-division, - cmp-method, - reload-builtin, - zip-builtin-not-iterating, - intern-builtin, - unichr-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - input-builtin, - round-builtin, - hex-method, - nonzero-method, - map-builtin-not-iterating, - - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -files-output=no - -# Tells whether to display a full report or only the messages -reports=yes - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=100 - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=yes - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - -# Maximum number of lines in a module -#max-module-lines=1000 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - - - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[BASIC] - -# List of builtins function names that should not be used, separated by a comma -bad-functions=input - -# Good variable names which should always be accepted, separated by a comma -good-names=i,e,s,_,fd,fp,db,q,f,tx,to,b,bn, s3, id, op, cp, x, k, v, kv,j - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# Regular expression matching correct function names -# original: -#function-rgx=[a-z_][a-z0-9_]{2,30}$ -function-rgx=[a-zA-Z_][a-zA-Z0-9_]{2,40}$ - -# Naming hint for function names -function-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for variable names -variable-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct constant names -# original: -#const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ -const-rgx=(([a-zA-Z_][a-zA-Z0-9_]*)|(__.*__))$ - -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression matching correct attribute names -attr-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for attribute names -attr-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct argument names -argument-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for argument names -argument-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct class attribute names -# original: -#class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,40}|(__.*__))$ - -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct class names -# original: -#class-rgx=[A-Z_][a-zA-Z0-9]+$ -class-rgx=[a-zA-Z_][a-zA-Z0-9]+$ - -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression matching correct method names -# original: -#method-rgx=[a-z_][a-z0-9_]{2,30}$ -method-rgx=[a-zA-Z_][a-zA-Z0-9_]{2,40}$ - -# Naming hint for method names -method-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - - -[ELIF] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - - -[TYPECHECK] - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# List of classes names for which member attributes should not be checked -# (useful for classes with attributes dynamically set). This supports can work -# with qualified names. -ignored-classes= - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_$|dummy - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=5 - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of statements in function / method body -max-statements=50 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,TERMIOS,Bastion,rexec - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 6a1b0de..0000000 --- a/requirements.txt +++ /dev/null @@ -1,62 +0,0 @@ -appdirs==1.4.3 -certifi==2017.7.27.1 -chardet==3.0.4 -dateparser==0.6.0 -diff-match-patch==20121119 -ecdsa==0.13 -funcy==1.9.1 -humanize==0.5.1 -idna==2.6 -langdetect==1.0.7 -maya==0.3.3 -pendulum==1.3.0 -pipfile==0.0.2 -prettytable==0.7.2 -pycrypto==2.6.1 -pylibscrypt==1.6.1 -python-dateutil==2.6.1 -pytz==2017.2 -pytzdata==2017.2.2 -regex==2017.09.23 -requests==2.18.4 -ruamel.yaml==0.15.34 -scrypt==0.8.0 -six==1.11.0 -toml==0.9.3.1 -toolz==0.8.2 -tzlocal==1.4 -ujson==1.35 -urllib3==1.22 -voluptuous==0.10.5 -w3lib==1.18.0 -alabaster==0.7.10 -astroid==1.5.3 -autopep8==1.3.2 -babel==2.5.1 -commonmark==0.5.4 -docutils==0.14 -imagesize==0.7.1 -isort==4.2.15 -jinja2==2.9.6 -lazy-object-proxy==1.3.1 -markupsafe==1.0 -mccabe==0.6.1 -pep8==1.7.0 -pip==9.0.1 -py==1.4.34 -pycodestyle==2.3.1 -pygments==2.2.0 -pylint==1.7.4 -pypandoc==1.4 -pytest==3.2.3 -pytest-pylint==0.7.1 -recommonmark==0.4.0 -setuptools==36.5.0 -snowballstemmer==1.2.1 -sphinx==1.6.4 -sphinxcontrib-programoutput==0.11 -sphinxcontrib-restbuilder==0.1 -sphinxcontrib-websupport==1.0.1 -wheel==0.30.0 -wrapt==1.10.11 -yapf==0.18.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index c328e2c..0000000 --- a/setup.cfg +++ /dev/null @@ -1,64 +0,0 @@ -[metadata] -name = steem -version = 0.18.103 -description = Official python steem library -long_description = file: README.rst -keywords = steem, steemit, cryptocurrency, blockchain -license = MIT -classifiers = - Intended Audience :: Developers - License :: OSI Approved :: MIT License - Natural Language :: English - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.5 - Topic :: Software Development :: Libraries - Topic :: Software Development :: Libraries :: Python Modules - Development Status :: 4 - Beta - -[options] -packages = find: -scripts = - bin/steempy - bin/piston - bin/steemtail - -[options.packages.find] -exclude = scripts - -[aliases] -test=pytest - -[build_sphinx] -source-dir = docs/ -build-dir = docs/_build -all_files = 1 - - -[pycodestyle] -# formerly pep8 -ignore = E501 - - -[pep8] -# backwards compat -ignore = E501 - - -[style] -# google yapf config - - -[tool:pytest] -norecursedirs=dist docs build .tox deploy -addopts = - -[bdist_wheel] -universal=1 - - -[coverage:run] -branch = True -source = steem - -[coverage:xml] -output = coverage.xml diff --git a/setup.py b/setup.py deleted file mode 100644 index bfc6a5a..0000000 --- a/setup.py +++ /dev/null @@ -1,15 +0,0 @@ -# coding=utf-8 -import os -import sys - -from setuptools import find_packages -from setuptools import setup - -import package_meta # this is a simple tool for loading metadata from Pipfile and other places - -assert sys.version_info[0] == package_meta.required_python_major and sys.version_info[1] >= package_meta.required_python_minor, "%s requires Python %s or newer" % (package_meta.package_name, package_meta.required_python_ver) - -# yapf: disable -setup(setup_requires=package_meta.dev_requirements, - install_requires=package_meta.default_requirements, - tests_require=package_meta.default_requirements+package_meta.dev_requirements) diff --git a/steem/steemd.py b/steem/steemd.py index 880803b..3e161cb 100644 --- a/steem/steemd.py +++ b/steem/steemd.py @@ -50,8 +50,8 @@ class Steemd(HttpClient): def __init__(self, nodes=None, **kwargs): if not nodes: - nodes = get_config_node_list() or ['https://steemd.steemit.com'] - + nodes = get_config_node_list() or ['https://api.steemit.com'] + super(Steemd, self).__init__(nodes, **kwargs) @property From 2ce14b88fdb200d7dc6c066cb9a907b3811abb52 Mon Sep 17 00:00:00 2001 From: John White Date: Wed, 24 Jan 2018 17:18:37 -0600 Subject: [PATCH 002/166] Added basic setup files. --- setup.cfg | 2 ++ setup.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..4489b56 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +description-file=README.md \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..1973130 --- /dev/null +++ b/setup.py @@ -0,0 +1,18 @@ +from distutils.core import setup +setup( + name='steem', + version='0.18.103', + description='Official Python Steem Library', + # long_description = file: README.rst + keywords=['steem', 'steemit', 'cryptocurrency', 'blockchain'], + license='MIT', + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Natural Language :: English', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Topic :: Software Development :: Libraries', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Development Status :: 4 - Beta'] +) From abaa285905f89957838eadf4a4a8321fb968507d Mon Sep 17 00:00:00 2001 From: John White Date: Wed, 24 Jan 2018 18:05:10 -0600 Subject: [PATCH 003/166] Updated gitignore and setup.py for distribution. --- .gitignore | 2 ++ setup.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 901860e..e8119b7 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,5 @@ tests/failed_blocks/ /tests/envdir-to-envfile.sh /deploy/ /test.py + +.DS_Store \ No newline at end of file diff --git a/setup.py b/setup.py index 1973130..a97eca6 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,9 @@ # long_description = file: README.rst keywords=['steem', 'steemit', 'cryptocurrency', 'blockchain'], license='MIT', + url='https://github.com/steemit/steem-python', + maintainer='steemit_inc', + maintainer_email='john@steemit.com', classifiers=[ 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', From 69502a3eb75afaeb6c431d89a5e3819d8c2169dc Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Thu, 25 Jan 2018 00:31:14 -0600 Subject: [PATCH 004/166] add console scripts entrypoint, add modules --- setup.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/setup.py b/setup.py index a97eca6..98959b8 100644 --- a/setup.py +++ b/setup.py @@ -9,6 +9,14 @@ url='https://github.com/steemit/steem-python', maintainer='steemit_inc', maintainer_email='john@steemit.com', + py_modules=[ + 'steem', + 'steem.cli', + 'steem.steem', + ], + entry_points = { + 'console_scripts': ['steempy=steem.cli:legacy'], + }, classifiers=[ 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', From 8f7461e53bd00622207d00274e9e66b003516b43 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Thu, 25 Jan 2018 00:31:39 -0600 Subject: [PATCH 005/166] rename steemd.exec to steemd.call because exec is reserved word --- steem/blockchain.py | 4 +- steem/steem.py | 2 +- steem/steemd.py | 194 ++++++++++++++++++++++---------------------- steem/wallet.py | 2 +- 4 files changed, 101 insertions(+), 101 deletions(-) diff --git a/steem/blockchain.py b/steem/blockchain.py index 077a080..d13d266 100644 --- a/steem/blockchain.py +++ b/steem/blockchain.py @@ -114,8 +114,8 @@ def reliable_query(_client,_method,_api,*_args): # this will ALWAYS eventually return, at all costs retval = None while retval is None: - try: - retval = _client.exec(_method,*_args,api=_api) + try: + retval = _client.call(_method,*_args,api=_api) except Exception as e: logger.info('Failed to get response', extra=dict(exc=e,response=retval,api_name=_api,api_method=_method,api_args=_args)) retval = None diff --git a/steem/steem.py b/steem/steem.py index ef14f95..f351602 100644 --- a/steem/steem.py +++ b/steem/steem.py @@ -58,7 +58,7 @@ def __getattr__(self, item): if hasattr(self.commit, item): return getattr(self.commit, item) if item.endswith("_api"): - return Steem.Api(api_name=item, exec_method=self.steemd.exec) + return Steem.Api(api_name=item, exec_method=self.steemd.call) raise AttributeError('Steem has no attribute "%s"' % item) diff --git a/steem/steemd.py b/steem/steemd.py index 3e161cb..36fcf3e 100644 --- a/steem/steemd.py +++ b/steem/steemd.py @@ -154,7 +154,7 @@ def get_account(self, account: str): dict: Account information. """ - return first(self.exec('get_accounts', [account])) + return first(self.call('get_accounts', [account])) def get_all_usernames(self, last_user=''): """ Fetch the full list of STEEM usernames. """ @@ -223,16 +223,16 @@ def get_blocks_range(self, start: int, end: int): # steemd api generated methods # ################################ # def set_subscribe_callback(self, callback: object, clear_filter: object): - # return self.exec('set_subscribe_callback', callback, clear_filter, api='database_api') + # return self.call('set_subscribe_callback', callback, clear_filter, api='database_api') # # def set_pending_transaction_callback(self, callback: object): - # return self.exec('set_pending_transaction_callback', callback, api='database_api') + # return self.call('set_pending_transaction_callback', callback, api='database_api') # # def set_block_applied_callback(self, callback: object): - # return self.exec('set_block_applied_callback', callback, api='database_api') + # return self.call('set_block_applied_callback', callback, api='database_api') # # def cancel_all_subscriptions(self): - # return self.exec('cancel_all_subscriptions', api='database_api') + # return self.call('cancel_all_subscriptions', api='database_api') def get_reward_fund(self, fund_name: str = 'post'): """ Get details for a reward fund. @@ -257,75 +257,75 @@ def get_reward_fund(self, fund_name: str = 'post'): 'reward_balance': '555660.895 STEEM'} """ - return self.exec('get_reward_fund', fund_name, api='database_api') + return self.call('get_reward_fund', fund_name, api='database_api') def get_expiring_vesting_delegations(self, account: str, start: PointInTime, limit: int): """ get_expiring_vesting_delegations """ - return self.exec('get_expiring_vesting_delegations', account, start, limit, api='database_api') + return self.call('get_expiring_vesting_delegations', account, start, limit, api='database_api') def get_trending_tags(self, after_tag: str, limit: int): """ get_trending_tags """ - return self.exec('get_trending_tags', after_tag, limit, api='database_api') + return self.call('get_trending_tags', after_tag, limit, api='database_api') def get_tags_used_by_author(self, account: str): """ get_tags_used_by_author """ - return self.exec('get_tags_used_by_author', account, api='database_api') + return self.call('get_tags_used_by_author', account, api='database_api') def get_discussions_by_trending(self, discussion_query: dict): """ get_discussions_by_trending """ - return self.exec('get_discussions_by_trending', discussion_query, api='database_api') + return self.call('get_discussions_by_trending', discussion_query, api='database_api') def get_comment_discussions_by_payout(self, discussion_query: dict): """ get_comment_discussions_by_payout """ - return self.exec('get_comment_discussions_by_payout', discussion_query, api='database_api') + return self.call('get_comment_discussions_by_payout', discussion_query, api='database_api') def get_post_discussions_by_payout(self, discussion_query: dict): """ get_post_discussions_by_payout """ - return self.exec('get_post_discussions_by_payout', discussion_query, api='database_api') + return self.call('get_post_discussions_by_payout', discussion_query, api='database_api') def get_discussions_by_created(self, discussion_query: dict): """ get_discussions_by_created """ - return self.exec('get_discussions_by_created', discussion_query, api='database_api') + return self.call('get_discussions_by_created', discussion_query, api='database_api') def get_discussions_by_active(self, discussion_query: dict): """ get_discussions_by_active """ - return self.exec('get_discussions_by_active', discussion_query, api='database_api') + return self.call('get_discussions_by_active', discussion_query, api='database_api') def get_discussions_by_cashout(self, discussion_query: dict): """ get_discussions_by_cashout """ - return self.exec('get_discussions_by_cashout', discussion_query, api='database_api') + return self.call('get_discussions_by_cashout', discussion_query, api='database_api') def get_discussions_by_payout(self, discussion_query: dict): """ get_discussions_by_payout """ - return self.exec('get_discussions_by_payout', discussion_query, api='database_api') + return self.call('get_discussions_by_payout', discussion_query, api='database_api') def get_discussions_by_votes(self, discussion_query: dict): """ get_discussions_by_votes """ - return self.exec('get_discussions_by_votes', discussion_query, api='database_api') + return self.call('get_discussions_by_votes', discussion_query, api='database_api') def get_discussions_by_children(self, discussion_query: dict): """ get_discussions_by_children """ - return self.exec('get_discussions_by_children', discussion_query, api='database_api') + return self.call('get_discussions_by_children', discussion_query, api='database_api') def get_discussions_by_hot(self, discussion_query: dict): """ get_discussions_by_hot """ - return self.exec('get_discussions_by_hot', discussion_query, api='database_api') + return self.call('get_discussions_by_hot', discussion_query, api='database_api') def get_discussions_by_feed(self, discussion_query: dict): """ get_discussions_by_feed """ - return self.exec('get_discussions_by_feed', discussion_query, api='database_api') + return self.call('get_discussions_by_feed', discussion_query, api='database_api') def get_discussions_by_blog(self, discussion_query: dict): """ get_discussions_by_blog """ - return self.exec('get_discussions_by_blog', discussion_query, api='database_api') + return self.call('get_discussions_by_blog', discussion_query, api='database_api') def get_discussions_by_comments(self, discussion_query: dict): """ get_discussions_by_comments """ - return self.exec('get_discussions_by_comments', discussion_query, api='database_api') + return self.call('get_discussions_by_comments', discussion_query, api='database_api') def get_discussions_by_promoted(self, discussion_query: dict): """ get_discussions_by_promoted """ - return self.exec('get_discussions_by_promoted', discussion_query, api='database_api') + return self.call('get_discussions_by_promoted', discussion_query, api='database_api') def get_block_header(self, block_num: int): """ Get block headers, given a block number. @@ -350,7 +350,7 @@ def get_block_header(self, block_num: int): 'transaction_merkle_root': '4ddc419e531cccee6da660057d606d11aab9f3a5', 'witness': 'chainsquad.com'} """ - return self.exec('get_block_header', block_num, api='database_api') + return self.call('get_block_header', block_num, api='database_api') def get_block(self, block_num: int): """ Get the full block, transactions and all, given a block number. @@ -391,23 +391,23 @@ def get_block(self, block_num: int): """ - return self.exec('get_block', block_num, api='database_api') + return self.call('get_block', block_num, api='database_api') def get_ops_in_block(self, block_num: int, virtual_only: bool): """ get_ops_in_block """ - return self.exec('get_ops_in_block', block_num, virtual_only, api='database_api') + return self.call('get_ops_in_block', block_num, virtual_only, api='database_api') def get_state(self, path: str): """ get_state """ - return self.exec('get_state', path, api='database_api') + return self.call('get_state', path, api='database_api') def get_config(self): """ Get internal chain configuration. """ - return self.exec('get_config', api='database_api') + return self.call('get_config', api='database_api') def get_dynamic_global_properties(self): """ get_dynamic_global_properties """ - return self.exec('get_dynamic_global_properties', api='database_api') + return self.call('get_dynamic_global_properties', api='database_api') def get_chain_properties(self): """ Get witness elected chain properties. @@ -419,7 +419,7 @@ def get_chain_properties(self): 'sbd_interest_rate': 250} """ - return self.exec('get_chain_properties', api='database_api') + return self.call('get_chain_properties', api='database_api') def get_feed_history(self): """ Get the hourly averages of witness reported STEEM/SBD prices. @@ -435,7 +435,7 @@ def get_feed_history(self): {'base': '0.093 SBD', 'quote': '1.010 STEEM'}, """ - return self.exec('get_feed_history', api='database_api') + return self.call('get_feed_history', api='database_api') def get_current_median_history_price(self): """ Get the average STEEM/SBD price. @@ -447,11 +447,11 @@ def get_current_median_history_price(self): {'base': '0.093 SBD', 'quote': '1.010 STEEM'} """ - return self.exec('get_current_median_history_price', api='database_api') + return self.call('get_current_median_history_price', api='database_api') def get_witness_schedule(self): """ get_witness_schedule """ - return self.exec('get_witness_schedule', api='database_api') + return self.call('get_witness_schedule', api='database_api') def get_hardfork_version(self): """ Get the current version of the chain. @@ -460,26 +460,26 @@ def get_hardfork_version(self): This is not the same as latest minor version. """ - return self.exec('get_hardfork_version', api='database_api') + return self.call('get_hardfork_version', api='database_api') def get_next_scheduled_hardfork(self): """ get_next_scheduled_hardfork """ - return self.exec('get_next_scheduled_hardfork', api='database_api') + return self.call('get_next_scheduled_hardfork', api='database_api') def get_accounts(self, account_names: list): """ Lookup account information such as user profile, public keys, balances, etc. This method is same as ``get_account``, but supports querying for multiple accounts at the time. """ - return self.exec('get_accounts', account_names, api='database_api') + return self.call('get_accounts', account_names, api='database_api') def get_account_references(self, account_id: int): """ get_account_references """ - return self.exec('get_account_references', account_id, api='database_api') + return self.call('get_account_references', account_id, api='database_api') def lookup_account_names(self, account_names: list): """ lookup_account_names """ - return self.exec('lookup_account_names', account_names, api='database_api') + return self.call('lookup_account_names', account_names, api='database_api') def lookup_accounts(self, after: Union[str, int], limit: int) -> List[str]: """Get a list of usernames from all registered accounts. @@ -492,15 +492,15 @@ def lookup_accounts(self, after: Union[str, int], limit: int) -> List[str]: list: List of usernames in requested chunk. """ - return self.exec('lookup_accounts', after, limit, api='database_api') + return self.call('lookup_accounts', after, limit, api='database_api') def get_account_count(self): """ How many accounts are currently registered on STEEM? """ - return self.exec('get_account_count', api='database_api') + return self.call('get_account_count', api='database_api') def get_conversion_requests(self, account: str): """ get_conversion_requests """ - return self.exec('get_conversion_requests', account, api='database_api') + return self.call('get_conversion_requests', account, api='database_api') def get_account_history(self, account: str, index_from: int, limit: int): """ History of all operations for a given account. @@ -579,35 +579,35 @@ def get_account_history(self, account: str, index_from: int, limit: int): """ - return self.exec('get_account_history', account, index_from, limit, api='database_api') + return self.call('get_account_history', account, index_from, limit, api='database_api') def get_owner_history(self, account: str): """ get_owner_history """ - return self.exec('get_owner_history', account, api='database_api') + return self.call('get_owner_history', account, api='database_api') def get_recovery_request(self, account: str): """ get_recovery_request """ - return self.exec('get_recovery_request', account, api='database_api') + return self.call('get_recovery_request', account, api='database_api') def get_escrow(self, from_account: str, escrow_id: int): """ get_escrow """ - return self.exec('get_escrow', from_account, escrow_id, api='database_api') + return self.call('get_escrow', from_account, escrow_id, api='database_api') def get_withdraw_routes(self, account: str, withdraw_route_type: str): """ get_withdraw_routes """ - return self.exec('get_withdraw_routes', account, withdraw_route_type, api='database_api') + return self.call('get_withdraw_routes', account, withdraw_route_type, api='database_api') def get_account_bandwidth(self, account: str, bandwidth_type: object): """ get_account_bandwidth """ - return self.exec('get_account_bandwidth', account, bandwidth_type, api='database_api') + return self.call('get_account_bandwidth', account, bandwidth_type, api='database_api') def get_savings_withdraw_from(self, account: str): """ get_savings_withdraw_from """ - return self.exec('get_savings_withdraw_from', account, api='database_api') + return self.call('get_savings_withdraw_from', account, api='database_api') def get_savings_withdraw_to(self, account: str): """ get_savings_withdraw_to """ - return self.exec('get_savings_withdraw_to', account, api='database_api') + return self.call('get_savings_withdraw_to', account, api='database_api') def get_order_book(self, limit: int): """ Get the internal market order book. @@ -653,11 +653,11 @@ def get_order_book(self, limit: int): """ - return self.exec('get_order_book', limit, api='database_api') + return self.call('get_order_book', limit, api='database_api') def get_open_orders(self, account: str): """ get_open_orders """ - return self.exec('get_open_orders', account, api='database_api') + return self.call('get_open_orders', account, api='database_api') def get_liquidity_queue(self, start_account: str, limit: int): """ Get the liquidity queue. @@ -666,31 +666,31 @@ def get_liquidity_queue(self, start_account: str, limit: int): This feature is currently not in use, and might be deprecated in the future. """ - return self.exec('get_liquidity_queue', start_account, limit, api='database_api') + return self.call('get_liquidity_queue', start_account, limit, api='database_api') def get_transaction_hex(self, signed_transaction: SignedTransaction): """ get_transaction_hex """ - return self.exec('get_transaction_hex', signed_transaction, api='database_api') + return self.call('get_transaction_hex', signed_transaction, api='database_api') def get_transaction(self, transaction_id: str): """ get_transaction """ - return self.exec('get_transaction', transaction_id, api='database_api') + return self.call('get_transaction', transaction_id, api='database_api') def get_required_signatures(self, signed_transaction: SignedTransaction, available_keys: list): """ get_required_signatures """ - return self.exec('get_required_signatures', signed_transaction, available_keys, api='database_api') + return self.call('get_required_signatures', signed_transaction, available_keys, api='database_api') def get_potential_signatures(self, signed_transaction: SignedTransaction): """ get_potential_signatures """ - return self.exec('get_potential_signatures', signed_transaction, api='database_api') + return self.call('get_potential_signatures', signed_transaction, api='database_api') def verify_authority(self, signed_transaction: SignedTransaction): """ verify_authority """ - return self.exec('verify_authority', signed_transaction, api='database_api') + return self.call('verify_authority', signed_transaction, api='database_api') def verify_account_authority(self, account: str, keys: list): """ verify_account_authority """ - return self.exec('verify_account_authority', account, keys, api='database_api') + return self.call('verify_account_authority', account, keys, api='database_api') def get_active_votes(self, author: str, permlink: str): """ Get all votes for the given post. @@ -725,7 +725,7 @@ def get_active_votes(self, author: str, permlink: str): 'voter': 'flourish', 'weight': '2334690471157'}] """ - return self.exec('get_active_votes', author, permlink, api='database_api') + return self.call('get_active_votes', author, permlink, api='database_api') def get_account_votes(self, account: str): """ All votes the given account ever made. @@ -748,15 +748,15 @@ def get_account_votes(self, account: str): """ - return self.exec('get_account_votes', account, api='database_api') + return self.call('get_account_votes', account, api='database_api') def get_content(self, author: str, permlink: str): """ get_content """ - return self.exec('get_content', author, permlink, api='database_api') + return self.call('get_content', author, permlink, api='database_api') def get_content_replies(self, author: str, permlink: str): """ get_content_replies """ - return self.exec('get_content_replies', author, permlink, api='database_api') + return self.call('get_content_replies', author, permlink, api='database_api') def get_discussions_by_author_before_date(self, author: str, @@ -764,146 +764,146 @@ def get_discussions_by_author_before_date(self, before_date: PointInTime, limit: int): """ get_discussions_by_author_before_date """ - return self.exec('get_discussions_by_author_before_date', + return self.call('get_discussions_by_author_before_date', author, start_permlink, before_date, limit, api='database_api') def get_replies_by_last_update(self, account: str, start_permlink: str, limit: int): """ get_replies_by_last_update """ - return self.exec('get_replies_by_last_update', account, start_permlink, limit, api='database_api') + return self.call('get_replies_by_last_update', account, start_permlink, limit, api='database_api') def get_witnesses(self, witness_ids: list): """ get_witnesses """ - return self.exec('get_witnesses', witness_ids, api='database_api') + return self.call('get_witnesses', witness_ids, api='database_api') def get_witness_by_account(self, account: str): """ get_witness_by_account """ - return self.exec('get_witness_by_account', account, api='database_api') + return self.call('get_witness_by_account', account, api='database_api') def get_witnesses_by_vote(self, from_account: str, limit: int): """ get_witnesses_by_vote """ - return self.exec('get_witnesses_by_vote', from_account, limit, api='database_api') + return self.call('get_witnesses_by_vote', from_account, limit, api='database_api') def lookup_witness_accounts(self, from_account: str, limit: int): """ lookup_witness_accounts """ - return self.exec('lookup_witness_accounts', from_account, limit, api='database_api') + return self.call('lookup_witness_accounts', from_account, limit, api='database_api') def get_witness_count(self): """ get_witness_count """ - return self.exec('get_witness_count', api='database_api') + return self.call('get_witness_count', api='database_api') def get_active_witnesses(self): """ Get a list of currently active witnesses. """ - return self.exec('get_active_witnesses', api='database_api') + return self.call('get_active_witnesses', api='database_api') def get_vesting_delegations(self, account: str, from_account: str, limit: int): """ get_vesting_delegations """ - return self.exec('get_vesting_delegations', account, from_account, limit, api='database_api') + return self.call('get_vesting_delegations', account, from_account, limit, api='database_api') def login(self, username: str, password: str): """ login """ - return self.exec('login', username, password, api='login_api') + return self.call('login', username, password, api='login_api') def get_api_by_name(self, api_name: str): """ get_api_by_name """ - return self.exec('get_api_by_name', api_name, api='login_api') + return self.call('get_api_by_name', api_name, api='login_api') def get_version(self): """ Get steemd version of the node currently connected to. """ - return self.exec('get_version', api='login_api') + return self.call('get_version', api='login_api') def get_followers(self, account: str, start_follower: str, follow_type: str, limit: int): """ get_followers """ - return self.exec('get_followers', account, start_follower, follow_type, limit, api='follow_api') + return self.call('get_followers', account, start_follower, follow_type, limit, api='follow_api') def get_following(self, account: str, start_follower: str, follow_type: str, limit: int): """ get_following """ - return self.exec('get_following', account, start_follower, follow_type, limit, api='follow_api') + return self.call('get_following', account, start_follower, follow_type, limit, api='follow_api') def get_follow_count(self, account: str): """ get_follow_count """ - return self.exec('get_follow_count', account, api='follow_api') + return self.call('get_follow_count', account, api='follow_api') def get_feed_entries(self, account: str, entry_id: int, limit: int): """ get_feed_entries """ - return self.exec('get_feed_entries', account, entry_id, limit, api='follow_api') + return self.call('get_feed_entries', account, entry_id, limit, api='follow_api') def get_feed(self, account: str, entry_id: int, limit: int): """ get_feed """ - return self.exec('get_feed', account, entry_id, limit, api='follow_api') + return self.call('get_feed', account, entry_id, limit, api='follow_api') def get_blog_entries(self, account: str, entry_id: int, limit: int): """ get_blog_entries """ - return self.exec('get_blog_entries', account, entry_id, limit, api='follow_api') + return self.call('get_blog_entries', account, entry_id, limit, api='follow_api') def get_blog(self, account: str, entry_id: int, limit: int): """ get_blog """ - return self.exec('get_blog', account, entry_id, limit, api='follow_api') + return self.call('get_blog', account, entry_id, limit, api='follow_api') def get_account_reputations(self, account: str, limit: int): """ get_account_reputations """ - return self.exec('get_account_reputations', account, limit, api='follow_api') + return self.call('get_account_reputations', account, limit, api='follow_api') def get_reblogged_by(self, author: str, permlink: str): """ get_reblogged_by """ - return self.exec('get_reblogged_by', author, permlink, api='follow_api') + return self.call('get_reblogged_by', author, permlink, api='follow_api') def get_blog_authors(self, blog_account: str): """ get_blog_authors """ - return self.exec('get_blog_authors', blog_account, api='follow_api') + return self.call('get_blog_authors', blog_account, api='follow_api') def broadcast_transaction(self, signed_transaction: SignedTransaction): """ broadcast_transaction """ - return self.exec('broadcast_transaction', signed_transaction, api='network_broadcast_api') + return self.call('broadcast_transaction', signed_transaction, api='network_broadcast_api') # def broadcast_transaction_with_callback(self, callback: object, signed_transaction: SignedTransaction): - # return self.exec('broadcast_transaction_with_callback', callback, signed_transaction, + # return self.call('broadcast_transaction_with_callback', callback, signed_transaction, # api='network_broadcast_api') def broadcast_transaction_synchronous(self, signed_transaction: SignedTransaction): """ broadcast_transaction_synchronous """ - return self.exec('broadcast_transaction_synchronous', signed_transaction, api='network_broadcast_api') + return self.call('broadcast_transaction_synchronous', signed_transaction, api='network_broadcast_api') def broadcast_block(self, block: Block): """ broadcast_block """ - return self.exec('broadcast_block', block, api='network_broadcast_api') + return self.call('broadcast_block', block, api='network_broadcast_api') def set_max_block_age(self, max_block_age: int): """ set_max_block_age """ - return self.exec('set_max_block_age', max_block_age, api='network_broadcast_api') + return self.call('set_max_block_age', max_block_age, api='network_broadcast_api') def get_ticker(self): """ Returns the market ticker for the internal SBD:STEEM market. """ - return self.exec('get_ticker', api='market_history_api') + return self.call('get_ticker', api='market_history_api') def get_volume(self): """ Returns the market volume for the past 24 hours. """ - return self.exec('get_volume', api='market_history_api') + return self.call('get_volume', api='market_history_api') # def get_order_book(self, limit: int): - # return self.exec('get_order_book', limit, api='market_history_api') + # return self.call('get_order_book', limit, api='market_history_api') def get_trade_history(self, start: PointInTime, end: PointInTime, limit: int): """ Returns the trade history for the internal SBD:STEEM market. """ - return self.exec('get_trade_history', start, end, limit, api='market_history_api') + return self.call('get_trade_history', start, end, limit, api='market_history_api') def get_recent_trades(self, limit: int) -> List[Any]: """ Returns the N most recent trades for the internal SBD:STEEM market. """ - return self.exec('get_recent_trades', limit, api='market_history_api') + return self.call('get_recent_trades', limit, api='market_history_api') def get_market_history(self, bucket_seconds: int, start: PointInTime, end: PointInTime): """ Returns the market history for the internal SBD:STEEM market. """ - return self.exec('get_market_history', bucket_seconds, start, end, api='market_history_api') + return self.call('get_market_history', bucket_seconds, start, end, api='market_history_api') def get_market_history_buckets(self): """ Returns the bucket seconds being tracked by the plugin. """ - return self.exec('get_market_history_buckets', api='market_history_api') + return self.call('get_market_history_buckets', api='market_history_api') def get_key_references(self, public_keys: List[str]): """ get_key_references """ if type(public_keys) == str: public_keys = [public_keys] - return self.exec('get_key_references', public_keys, api='account_by_key_api') + return self.call('get_key_references', public_keys, api='account_by_key_api') if __name__ == '__main__': diff --git a/steem/wallet.py b/steem/wallet.py index e0675be..0414982 100644 --- a/steem/wallet.py +++ b/steem/wallet.py @@ -319,7 +319,7 @@ def getAccountFromPublicKey(self, pub): # FIXME, this only returns the first associated key. # If the key is used by multiple accounts, this # will surely lead to undesired behavior - names = self.steemd.exec('get_key_references', [pub], api="account_by_key_api")[0] + names = self.steemd.call('get_key_references', [pub], api="account_by_key_api")[0] if not names: return None else: From 876449b20559c34a5e2efcc9b4130afd5679fba2 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Thu, 25 Jan 2018 00:54:58 -0600 Subject: [PATCH 006/166] pep8 --- setup.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index 98959b8..5c5ca53 100644 --- a/setup.py +++ b/setup.py @@ -10,20 +10,19 @@ maintainer='steemit_inc', maintainer_email='john@steemit.com', py_modules=[ - 'steem', - 'steem.cli', - 'steem.steem', + 'steem', + 'steembase', ], - entry_points = { + entry_points={ 'console_scripts': ['steempy=steem.cli:legacy'], }, classifiers=[ - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Topic :: Software Development :: Libraries', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Development Status :: 4 - Beta'] + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Natural Language :: English', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Topic :: Software Development :: Libraries', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Development Status :: 4 - Beta'] ) From 626724401d5da472cb5a2643083d7230edf7dfe4 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Thu, 25 Jan 2018 01:05:16 -0600 Subject: [PATCH 007/166] switch from distutils to setuptools, autofind packages --- setup.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 5c5ca53..48f02d2 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ -from distutils.core import setup +from setuptools import setup, find_packages + setup( name='steem', version='0.18.103', @@ -9,10 +10,7 @@ url='https://github.com/steemit/steem-python', maintainer='steemit_inc', maintainer_email='john@steemit.com', - py_modules=[ - 'steem', - 'steembase', - ], + packages=find_packages(), entry_points={ 'console_scripts': ['steempy=steem.cli:legacy'], }, From 411995b07a71cabe0da929f2037251c9814b9fcd Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Thu, 25 Jan 2018 01:49:50 -0600 Subject: [PATCH 008/166] update readme for clarity --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2ec8776..a0fd8b2 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,30 @@ # Official Python STEEM Library -`steem-python` is the official STEEM library for Python. It comes with a BIP38 encrypted wallet and a practical CLI utility called `steempy`. + +`steem-python` is the official Steem library for Python. It comes with a +BIP38 encrypted wallet and a practical CLI utility called `steempy`. + +Currently only python3 is supported. Python2 support is planned. ## Installation -You can install `steem-python` with `pip`: + +You can install `steem-python` with `pip3`: ``` -pip install -U steem +pip3 install --user steem ``` ## Documentation + Documentation is available at **http://steem.readthedocs.io** ## TODO + +* fix parts that were copied from python-graphenelib that only support + python3 to support python2 as well * more unit-tests * 100% documentation coverage +* migrate to click CLI library ## Notice -This library is under *active development*. Use at own discretion. +This library is *under development*. Beware. From 5ca60c0787053b3e36b4754d344c527c68c26f04 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Thu, 25 Jan 2018 01:49:58 -0600 Subject: [PATCH 009/166] add deps to setup.py --- setup.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/setup.py b/setup.py index 48f02d2..9021243 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,13 @@ maintainer='steemit_inc', maintainer_email='john@steemit.com', packages=find_packages(), + install_requires = [ + 'appdirs', + 'ecdsa', + 'funcy', + 'voluptuous', + 'pycrypto', + ], entry_points={ 'console_scripts': ['steempy=steem.cli:legacy'], }, From 0fddfdc93312919bbab80f2f6d53849446557c1b Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Thu, 25 Jan 2018 01:50:31 -0600 Subject: [PATCH 010/166] remove python3isms, rename reserved word exec to call --- steem/commit.py | 2 +- steem/steemd.py | 2 +- steembase/base58.py | 1 + steembase/http_client.py | 8 ++++---- steembase/operations.py | 8 ++++---- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/steem/commit.py b/steem/commit.py index 8ee9516..383dc7c 100644 --- a/steem/commit.py +++ b/steem/commit.py @@ -812,7 +812,7 @@ def claim_reward_balance(self, ) return self.finalizeOp(op, account, "posting") - def delegate_vesting_shares(self, to_account: str, vesting_shares: str, account=None): + def delegate_vesting_shares(self, to_account, vesting_shares, account=None): """ Delegate SP to another account. Args: diff --git a/steem/steemd.py b/steem/steemd.py index 36fcf3e..3aa7178 100644 --- a/steem/steemd.py +++ b/steem/steemd.py @@ -180,7 +180,7 @@ def _get_blocks(self, blocks: Union[List[int], Set[int]]): A generator with results. """ - results = self.exec_multi_with_futures('get_block', blocks, max_workers=10) + results = self.call_multi_with_futures('get_block', blocks, max_workers=10) return ({**x, 'block_num': int(x['block_id'][:8], base=16)} for x in results if x) def get_blocks(self, block_nums: List[int]): diff --git a/steembase/base58.py b/steembase/base58.py index e98357a..e1e8772 100644 --- a/steembase/base58.py +++ b/steembase/base58.py @@ -6,6 +6,7 @@ log = logging.getLogger(__name__) """ This class and the methods require python3 """ +# FIXME this library needs to support both 2 and 3 assert sys.version_info[0] == 3, "graphenelib requires python3" """ Default Prefix """ diff --git a/steembase/http_client.py b/steembase/http_client.py index d77b724..0caa706 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -32,13 +32,13 @@ class HttpClient(object): rpc = HttpClient(['https://steemd-node1.com', 'https://steemd-node2.com']) any call available to that port can be issued using the instance - via the syntax ``rpc.exec('command', *parameters)``. + via the syntax ``rpc.call('command', *parameters)``. Example: .. code-block:: python - rpc.exec( + rpc.call( 'get_followers', 'furion', 'abit', 'blog', 10, api='follow_api' @@ -134,7 +134,7 @@ def json_rpc_body(name, *args, api=None, as_json=True, _id=0, kwargs=None): else: return body_dict - def exec(self, name, *args, api=None, return_with_args=None, _ret_cnt=0, kwargs=None): + def call(self, name, *args, api=None, return_with_args=None, _ret_cnt=0, kwargs=None): """ Execute a method against steemd RPC. Warnings: @@ -215,7 +215,7 @@ def _return(self, response=None, args=None, return_with_args=None): else: return result - def exec_multi_with_futures(self, name, params, api=None, max_workers=None): + def call_multi_with_futures(self, name, params, api=None, max_workers=None): with concurrent.futures.ThreadPoolExecutor( max_workers=max_workers) as executor: # Start the load operations and mark each future with its URL diff --git a/steembase/operations.py b/steembase/operations.py index b7364c8..ab33598 100644 --- a/steembase/operations.py +++ b/steembase/operations.py @@ -47,7 +47,7 @@ def __init__(self, op): self.opId = operations[self.to_method_name(self.name)] @staticmethod - def get_operation_name_for_id(_id: int): + def get_operation_name_for_id(_id): """ Convert an operation id into the corresponding string """ for key, value in operations.items(): @@ -55,18 +55,18 @@ def get_operation_name_for_id(_id: int): return key @staticmethod - def to_class_name(method_name: str): + def to_class_name(method_name): """ Take a name of a method, like feed_publish and turn it into class name like FeedPublish. """ return ''.join(map(str.title, method_name.split('_'))) @staticmethod - def to_method_name(class_name: str): + def to_method_name(class_name): """ Take a name of a class, like FeedPublish and turn it into method name like feed_publish. """ words = re.findall('[A-Z][^A-Z]*', class_name) return '_'.join(map(str.lower, words)) @staticmethod - def get_class(class_name: str): + def get_class(class_name): """ Given name of a class from `operations`, return real class. """ module = importlib.import_module('steembase.operations') return getattr(module, class_name) From 2b002fb2a5f45fa7ea9201385785663ef731723d Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 25 Jan 2018 13:33:47 -0600 Subject: [PATCH 011/166] Removed version checking from base58.py and changed the dict merging used in account.py. --- steem/account.py | 29 ++++++++++++++++++----------- steembase/base58.py | 2 +- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/steem/account.py b/steem/account.py index c241e12..6d005eb 100644 --- a/steem/account.py +++ b/steem/account.py @@ -126,11 +126,14 @@ def get_following(self): def _get_followers(self, direction="follower", last_user=""): if direction == "follower": - followers = self.steemd.get_followers(self.name, last_user, "blog", 100) + followers = self.steemd.get_followers( + self.name, last_user, "blog", 100) elif direction == "following": - followers = self.steemd.get_following(self.name, last_user, "blog", 100) + followers = self.steemd.get_following( + self.name, last_user, "blog", 100) if len(followers) >= 100: - followers += self._get_followers(direction=direction, last_user=followers[-1][direction])[1:] + followers += self._get_followers(direction=direction, + last_user=followers[-1][direction])[1:] return followers def has_voted(self, post): @@ -217,14 +220,18 @@ def export(self, load_extras=True): "conversion_requests": self.get_conversion_requests(), } - return { - **self, - **extras, - "profile": self.profile, - "sp": self.sp, - "rep": self.rep, - "balances": self.get_balances(), - } + composedDict = self.copy() + composedDict.update(extras) + composedDict.update( + { + "profile": self.profile, + "sp": self.sp, + "rep": self.rep, + "balances": self.get_balances(), + } + ) + + return composedDict def get_account_history(self, index, limit, start=None, stop=None, order=-1, filter_by=None, raw_output=False): """ A generator over steemd.get_account_history. diff --git a/steembase/base58.py b/steembase/base58.py index e1e8772..db7676c 100644 --- a/steembase/base58.py +++ b/steembase/base58.py @@ -7,7 +7,7 @@ """ This class and the methods require python3 """ # FIXME this library needs to support both 2 and 3 -assert sys.version_info[0] == 3, "graphenelib requires python3" +#assert sys.version_info[0] == 3, "graphenelib requires python3" """ Default Prefix """ PREFIX = "STM" From 39697a04edda29be8d1c98ee77a20a3b684fab44 Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 25 Jan 2018 16:22:19 -0600 Subject: [PATCH 012/166] Refactored statements to per PEP-380 reference. --- steem/account.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/steem/account.py b/steem/account.py index 6d005eb..baf17a5 100644 --- a/steem/account.py +++ b/steem/account.py @@ -268,18 +268,17 @@ def construct_op(account_name): # index can change during reindexing in # future hard-forks. Thus we cannot take it for granted. - immutable = { - **op, - **block_props, + immutable = op.copy() + immutable.update(block_props) + immutable.update({ 'account': account_name, 'type': op_type, - } + }) _id = Blockchain.hash_op(immutable) - return { - **immutable, + return immutable.update({ '_id': _id, 'index': index, - } + }) if filter_by is None: yield construct_op(self.name) @@ -302,7 +301,7 @@ def history(self, filter_by=None, start=0, batch_size=1000, raw_output=False): start_index = start + batch_size i = start_index while i < max_index + batch_size: - yield from self.get_account_history( + for account_history in self.get_account_history( index=i, limit=batch_size, start=i - batch_size, @@ -310,7 +309,8 @@ def history(self, filter_by=None, start=0, batch_size=1000, raw_output=False): order=1, filter_by=filter_by, raw_output=raw_output, - ) + ): + yield account_history i += (batch_size + 1) def history_reverse(self, filter_by=None, batch_size=1000, raw_output=False): @@ -324,11 +324,12 @@ def history_reverse(self, filter_by=None, batch_size=1000, raw_output=False): while i > 0: if i - batch_size < 0: batch_size = i - yield from self.get_account_history( + for account_history in self.get_account_history( index=i, limit=batch_size, order=-1, filter_by=filter_by, raw_output=raw_output, - ) + ): + yield account_history i -= (batch_size + 1) From 1e922a4306d274bfd3e8af83c808e806ef7aa0f9 Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 25 Jan 2018 16:45:49 -0600 Subject: [PATCH 013/166] Removed usage of suppress and replaced with a more traditional try/except block. --- steem/account.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/steem/account.py b/steem/account.py index baf17a5..ed9eac0 100644 --- a/steem/account.py +++ b/steem/account.py @@ -1,7 +1,6 @@ import datetime import math import time -from contextlib import suppress from funcy.colls import walk_values, get_in from funcy.seqs import take @@ -56,8 +55,11 @@ def converter(self): @property def profile(self): - with suppress(TypeError): + try: return get_in(self, ['json_metadata', 'profile'], default={}) + except TypeError: + pass + return {} @property From 618f4f5e31277ff40be7972c79f46f87f983a191 Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 25 Jan 2018 17:07:31 -0600 Subject: [PATCH 014/166] Removed all other references to suppress and yield from. --- docs/examples.rst | 12 +++++++----- steem/blockchain.py | 6 ++++-- steem/blog.py | 3 ++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/examples.rst b/docs/examples.rst index 29ef1cb..9a3ef09 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -12,7 +12,7 @@ You can run this script as many times as you like, and it will continue from the import json import os - from contextlib import suppress + from steem.blockchain import Blockchain @@ -53,8 +53,10 @@ You can run this script as many times as you like, and it will continue from the if __name__ == '__main__': output_file = '/home/user/Downloads/steem.blockchain.json' - with suppress(KeyboardInterrupt): + try: run(output_file) + except KeyboardInterrupt: + pass To see how many blocks we currently have, we can simply perform a line count. @@ -188,8 +190,6 @@ Make sure to set ``whoami`` to your Steem username before running. :: - from contextlib import suppress - from steem.blockchain import Blockchain from steem.post import Post @@ -213,5 +213,7 @@ Make sure to set ``whoami`` to your Steem username before running. post.upvote(weight=upvote_pct, voter=whoami) if __name__ == '__main__': - with suppress(KeyboardInterrupt): + try: run() + except KeyboardInterrupt: + pass diff --git a/steem/blockchain.py b/steem/blockchain.py index d13d266..0f74a9e 100644 --- a/steem/blockchain.py +++ b/steem/blockchain.py @@ -82,7 +82,8 @@ def stream_from(self, start_block=None, end_block=None, batch_operations=False, elif batch_operations: yield self.steem.get_ops_in_block(block_num, False) else: - yield from self.steem.get_ops_in_block(block_num, False) + for ops in self.steem.get_ops_in_block(block_num, False): + yield ops # next round start_block = head_block + 1 @@ -158,7 +159,8 @@ def get_reliable_ops_in_block(_client,_block_num): elif batch_operations: yield get_reliable_ops_in_block(_reliable_client,head_block) else: - yield from get_reliable_ops_in_block(_reliable_client,head_block) + for reliable_ops in get_reliable_ops_in_block(_reliable_client,head_block): + yield reliable_ops sleep_interval = sleep_interval/2 time.sleep(sleep_interval) diff --git a/steem/blog.py b/steem/blog.py index c2565b2..5308c1c 100644 --- a/steem/blog.py +++ b/steem/blog.py @@ -82,7 +82,8 @@ def all(self): while True: chunk = self.take(10) if chunk: - yield from iter(chunk) + for little_chunk in iter(chunk): + yield little_chunk else: break From 45f8750e070ff036e92503ee46b5d70a608d53a7 Mon Sep 17 00:00:00 2001 From: John White Date: Fri, 26 Jan 2018 18:23:13 -0600 Subject: [PATCH 015/166] More python3-isms removed. --- steem/blockchain.py | 12 +++++------- steem/utils.py | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/steem/blockchain.py b/steem/blockchain.py index 0f74a9e..81f241d 100644 --- a/steem/blockchain.py +++ b/steem/blockchain.py @@ -2,7 +2,6 @@ import json import time import warnings -from typing import Union from .instance import shared_steemd_instance,stm from .utils import parse_time @@ -167,7 +166,7 @@ def get_reliable_ops_in_block(_client,_block_num): start_block = head_block + 1 - def stream(self, filter_by: Union[str, list] = list(), *args, **kwargs): + def stream(self, filter_by = list(), *args, **kwargs): """ Yield a stream of operations, starting with current head block. Args: @@ -193,16 +192,15 @@ def stream(self, filter_by: Union[str, list] = list(), *args, **kwargs): if kwargs.get('raw_output'): yield event else: - yield { - **op, + yield op.update({ "_id": self.hash_op(event), "type": op_type, "timestamp": parse_time(event.get("timestamp")), "block_num": event.get("block"), "trx_id": event.get("trx_id"), - } + }) - def history(self, filter_by: Union[str, list] = list(), start_block=1, end_block=None, raw_output=False, **kwargs): + def history(self, filter_by = list(), start_block=1, end_block=None, raw_output=False, **kwargs): """ Yield a stream of historic operations. Similar to ``Blockchain.stream()``, but starts at beginning of chain unless ``start_block`` is set. @@ -229,7 +227,7 @@ def replay(self, **kwargs): return self.history(**kwargs) @staticmethod - def hash_op(event: dict): + def hash_op(event): """ This method generates a hash of blockchain operation. """ data = json.dumps(event, sort_keys=True) return hashlib.sha1(bytes(data, 'utf-8')).hexdigest() diff --git a/steem/utils.py b/steem/utils.py index a086e73..d667cdc 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -26,7 +26,7 @@ MIN_TEXT_LENGTH_FOR_DETECTION = 20 -def block_num_from_hash(block_hash: str) -> int: +def block_num_from_hash(block_hash): """ return the first 4 bytes (8 hex digits) of the block ID (the block_num) Args: @@ -38,7 +38,7 @@ def block_num_from_hash(block_hash: str) -> int: return int(str(block_hash)[:8], base=16) -def block_num_from_previous(previous_block_hash: str) -> int: +def block_num_from_previous(previous_block_hash): """ Args: From 92900cd6a850ab1e45a7f76c5833da4c234a0e20 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 02:26:50 -0600 Subject: [PATCH 016/166] added more deps, explicitly error on python2 use --- setup.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9021243..1ebef44 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,13 @@ +#!/usr/bin/env python3 from setuptools import setup, find_packages +import sys +if sys.version_info < (3,0): + sys.exit( + 'Sorry, python2 is not yet supported. ' + + 'Please use python3.' + ) + setup( name='steem', version='0.18.103', @@ -13,10 +21,17 @@ packages=find_packages(), install_requires = [ 'appdirs', + 'certifi', 'ecdsa', 'funcy', - 'voluptuous', + 'langdetect', + 'prettytable', 'pycrypto', + 'scrypt', + 'toolz', + 'urllib3', + 'voluptuous', + 'w3lib', ], entry_points={ 'console_scripts': ['steempy=steem.cli:legacy'], From 0a441298a50dc999fbadff80e1c7c68af69cdc21 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 02:38:59 -0600 Subject: [PATCH 017/166] gitignore: ignore .vagrant dir --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e8119b7..b990971 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ README.rst +.vagrant # Byte-compiled / optimized / DLL files __pycache__/ @@ -114,4 +115,4 @@ tests/failed_blocks/ /deploy/ /test.py -.DS_Store \ No newline at end of file +.DS_Store From 96b7bfb0fc1677c8f8d41906caf2a16dfa4afed4 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 02:39:15 -0600 Subject: [PATCH 018/166] add test runner --- setup.cfg | 4 +++- setup.py | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 4489b56..10e6d8a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,4 @@ [metadata] -description-file=README.md \ No newline at end of file +description-file=README.md +[aliases] +test=pytest diff --git a/setup.py b/setup.py index 1ebef44..c08420a 100644 --- a/setup.py +++ b/setup.py @@ -19,6 +19,12 @@ maintainer='steemit_inc', maintainer_email='john@steemit.com', packages=find_packages(), + setup_requires=[ + 'pytest-runner', + ], + tests_require=[ + 'pytest', + ], install_requires = [ 'appdirs', 'certifi', From 824969d824f631e67c788741bb9d0d0155cbd174 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 02:39:33 -0600 Subject: [PATCH 019/166] add Vagrantfile for reproducible testing env --- Vagrantfile | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 Vagrantfile diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..b974433 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,24 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.configure("2") do |config| + config.vm.box = "ubuntu/xenial64" + + # config.vm.provider "virtualbox" do |vb| + # # Display the VirtualBox GUI when booting the machine + # vb.gui = true + # + # # Customize the amount of memory on the VM: + # vb.memory = "1024" + # end + config.vm.provision "shell", inline: <<-SHELL + apt-get update + apt-get upgrade -y + apt-get install -y libssl-dev + apt-get install -y python3-pip + pip3 install --upgrade pip + cd /vagrant && pip3 install . + cd /vagrant && python3 setup.py test + steempy info + SHELL +end From 0bc86c3b3164722994f52c0bae3077ee2adfc4c1 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 03:51:03 -0600 Subject: [PATCH 020/166] fix broken test resulting from function rename one of the tests was still calling 'exec' instead of 'call' (exec is a reserved word in python2). --- steembase/http_client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index 0caa706..4c86a6d 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -135,7 +135,7 @@ def json_rpc_body(name, *args, api=None, as_json=True, _id=0, kwargs=None): return body_dict def call(self, name, *args, api=None, return_with_args=None, _ret_cnt=0, kwargs=None): - """ Execute a method against steemd RPC. + """ Call a remote procedure in steemd. Warnings: This command will auto-retry in case of node failure, as well as handle @@ -164,7 +164,7 @@ def call(self, name, *args, api=None, return_with_args=None, _ret_cnt=0, kwargs= self.next_node() logging.debug('Switched node to %s due to exception: %s' % (self.hostname, e.__class__.__name__)) - return self.exec(name, *args, + return self.call(name, *args, return_with_args=return_with_args, _ret_cnt=_ret_cnt + 1) except Exception as e: @@ -222,7 +222,7 @@ def call_multi_with_futures(self, name, params, api=None, max_workers=None): def ensure_list(parameter): return parameter if type(parameter) in (list, tuple, set) else [parameter] - futures = (executor.submit(self.exec, name, *ensure_list(param), api=api) + futures = (executor.submit(self.call, name, *ensure_list(param), api=api) for param in params) for future in concurrent.futures.as_completed(futures): yield future.result() From 624a779156de7f84e5dbe1a08ed3dd1b9809e1bc Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 03:53:50 -0600 Subject: [PATCH 021/166] add makefile to run tests under vagrant --- Makefile | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..03444f5 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +default: run_tests_in_vagrant + +.PHONY: run_tests_in_vagrant + +run_tests_in_vagrant: + vagrant destroy -f + vagrant up + +clean: + rm -rf build/ dist/ *.egg-info .eggs/ .tox/ \ + __pycache__/ .cache/ .coverage htmlcov src From e3f4640814d147aeb4ca8c8f2bfc47a22783e953 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 04:58:08 -0600 Subject: [PATCH 022/166] add docker ignores --- .dockerignore | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..543fffc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,56 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + + +# dotenv +.env + +# Rope project settings +.ropeproject + +# Vim +Session.vim + +# Pycharm +.idea + +# junk +blocks.json + +# sqlite db for testing +local.db +/tests/fastest-test.sh +/tests/sbds-install.sh +/tests/test.sh +/tests/fast-test.sh +/tests/sync.sh +/tests/failed_blocks/ +/envd +!/sbds.egg-info/ +/tests/envdir-to-envfile.sh +*.db +envfile +/deploy From c87adf5215b2562c8636ae445e221c946f39610e Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 04:58:45 -0600 Subject: [PATCH 023/166] updated to use pipenv --- Dockerfile | 50 +------ Makefile | 31 +++- Pipfile | 36 +++++ Pipfile.lock | 334 +++++++++++++++++++++++++++++++++++++++++++ Vagrantfile | 4 +- setup.cfg | 5 + setup.py | 28 ++-- tests/__init__.py | 0 tests/test_import.py | 4 +- 9 files changed, 422 insertions(+), 70 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 tests/__init__.py diff --git a/Dockerfile b/Dockerfile index 92ecc6d..fc8df5d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,55 +5,19 @@ ENV LANG en_US.UTF-8 ENV LC_ALL en_US.UTF-8 # Stuff for building steem-python -ARG BUILD_ROOT=/buildroot -ARG BUILD_OUTPUT=${BUILD_ROOT}/build +ARG BUILD_ROOT=/build # Now we install the essentials RUN \ apt-get update && \ - apt-get install -y \ - build-essential \ - git \ - libffi-dev \ - libssl-dev \ - make \ - python3 \ - python3-dev \ - python3-pip \ - libxml2-dev \ - libxslt-dev \ - runit \ - wget \ - pandoc + apt-get install -y python3-pip libssl-dev build-essential -# This updates the distro-provided pip and gives us pip3.5 binary -RUN python3.5 -m pip install --upgrade pip +# This updates the distro-provided pip +RUN pip3 install --upgrade pip -# We use pipenv to setup stuff -RUN python3.5 -m pip install -U pipenv +COPY . ${BUILD_ROOT} WORKDIR ${BUILD_ROOT} -# Copy just enough to build python dependencies in pipenv -COPY ./Pipfile ${BUILD_ROOT}/Pipfile - -# Install the dependencies found in the lockfile here -RUN cd ${BUILD_ROOT} && \ - python3.5 -m pipenv lock --three --hashes && \ - python3.5 -m pipenv lock --three -r >requirements.txt && \ - python3.5 -m pip install -r requirements.txt - -# Copy rest of the code into place -COPY . ${BUILD_ROOT}/src - -WORKDIR ${BUILD_ROOT}/src - -# Do build+install -RUN cd ${BUILD_ROOT}/src && \ - make build-without-docker && \ - make install-global && \ - make install-pipenv - -WORKDIR ${BUILD_ROOT}/src - - +# run tests +RUN make test diff --git a/Makefile b/Makefile index 03444f5..827ee41 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,30 @@ -default: run_tests_in_vagrant +PROJECT := $(shell basename $(shell pwd)) -.PHONY: run_tests_in_vagrant +default: docker_test -run_tests_in_vagrant: - vagrant destroy -f - vagrant up +.PHONY: fmt init test docker_test + +docker_test: + docker build . clean: rm -rf build/ dist/ *.egg-info .eggs/ .tox/ \ - __pycache__/ .cache/ .coverage htmlcov src + .cache/ .coverage htmlcov src + +test: clean + pip3 install --upgrade pip + pip3 install --upgrade pipenv + pipenv install --three --dev + pipenv install . + pipenv run py.test + +fmt: + pipenv run yapf --recursive --in-place --style pep8 $(PROJECT) + pipenv run autopep8 --recursive --in-place $(PROJECT) + +init: + pip3 install --upgrade pip + pip3 install --upgrade pipenv + pipenv lock + pipenv install --three --dev + pipenv install . diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..1e00e91 --- /dev/null +++ b/Pipfile @@ -0,0 +1,36 @@ +[[source]] + +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + + +[packages] +appdirs = "*" +certifi = "*" +ecdsa = "*" +funcy = "*" +langdetect = "*" +prettytable = "*" +pycrypto = "*" +scrypt = "*" +toolz = "*" +urllib3 = "*" +voluptuous = "*" +w3lib = "*" + + +[dev-packages] +ipython = "*" +pep8 = "*" +pytest = "*" +pytest-pep8 = "*" +pytest-console-scripts = "*" +pytest-pylint = "*" +recommonmark = "*" +yapf = "*" +autopep8 = "*" + + +#[requires] +#python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..2c1a452 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,334 @@ +{ + "_meta": { + "hash": { + "sha256": "a4eec9d4e39ab3323683dfe63d365ff0598329cd7b6921913af4e5c43ad5fcb1" + }, + "host-environment-markers": { + "implementation_name": "cpython", + "implementation_version": "3.6.4", + "os_name": "posix", + "platform_machine": "x86_64", + "platform_python_implementation": "CPython", + "platform_release": "17.3.0", + "platform_system": "Darwin", + "platform_version": "Darwin Kernel Version 17.3.0: Thu Nov 9 18:09:22 PST 2017; root:xnu-4570.31.3~1/RELEASE_X86_64", + "python_full_version": "3.6.4", + "python_version": "3.6", + "sys_platform": "darwin" + }, + "pipfile-spec": 6, + "requires": {}, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "appdirs": { + "hashes": [ + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e", + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92" + ], + "version": "==1.4.3" + }, + "certifi": { + "hashes": [ + "sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296", + "sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d" + ], + "version": "==2018.1.18" + }, + "ecdsa": { + "hashes": [ + "sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c", + "sha256:64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa" + ], + "version": "==0.13" + }, + "funcy": { + "hashes": [ + "sha256:215c95e81142d831a0a71a84777268ce852b57e87582c743c662514b85be6bcd" + ], + "version": "==1.10" + }, + "langdetect": { + "hashes": [ + "sha256:91a170d5f0ade380db809b3ba67f08e95fe6c6c8641f96d67a51ff7e98a9bf30" + ], + "version": "==1.0.7" + }, + "prettytable": { + "hashes": [ + "sha256:853c116513625c738dc3ce1aee148b5b5757a86727e67eff6502c7ca59d43c36", + "sha256:2d5460dc9db74a32bcc8f9f67de68b2c4f4d2f01fa3bd518764c69156d9cacd9", + "sha256:a53da3b43d7a5c229b5e3ca2892ef982c46b7923b51e98f0db49956531211c4f" + ], + "version": "==0.7.2" + }, + "pycrypto": { + "hashes": [ + "sha256:f2ce1e989b272cfcb677616763e0a2e7ec659effa67a88aa92b3a65528f60a3c" + ], + "version": "==2.6.1" + }, + "scrypt": { + "hashes": [ + "sha256:dc9abe69799ca423b938a06ddc33e5873e493ffcd68dbb9ba48396979b210d39", + "sha256:4e3cd639cc83e3f2c2241a01ebef9487c48b1ddf2d78f430d82d8f9ef60c6271", + "sha256:ae2fd88756fb4d98ccc5e2639af55eeb80863b3f9f6e0f539e5ce050964cdd5e", + "sha256:60e8c96a287ab892d9c7e1523d157ccfbdbe66da0c31738c8ed5732c2eea6a23", + "sha256:c0f90cabb8f6eaec05de5ce9aa9a9b67dc63d644e6b803beb1c43ae9b9452b65", + "sha256:c37a1f8440d7c621d9f23f3c1f2a28848bc50fefbca581fd7a1b01583a083c07", + "sha256:3422d11652cd12550540675e9fb54a1de6d60f3cbfedfb067284ef028589e2ee", + "sha256:d4a5a4f53450b8ef629bbf1ee4be6105c69936e49b3d8bc621ac2287f0c86020", + "sha256:6109a4df8c88f851df18a1a451e533dcc47e17cfe0e4561f4e08a82669ddc942", + "sha256:136f7d1caf596c5ee1fc7eab223605e956c3f61f77090fd9ea4e3e57a2040b78", + "sha256:c8909a2089fd1199781aa7ce2cb66b8866d40a9f9e1fba082e067ed9524d87e9", + "sha256:4333b67f190e5eaddc8800aefe33abd7e81b589bbe84a84be66872a4955dad6e", + "sha256:aeab005a8ae43a6e5d0165ce433a15955b151045d2bbfc52a8c96f100f825323", + "sha256:bb141584fa0ebdfb8a2a1fc7ddcf119ee18b1b9cd0fb3e4df9615760648b9d49" + ], + "version": "==0.8.0" + }, + "six": { + "hashes": [ + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9" + ], + "version": "==1.11.0" + }, + "toolz": { + "hashes": [ + "sha256:929f0a7ea7f61c178bd951bdae93920515d3fbdbafc8e6caf82d752b9b3b31c9" + ], + "version": "==0.9.0" + }, + "urllib3": { + "hashes": [ + "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", + "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" + ], + "version": "==1.22" + }, + "voluptuous": { + "hashes": [ + "sha256:7a7466f8dc3666a292d186d1d871a47bf2120836ccb900d5ba904674957a2396" + ], + "version": "==0.10.5" + }, + "w3lib": { + "hashes": [ + "sha256:aaf7362464532b1036ab0092e2eee78e8fd7b56787baa9ed4967457b083d011b", + "sha256:55994787e93b411c2d659068b51b9998d9d0c05e0df188e6daf8f45836e1ea38" + ], + "version": "==1.19.0" + } + }, + "develop": { + "appnope": { + "hashes": [ + "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", + "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" + ], + "markers": "sys_platform == 'darwin'", + "version": "==0.1.0" + }, + "attrs": { + "hashes": [ + "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450", + "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9" + ], + "version": "==17.4.0" + }, + "autopep8": { + "hashes": [ + "sha256:c7be71ab0cb2f50c9c22c82f0c9acaafc6f57492c3fbfee9790c415005c2b9a5" + ], + "version": "==1.3.4" + }, + "commonmark": { + "hashes": [ + "sha256:34d73ec8085923c023930dfc0bcd1c4286e28a2a82de094bb72fabcc0281cbe5" + ], + "version": "==0.5.4" + }, + "decorator": { + "hashes": [ + "sha256:94d1d8905f5010d74bbbd86c30471255661a14187c45f8d7f3e5aa8540fdb2e5", + "sha256:7d46dd9f3ea1cf5f06ee0e4e1277ae618cf48dfb10ada7c8427cd46c42702a0e" + ], + "version": "==4.2.1" + }, + "docutils": { + "hashes": [ + "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6", + "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", + "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274" + ], + "version": "==0.14" + }, + "ipython": { + "hashes": [ + "sha256:fcc6d46f08c3c4de7b15ae1c426e15be1b7932bcda9d83ce1a4304e8c1129df3", + "sha256:51c158a6c8b899898d1c91c6b51a34110196815cc905f9be0fa5878e19355608" + ], + "version": "==6.2.1" + }, + "ipython-genutils": { + "hashes": [ + "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", + "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" + ], + "version": "==0.2.0" + }, + "jedi": { + "hashes": [ + "sha256:d795f2c2e659f5ea39a839e5230d70a0b045d0daee7ca2403568d8f348d0ad89", + "sha256:d6e799d04d1ade9459ed0f20de47c32f2285438956a677d083d3c98def59fa97" + ], + "version": "==0.11.1" + }, + "mock": { + "hashes": [ + "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", + "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba" + ], + "version": "==2.0.0" + }, + "parso": { + "hashes": [ + "sha256:a7bb86fe0844304869d1c08e8bd0e52be931228483025c422917411ab82d628a", + "sha256:5815f3fe254e5665f3c5d6f54f086c2502035cb631a91341591b5a564203cffb" + ], + "version": "==0.1.1" + }, + "pbr": { + "hashes": [ + "sha256:60c25b7dfd054ef9bb0ae327af949dd4676aa09ac3a9471cdc871d8a9213f9ac", + "sha256:05f61c71aaefc02d8e37c0a3eeb9815ff526ea28b3b76324769e6158d7f95be1" + ], + "version": "==3.1.1" + }, + "pep8": { + "hashes": [ + "sha256:b22cfae5db09833bb9bd7c8463b53e1a9c9b39f12e304a8d0bba729c501827ee", + "sha256:fe249b52e20498e59e0b5c5256aa52ee99fc295b26ec9eaa85776ffdb9fe6374" + ], + "version": "==1.7.1" + }, + "pexpect": { + "hashes": [ + "sha256:144939a072a46d32f6e5ecc866509e1d613276781f7182148a08df52eaa7b022", + "sha256:8e287b171dbaf249d0b06b5f2e88cb7e694651d2d0b8c15bccb83170d3c55575" + ], + "markers": "sys_platform != 'win32'", + "version": "==4.3.1" + }, + "pickleshare": { + "hashes": [ + "sha256:c9a2541f25aeabc070f12f452e1f2a8eae2abd51e1cd19e8430402bdf4c1d8b5", + "sha256:84a9257227dfdd6fe1b4be1319096c20eb85ff1e82c7932f36efccfe1b09737b" + ], + "version": "==0.7.4" + }, + "pluggy": { + "hashes": [ + "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff" + ], + "version": "==0.6.0" + }, + "prompt-toolkit": { + "hashes": [ + "sha256:3f473ae040ddaa52b52f97f6b4a493cfa9f5920c255a12dc56a7d34397a398a4", + "sha256:1df952620eccb399c53ebb359cc7d9a8d3a9538cb34c5a1344bdbeb29fbcc381", + "sha256:858588f1983ca497f1cf4ffde01d978a3ea02b01c8a26a8bbc5cd2e66d816917" + ], + "version": "==1.0.15" + }, + "ptyprocess": { + "hashes": [ + "sha256:e8c43b5eee76b2083a9badde89fd1bbce6c8942d1045146e100b7b5e014f4f1a", + "sha256:e64193f0047ad603b71f202332ab5527c5e52aa7c8b609704fc28c0dc20c4365" + ], + "version": "==0.5.2" + }, + "py": { + "hashes": [ + "sha256:8cca5c229d225f8c1e3085be4fcf306090b00850fefad892f9d96c7b6e2f310f", + "sha256:ca18943e28235417756316bfada6cd96b23ce60dd532642690dcfdaba988a76d" + ], + "version": "==1.5.2" + }, + "pycodestyle": { + "hashes": [ + "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9", + "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766" + ], + "version": "==2.3.1" + }, + "pygments": { + "hashes": [ + "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", + "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" + ], + "version": "==2.2.0" + }, + "pytest": { + "hashes": [ + "sha256:b84878865558194630c6147f44bdaef27222a9f153bbd4a08908b16bf285e0b1", + "sha256:53548280ede7818f4dc2ad96608b9f08ae2cc2ca3874f2ceb6f97e3583f25bc4" + ], + "version": "==3.3.2" + }, + "pytest-console-scripts": { + "hashes": [ + "sha256:75188d816f7398956aee48dbff4ce6759d64fbf617f760c8c4cec77608afb8a3" + ], + "version": "==0.1.3" + }, + "recommonmark": { + "hashes": [ + "sha256:cd8bf902e469dae94d00367a8197fb7b81fcabc9cfb79d520e0d22d0fbeaa8b7", + "sha256:6e29c723abcf5533842376d87c4589e62923ecb6002a8e059eb608345ddaff9d" + ], + "version": "==0.4.0" + }, + "simplegeneric": { + "hashes": [ + "sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173" + ], + "version": "==0.8.1" + }, + "six": { + "hashes": [ + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9" + ], + "version": "==1.11.0" + }, + "traitlets": { + "hashes": [ + "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9", + "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835" + ], + "version": "==4.3.2" + }, + "wcwidth": { + "hashes": [ + "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c", + "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e" + ], + "version": "==0.1.7" + }, + "yapf": { + "hashes": [ + "sha256:a0bbc8ed274609f9c7575a5d69056fa393e26a778b3e070a72f4998b8e90c3cd", + "sha256:bd19f246be7193ad2acdc04702b92315f1ae28d49c82f6671afdeefe9d32f468" + ], + "version": "==0.20.1" + } + } +} diff --git a/Vagrantfile b/Vagrantfile index b974433..ce7513b 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -16,9 +16,7 @@ Vagrant.configure("2") do |config| apt-get upgrade -y apt-get install -y libssl-dev apt-get install -y python3-pip - pip3 install --upgrade pip - cd /vagrant && pip3 install . - cd /vagrant && python3 setup.py test + cd /vagrant && make test steempy info SHELL end diff --git a/setup.cfg b/setup.cfg index 10e6d8a..20905f1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,3 +2,8 @@ description-file=README.md [aliases] test=pytest + +[tool:pytest] +norecursedirs=dist docs build .tox deploy +addopts = --pep8 +testpaths = tests diff --git a/setup.py b/setup.py index c08420a..8f632de 100644 --- a/setup.py +++ b/setup.py @@ -8,6 +8,14 @@ 'Please use python3.' ) + +from pipenv.project import Project +from pipenv.utils import convert_deps_to_pip + +pfile = Project(chdir=False).parsed_pipfile +requirements = convert_deps_to_pip(pfile['packages'], r=False) +test_requirements = convert_deps_to_pip(pfile['dev-packages'], r=False) + setup( name='steem', version='0.18.103', @@ -21,24 +29,10 @@ packages=find_packages(), setup_requires=[ 'pytest-runner', + 'pipenv', ], - tests_require=[ - 'pytest', - ], - install_requires = [ - 'appdirs', - 'certifi', - 'ecdsa', - 'funcy', - 'langdetect', - 'prettytable', - 'pycrypto', - 'scrypt', - 'toolz', - 'urllib3', - 'voluptuous', - 'w3lib', - ], + tests_require= test_requirements, + install_requires = requirements, entry_points={ 'console_scripts': ['steempy=steem.cli:legacy'], }, diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_import.py b/tests/test_import.py index f1eda7e..6fb3934 100644 --- a/tests/test_import.py +++ b/tests/test_import.py @@ -1,9 +1,11 @@ # -*- coding: utf-8 -*- +import os +import sys +sys.path.insert(0, os.path.abspath('..')) from steem import * from steembase import * - # pylint: disable=unused-import,unused-variable def test_import(): _ = Steem() From 6615b830439c42904a72ae1b3c10c5b887e18673 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 05:03:34 -0600 Subject: [PATCH 024/166] temporarily disable pep8 until we pass --- Pipfile | 4 ++-- setup.cfg | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Pipfile b/Pipfile index 1e00e91..4f7bc18 100644 --- a/Pipfile +++ b/Pipfile @@ -24,9 +24,9 @@ w3lib = "*" ipython = "*" pep8 = "*" pytest = "*" -pytest-pep8 = "*" +#pytest-pep8 = "*" pytest-console-scripts = "*" -pytest-pylint = "*" +#pytest-pylint = "*" recommonmark = "*" yapf = "*" autopep8 = "*" diff --git a/setup.cfg b/setup.cfg index 20905f1..46ef233 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,5 +5,5 @@ test=pytest [tool:pytest] norecursedirs=dist docs build .tox deploy -addopts = --pep8 +#addopts = --pep8 testpaths = tests From f8f0fcebbabc08076b6f50c847cac1aba357a8fc Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 05:49:59 -0600 Subject: [PATCH 025/166] improve docker caching under pipenv --- Dockerfile | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index fc8df5d..b5ab315 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,9 +15,20 @@ RUN \ # This updates the distro-provided pip RUN pip3 install --upgrade pip -COPY . ${BUILD_ROOT} +RUN mkdir ${BUILD_ROOT} + +COPY Makefile ${BUILD_ROOT}/ +COPY Pipfile ${BUILD_ROOT}/ +COPY Pipfile.lock ${BUILD_ROOT}/ WORKDIR ${BUILD_ROOT} +RUN pip3 install --upgrade pip && \ + pip3 install --upgrade pipenv && \ + pipenv install --three --dev && \ + pipenv install . + +COPY . ${BUILD_ROOT} + # run tests -RUN make test +RUN pipenv run py.test From 9da8009fc86a70ab984da292af2037b85a6fcc4d Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 07:05:10 -0600 Subject: [PATCH 026/166] now passes pep8, pep8 included in tests. --- Makefile | 6 +- Pipfile | 4 +- Pipfile.lock | 108 +++- bin/piston | 4 +- bin/steempy | 4 +- bin/steemtail | 41 +- docs/conf.py | 12 +- scripts/doc_rst_convert.py | 7 +- scripts/steemd_gen.py | 75 ++- setup.cfg | 11 +- setup.py | 24 +- steem/__init__.py | 1 - steem/account.py | 67 +- steem/aes.py | 14 +- steem/amount.py | 14 +- steem/block.py | 3 +- steem/blockchain.py | 232 ++++--- steem/blog.py | 15 +- steem/cli.py | 909 ++++++++++---------------- steem/commit.py | 930 +++++++++++++++------------ steem/converter.py | 24 +- steem/dex.py | 151 +++-- steem/instance.py | 14 +- steem/post.py | 95 +-- steem/profile.py | 1 - steem/steem.py | 59 +- steem/steemd.py | 459 ++++++++----- steem/transactionbuilder.py | 50 +- steem/utils.py | 29 +- steem/wallet.py | 82 +-- steem/witness.py | 4 +- steembase/account.py | 39 +- steembase/base58.py | 27 +- steembase/bip38.py | 18 +- steembase/chains.py | 15 +- steembase/exceptions.py | 8 +- steembase/http_client.py | 87 ++- steembase/memo.py | 2 +- steembase/operations.py | 469 +++++++------- steembase/storage.py | 107 ++- steembase/transactions.py | 133 ++-- steembase/types.py | 11 +- tests/steem/test_memo.py | 16 +- tests/steem/test_post.py | 8 +- tests/steem/test_steemd.py | 6 +- tests/steem/test_transactions.py | 527 ++++++++------- tests/steembase/test_base58.py | 246 ++++--- tests/steembase/test_base_account.py | 475 +++++++++----- tests/steembase/test_bip38.py | 49 +- tests/test_import.py | 5 +- 50 files changed, 3205 insertions(+), 2492 deletions(-) diff --git a/Makefile b/Makefile index 827ee41..656b1f7 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ PROJECT := $(shell basename $(shell pwd)) +MODULES := steem steembase tests default: docker_test @@ -19,8 +20,9 @@ test: clean pipenv run py.test fmt: - pipenv run yapf --recursive --in-place --style pep8 $(PROJECT) - pipenv run autopep8 --recursive --in-place $(PROJECT) + pipenv run yapf --recursive --in-place --style pep8 $(MODULES) + #pipenv run autopep8 --recursive --in-place $(MODULES) + pipenv run pycodestyle $(MODULES) init: pip3 install --upgrade pip diff --git a/Pipfile b/Pipfile index 4f7bc18..1e00e91 100644 --- a/Pipfile +++ b/Pipfile @@ -24,9 +24,9 @@ w3lib = "*" ipython = "*" pep8 = "*" pytest = "*" -#pytest-pep8 = "*" +pytest-pep8 = "*" pytest-console-scripts = "*" -#pytest-pylint = "*" +pytest-pylint = "*" recommonmark = "*" yapf = "*" autopep8 = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 2c1a452..547188d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "a4eec9d4e39ab3323683dfe63d365ff0598329cd7b6921913af4e5c43ad5fcb1" + "sha256": "d10840e543ef7e1f9c9bde62f64e00e3cd65f345cb20740ecf2972d2ee76d911" }, "host-environment-markers": { "implementation_name": "cpython", @@ -128,6 +128,13 @@ } }, "develop": { + "apipkg": { + "hashes": [ + "sha256:65d2aa68b28e7d31233bb2ba8eb31cda40e4671f8ac2d6b241e358c9652a74b9", + "sha256:2e38399dbe842891fe85392601aab8f40a8f4cc5a9053c326de35a1cc0297ac6" + ], + "version": "==1.4" + }, "appnope": { "hashes": [ "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", @@ -136,6 +143,13 @@ "markers": "sys_platform == 'darwin'", "version": "==0.1.0" }, + "astroid": { + "hashes": [ + "sha256:db5cfc9af6e0b60cd07c19478fb54021fc20d2d189882fbcbc94fc69a8aecc58", + "sha256:f0a0e386dbca9f93ea9f3ea6f32b37a24720502b7baa9cb17c3976a680d43a06" + ], + "version": "==1.6.1" + }, "attrs": { "hashes": [ "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450", @@ -170,6 +184,13 @@ ], "version": "==0.14" }, + "execnet": { + "hashes": [ + "sha256:fc155a6b553c66c838d1a22dba1dc9f5f505c43285a878c6f74a79c024750b83", + "sha256:a7a84d5fa07a089186a329528f127c9d73b9de57f1a1131b82bb5320ee651f6a" + ], + "version": "==1.5.0" + }, "ipython": { "hashes": [ "sha256:fcc6d46f08c3c4de7b15ae1c426e15be1b7932bcda9d83ce1a4304e8c1129df3", @@ -184,6 +205,13 @@ ], "version": "==0.2.0" }, + "isort": { + "hashes": [ + "sha256:cd5d3fc2c16006b567a17193edf4ed9830d9454cbeb5a42ac80b36ea00c23db4", + "sha256:79f46172d3a4e2e53e7016e663cc7a8b538bec525c36675fcfd2767df30b3983" + ], + "version": "==4.2.15" + }, "jedi": { "hashes": [ "sha256:d795f2c2e659f5ea39a839e5230d70a0b045d0daee7ca2403568d8f348d0ad89", @@ -191,6 +219,47 @@ ], "version": "==0.11.1" }, + "lazy-object-proxy": { + "hashes": [ + "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019", + "sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39", + "sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c", + "sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e", + "sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b", + "sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6", + "sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b", + "sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d", + "sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff", + "sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd", + "sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f", + "sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514", + "sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92", + "sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35", + "sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff", + "sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252", + "sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7", + "sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b", + "sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f", + "sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4", + "sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577", + "sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d", + "sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109", + "sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2", + "sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33", + "sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d", + "sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5", + "sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088", + "sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a" + ], + "version": "==1.3.1" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, "mock": { "hashes": [ "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", @@ -276,6 +345,13 @@ ], "version": "==2.2.0" }, + "pylint": { + "hashes": [ + "sha256:156839bedaa798febee72893beef00c650c2e7abafb5586fc7a6a56be7f80412", + "sha256:4fe3b99da7e789545327b75548cee6b511e4faa98afe268130fea1af4b5ec022" + ], + "version": "==1.8.2" + }, "pytest": { "hashes": [ "sha256:b84878865558194630c6147f44bdaef27222a9f153bbd4a08908b16bf285e0b1", @@ -283,12 +359,36 @@ ], "version": "==3.3.2" }, + "pytest-cache": { + "hashes": [ + "sha256:be7468edd4d3d83f1e844959fd6e3fd28e77a481440a7118d430130ea31b07a9" + ], + "version": "==1.0" + }, "pytest-console-scripts": { "hashes": [ "sha256:75188d816f7398956aee48dbff4ce6759d64fbf617f760c8c4cec77608afb8a3" ], "version": "==0.1.3" }, + "pytest-pep8": { + "hashes": [ + "sha256:032ef7e5fa3ac30f4458c73e05bb67b0f036a8a5cb418a534b3170f89f120318" + ], + "version": "==1.0.6" + }, + "pytest-pylint": { + "hashes": [ + "sha256:9b8ca25823b2f39e89d8170453f5282e57b973395060e838ced5f8c09271ca65", + "sha256:2efaf761472637df9a8f4a3f4fac37f8ce433d70957c5f5767c4be322a42a3d2", + "sha256:9f38725b22967a56724115c9df0a93dda37fea71dd5495fb1354b82e3d938d0d", + "sha256:85da6403c69eb715b9703df640818f337603f2cac947f932b033588851aaaf16", + "sha256:b85763dc36757bfb736b07fecb4f67a0892dcb00868e01f150c7424f608bd62e", + "sha256:ec63f7c4c05331654ab54fda8e68b8a11512009d506a8e35ee9b6d40a359356d", + "sha256:2bb26948f0355d14b274742153a6b4daa51e6d60481143bfd7f025699a27210d" + ], + "version": "==0.7.1" + }, "recommonmark": { "hashes": [ "sha256:cd8bf902e469dae94d00367a8197fb7b81fcabc9cfb79d520e0d22d0fbeaa8b7", @@ -323,6 +423,12 @@ ], "version": "==0.1.7" }, + "wrapt": { + "hashes": [ + "sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6" + ], + "version": "==1.10.11" + }, "yapf": { "hashes": [ "sha256:a0bbc8ed274609f9c7575a5d69056fa393e26a778b3e070a72f4998b8e90c3cd", diff --git a/bin/piston b/bin/piston index 920a173..eaeb58f 100755 --- a/bin/piston +++ b/bin/piston @@ -3,5 +3,5 @@ import steem import steem.cli -if __name__=='__main__': - steem.cli.legacy() +if __name__ == '__main__': + steem.cli.legacy() diff --git a/bin/steempy b/bin/steempy index 920a173..eaeb58f 100755 --- a/bin/steempy +++ b/bin/steempy @@ -3,5 +3,5 @@ import steem import steem.cli -if __name__=='__main__': - steem.cli.legacy() +if __name__ == '__main__': + steem.cli.legacy() diff --git a/bin/steemtail b/bin/steemtail index bd92e42..fcd8192 100755 --- a/bin/steemtail +++ b/bin/steemtail @@ -7,32 +7,43 @@ import pprint import json from steem.blockchain import Blockchain + def main(): - parser = argparse.ArgumentParser(description="UNIX tail(1)-like tool for the steem blockchain") - parser.add_argument('-f','--follow',help='Constantly stream output to stdout', action='store_true') - parser.add_argument('-n','--lines', type=int, default=10, help='How many ops to show') - parser.add_argument('-j','--json', help='Output as JSON instead of human-readable pretty-printed format', action='store_true') + parser = argparse.ArgumentParser( + description="UNIX tail(1)-like tool for the steem blockchain") + parser.add_argument( + '-f', + '--follow', + help='Constantly stream output to stdout', + action='store_true') + parser.add_argument( + '-n', '--lines', type=int, default=10, help='How many ops to show') + parser.add_argument( + '-j', + '--json', + help='Output as JSON instead of human-readable pretty-printed format', + action='store_true') args = parser.parse_args(sys.argv[1:]) b = Blockchain() stream = b.reliable_stream() op_count = 0 - if args.json: - if not args.follow: sys.stdout.write('[') + if args.json: + if not args.follow: sys.stdout.write('[') for op in stream: if args.json: - sys.stdout.write('%s' % json.dumps(op)) + sys.stdout.write('%s' % json.dumps(op)) else: - pprint.pprint(op) + pprint.pprint(op) op_count += 1 if not args.follow: - if op_count > args.lines: - if args.json: sys.stdout.write(']') - return - else: - if args.json: sys.stdout.write(',') + if op_count > args.lines: + if args.json: sys.stdout.write(']') + return + else: + if args.json: sys.stdout.write(',') -if __name__=='__main__': - main() +if __name__ == '__main__': + main() diff --git a/docs/conf.py b/docs/conf.py index 2bab934..ff20770 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,7 +21,6 @@ # import sys # sys.path.insert(0, os.path.abspath('.')) - # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. @@ -145,10 +144,8 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'steem-python', 'steem-python Documentation', - [author], 1) -] +man_pages = [(master_doc, 'steem-python', 'steem-python Documentation', + [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -156,7 +153,6 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'steem-python', 'steem-python Documentation', - author, 'steem-python', 'One line description of project.', - 'Miscellaneous'), + (master_doc, 'steem-python', 'steem-python Documentation', author, + 'steem-python', 'One line description of project.', 'Miscellaneous'), ] diff --git a/scripts/doc_rst_convert.py b/scripts/doc_rst_convert.py index c7879bd..566a509 100644 --- a/scripts/doc_rst_convert.py +++ b/scripts/doc_rst_convert.py @@ -1,3 +1,6 @@ import pypandoc -pypandoc.convert(source='README.md', format='markdown_github', to='rst', outputfile='README.rst') - +pypandoc.convert( + source='README.md', + format='markdown_github', + to='rst', + outputfile='README.rst') diff --git a/scripts/steemd_gen.py b/scripts/steemd_gen.py index 680f912..d3e8de6 100644 --- a/scripts/steemd_gen.py +++ b/scripts/steemd_gen.py @@ -39,7 +39,8 @@ { 'api': 'database_api', 'method': 'get_expiring_vesting_delegations', - 'params': [('account', 'str'), ('start', 'PointInTime'), ('limit', 'int')], + 'params': [('account', 'str'), ('start', 'PointInTime'), ('limit', + 'int')], }, { 'api': 'database_api', @@ -214,7 +215,8 @@ { 'api': 'database_api', 'method': 'get_account_history', - 'params': [('account', 'str'), ('index_from', 'int'), ('limit', 'int')], + 'params': [('account', 'str'), ('index_from', 'int'), ('limit', + 'int')], }, { 'api': 'database_api', @@ -279,7 +281,8 @@ { 'api': 'database_api', 'method': 'get_required_signatures', - 'params': [('signed_transaction', 'object'), ('available_keys', 'list')], + 'params': [('signed_transaction', 'object'), ('available_keys', + 'list')], }, { 'api': 'database_api', @@ -317,15 +320,20 @@ 'params': [('author', 'str'), ('permlink', 'str')], }, { - 'api': 'database_api', - 'method': 'get_discussions_by_author_before_date', - 'params': [('author', 'str'), ('start_permlink', 'str'), ('before_date', 'object'), ('limit', 'int')], - + 'api': + 'database_api', + 'method': + 'get_discussions_by_author_before_date', + 'params': [('author', 'str'), ('start_permlink', 'str'), + ('before_date', 'object'), ('limit', 'int')], }, { - 'api': 'database_api', - 'method': 'get_replies_by_last_update', - 'params': [('account', 'str'), ('start_permlink', 'str'), ('limit', 'int')], + 'api': + 'database_api', + 'method': + 'get_replies_by_last_update', + 'params': [('account', 'str'), ('start_permlink', 'str'), ('limit', + 'int')], }, { 'api': 'database_api', @@ -365,7 +373,8 @@ { 'api': 'database_api', 'method': 'get_vesting_delegations', - 'params': [('account', 'str'), ('from_account', 'str'), ('limit', 'int')], + 'params': [('account', 'str'), ('from_account', 'str'), ('limit', + 'int')], }, { 'api': 'login_api', @@ -383,14 +392,20 @@ 'params': [], }, { - 'api': 'follow_api', - 'method': 'get_followers', - 'params': [('account', 'str'), ('start_follower', 'str'), ('follow_type', 'str'), ('limit', 'int')], + 'api': + 'follow_api', + 'method': + 'get_followers', + 'params': [('account', 'str'), ('start_follower', 'str'), + ('follow_type', 'str'), ('limit', 'int')], }, { - 'api': 'follow_api', - 'method': 'get_following', - 'params': [('account', 'str'), ('start_follower', 'str'), ('follow_type', 'str'), ('limit', 'int')], + 'api': + 'follow_api', + 'method': + 'get_following', + 'params': [('account', 'str'), ('start_follower', 'str'), + ('follow_type', 'str'), ('limit', 'int')], }, { 'api': 'follow_api', @@ -473,9 +488,12 @@ 'params': [('limit', 'int')], }, { - 'api': 'market_history_api', - 'method': 'get_trade_history', - 'params': [('start', 'PointInTime'), ('end', 'PointInTime'), ('limit', 'int')], + 'api': + 'market_history_api', + 'method': + 'get_trade_history', + 'params': [('start', 'PointInTime'), ('end', 'PointInTime'), ('limit', + 'int')], }, { 'api': 'market_history_api', @@ -484,9 +502,12 @@ 'returns': 'List[Any]', }, { - 'api': 'market_history_api', - 'method': 'get_market_history', - 'params': [('bucket_seconds', 'int'), ('start', 'PointInTime'), ('end', 'PointInTime')], + 'api': + 'market_history_api', + 'method': + 'get_market_history', + 'params': [('bucket_seconds', 'int'), ('start', 'PointInTime'), + ('end', 'PointInTime')], }, { 'api': 'market_history_api', @@ -527,8 +548,7 @@ def steemd_codegen(): method_arguments=''.join(method_arg_mapper(endpoint['params'])), call_arguments=''.join(call_arg_mapper(endpoint['params'])), return_hints=return_hints, - api=endpoint['api'] - ) + api=endpoint['api']) sys.stdout.write(fn) @@ -548,7 +568,10 @@ def inspect_steemd_implementation(): s = Steem(re_raise=False) for api in _apis: err = s.exec('nonexistentmethodcall', api=api) - [avail_methods.append(x) for x in err['data']['stack'][0]['data']['api'].keys()] + [ + avail_methods.append(x) + for x in err['data']['stack'][0]['data']['api'].keys() + ] avail_methods = set(avail_methods) diff --git a/setup.cfg b/setup.cfg index 46ef233..5b0ba6b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,5 +5,14 @@ test=pytest [tool:pytest] norecursedirs=dist docs build .tox deploy -#addopts = --pep8 +addopts = --pep8 testpaths = tests + +[yapf] +indent_width = 4 +column_limit = 77 +based_on_style = pep8 +spaces_before_comment = 2 +split_before_logical_operator = true +dedent_closing_brackets = true +i18n_comment = NOQA diff --git a/setup.py b/setup.py index 8f632de..bac44da 100644 --- a/setup.py +++ b/setup.py @@ -1,16 +1,11 @@ #!/usr/bin/env python3 +from pipenv.project import Project +from pipenv.utils import convert_deps_to_pip from setuptools import setup, find_packages - import sys -if sys.version_info < (3,0): - sys.exit( - 'Sorry, python2 is not yet supported. ' + - 'Please use python3.' - ) - -from pipenv.project import Project -from pipenv.utils import convert_deps_to_pip +if sys.version_info < (3, 0): + sys.exit('Sorry, python2 is not yet supported. Please use python3.') pfile = Project(chdir=False).parsed_pipfile requirements = convert_deps_to_pip(pfile['packages'], r=False) @@ -31,18 +26,17 @@ 'pytest-runner', 'pipenv', ], - tests_require= test_requirements, - install_requires = requirements, + tests_require=test_requirements, + install_requires=requirements, entry_points={ 'console_scripts': ['steempy=steem.cli:legacy'], }, classifiers=[ 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', - 'Programming Language :: Python :: 3', + 'Natural Language :: English', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Python Modules', - 'Development Status :: 4 - Beta'] -) + 'Development Status :: 4 - Beta' + ]) diff --git a/steem/__init__.py b/steem/__init__.py index 547100d..a1dae32 100644 --- a/steem/__init__.py +++ b/steem/__init__.py @@ -2,4 +2,3 @@ from .steem import Steem __version__ = '0.18.103' - diff --git a/steem/account.py b/steem/account.py index c241e12..62b1789 100644 --- a/steem/account.py +++ b/steem/account.py @@ -20,7 +20,8 @@ class Account(dict): """ This class allows to easily access Account data :param str account_name: Name of the account - :param Steemd steemd_instance: Steemd() instance to use when accessing a RPC + :param Steemd steemd_instance: Steemd() instance to use when + accessing a RPC """ @@ -92,9 +93,12 @@ def get_balances(self): } totals = { - 'STEEM': sum([available['STEEM'], savings['STEEM'], rewards['STEEM']]), - 'SBD': sum([available['SBD'], savings['SBD'], rewards['SBD']]), - 'VESTS': sum([available['VESTS'], rewards['VESTS']]), + 'STEEM': + sum([available['STEEM'], savings['STEEM'], rewards['STEEM']]), + 'SBD': + sum([available['SBD'], savings['SBD'], rewards['SBD']]), + 'VESTS': + sum([available['VESTS'], rewards['VESTS']]), } total = walk_values(rpartial(round, 3), totals) @@ -119,18 +123,25 @@ def voting_power(self): return self['voting_power'] / 100 def get_followers(self): - return [x['follower'] for x in self._get_followers(direction="follower")] + return [ + x['follower'] for x in self._get_followers(direction="follower") + ] def get_following(self): - return [x['following'] for x in self._get_followers(direction="following")] + return [ + x['following'] for x in self._get_followers(direction="following") + ] def _get_followers(self, direction="follower", last_user=""): if direction == "follower": - followers = self.steemd.get_followers(self.name, last_user, "blog", 100) + followers = self.steemd.get_followers(self.name, last_user, "blog", + 100) elif direction == "following": - followers = self.steemd.get_following(self.name, last_user, "blog", 100) + followers = self.steemd.get_following(self.name, last_user, "blog", + 100) if len(followers) >= 100: - followers += self._get_followers(direction=direction, last_user=followers[-1][direction])[1:] + followers += self._get_followers( + direction=direction, last_user=followers[-1][direction])[1:] return followers def has_voted(self, post): @@ -138,13 +149,16 @@ def has_voted(self, post): return self.name in active_votes def curation_stats(self): - trailing_24hr_t = time.time() - datetime.timedelta(hours=24).total_seconds() - trailing_7d_t = time.time() - datetime.timedelta(days=7).total_seconds() + trailing_24hr_t = time.time() - datetime.timedelta( + hours=24).total_seconds() + trailing_7d_t = time.time() - datetime.timedelta( + days=7).total_seconds() reward_24h = 0.0 reward_7d = 0.0 - for reward in take(5000, self.history_reverse(filter_by="curation_reward")): + for reward in take( + 5000, self.history_reverse(filter_by="curation_reward")): timestamp = parse_time(reward['timestamp']).timestamp() if timestamp > trailing_7d_t: @@ -199,9 +213,11 @@ def filter_by_date(items, start_time, end_time=None): return filtered_items def export(self, load_extras=True): - """ This method returns a dictionary that is type-safe to store as JSON or in a database. + """ This method returns a dictionary that is type-safe to store as + JSON or in a database. - :param bool load_extras: Fetch extra information related to the account (this might take a while). + :param bool load_extras: Fetch extra information related to the + account (this might take a while). """ extras = dict() if load_extras: @@ -226,7 +242,14 @@ def export(self, load_extras=True): "balances": self.get_balances(), } - def get_account_history(self, index, limit, start=None, stop=None, order=-1, filter_by=None, raw_output=False): + def get_account_history(self, + index, + limit, + start=None, + stop=None, + order=-1, + filter_by=None, + raw_output=False): """ A generator over steemd.get_account_history. It offers serialization, filtering and fine grained iteration control. @@ -238,7 +261,8 @@ def get_account_history(self, index, limit, start=None, stop=None, order=-1, fil stop (int): (Optional) stop iteration early at this index order: (1, -1): 1 for chronological, -1 for reverse order filter_by (str, list): filter out all but these operations - raw_output (bool): (Defaults to False). If True, return history in steemd format (unchanged). + raw_output (bool): (Defaults to False). If True, return history in + steemd format (unchanged). """ history = self.steemd.get_account_history(self.name, index, limit) for item in history[::order]: @@ -285,7 +309,11 @@ def construct_op(account_name): if op_type == filter_by: yield construct_op(self.name) - def history(self, filter_by=None, start=0, batch_size=1000, raw_output=False): + def history(self, + filter_by=None, + start=0, + batch_size=1000, + raw_output=False): """ Stream account history in chronological order. """ max_index = self.virtual_op_count() @@ -306,7 +334,10 @@ def history(self, filter_by=None, start=0, batch_size=1000, raw_output=False): ) i += (batch_size + 1) - def history_reverse(self, filter_by=None, batch_size=1000, raw_output=False): + def history_reverse(self, + filter_by=None, + batch_size=1000, + raw_output=False): """ Stream account history in reverse chronological order. """ start_index = self.virtual_op_count() diff --git a/steem/aes.py b/steem/aes.py index e665a46..ca9f9d2 100644 --- a/steem/aes.py +++ b/steem/aes.py @@ -6,9 +6,13 @@ class AESCipher(object): """ - A classical AES Cipher. Can use any size of data and any size of password thanks to padding. - Also ensure the coherence and the type of the data with a unicode to byte converter. + + A classical AES Cipher. Can use any size of data and any size of + password thanks to padding. Also ensure the coherence and the type of + the data with a unicode to byte converter. + """ + def __init__(self, key): self.bs = 32 self.key = hashlib.sha256(AESCipher.str_to_bytes(key)).digest() @@ -21,7 +25,8 @@ def str_to_bytes(data): return data def _pad(self, s): - return s + (self.bs - len(s) % self.bs) * AESCipher.str_to_bytes(chr(self.bs - len(s) % self.bs)) + return s + (self.bs - len(s) % self.bs) * AESCipher.str_to_bytes( + chr(self.bs - len(s) % self.bs)) @staticmethod def _unpad(s): @@ -37,4 +42,5 @@ def decrypt(self, enc): enc = base64.b64decode(enc) iv = enc[:AES.block_size] cipher = AES.new(self.key, AES.MODE_CBC, iv) - return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8') + return self._unpad(cipher.decrypt( + enc[AES.block_size:])).decode('utf-8') diff --git a/steem/amount.py b/steem/amount.py index ff12ca4..0bdff2a 100644 --- a/steem/amount.py +++ b/steem/amount.py @@ -1,8 +1,11 @@ class Amount(dict): - """ This class helps deal and calculate with the different assets on the chain. + """ This class helps deal and calculate with the different assets on the + chain. - :param str amountString: Amount string as used by the backend (e.g. "10 SBD") + :param str amountString: Amount string as used by the backend + (e.g. "10 SBD") """ + def __init__(self, amount_string="0 SBD"): if isinstance(amount_string, Amount): self["amount"] = amount_string["amount"] @@ -10,7 +13,9 @@ def __init__(self, amount_string="0 SBD"): elif isinstance(amount_string, str): self["amount"], self["asset"] = amount_string.split(" ") else: - raise ValueError("Need an instance of 'Amount' or a string with amount and asset") + raise ValueError( + "Need an instance of 'Amount' or a string with amount " + + "and asset") self["amount"] = float(self["amount"]) @@ -38,7 +43,8 @@ def __str__(self): # default else: prec = 6 - return "{:.{prec}f} {}".format(self["amount"], self["asset"], prec=prec) + return "{:.{prec}f} {}".format( + self["amount"], self["asset"], prec=prec) def __float__(self): return self["amount"] diff --git a/steem/block.py b/steem/block.py index 9751431..ad8e514 100644 --- a/steem/block.py +++ b/steem/block.py @@ -8,7 +8,8 @@ class Block(dict): """ Read a single block from the chain :param int block: block number - :param Steemd steemd_instance: Steemd() instance to use when accessing a RPC + :param Steemd steemd_instance: Steemd() instance to use when + accessing a RPC """ diff --git a/steem/blockchain.py b/steem/blockchain.py index d13d266..d639862 100644 --- a/steem/blockchain.py +++ b/steem/blockchain.py @@ -4,12 +4,13 @@ import warnings from typing import Union -from .instance import shared_steemd_instance,stm +from .instance import shared_steemd_instance, stm from .utils import parse_time import logging logger = logging.getLogger(__name__) + class Blockchain(object): """ Access the blockchain and read data from it. @@ -46,22 +47,34 @@ def get_current_block(self): """ return self.steem.get_block(self.get_current_block_num()) - def stream_from(self, start_block=None, end_block=None, batch_operations=False, full_blocks=False, **kwargs): - """ This call yields raw blocks or operations depending on ``full_blocks`` param. - + def stream_from(self, + start_block=None, + end_block=None, + batch_operations=False, + full_blocks=False, + **kwargs): + """ This call yields raw blocks or operations depending on + ``full_blocks`` param. + By default, this generator will yield operations, one by one. - You can choose to yield lists of operations, batched to contain + You can choose to yield lists of operations, batched to contain all operations for each block with ``batch_operations=True``. You can also yield full blocks instead, with ``full_blocks=True``. - - Args: - start_block (int): Block to start with. If not provided, current (head) block is used. - end_block (int): Stop iterating at this block. If not provided, this generator will run forever (streaming mode). - batch_operations (bool): (Defaults to False) Rather than yielding operations one by one, - yield a list of all operations for each block. - full_blocks (bool): (Defaults to False) Rather than yielding operations, return raw, unedited blocks as - provided by steemd. This mode will NOT include virtual operations. - """ + + Args: start_block (int): Block to start with. If not provided, current + (head) block is used. + + end_block (int): Stop iterating at this block. If not provided, this + generator will run forever (streaming mode). + + batch_operations (bool): (Defaults to False) Rather than yielding + operations one by one, yield a list of all operations for each block. + + full_blocks (bool): (Defaults to False) Rather than yielding + operations, return raw, unedited blocks as provided by steemd. This + mode will NOT include virtual operations. + + """ _ = kwargs # we need this # Let's find out how often blocks are generated! @@ -75,7 +88,8 @@ def stream_from(self, start_block=None, end_block=None, batch_operations=False, for block_num in range(start_block, head_block + 1): if end_block and block_num > end_block: - raise StopIteration("Reached stop block at: #%s" % end_block) + raise StopIteration( + "Reached stop block at: #%s" % end_block) if full_blocks: yield self.steem.get_block(block_num) @@ -88,82 +102,125 @@ def stream_from(self, start_block=None, end_block=None, batch_operations=False, start_block = head_block + 1 time.sleep(block_interval) + def reliable_stream(self, + start_block=None, + block_interval=None, + update_interval=False, + batch_operations=False, + full_blocks=False, + timeout=None, + **kwargs): + """ - def reliable_stream(self, start_block=None, block_interval=None, update_interval=False, batch_operations=False, full_blocks=False, timeout=None, **kwargs): - """ A version of stream_from() intended for use in services that NEED reliable (nonstop) streaming + A version of stream_from() intended for use in services that NEED + reliable (nonstop) streaming - By default, works same as stream_from() but will also keep trying until getting a response from steemd, allowing catching up after server downtime. + By default, works same as stream_from() but will also keep trying + until getting a response from steemd, allowing catching up after + server downtime. - Warnings: - To ensure reliability, this method does some weird none-standard things with the steemd client + Warnings: To ensure reliability, this method does some weird + none-standard things with the steemd client Args: - start_block (int): Block to start with. If not provided, current (head) block is used. - block_interval (int): Time between block generations. If not provided, will attempt to query steemd for this value - batch_operations (bool): (Defaults to False) Rather than yielding operations one by one, - yield a list of all operations for each block. - full_blocks (bool): (Defaults to False) Rather than yielding operations, return raw, unedited blocks as - provided by steemd. This mode will NOT include virtual operations. - timeout (int): Time to wait on response from steemd before assuming timeout and retrying queries. If not provided, this will default to block_interval/4 - for all queries except get_block_interval() - where it will default to 2 seconds for initial setup + + start_block (int): Block to start with. If not provided, current + (head) block is used. + + block_interval (int): Time between block generations. If not + provided, will attempt to query steemd for this value + + batch_operations (bool): (Defaults to False) Rather than yielding + operations one by one, yield a list of all operations for each + block. + + full_blocks (bool): (Defaults to False) Rather than yielding + operations, return raw, unedited blocks as provided by steemd. This + mode will NOT include virtual operations. + + timeout (int): Time to wait on response from steemd before assuming + timeout and retrying queries. If not provided, this will default to + block_interval/4 for all queries except get_block_interval() - + where it will default to 2 seconds for initial setup + """ + def get_reliable_client(_timeout): # we want to fail fast and try the next node quickly - return stm.steemd.Steemd(nodes=self.steem.nodes,retries=1,timeout=_timeout,re_raise=True) - def reliable_query(_client,_method,_api,*_args): + return stm.steemd.Steemd( + nodes=self.steem.nodes, + retries=1, + timeout=_timeout, + re_raise=True) + + def reliable_query(_client, _method, _api, *_args): # this will ALWAYS eventually return, at all costs retval = None while retval is None: - try: - retval = _client.call(_method,*_args,api=_api) - except Exception as e: - logger.info('Failed to get response', extra=dict(exc=e,response=retval,api_name=_api,api_method=_method,api_args=_args)) - retval = None - if retval is None: time.sleep(1) + try: + retval = _client.call(_method, *_args, api=_api) + except Exception as e: + logger.info( + 'Failed to get response', + extra=dict( + exc=e, + response=retval, + api_name=_api, + api_method=_method, + api_args=_args)) + retval = None + if retval is None: + time.sleep(1) return retval def get_reliable_block_interval(_client): - return reliable_query(_client,'get_config','database_api').get('STEEMIT_BLOCK_INTERVAL') + return reliable_query(_client, 'get_config', + 'database_api').get('STEEMIT_BLOCK_INTERVAL') def get_reliable_current_block(_client): - return reliable_query(_client,'get_dynamic_global_properties','database_api').get(self.mode) + return reliable_query(_client, 'get_dynamic_global_properties', + 'database_api').get(self.mode) - def get_reliable_blockdata(_client,_block_num): - return reliable_query(_client,'get_block', 'database_api', block_num) + def get_reliable_blockdata(_client, _block_num): + return reliable_query(_client, 'get_block', 'database_api', + block_num) - def get_reliable_ops_in_block(_client,_block_num): - return reliable_query(_client,'get_ops_in_block','database_api',block_num,False) + def get_reliable_ops_in_block(_client, _block_num): + return reliable_query(_client, 'get_ops_in_block', 'database_api', + block_num, False) if timeout is None: - if block_interval is None: - _reliable_client = get_reliable_client(2) - block_interval = get_reliable_block_interval(_reliable_client) - else: - timeout = block_interval/4 - _reliable_client = get_reliable_client(timeout) + if block_interval is None: + _reliable_client = get_reliable_client(2) + block_interval = get_reliable_block_interval(_reliable_client) + else: + timeout = block_interval / 4 + _reliable_client = get_reliable_client(timeout) else: - _reliable_client = get_reliable_client(timeout) + _reliable_client = get_reliable_client(timeout) if block_interval is None: - block_interval = get_reliable_block_interval(_reliable_client) + block_interval = get_reliable_block_interval(_reliable_client) if start_block is None: - start_block = get_reliable_current_block(_reliable_client) + start_block = get_reliable_current_block(_reliable_client) while True: - sleep_interval = block_interval/4 - head_block = get_reliable_current_block(_reliable_client) + sleep_interval = block_interval / 4 + head_block = get_reliable_current_block(_reliable_client) - for block_num in range(start_block,head_block+1): - if full_blocks: - yield get_reliable_current_block(_reliable_client,head_block) - elif batch_operations: - yield get_reliable_ops_in_block(_reliable_client,head_block) - else: - yield from get_reliable_ops_in_block(_reliable_client,head_block) - sleep_interval = sleep_interval/2 - - time.sleep(sleep_interval) - start_block = head_block + 1 + for block_num in range(start_block, head_block + 1): + if full_blocks: + yield get_reliable_current_block(_reliable_client, + head_block) + elif batch_operations: + yield get_reliable_ops_in_block(_reliable_client, + head_block) + else: + yield from get_reliable_ops_in_block( + _reliable_client, head_block) + sleep_interval = sleep_interval / 2 + time.sleep(sleep_interval) + start_block = head_block + 1 def stream(self, filter_by: Union[str, list] = list(), *args, **kwargs): """ Yield a stream of operations, starting with current head block. @@ -180,8 +237,10 @@ def stream(self, filter_by: Union[str, list] = list(), *args, **kwargs): events = ops if type(ops) == dict: if 'witness_signature' in ops: - raise ValueError('Blockchain.stream() is for operation level streams. ' - 'For block level streaming, use Blockchain.stream_from()') + raise ValueError( + 'Blockchain.stream() is for operation level streams. ' + 'For block level streaming, use ' + 'Blockchain.stream_from()') events = [ops] for event in events: @@ -200,30 +259,39 @@ def stream(self, filter_by: Union[str, list] = list(), *args, **kwargs): "trx_id": event.get("trx_id"), } - def history(self, filter_by: Union[str, list] = list(), start_block=1, end_block=None, raw_output=False, **kwargs): + def history(self, + filter_by: Union[str, list] = list(), + start_block=1, + end_block=None, + raw_output=False, + **kwargs): """ Yield a stream of historic operations. - - Similar to ``Blockchain.stream()``, but starts at beginning of chain unless ``start_block`` is set. - - Args: - filter_by (str, list): List of operations to filter for - start_block (int): Block to start with. If not provided, start of blockchain is used (block 1). - end_block (int): Stop iterating at this block. If not provided, this generator will run forever. - raw_output (bool): (Defaults to False). If True, return ops in a unmodified steemd structure. - """ + + Similar to ``Blockchain.stream()``, but starts at beginning of chain + unless ``start_block`` is set. + + Args: filter_by (str, list): List of operations to filter for + start_block (int): Block to start with. If not provided, start of + blockchain is used (block 1). + end_block (int): Stop iterating at this + block. If not provided, this generator will run forever. + raw_output (bool): (Defaults to False). If True, return ops in a + unmodified steemd structure. """ + return self.stream( filter_by=filter_by, start_block=start_block, end_block=end_block, raw_output=raw_output, - **kwargs - ) + **kwargs) def ops(self, *args, **kwargs): - raise DeprecationWarning('Blockchain.ops() is deprecated. Please use Blockchain.stream_from() instead.') + raise DeprecationWarning('Blockchain.ops() is deprecated. Please use ' + + 'Blockchain.stream_from() instead.') def replay(self, **kwargs): - warnings.warn('Blockchain.replay() is deprecated. Please use Blockchain.history() instead.') + warnings.warn('Blockchain.replay() is deprecated. ' + + 'Please use Blockchain.history() instead.') return self.history(**kwargs) @staticmethod @@ -235,5 +303,7 @@ def hash_op(event: dict): def get_all_usernames(self, *args, **kwargs): """ Fetch the full list of STEEM usernames. """ _ = args, kwargs - warnings.warn('Blockchain.get_all_usernames() is now at Steemd.get_all_usernames().') + warnings.warn( + 'Blockchain.get_all_usernames() is now Steemd.get_all_usernames().' + ) return self.steem.get_all_usernames() diff --git a/steem/blog.py b/steem/blog.py index c2565b2..ea15e02 100644 --- a/steem/blog.py +++ b/steem/blog.py @@ -13,7 +13,8 @@ class Blog: Args: account_name (str): Name of the account - comments_only (bool): (Default False). Toggle between posts and comments. + comments_only (bool): (Default False). Toggle between posts + and comments. steemd_instance (Steemd): Steemd instance overload Returns: @@ -39,7 +40,10 @@ class Blog: """ - def __init__(self, account_name: str, comments_only=False, steemd_instance=None): + def __init__(self, + account_name: str, + comments_only=False, + steemd_instance=None): self.steem = steemd_instance or shared_steemd_instance() self.comments_only = comments_only self.account = Account(account_name) @@ -56,11 +60,14 @@ def take(self, limit=5): List of posts/comments in a batch of size up to `limit`. """ # get main posts only - comment_filter = is_comment if self.comments_only else complement(is_comment) + comment_filter = is_comment if self.comments_only else complement( + is_comment) hist = filter(comment_filter, self.history) # filter out reblogs - match_author = lambda x: x['author'] == self.account.name + def match_author(x): + return x['author'] == self.account.name + hist2 = filter(match_author, hist) # post edits will re-appear in history diff --git a/steem/cli.py b/steem/cli.py index b58a430..5f15d38 100644 --- a/steem/cli.py +++ b/steem/cli.py @@ -37,9 +37,7 @@ def legacy(): parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, - description="Command line tool to interact with the Steem network" - ) - + description="Command line tool to interact with the Steem network") """ Default settings for all tools """ @@ -51,41 +49,34 @@ def legacy(): ) parser.add_argument( - '--no-broadcast', '-d', + '--no-broadcast', + '-d', action='store_true', - help='Do not broadcast anything' - ) + help='Do not broadcast anything') parser.add_argument( - '--no-wallet', '-p', + '--no-wallet', + '-p', action='store_true', - help='Do not load the wallet' - ) + help='Do not load the wallet') parser.add_argument( - '--unsigned', '-x', + '--unsigned', + '-x', action='store_true', - help='Do not try to sign the transaction' - ) + help='Do not try to sign the transaction') parser.add_argument( - '--expires', '-e', + '--expires', + '-e', default=60, - help='Expiration time in seconds (defaults to 30)' - ) + help='Expiration time in seconds (defaults to 30)') parser.add_argument( - '--verbose', '-v', - type=int, - default=3, - help='Verbosity' - ) + '--verbose', '-v', type=int, default=3, help='Verbosity') parser.add_argument( '--version', action='version', version='%(prog)s {version}'.format( - version=pkg_resources.require("steem")[0].version - ) - ) + version=pkg_resources.require("steem")[0].version)) subparsers = parser.add_subparsers(help='sub-command help') - """ Command "set" """ @@ -94,95 +85,87 @@ def legacy(): 'key', type=str, choices=availableConfigurationKeys, - help='Configuration key' - ) - setconfig.add_argument( - 'value', - type=str, - help='Configuration value' - ) + help='Configuration key') + setconfig.add_argument('value', type=str, help='Configuration value') setconfig.set_defaults(command="set") - """ Command "config" """ - configconfig = subparsers.add_parser('config', help='Show local configuration') + configconfig = subparsers.add_parser( + 'config', help='Show local configuration') configconfig.set_defaults(command="config") - """ Command "info" """ - parser_info = subparsers.add_parser('info', help='Show basic STEEM blockchain info') + parser_info = subparsers.add_parser( + 'info', help='Show basic STEEM blockchain info') parser_info.set_defaults(command="info") parser_info.add_argument( 'objects', nargs='*', type=str, - help='General information about the blockchain, a block, an account name, a post, a public key, ...' - ) - + help='General information about the blockchain, a block, an account' + ' name, a post, a public key, ...') """ Command "changewalletpassphrase" """ - changepasswordconfig = subparsers.add_parser('changewalletpassphrase', help='Change wallet password') + changepasswordconfig = subparsers.add_parser( + 'changewalletpassphrase', help='Change wallet password') changepasswordconfig.set_defaults(command="changewalletpassphrase") - """ Command "addkey" """ - addkey = subparsers.add_parser('addkey', help='Add a new key to the wallet') + addkey = subparsers.add_parser( + 'addkey', help='Add a new key to the wallet') addkey.add_argument( '--unsafe-import-key', nargs='*', type=str, - help='private key to import into the wallet (unsafe, unless you delete your bash history)' - ) + help='private key to import into the wallet (unsafe, unless you ' + + 'delete your shell history)') addkey.set_defaults(command="addkey") - parsewif = subparsers.add_parser('parsewif', help='Parse a WIF private key without importing') + parsewif = subparsers.add_parser( + 'parsewif', help='Parse a WIF private key without importing') parsewif.add_argument( - '--unsafe-import-key', - nargs='*', - type=str, - help='WIF key to parse (unsafe, delete your bash history)' - ) + '--unsafe-import-key', + nargs='*', + type=str, + help='WIF key to parse (unsafe, delete your bash history)') parsewif.set_defaults(command='parsewif') - """ Command "delkey" """ - delkey = subparsers.add_parser('delkey', help='Delete keys from the wallet') + delkey = subparsers.add_parser( + 'delkey', help='Delete keys from the wallet') delkey.add_argument( 'pub', nargs='*', type=str, - help='the public key to delete from the wallet' - ) + help='the public key to delete from the wallet') delkey.set_defaults(command="delkey") - """ Command "getkey" """ - getkey = subparsers.add_parser('getkey', help='Dump the privatekey of a pubkey from the wallet') + getkey = subparsers.add_parser( + 'getkey', help='Dump the privatekey of a pubkey from the wallet') getkey.add_argument( 'pub', type=str, - help='the public key for which to show the private key' - ) + help='the public key for which to show the private key') getkey.set_defaults(command="getkey") - """ Command "listkeys" """ - listkeys = subparsers.add_parser('listkeys', help='List available keys in your wallet') + listkeys = subparsers.add_parser( + 'listkeys', help='List available keys in your wallet') listkeys.set_defaults(command="listkeys") - """ Command "listaccounts" """ - listaccounts = subparsers.add_parser('listaccounts', help='List available accounts in your wallet') + listaccounts = subparsers.add_parser( + 'listaccounts', help='List available accounts in your wallet') listaccounts.set_defaults(command="listaccounts") - """ Command "upvote" """ @@ -191,23 +174,20 @@ def legacy(): parser_upvote.add_argument( 'post', type=str, - help='@author/permlink-identifier of the post to upvote to (e.g. @xeroc/python-steem-0-1)' - ) + help='@author/permlink-identifier of the post to upvote ' + + 'to (e.g. @xeroc/python-steem-0-1)') parser_upvote.add_argument( '--account', type=str, required=False, default=configStorage["default_account"], - help='The voter account name' - ) + help='The voter account name') parser_upvote.add_argument( '--weight', type=float, default=configStorage["default_vote_weight"], required=False, - help='Actual weight (from 0.1 to 100.0)' - ) - + help='Actual weight (from 0.1 to 100.0)') """ Command "downvote" """ @@ -217,673 +197,535 @@ def legacy(): '--account', type=str, default=configStorage["default_account"], - help='The voter account name' - ) + help='The voter account name') parser_downvote.add_argument( 'post', type=str, - help='@author/permlink-identifier of the post to downvote to (e.g. @xeroc/python-steem-0-1)' - ) + help='@author/permlink-identifier of the post to downvote ' + + 'to (e.g. @xeroc/python-steem-0-1)') parser_downvote.add_argument( '--weight', type=float, default=configStorage["default_vote_weight"], required=False, - help='Actual weight (from 0.1 to 100.0)' - ) - + help='Actual weight (from 0.1 to 100.0)') """ Command "transfer" """ parser_transfer = subparsers.add_parser('transfer', help='Transfer STEEM') parser_transfer.set_defaults(command="transfer") + parser_transfer.add_argument('to', type=str, help='Recipient') parser_transfer.add_argument( - 'to', - type=str, - help='Recipient' - ) - parser_transfer.add_argument( - 'amount', - type=float, - help='Amount to transfer' - ) + 'amount', type=float, help='Amount to transfer') parser_transfer.add_argument( 'asset', type=str, choices=["STEEM", "SBD"], - help='Asset to transfer (i.e. STEEM or SDB)' - ) + help='Asset to transfer (i.e. STEEM or SDB)') parser_transfer.add_argument( - 'memo', - type=str, - nargs="?", - default="", - help='Optional memo' - ) + 'memo', type=str, nargs="?", default="", help='Optional memo') parser_transfer.add_argument( '--account', type=str, required=False, default=configStorage["default_account"], - help='Transfer from this account' - ) - + help='Transfer from this account') """ Command "powerup" """ - parser_powerup = subparsers.add_parser('powerup', help='Power up (vest STEEM as STEEM POWER)') + parser_powerup = subparsers.add_parser( + 'powerup', help='Power up (vest STEEM as STEEM POWER)') parser_powerup.set_defaults(command="powerup") parser_powerup.add_argument( - 'amount', - type=str, - help='Amount of VESTS to powerup' - ) + 'amount', type=str, help='Amount of VESTS to powerup') parser_powerup.add_argument( '--account', type=str, required=False, default=configStorage["default_account"], - help='Powerup from this account' - ) + help='Powerup from this account') parser_powerup.add_argument( '--to', type=str, required=False, default=None, - help='Powerup this account' - ) - + help='Powerup this account') """ Command "powerdown" """ - parser_powerdown = subparsers.add_parser('powerdown', help='Power down (start withdrawing STEEM from steem POWER)') + parser_powerdown = subparsers.add_parser( + 'powerdown', + help='Power down (start withdrawing STEEM from steem POWER)') parser_powerdown.set_defaults(command="powerdown") parser_powerdown.add_argument( - 'amount', - type=str, - help='Amount of VESTS to powerdown' - ) + 'amount', type=str, help='Amount of VESTS to powerdown') parser_powerdown.add_argument( '--account', type=str, required=False, default=configStorage["default_account"], - help='powerdown from this account' - ) - + help='powerdown from this account') """ Command "powerdownroute" """ - parser_powerdownroute = subparsers.add_parser('powerdownroute', help='Setup a powerdown route') + parser_powerdownroute = subparsers.add_parser( + 'powerdownroute', help='Setup a powerdown route') parser_powerdownroute.set_defaults(command="powerdownroute") parser_powerdownroute.add_argument( 'to', type=str, default=configStorage["default_account"], - help='The account receiving either VESTS/SteemPower or STEEM.' - ) + help='The account receiving either VESTS/SteemPower or STEEM.') parser_powerdownroute.add_argument( '--percentage', type=float, default=100, - help='The percent of the withdraw to go to the "to" account' - ) + help='The percent of the withdraw to go to the "to" account') parser_powerdownroute.add_argument( '--account', type=str, default=configStorage["default_account"], - help='The account which is powering down' - ) + help='The account which is powering down') parser_powerdownroute.add_argument( '--auto_vest', action='store_true', help=('Set to true if the from account should receive the VESTS as' - 'VESTS, or false if it should receive them as STEEM.') - ) - + 'VESTS, or false if it should receive them as STEEM.')) """ Command "convert" """ - parser_convert = subparsers.add_parser('convert', help='Convert STEEMDollars to Steem (takes a week to settle)') + parser_convert = subparsers.add_parser( + 'convert', + help='Convert STEEMDollars to Steem (takes a week to settle)') parser_convert.set_defaults(command="convert") parser_convert.add_argument( - 'amount', - type=float, - help='Amount of SBD to convert' - ) + 'amount', type=float, help='Amount of SBD to convert') parser_convert.add_argument( '--account', type=str, required=False, default=configStorage["default_account"], - help='Convert from this account' - ) - + help='Convert from this account') """ Command "balance" """ - parser_balance = subparsers.add_parser('balance', help='Show the balance of one more more accounts') + parser_balance = subparsers.add_parser( + 'balance', help='Show the balance of one more more accounts') parser_balance.set_defaults(command="balance") parser_balance.add_argument( 'account', type=str, nargs="*", default=configStorage["default_account"], - help='balance of these account (multiple accounts allowed)' - ) - + help='balance of these account (multiple accounts allowed)') """ Command "interest" """ - interest = subparsers.add_parser('interest', help='Get information about interest payment') + interest = subparsers.add_parser( + 'interest', help='Get information about interest payment') interest.set_defaults(command="interest") interest.add_argument( 'account', type=str, nargs="*", default=configStorage["default_account"], - help='Inspect these accounts' - ) - + help='Inspect these accounts') """ Command "permissions" """ - parser_permissions = subparsers.add_parser('permissions', help='Show permissions of an account') + parser_permissions = subparsers.add_parser( + 'permissions', help='Show permissions of an account') parser_permissions.set_defaults(command="permissions") parser_permissions.add_argument( 'account', type=str, nargs="?", default=configStorage["default_account"], - help='Account to show permissions for' - ) - + help='Account to show permissions for') """ Command "allow" """ - parser_allow = subparsers.add_parser('allow', help='Allow an account/key to interact with your account') + parser_allow = subparsers.add_parser( + 'allow', help='Allow an account/key to interact with your account') parser_allow.set_defaults(command="allow") parser_allow.add_argument( '--account', type=str, nargs="?", default=configStorage["default_account"], - help='The account to allow action for' - ) + help='The account to allow action for') parser_allow.add_argument( 'foreign_account', type=str, nargs="?", - help='The account or key that will be allowed to interact as your account' - ) + help='The account or key that will be allowed to interact with ' + + 'your account') parser_allow.add_argument( '--permission', type=str, default="posting", choices=["owner", "posting", "active"], - help='The permission to grant (defaults to "posting")' - ) + help='The permission to grant (defaults to "posting")') parser_allow.add_argument( '--weight', type=int, default=None, help=('The weight to use instead of the (full) threshold. ' 'If the weight is smaller than the threshold, ' - 'additional signatures are required') - ) + 'additional signatures are required')) parser_allow.add_argument( '--threshold', type=int, default=None, help=('The permission\'s threshold that needs to be reached ' - 'by signatures to be able to interact') - ) - + 'by signatures to be able to interact')) """ Command "disallow" """ - parser_disallow = subparsers.add_parser('disallow', - help='Remove allowance an account/key to interact with your account') + parser_disallow = subparsers.add_parser( + 'disallow', + help='Remove allowance an account/key to interact with your account') parser_disallow.set_defaults(command="disallow") parser_disallow.add_argument( '--account', type=str, nargs="?", default=configStorage["default_account"], - help='The account to disallow action for' - ) + help='The account to disallow action for') parser_disallow.add_argument( 'foreign_account', type=str, - help='The account or key whose allowance to interact as your account will be removed' - ) + help='The account or key whose allowance to interact as your ' + + 'account will be removed') parser_disallow.add_argument( '--permission', type=str, default="posting", choices=["owner", "posting", "active"], - help='The permission to remove (defaults to "posting")' - ) + help='The permission to remove (defaults to "posting")') parser_disallow.add_argument( '--threshold', type=int, default=None, help=('The permission\'s threshold that needs to be reached ' - 'by signatures to be able to interact') - ) - + 'by signatures to be able to interact')) """ Command "newaccount" """ - parser_newaccount = subparsers.add_parser('newaccount', help='Create a new account') + parser_newaccount = subparsers.add_parser( + 'newaccount', help='Create a new account') parser_newaccount.set_defaults(command="newaccount") parser_newaccount.add_argument( - 'accountname', - type=str, - help='New account name' - ) + 'accountname', type=str, help='New account name') parser_newaccount.add_argument( '--account', type=str, required=False, default=configStorage["default_account"], - help='Account that pays the fee' - ) + help='Account that pays the fee') parser_newaccount.add_argument( '--fee', type=str, required=False, default='0 STEEM', - help='Base Fee to pay. Delegate the rest.' - ) - + help='Base Fee to pay. Delegate the rest.') """ Command "importaccount" """ - parser_importaccount = subparsers.add_parser('importaccount', help='Import an account using a passphrase') + parser_importaccount = subparsers.add_parser( + 'importaccount', help='Import an account using a passphrase') parser_importaccount.set_defaults(command="importaccount") - parser_importaccount.add_argument( - 'account', - type=str, - help='Account name' - ) + parser_importaccount.add_argument('account', type=str, help='Account name') parser_importaccount.add_argument( '--roles', type=str, nargs="*", default=["active", "posting", "memo"], # no owner - help='Import specified keys (owner, active, posting, memo)' - ) - + help='Import specified keys (owner, active, posting, memo)') """ Command "updateMemoKey" """ - parser_updateMemoKey = subparsers.add_parser('updatememokey', help='Update an account\'s memo key') + parser_updateMemoKey = subparsers.add_parser( + 'updatememokey', help='Update an account\'s memo key') parser_updateMemoKey.set_defaults(command="updatememokey") parser_updateMemoKey.add_argument( '--account', type=str, nargs="?", default=configStorage["default_account"], - help='The account to updateMemoKey action for' - ) + help='The account to updateMemoKey action for') parser_updateMemoKey.add_argument( - '--key', - type=str, - default=None, - help='The new memo key' - ) - + '--key', type=str, default=None, help='The new memo key') """ Command "approvewitness" """ - parser_approvewitness = subparsers.add_parser('approvewitness', help='Approve a witnesses') + parser_approvewitness = subparsers.add_parser( + 'approvewitness', help='Approve a witnesses') parser_approvewitness.set_defaults(command="approvewitness") parser_approvewitness.add_argument( - 'witness', - type=str, - help='Witness to approve' - ) + 'witness', type=str, help='Witness to approve') parser_approvewitness.add_argument( '--account', type=str, required=False, default=configStorage["default_account"], - help='Your account' - ) - + help='Your account') """ Command "disapprovewitness" """ - parser_disapprovewitness = subparsers.add_parser('disapprovewitness', help='Disapprove a witnesses') + parser_disapprovewitness = subparsers.add_parser( + 'disapprovewitness', help='Disapprove a witnesses') parser_disapprovewitness.set_defaults(command="disapprovewitness") parser_disapprovewitness.add_argument( - 'witness', - type=str, - help='Witness to disapprove' - ) + 'witness', type=str, help='Witness to disapprove') parser_disapprovewitness.add_argument( '--account', type=str, required=False, default=configStorage["default_account"], - help='Your account' - ) - + help='Your account') """ Command "sign" """ - parser_sign = subparsers.add_parser('sign', help='Sign a provided transaction with available and required keys') + parser_sign = subparsers.add_parser( + 'sign', + help='Sign a provided transaction with available and required keys') parser_sign.set_defaults(command="sign") parser_sign.add_argument( '--file', type=str, required=False, - help='Load transaction from file. If "-", read from stdin (defaults to "-")' - ) - + help='Load transaction from file. If "-", read from ' + + 'stdin (defaults to "-")') """ Command "broadcast" """ - parser_broadcast = subparsers.add_parser('broadcast', help='broadcast a signed transaction') + parser_broadcast = subparsers.add_parser( + 'broadcast', help='broadcast a signed transaction') parser_broadcast.set_defaults(command="broadcast") parser_broadcast.add_argument( '--file', type=str, required=False, - help='Load transaction from file. If "-", read from stdin (defaults to "-")' - ) - + help='Load transaction from file. If "-", read from ' + + 'stdin (defaults to "-")') """ Command "orderbook" """ - orderbook = subparsers.add_parser('orderbook', help='Obtain orderbook of the internal market') + orderbook = subparsers.add_parser( + 'orderbook', help='Obtain orderbook of the internal market') orderbook.set_defaults(command="orderbook") orderbook.add_argument( '--chart', action='store_true', - help="Enable charting (requires matplotlib)" - ) - + help="Enable charting (requires matplotlib)") """ Command "buy" """ - parser_buy = subparsers.add_parser('buy', help='Buy STEEM or SBD from the internal market') + parser_buy = subparsers.add_parser( + 'buy', help='Buy STEEM or SBD from the internal market') parser_buy.set_defaults(command="buy") - parser_buy.add_argument( - 'amount', - type=float, - help='Amount to buy' - ) + parser_buy.add_argument('amount', type=float, help='Amount to buy') parser_buy.add_argument( 'asset', type=str, choices=["STEEM", "SBD"], - help='Asset to buy (i.e. STEEM or SDB)' - ) + help='Asset to buy (i.e. STEEM or SDB)') parser_buy.add_argument( - 'price', - type=float, - help='Limit buy price denoted in (SBD per STEEM)' - ) + 'price', type=float, help='Limit buy price denoted in (SBD per STEEM)') parser_buy.add_argument( '--account', type=str, required=False, default=configStorage["default_account"], - help='Buy with this account (defaults to "default_account")' - ) - + help='Buy with this account (defaults to "default_account")') """ Command "sell" """ - parser_sell = subparsers.add_parser('sell', help='Sell STEEM or SBD from the internal market') + parser_sell = subparsers.add_parser( + 'sell', help='Sell STEEM or SBD from the internal market') parser_sell.set_defaults(command="sell") - parser_sell.add_argument( - 'amount', - type=float, - help='Amount to sell' - ) + parser_sell.add_argument('amount', type=float, help='Amount to sell') parser_sell.add_argument( 'asset', type=str, choices=["STEEM", "SBD"], - help='Asset to sell (i.e. STEEM or SDB)' - ) + help='Asset to sell (i.e. STEEM or SDB)') parser_sell.add_argument( 'price', type=float, - help='Limit sell price denoted in (SBD per STEEM)' - ) + help='Limit sell price denoted in (SBD per STEEM)') parser_sell.add_argument( '--account', type=str, required=False, default=configStorage["default_account"], - help='Sell from this account (defaults to "default_account")' - ) + help='Sell from this account (defaults to "default_account")') """ Command "cancel" """ - parser_cancel = subparsers.add_parser('cancel', help='Cancel order in the internal market') + parser_cancel = subparsers.add_parser( + 'cancel', help='Cancel order in the internal market') parser_cancel.set_defaults(command="cancel") - parser_cancel.add_argument( - 'orderid', - type=int, - help='Orderid' - ) + parser_cancel.add_argument('orderid', type=int, help='Orderid') parser_cancel.add_argument( '--account', type=str, required=False, default=configStorage["default_account"], - help='Cancel from this account (defaults to "default_account")' - ) - + help='Cancel from this account (defaults to "default_account")') """ Command "resteem" """ - parser_resteem = subparsers.add_parser('resteem', help='Resteem an existing post') + parser_resteem = subparsers.add_parser( + 'resteem', help='Resteem an existing post') parser_resteem.set_defaults(command="resteem") parser_resteem.add_argument( 'identifier', type=str, - help='@author/permlink-identifier of the post to resteem' - ) + help='@author/permlink-identifier of the post to resteem') parser_resteem.add_argument( '--account', type=str, required=False, default=configStorage["default_account"], - help='Resteem as this user (requires to have the key installed in the wallet)' - ) - + help='Resteem as this user (requires to have the ' + + 'key installed in the wallet)') """ Command "follow" """ - parser_follow = subparsers.add_parser('follow', help='Follow another account') + parser_follow = subparsers.add_parser( + 'follow', help='Follow another account') parser_follow.set_defaults(command="follow") - parser_follow.add_argument( - 'follow', - type=str, - help='Account to follow' - ) + parser_follow.add_argument('follow', type=str, help='Account to follow') parser_follow.add_argument( '--account', type=str, required=False, default=configStorage["default_account"], - help='Follow from this account' - ) + help='Follow from this account') parser_follow.add_argument( '--what', type=str, required=False, nargs="*", default=["blog"], - help='Follow these objects (defaults to "blog")' - ) - + help='Follow these objects (defaults to "blog")') """ Command "unfollow" """ - parser_unfollow = subparsers.add_parser('unfollow', help='unfollow another account') + parser_unfollow = subparsers.add_parser( + 'unfollow', help='unfollow another account') parser_unfollow.set_defaults(command="unfollow") parser_unfollow.add_argument( - 'unfollow', - type=str, - help='Account to unfollow' - ) + 'unfollow', type=str, help='Account to unfollow') parser_unfollow.add_argument( '--account', type=str, required=False, default=configStorage["default_account"], - help='Unfollow from this account' - ) + help='Unfollow from this account') parser_unfollow.add_argument( '--what', type=str, required=False, nargs="*", default=[], - help='Unfollow these objects (defaults to "blog")' - ) - + help='Unfollow these objects (defaults to "blog")') """ Command "setprofile" """ - parser_setprofile = subparsers.add_parser('setprofile', help='Set a variable in an account\'s profile') + parser_setprofile = subparsers.add_parser( + 'setprofile', help='Set a variable in an account\'s profile') parser_setprofile.set_defaults(command="setprofile") parser_setprofile.add_argument( '--account', type=str, required=False, default=configStorage["default_account"], - help='setprofile as this user (requires to have the key installed in the wallet)' - ) - parser_setprofile_a = parser_setprofile.add_argument_group('Multiple keys at once') + help='setprofile as this user (requires to have the key ' + + 'installed in the wallet)') + parser_setprofile_a = parser_setprofile.add_argument_group( + 'Multiple keys at once') parser_setprofile_a.add_argument( - '--pair', - type=str, - nargs='*', - help='"Key=Value" pairs' - ) - parser_setprofile_b = parser_setprofile.add_argument_group('Just a single key') + '--pair', type=str, nargs='*', help='"Key=Value" pairs') + parser_setprofile_b = parser_setprofile.add_argument_group( + 'Just a single key') parser_setprofile_b.add_argument( - 'variable', - type=str, - nargs='?', - help='Variable to set' - ) + 'variable', type=str, nargs='?', help='Variable to set') parser_setprofile_b.add_argument( - 'value', - type=str, - nargs='?', - help='Value to set' - ) - + 'value', type=str, nargs='?', help='Value to set') """ Command "delprofile" """ - parser_delprofile = subparsers.add_parser('delprofile', help='Set a variable in an account\'s profile') + parser_delprofile = subparsers.add_parser( + 'delprofile', help='Set a variable in an account\'s profile') parser_delprofile.set_defaults(command="delprofile") parser_delprofile.add_argument( '--account', type=str, required=False, default=configStorage["default_account"], - help='delprofile as this user (requires to have the key installed in the wallet)' - ) + help='delprofile as this user (requires to have the ' + + 'key installed in the wallet)') parser_delprofile.add_argument( - 'variable', - type=str, - nargs='*', - help='Variable to set' - ) - + 'variable', type=str, nargs='*', help='Variable to set') """ Command "witnessupdate" """ - parser_witnessprops = subparsers.add_parser('witnessupdate', help='Change witness properties') + parser_witnessprops = subparsers.add_parser( + 'witnessupdate', help='Change witness properties') parser_witnessprops.set_defaults(command="witnessupdate") parser_witnessprops.add_argument( '--witness', type=str, default=configStorage["default_account"], - help='Witness name' - ) + help='Witness name') parser_witnessprops.add_argument( '--maximum_block_size', type=float, required=False, - help='Max block size' - ) + help='Max block size') parser_witnessprops.add_argument( '--account_creation_fee', type=float, required=False, - help='Account creation fee' - ) + help='Account creation fee') parser_witnessprops.add_argument( '--sbd_interest_rate', type=float, required=False, - help='SBD interest rate in percent' - ) + help='SBD interest rate in percent') parser_witnessprops.add_argument( - '--url', - type=str, - required=False, - help='Witness URL' - ) + '--url', type=str, required=False, help='Witness URL') parser_witnessprops.add_argument( - '--signing_key', - type=str, - required=False, - help='Signing Key' - ) - + '--signing_key', type=str, required=False, help='Signing Key') """ Command "witnesscreate" """ - parser_witnesscreate = subparsers.add_parser('witnesscreate', help='Create a witness') + parser_witnesscreate = subparsers.add_parser( + 'witnesscreate', help='Create a witness') parser_witnesscreate.set_defaults(command="witnesscreate") + parser_witnesscreate.add_argument('witness', type=str, help='Witness name') parser_witnesscreate.add_argument( - 'witness', - type=str, - help='Witness name' - ) - parser_witnesscreate.add_argument( - 'signing_key', - type=str, - help='Signing Key' - ) + 'signing_key', type=str, help='Signing Key') parser_witnesscreate.add_argument( '--maximum_block_size', type=float, default="65536", - help='Max block size' - ) + help='Max block size') parser_witnesscreate.add_argument( '--account_creation_fee', type=float, default=30, - help='Account creation fee' - ) + help='Account creation fee') parser_witnesscreate.add_argument( '--sbd_interest_rate', type=float, default=0.0, - help='SBD interest rate in percent' - ) + help='SBD interest rate in percent') parser_witnesscreate.add_argument( - '--url', - type=str, - default="", - help='Witness URL' - ) - + '--url', type=str, default="", help='Witness URL') """ Parse Arguments """ @@ -891,13 +733,11 @@ def legacy(): # Logging log = logging.getLogger(__name__) - verbosity = ["critical", - "error", - "warn", - "info", - "debug"][int(min(args.verbose, 4))] + verbosity = ["critical", "error", "warn", "info", "debug"][int( + min(args.verbose, 4))] log.setLevel(getattr(logging, verbosity.upper())) - formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s') ch = logging.StreamHandler() ch.setLevel(getattr(logging, verbosity.upper())) ch.setFormatter(formatter) @@ -905,20 +745,14 @@ def legacy(): # GrapheneAPI logging if args.verbose > 4: - verbosity = ["critical", - "error", - "warn", - "info", - "debug"][int(min((args.verbose - 4), 4))] + verbosity = ["critical", "error", "warn", "info", "debug"][int( + min((args.verbose - 4), 4))] gphlog = logging.getLogger("graphenebase") gphlog.setLevel(getattr(logging, verbosity.upper())) gphlog.addHandler(ch) if args.verbose > 8: - verbosity = ["critical", - "error", - "warn", - "info", - "debug"][int(min((args.verbose - 8), 4))] + verbosity = ["critical", "error", "warn", "info", "debug"][int( + min((args.verbose - 8), 4))] gphlog = logging.getLogger("grapheneapi") gphlog.setLevel(getattr(logging, verbosity.upper())) gphlog.addHandler(ch) @@ -941,8 +775,7 @@ def legacy(): steem = stm.Steem(no_broadcast=args.no_broadcast, **options) if args.command == "set": - if (args.key in ["default_account"] and - args.value[0] == "@"): + if (args.key in ["default_account"] and args.value[0] == "@"): args.value = args.value[1:] configStorage[args.key] = args.value @@ -950,7 +783,8 @@ def legacy(): t = PrettyTable(["Key", "Value"]) t.align = "l" for key in configStorage: - if key in availableConfigurationKeys: # hide internal config data + # hide internal config data + if key in availableConfigurationKeys: t.add_row([key, configStorage[key]]) print(t) @@ -963,12 +797,9 @@ def legacy(): median_price = steem.get_current_median_history_price() steem_per_mvest = ( Amount(info["total_vesting_fund_steem"]).amount / - (Amount(info["total_vesting_shares"]).amount / 1e6) - ) - price = ( - Amount(median_price["base"]).amount / - Amount(median_price["quote"]).amount - ) + (Amount(info["total_vesting_shares"]).amount / 1e6)) + price = (Amount(median_price["base"]).amount / Amount( + median_price["quote"]).amount) for key in info: t.add_row([key, info[key]]) t.add_row(["steem per mvest", steem_per_mvest]) @@ -999,22 +830,14 @@ def legacy(): for key in sorted(account): value = account[key] if key == "json_metadata": - value = json.dumps( - json.loads(value or "{}"), - indent=4 - ) - if key in ["posting", - "witness_votes", - "active", - "owner"]: + value = json.dumps(json.loads(value or "{}"), indent=4) + if key in ["posting", "witness_votes", "active", "owner"]: value = json.dumps(value, indent=4) if key == "reputation" and int(value) > 0: value = int(value) rep = (max(log10(value) - 9, 0) * 9 + 25 if value > 0 else max(log10(-value) - 9, 0) * -9 + 25) - value = "{:.2f} ({:d})".format( - rep, value - ) + value = "{:.2f} ({:d})".format(rep, value) t.add_row([key, value]) print(t) @@ -1025,12 +848,11 @@ def legacy(): t.align = "l" for key in sorted(witness): value = witness[key] - if key in ["props", - "sbd_exchange_rate"]: + if key in ["props", "sbd_exchange_rate"]: value = json.dumps(value, indent=4) t.add_row([key, value]) print(t) - except: + except: # noqa FIXME(sneak) pass # Public Key elif re.match("^STM.{48,55}$", obj): @@ -1050,10 +872,7 @@ def legacy(): t.align = "l" for key in sorted(post): value = post[key] - if (key in ["tags", - "json_metadata", - "active_votes" - ]): + if (key in ["tags", "json_metadata", "active_votes"]): value = json.dumps(value, indent=4) t.add_row([key, value]) print(t) @@ -1086,7 +905,8 @@ def legacy(): installed_keys = steem.commit.wallet.getPublicKeys() if len(installed_keys) == 1: - name = steem.commit.wallet.getAccountFromPublicKey(installed_keys[0]) + name = steem.commit.wallet.getAccountFromPublicKey( + installed_keys[0]) print("=" * 30) print("Would you like to make %s a default user?" % name) print() @@ -1095,32 +915,30 @@ def legacy(): print("=" * 30) elif args.command == "delkey": - if confirm( - "Are you sure you want to delete keys from your wallet?\n" - "This step is IRREVERSIBLE! If you don't have a backup, " - "You may lose access to your account!" - ): + if confirm("Are you sure you want to delete keys from your wallet?\n" + "This step is IRREVERSIBLE! If you don't have a backup, " + "You may lose access to your account!"): for pub in args.pub: steem.commit.wallet.removePrivateKeyFromPublicKey(pub) elif args.command == "parsewif": if args.unsafe_import_key: - for key in args.unsafe_import_key: - try: - print(PrivateKey(key).pubkey) - except Exception as e: - print(str(e)) + for key in args.unsafe_import_key: + try: + print(PrivateKey(key).pubkey) + except Exception as e: + print(str(e)) else: - import getpass - while True: - wifkey = getpass.getpass('Private Key (wif) [Enter to quit:') - if not wifkey: - break - try: - print(PrivateKey(wifkey).pubkey) - except Exception as e: - print(str(e)) - continue + import getpass + while True: + wifkey = getpass.getpass('Private Key (wif) [Enter to quit:') + if not wifkey: + break + try: + print(PrivateKey(wifkey).pubkey) + except Exception as e: + print(str(e)) + continue elif args.command == "getkey": print(steem.commit.wallet.getPrivateKeyForPublicKey(args.pub)) @@ -1136,8 +954,7 @@ def legacy(): t.align = "l" for account in steem.commit.wallet.getAccounts(): t.add_row([ - account["name"] or "n/a", - account["type"] or "n/a", + account["name"] or "n/a", account["type"] or "n/a", account["pubkey"] ]) print(t) @@ -1154,26 +971,25 @@ def legacy(): print_json(post.vote(weight, voter=args.account)) elif args.command == "transfer": - print_json(steem.commit.transfer( - args.to, - args.amount, - args.asset, - memo=args.memo, - account=args.account - )) + print_json( + steem.commit.transfer( + args.to, + args.amount, + args.asset, + memo=args.memo, + account=args.account)) elif args.command == "powerup": - print_json(steem.commit.transfer_to_vesting( - args.amount, - account=args.account, - to=args.to - )) + print_json( + steem.commit.transfer_to_vesting( + args.amount, account=args.account, to=args.to)) elif args.command == "powerdown": - print_json(steem.commit.withdraw_vesting( - args.amount, - account=args.account, - )) + print_json( + steem.commit.withdraw_vesting( + args.amount, + account=args.account, + )) elif args.command == "convert": print_json(steem.commit.convert( @@ -1182,12 +998,12 @@ def legacy(): )) elif args.command == "powerdownroute": - print_json(steem.commit.set_withdraw_vesting_route( - args.to, - percentage=args.percentage, - account=args.account, - auto_vest=args.auto_vest - )) + print_json( + steem.commit.set_withdraw_vesting_route( + args.to, + percentage=args.percentage, + account=args.account, + auto_vest=args.auto_vest)) elif args.command == "balance": if args.account and isinstance(args.account, list): @@ -1226,11 +1042,10 @@ def legacy(): print("Please specify an account: steempy balance ") elif args.command == "interest": - t = PrettyTable(["Account", - "Last Interest Payment", - "Next Payment", - "Interest rate", - "Interest"]) + t = PrettyTable([ + "Account", "Last Interest Payment", "Next Payment", + "Interest rate", "Interest" + ]) t.align = "r" if isinstance(args.account, str): args.account = [args.account] @@ -1253,39 +1068,43 @@ def legacy(): elif args.command == "allow": if not args.foreign_account: from steembase.account import PasswordKey - pwd = get_terminal(text="Password for Key Derivation: ", confirm=True) - args.foreign_account = format(PasswordKey(args.account, pwd, args.permission).get_public(), "STM") - print_json(steem.commit.allow( - args.foreign_account, - weight=args.weight, - account=args.account, - permission=args.permission, - threshold=args.threshold - )) + pwd = get_terminal( + text="Password for Key Derivation: ", confirm=True) + args.foreign_account = format( + PasswordKey(args.account, pwd, args.permission).get_public(), + "STM") + print_json( + steem.commit.allow( + args.foreign_account, + weight=args.weight, + account=args.account, + permission=args.permission, + threshold=args.threshold)) elif args.command == "disallow": - print_json(steem.commit.disallow( - args.foreign_account, - account=args.account, - permission=args.permission, - threshold=args.threshold - )) + print_json( + steem.commit.disallow( + args.foreign_account, + account=args.account, + permission=args.permission, + threshold=args.threshold)) elif args.command == "updatememokey": if not args.key: # Loop until both match from steembase.account import PasswordKey - pw = get_terminal(text="Password for Memo Key: ", confirm=True, allowedempty=False) + pw = get_terminal( + text="Password for Memo Key: ", + confirm=True, + allowedempty=False) memo_key = PasswordKey(args.account, pw, "memo") args.key = format(memo_key.get_public_key(), "STM") memo_privkey = memo_key.get_private_key() # Add the key to the wallet if not args.no_broadcast: steem.commit.wallet.addPrivateKey(memo_privkey) - print_json(steem.commit.update_memo_key( - args.key, - account=args.account - )) + print_json( + steem.commit.update_memo_key(args.key, account=args.account)) elif args.command == "newaccount": import getpass @@ -1295,19 +1114,18 @@ def legacy(): print("You cannot chosen an empty password!") continue else: - pwck = getpass.getpass( - "Confirm New Account Passphrase: " - ) + pwck = getpass.getpass("Confirm New Account Passphrase: ") if pw == pwck: break else: print("Given Passphrases do not match!") - print_json(steem.commit.create_account( - args.accountname, - creator=args.account, - password=pw, - delegation_fee_steem=args.fee, - )) + print_json( + steem.commit.create_account( + args.accountname, + creator=args.account, + password=pw, + delegation_fee_steem=args.fee, + )) elif args.command == "importaccount": from steembase.account import PasswordKey @@ -1337,7 +1155,9 @@ def legacy(): if "posting" in args.roles: posting_key = PasswordKey(args.account, password, role="posting") posting_pubkey = format(posting_key.get_public_key(), "STM") - if posting_pubkey in [x[0] for x in account["posting"]["key_auths"]]: + if posting_pubkey in [ + x[0] for x in account["posting"]["key_auths"] + ]: print("Importing posting key!") posting_privkey = posting_key.get_private_key() steem.commit.wallet.addPrivateKey(posting_privkey) @@ -1383,12 +1203,8 @@ def legacy(): else: price = args.price dex = Dex(steem) - print_json(dex.buy( - args.amount, - args.asset, - price, - account=args.account - )) + print_json( + dex.buy(args.amount, args.asset, price, account=args.account)) elif args.command == "sell": if args.asset == "SBD": @@ -1396,50 +1212,34 @@ def legacy(): else: price = args.price dex = Dex(steem) - print_json(dex.sell( - args.amount, - args.asset, - price, - account=args.account - )) + print_json( + dex.sell(args.amount, args.asset, price, account=args.account)) elif args.command == "cancel": dex = Dex(steem) - print_json( - dex.cancel(args.orderid) - ) + print_json(dex.cancel(args.orderid)) elif args.command == "approvewitness": - print_json(steem.commit.approve_witness( - args.witness, - account=args.account - )) + print_json( + steem.commit.approve_witness(args.witness, account=args.account)) elif args.command == "disapprovewitness": - print_json(steem.commit.disapprove_witness( - args.witness, - account=args.account - )) + print_json( + steem.commit.disapprove_witness( + args.witness, account=args.account)) elif args.command == "resteem": - print_json(steem.commit.resteem( - args.identifier, - account=args.account - )) + print_json(steem.commit.resteem(args.identifier, account=args.account)) elif args.command == "follow": - print_json(steem.commit.follow( - args.follow, - what=args.what, - account=args.account - )) + print_json( + steem.commit.follow( + args.follow, what=args.what, account=args.account)) elif args.command == "unfollow": - print_json(steem.commit.unfollow( - args.unfollow, - what=args.what, - account=args.account - )) + print_json( + steem.commit.unfollow( + args.unfollow, what=args.what, account=args.account)) elif args.command == "setprofile": from .profile import Profile @@ -1457,17 +1257,13 @@ def legacy(): profile = Profile(keys, values) account = Account(args.account) - account["json_metadata"] = Profile( - account["json_metadata"] - if account["json_metadata"] - else {} - ) + account["json_metadata"] = Profile(account["json_metadata"] + if account["json_metadata"] else {}) account["json_metadata"].update(profile) - print_json(steem.commit.update_account_profile( - account["json_metadata"], - account=args.account - )) + print_json( + steem.commit.update_account_profile( + account["json_metadata"], account=args.account)) elif args.command == "delprofile": from .profile import Profile @@ -1477,41 +1273,41 @@ def legacy(): for var in args.variable: account["json_metadata"].remove(var) - print_json(steem.commit.update_account_profile( - account["json_metadata"], - account=args.account - )) + print_json( + steem.commit.update_account_profile( + account["json_metadata"], account=args.account)) elif args.command == "witnessupdate": witness = Witness(args.witness) props = witness["props"] if args.account_creation_fee: - props["account_creation_fee"] = str(Amount("%f STEEM" % args.account_creation_fee)) + props["account_creation_fee"] = str( + Amount("%f STEEM" % args.account_creation_fee)) if args.maximum_block_size: props["maximum_block_size"] = args.maximum_block_size if args.sbd_interest_rate: props["sbd_interest_rate"] = int(args.sbd_interest_rate * 100) - print_json(steem.commit.witness_update( - args.signing_key or witness["signing_key"], - args.url or witness["url"], - props, - account=args.witness - )) + print_json( + steem.commit.witness_update( + args.signing_key or witness["signing_key"], + args.url or witness["url"], + props, + account=args.witness)) elif args.command == "witnesscreate": props = { - "account_creation_fee": str(Amount("%f STEEM" % args.account_creation_fee)), - "maximum_block_size": args.maximum_block_size, - "sbd_interest_rate": int(args.sbd_interest_rate * 100) + "account_creation_fee": + str(Amount("%f STEEM" % args.account_creation_fee)), + "maximum_block_size": + args.maximum_block_size, + "sbd_interest_rate": + int(args.sbd_interest_rate * 100) } - print_json(steem.commit.witness_update( - args.signing_key, - args.url, - props, - account=args.witness - )) + print_json( + steem.commit.witness_update( + args.signing_key, args.url, props, account=args.witness)) else: print("No valid command given") @@ -1526,10 +1322,7 @@ def confirm(question, default="yes"): :rtype: bool """ - valid = { - "yes": True, "y": True, "ye": True, - "no": False, "n": False - } + valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False} if default is None: prompt = " [y/n] " elif default == "yes": @@ -1560,9 +1353,7 @@ def get_terminal(text="Password", confirm=False, allowedempty=False): else: if not confirm: break - pwck = getpass.getpass( - "Confirm " + text - ) + pwck = getpass.getpass("Confirm " + text) if pw == pwck: break else: @@ -1574,13 +1365,11 @@ def format_operation_details(op, memos=False): if op[0] == "vote": return "%s: %s" % ( op[1]["voter"], - construct_identifier(op[1]["author"], op[1]["permlink"]) - ) + construct_identifier(op[1]["author"], op[1]["permlink"])) elif op[0] == "comment": return "%s: %s" % ( op[1]["author"], - construct_identifier(op[1]["author"], op[1]["permlink"]) - ) + construct_identifier(op[1]["author"], op[1]["permlink"])) elif op[0] == "transfer": str_ = "%s -> %s %s" % ( op[1]["from"], @@ -1597,9 +1386,7 @@ def format_operation_details(op, memos=False): str_ += " (%s)" % memo return str_ elif op[0] == "interest": - return "%s" % ( - op[1]["interest"] - ) + return "%s" % (op[1]["interest"]) else: return json.dumps(op[1], indent=4) diff --git a/steem/commit.py b/steem/commit.py index 383dc7c..1718faa 100644 --- a/steem/commit.py +++ b/steem/commit.py @@ -22,7 +22,8 @@ from .converter import Converter from .instance import shared_steemd_instance from .transactionbuilder import TransactionBuilder -from .utils import derive_permlink, resolve_identifier, fmt_time_string, keep_in_dict +from .utils import derive_permlink, resolve_identifier, \ + fmt_time_string, keep_in_dict from .wallet import Wallet log = logging.getLogger(__name__) @@ -30,7 +31,6 @@ STEEMIT_100_PERCENT = 10000 STEEMIT_1_PERCENT = (STEEMIT_100_PERCENT / 100) - # TODO # account_witness_proxy [active] # account_update [owner, active] @@ -54,16 +54,19 @@ # enable_content_editing_operation [active] + class Commit(object): """ Commit things to the Steem network. - This class contains helper methods to construct, sign and broadcast common transactions, such as posting, - voting, sending funds, etc. + This class contains helper methods to construct, sign and broadcast + common transactions, such as posting, voting, sending funds, etc. :param Steemd steemd: Steemd node to connect to* :param bool offline: Do **not** broadcast transactions! *(optional)* :param bool debug: Enable Debugging *(optional)* - :param list,dict,string keys: Predefine the wif keys to shortcut the wallet database + + :param list,dict,string keys: Predefine the wif keys to shortcut the + wallet database Three wallet operation modes are possible: @@ -97,7 +100,9 @@ def finalizeOp(self, ops, account, permission): the wallet, finalizes the transaction, signs it and broadacasts it - :param operation ops: The operation (or list of operaions) to broadcast + :param operation ops: The operation (or list of operaions) to + broadcast + :param operation account: The account that authorizes the operation :param string permission: The required permission for @@ -111,11 +116,12 @@ def finalizeOp(self, ops, account, permission): posting permission. Neither can you use different accounts for different operations! """ - tx = TransactionBuilder(None, - steemd_instance=self.steemd, - wallet_instance=self.wallet, - no_broadcast=self.no_broadcast, - expiration=self.expiration) + tx = TransactionBuilder( + None, + steemd_instance=self.steemd, + wallet_instance=self.wallet, + no_broadcast=self.no_broadcast, + expiration=self.expiration) tx.appendOps(ops) if self.unsigned: @@ -136,11 +142,12 @@ def sign(self, unsigned_trx, wifs=[]): from the wallet as defined in "missing_signatures" key of the transactizions. """ - tx = TransactionBuilder(unsigned_trx, - steemd_instance=self.steemd, - wallet_instance=self.wallet, - no_broadcast=self.no_broadcast, - expiration=self.expiration) + tx = TransactionBuilder( + unsigned_trx, + steemd_instance=self.steemd, + wallet_instance=self.wallet, + no_broadcast=self.no_broadcast, + expiration=self.expiration) tx.appendMissingSignatures(wifs) tx.sign() return tx.json() @@ -150,11 +157,12 @@ def broadcast(self, signed_trx): :param tx signed_trx: Signed transaction to broadcast """ - tx = TransactionBuilder(signed_trx, - steemd_instance=self.steemd, - wallet_instance=self.wallet, - no_broadcast=self.no_broadcast, - expiration=self.expiration) + tx = TransactionBuilder( + signed_trx, + steemd_instance=self.steemd, + wallet_instance=self.wallet, + no_broadcast=self.no_broadcast, + expiration=self.expiration) return tx.broadcast() def post(self, @@ -171,54 +179,68 @@ def post(self, self_vote=False): """ Create a new post. - If this post is intended as a reply/comment, `reply_identifier` needs to be set with the identifier of the parent - post/comment (eg. `@author/permlink`). + If this post is intended as a reply/comment, `reply_identifier` needs + to be set with the identifier of the parent post/comment (eg. + `@author/permlink`). - Optionally you can also set json_metadata, comment_options and upvote the newly created post as an author. + Optionally you can also set json_metadata, comment_options and upvote + the newly created post as an author. - Setting category, tags or community will override the values provided in json_metadata and/or comment_options - where appropriate. + Setting category, tags or community will override the values provided + in json_metadata and/or comment_options where appropriate. Args: - title (str): Title of the post - body (str): Body of the post/comment - author (str): Account are you posting from - permlink (str): Manually set the permlink (defaults to None). - If left empty, it will be derived from title automatically. - reply_identifier (str): Identifier of the parent post/comment (only if this post is a reply/comment). - json_metadata (str, dict): JSON meta object that can be attached to the post. - comment_options (str, dict): JSON options object that can be attached to the post. - Example:: - - comment_options = { - 'max_accepted_payout': '1000000.000 SBD', - 'percent_steem_dollars': 10000, - 'allow_votes': True, - 'allow_curation_rewards': True, - 'extensions': [[0, { - 'beneficiaries': [ - {'account': 'account1', 'weight': 5000}, - {'account': 'account2', 'weight': 5000}, - ]} - ]] - } - community (str): (Optional) Name of the community we are posting into. This will also override the - community specified in `json_metadata`. - tags (str, list): (Optional) A list of tags (5 max) to go with the post. This will also override the - tags specified in `json_metadata`. The first tag will be used as a 'category'. - If provided as a string, it should be space separated. - beneficiaries (list of dicts): (Optional) A list of beneficiaries for posting reward distribution. - This argument overrides beneficiaries as specified in `comment_options`. + title (str): Title of the post + body (str): Body of the post/comment + author (str): Account are you posting from + permlink (str): Manually set the permlink (defaults to None). + If left empty, it will be derived from title automatically. + reply_identifier (str): Identifier of the parent post/comment (only + if this post is a reply/comment). + json_metadata (str, dict): JSON meta object that can be attached to + the post. + comment_options (str, dict): JSON options object that can be + attached to the post. + + Example:: + comment_options = { + 'max_accepted_payout': '1000000.000 SBD', + 'percent_steem_dollars': 10000, + 'allow_votes': True, + 'allow_curation_rewards': True, + 'extensions': [[0, { + 'beneficiaries': [ + {'account': 'account1', 'weight': 5000}, + {'account': 'account2', 'weight': 5000}, + ]} + ]] + } - For example, if we would like to split rewards between account1 and account2:: + community (str): (Optional) Name of the community we are posting + into. This will also override the community specified in + `json_metadata`. - beneficiaries = [ - {'account': 'account1', 'weight': 5000}, - {'account': 'account2', 'weight': 5000} - ] + tags (str, list): (Optional) A list of tags (5 max) to go with the + post. This will also override the tags specified in + `json_metadata`. The first tag will be used as a 'category'. If + provided as a string, it should be space separated. + + beneficiaries (list of dicts): (Optional) A list of beneficiaries + for posting reward distribution. This argument overrides + beneficiaries as specified in `comment_options`. + + For example, if we would like to split rewards between account1 and + account2:: + + beneficiaries = [ + {'account': 'account1', 'weight': 5000}, + {'account': 'account2', 'weight': 5000} + ] + + self_vote (bool): (Optional) Upvote the post as author, right after + posting. - self_vote (bool): (Optional) Upvote the post as author, right after posting. """ # prepare json_metadata @@ -250,7 +272,8 @@ def post(self, # deal with replies/categories if reply_identifier: - parent_author, parent_permlink = resolve_identifier(reply_identifier) + parent_author, parent_permlink = resolve_identifier( + reply_identifier) if not permlink: permlink = derive_permlink(title, parent_permlink) elif category: @@ -265,69 +288,71 @@ def post(self, permlink = derive_permlink(title) post_op = operations.Comment( - **{"parent_author": parent_author, - "parent_permlink": parent_permlink, - "author": author, - "permlink": permlink, - "title": title, - "body": body, - "json_metadata": json_metadata} - ) + **{ + "parent_author": parent_author, + "parent_permlink": parent_permlink, + "author": author, + "permlink": permlink, + "title": title, + "body": body, + "json_metadata": json_metadata + }) ops = [post_op] # if comment_options are used, add a new op to the transaction if comment_options or beneficiaries: - options = keep_in_dict(comment_options or {}, - ['max_accepted_payout', - 'percent_steem_dollars', - 'allow_votes', - 'allow_curation_rewards', - 'extensions' - ]) + options = keep_in_dict(comment_options or {}, [ + 'max_accepted_payout', 'percent_steem_dollars', 'allow_votes', + 'allow_curation_rewards', 'extensions' + ]) # override beneficiaries extension if beneficiaries: # validate schema # or just simply vo.Schema([{'account': str, 'weight': int}]) - schema = vo.Schema([ - { - vo.Required('account'): vo.All(str, vo.Length(max=16)), - vo.Required('weight', default=10000): vo.All(int, vo.Range(min=1, max=10000)) - } - ]) + schema = vo.Schema([{ + vo.Required('account'): + vo.All(str, vo.Length(max=16)), + vo.Required('weight', default=10000): + vo.All(int, vo.Range(min=1, max=10000)) + }]) schema(beneficiaries) options['beneficiaries'] = beneficiaries default_max_payout = "1000000.000 SBD" comment_op = operations.CommentOptions( - **{"author": author, - "permlink": permlink, - "max_accepted_payout": options.get("max_accepted_payout", default_max_payout), - "percent_steem_dollars": int(options.get("percent_steem_dollars", 10000)), - "allow_votes": options.get("allow_votes", True), - "allow_curation_rewards": options.get("allow_curation_rewards", True), - "extensions": options.get("extensions", []), - "beneficiaries": options.get("beneficiaries"), - } - - ) + **{ + "author": + author, + "permlink": + permlink, + "max_accepted_payout": + options.get("max_accepted_payout", default_max_payout), + "percent_steem_dollars": + int(options.get("percent_steem_dollars", 10000)), + "allow_votes": + options.get("allow_votes", True), + "allow_curation_rewards": + options.get("allow_curation_rewards", True), + "extensions": + options.get("extensions", []), + "beneficiaries": + options.get("beneficiaries"), + }) ops.append(comment_op) if self_vote: vote_op = operations.Vote( - **{'voter': author, - 'author': author, - 'permlink': permlink, - 'weight': 10000, - } - ) + **{ + 'voter': author, + 'author': author, + 'permlink': permlink, + 'weight': 10000, + }) ops.append(vote_op) return self.finalizeOp(ops, author, "posting") - def vote(self, - identifier, - weight, - account=None): + def vote(self, identifier, weight, account=None): """ Vote for a post :param str identifier: Identifier for the post to upvote Takes @@ -336,8 +361,8 @@ def vote(self, not be 0.0 :param str account: Voter to use for voting. (Optional) - If ``voter`` is not defines, the ``default_account`` will be taken or - a ValueError will be raised + If ``voter`` is not defines, the ``default_account`` will be taken + or a ValueError will be raised .. code-block:: python @@ -351,37 +376,39 @@ def vote(self, post_author, post_permlink = resolve_identifier(identifier) op = operations.Vote( - **{"voter": account, - "author": post_author, - "permlink": post_permlink, - "weight": int(weight * STEEMIT_1_PERCENT)} - ) + **{ + "voter": account, + "author": post_author, + "permlink": post_permlink, + "weight": int(weight * STEEMIT_1_PERCENT) + }) return self.finalizeOp(op, account, "posting") - def create_account(self, - account_name, - json_meta=None, - password=None, - owner_key=None, - active_key=None, - posting_key=None, - memo_key=None, - additional_owner_keys=[], - additional_active_keys=[], - additional_posting_keys=[], - additional_owner_accounts=[], - additional_active_accounts=[], - additional_posting_accounts=[], - store_keys=True, - store_owner_key=False, - delegation_fee_steem='0 STEEM', - creator=None, - ): + def create_account( + self, + account_name, + json_meta=None, + password=None, + owner_key=None, + active_key=None, + posting_key=None, + memo_key=None, + additional_owner_keys=[], + additional_active_keys=[], + additional_posting_keys=[], + additional_owner_accounts=[], + additional_active_accounts=[], + additional_posting_accounts=[], + store_keys=True, + store_owner_key=False, + delegation_fee_steem='0 STEEM', + creator=None, + ): """ Create new account in Steem - The brainkey/password can be used to recover all generated keys (see - `steembase.account` for more details. + The brainkey/password can be used to recover all generated keys + (see `steembase.account` for more details. By default, this call will use ``default_account`` to register a new name ``account_name`` with all keys being @@ -390,30 +417,32 @@ def create_account(self, wallet. .. note:: Account creations cost a fee that is defined by - the network. If you create an account, you will - need to pay for that fee! - - **You can partially pay that fee by delegating VESTS.** - - To pay the fee in full in STEEM, leave ``delegation_fee_steem`` set to ``0 STEEM`` (Default). - - To pay the fee partially in STEEM, partially with delegated VESTS, set ``delegation_fee_steem`` - to a value greater than ``1 STEEM``. `Required VESTS will be calculated automatically.` - - To pay the fee with maximum amount of delegation, set ``delegation_fee_steem`` to ``1 STEEM``. - `Required VESTS will be calculated automatically.` + the network. If you create an account, you will + need to pay for that fee! + + **You can partially pay that fee by delegating VESTS.** + + To pay the fee in full in STEEM, leave + ``delegation_fee_steem`` set to ``0 STEEM`` (Default). + + To pay the fee partially in STEEM, partially with delegated + VESTS, set ``delegation_fee_steem`` to a value greater than ``1 + STEEM``. `Required VESTS will be calculated automatically.` + + To pay the fee with maximum amount of delegation, set + ``delegation_fee_steem`` to ``1 STEEM``. `Required VESTS will be + calculated automatically.` .. warning:: Don't call this method unless you know what you are doing! Be sure to understand what this method does and where to find the private keys for your account. - .. note:: Please note that this imports private keys - (if password is present) into the wallet by - default. However, it **does not import the owner - key** unless `store_owner_key` is set to True (default False). - Do NOT expect to be able to recover it from the wallet if you lose - your password! + .. note:: Please note that this imports private keys (if password is + present) into the wallet by default. However, it **does not import + the owner key** unless `store_owner_key` is set to True (default + False). Do NOT expect to be able to recover it from the wallet if + you lose your password! :param str account_name: (**required**) new account name :param str json_meta: Optional meta data for the account @@ -425,23 +454,40 @@ def create_account(self, can provide a password from which the keys will be derived :param list additional_owner_keys: Additional owner public keys + :param list additional_active_keys: Additional active public keys + :param list additional_posting_keys: Additional posting public keys - :param list additional_owner_accounts: Additional owner account names - :param list additional_active_accounts: Additional active account names - :param list additional_posting_accounts: Additional posting account names - :param bool store_keys: Store new keys in the wallet (default: ``True``) - :param bool store_owner_key: Store owner key in the wallet (default: ``False``) - :param str delegation_fee_steem: (Optional) If set, `creator` pay a fee of this amount, - and delegate the rest with VESTS (calculated automatically). - Minimum: 1 STEEM. If left to 0 (Default), full fee is paid - without VESTS delegation. + + :param list additional_owner_accounts: Additional owner account + names + + :param list additional_active_accounts: Additional active account + names + + :param list additional_posting_accounts: Additional posting account + names + + :param bool store_keys: Store new keys in the wallet (default: + ``True``) + + :param bool store_owner_key: Store owner key in the wallet + (default: ``False``) + + :param str delegation_fee_steem: (Optional) If set, `creator` pay a + fee of this amount, and delegate the rest with VESTS (calculated + automatically). Minimum: 1 STEEM. If left to 0 (Default), full fee + is paid without VESTS delegation. + :param str creator: which account should pay the registration fee (defaults to ``default_account``) - :raises AccountExistsException: if the account already exists on the blockchain + + :raises AccountExistsException: if the account already exists on the + blockchain """ - assert len(account_name) <= 16, "Account name must be at most 16 chars long" + assert len( + account_name) <= 16, "Account name must be at most 16 chars long" if not creator: creator = configStorage.get("default_account") @@ -450,14 +496,12 @@ def create_account(self, "Not creator account given. Define it with " + "creator=x, or set the default_account using steempy") if password and (owner_key or posting_key or active_key or memo_key): - raise ValueError( - "You cannot use 'password' AND provide keys!" - ) + raise ValueError("You cannot use 'password' AND provide keys!") # check if account already exists try: Account(account_name, steemd_instance=self.steemd) - except: + except: # noqa FIXME(sneak) pass else: raise AccountExistsException @@ -485,14 +529,17 @@ def create_account(self, self.wallet.addPrivateKey(posting_privkey) self.wallet.addPrivateKey(memo_privkey) elif owner_key and posting_key and active_key and memo_key: - posting_pubkey = PublicKey(posting_key, prefix=self.steemd.chain_params["prefix"]) - active_pubkey = PublicKey(active_key, prefix=self.steemd.chain_params["prefix"]) - owner_pubkey = PublicKey(owner_key, prefix=self.steemd.chain_params["prefix"]) - memo_pubkey = PublicKey(memo_key, prefix=self.steemd.chain_params["prefix"]) + posting_pubkey = PublicKey( + posting_key, prefix=self.steemd.chain_params["prefix"]) + active_pubkey = PublicKey( + active_key, prefix=self.steemd.chain_params["prefix"]) + owner_pubkey = PublicKey( + owner_key, prefix=self.steemd.chain_params["prefix"]) + memo_pubkey = PublicKey( + memo_key, prefix=self.steemd.chain_params["prefix"]) else: raise ValueError( - "Call incomplete! Provide either a password or public keys!" - ) + "Call incomplete! Provide either a password or public keys!") owner = format(owner_pubkey, self.steemd.chain_params["prefix"]) active = format(active_pubkey, self.steemd.chain_params["prefix"]) @@ -527,13 +574,17 @@ def create_account(self, required_fee_vests = 0 delegation_fee_steem = Amount(delegation_fee_steem).amount if delegation_fee_steem: - # creating accounts without delegation requires 30x account_creation_fee - # creating account with delegation allows one to use VESTS to pay the fee - # where the ratio must satisfy 1 STEEM in fee == 5 STEEM in delegated VESTS + # creating accounts without delegation requires 30x + # account_creation_fee creating account with delegation allows one + # to use VESTS to pay the fee where the ratio must satisfy 1 STEEM + # in fee == 5 STEEM in delegated VESTS + delegated_sp_fee_mult = 5 if delegation_fee_steem < 1: - raise ValueError("When creating account with delegation, at least 1 STEEM in fee must be paid.") + raise ValueError( + "When creating account with delegation, at least " + + "1 STEEM in fee must be paid.") # calculate required remaining fee in vests remaining_fee = required_fee_steem - delegation_fee_steem @@ -541,22 +592,30 @@ def create_account(self, required_sp = remaining_fee * delegated_sp_fee_mult required_fee_vests = Converter().sp_to_vests(required_sp) + 1 - s = {'creator': creator, - 'fee': '%s STEEM' % (delegation_fee_steem or required_fee_steem), - 'delegation': '%s VESTS' % required_fee_vests, - 'json_metadata': json_meta or {}, - 'memo_key': memo, - 'new_account_name': account_name, - 'owner': {'account_auths': owner_accounts_authority, - 'key_auths': owner_key_authority, - 'weight_threshold': 1}, - 'active': {'account_auths': active_accounts_authority, - 'key_auths': active_key_authority, - 'weight_threshold': 1}, - 'posting': {'account_auths': posting_accounts_authority, - 'key_auths': posting_key_authority, - 'weight_threshold': 1}, - 'prefix': self.steemd.chain_params["prefix"]} + s = { + 'creator': creator, + 'fee': '%s STEEM' % (delegation_fee_steem or required_fee_steem), + 'delegation': '%s VESTS' % required_fee_vests, + 'json_metadata': json_meta or {}, + 'memo_key': memo, + 'new_account_name': account_name, + 'owner': { + 'account_auths': owner_accounts_authority, + 'key_auths': owner_key_authority, + 'weight_threshold': 1 + }, + 'active': { + 'account_auths': active_accounts_authority, + 'key_auths': active_key_authority, + 'weight_threshold': 1 + }, + 'posting': { + 'account_auths': posting_accounts_authority, + 'key_auths': posting_key_authority, + 'weight_threshold': 1 + }, + 'prefix': self.steemd.chain_params["prefix"] + } op = operations.AccountCreateWithDelegation(**s) @@ -566,10 +625,17 @@ def transfer(self, to, amount, asset, memo="", account=None): """ Transfer SBD or STEEM to another account. :param str to: Recipient + :param float amount: Amount to transfer + :param str asset: Asset to transfer (``SBD`` or ``STEEM``) - :param str memo: (optional) Memo, may begin with `#` for encrypted messaging - :param str account: (optional) the source account for the transfer if not ``default_account`` + + :param str memo: (optional) Memo, may begin with `#` for encrypted + messaging + + :param str account: (optional) the source account for the transfer + if not ``default_account`` + """ if not account: account = configStorage.get("default_account") @@ -587,55 +653,65 @@ def transfer(self, to, amount, asset, memo="", account=None): nonce = random.getrandbits(64) memo = Memo.encode_memo( PrivateKey(memo_wif), - PublicKey(to_account["memo_key"], prefix=self.steemd.chain_params["prefix"]), + PublicKey( + to_account["memo_key"], + prefix=self.steemd.chain_params["prefix"]), nonce, memo, - prefix=self.steemd.chain_params["prefix"] - ) + prefix=self.steemd.chain_params["prefix"]) op = operations.Transfer( - **{"from": account, - "to": to, - "amount": '{:.{prec}f} {asset}'.format( - float(amount), - prec=3, - asset=asset - ), - "memo": memo - } - ) + **{ + "from": + account, + "to": + to, + "amount": + '{:.{prec}f} {asset}'.format( + float(amount), prec=3, asset=asset), + "memo": + memo + }) return self.finalizeOp(op, account, "active") def withdraw_vesting(self, amount, account=None): """ Withdraw VESTS from the vesting account. - :param float amount: number of VESTS to withdraw over a period of 104 weeks - :param str account: (optional) the source account for the transfer if not ``default_account`` - """ + :param float amount: number of VESTS to withdraw over a period of + 104 weeks + + :param str account: (optional) the source account for the transfer + if not ``default_account`` + + """ if not account: account = configStorage.get("default_account") if not account: raise ValueError("You need to provide an account") op = operations.WithdrawVesting( - **{"account": account, - "vesting_shares": '{:.{prec}f} {asset}'.format( - float(amount), - prec=6, - asset="VESTS" - ), - } - ) + **{ + "account": + account, + "vesting_shares": + '{:.{prec}f} {asset}'.format( + float(amount), prec=6, asset="VESTS"), + }) return self.finalizeOp(op, account, "active") def transfer_to_vesting(self, amount, to=None, account=None): """ Vest STEEM - :param float amount: number of STEEM to vest - :param str to: (optional) the source account for the transfer if not ``default_account`` - :param str account: (optional) the source account for the transfer if not ``default_account`` - """ + :param float amount: number of STEEM to vest + + :param str to: (optional) the source account for the transfer if not + ``default_account`` + + :param str account: (optional) the source account for the transfer + if not ``default_account`` + + """ if not account: account = configStorage.get("default_account") if not account: @@ -645,23 +721,29 @@ def transfer_to_vesting(self, amount, to=None, account=None): to = account # powerup on the same account op = operations.TransferToVesting( - **{"from": account, - "to": to, - "amount": '{:.{prec}f} {asset}'.format( - float(amount), - prec=3, - asset='STEEM') - } - ) + **{ + "from": + account, + "to": + to, + "amount": + '{:.{prec}f} {asset}'.format( + float(amount), prec=3, asset='STEEM') + }) return self.finalizeOp(op, account, "active") def convert(self, amount, account=None, request_id=None): """ Convert SteemDollars to Steem (takes one week to settle) - :param float amount: number of VESTS to withdraw over a period of 104 weeks - :param str account: (optional) the source account for the transfer if not ``default_account`` - :param str request_id: (optional) identifier for tracking the conversion` + :param float amount: number of VESTS to withdraw + + :param str account: (optional) the source account for the transfer + if not ``default_account`` + + :param str request_id: (optional) identifier for tracking the + conversion` + """ if not account: account = configStorage.get("default_account") @@ -673,14 +755,15 @@ def convert(self, amount, account=None, request_id=None): else: request_id = random.getrandbits(32) op = operations.Convert( - **{"owner": account, - "requestid": request_id, - "amount": '{:.{prec}f} {asset}'.format( - float(amount), - prec=3, - asset='SBD' - )} - ) + **{ + "owner": + account, + "requestid": + request_id, + "amount": + '{:.{prec}f} {asset}'.format( + float(amount), prec=3, asset='SBD') + }) return self.finalizeOp(op, account, "active") @@ -690,8 +773,10 @@ def transfer_to_savings(self, amount, asset, memo, to=None, account=None): :param float amount: STEEM or SBD amount :param float asset: 'STEEM' or 'SBD' :param str memo: (optional) Memo - :param str to: (optional) the source account for the transfer if not ``default_account`` - :param str account: (optional) the source account for the transfer if not ``default_account`` + :param str to: (optional) the source account for the transfer if + not ``default_account`` + :param str account: (optional) the source account for the transfer + if not ``default_account`` """ assert asset in ['STEEM', 'SBD'] @@ -705,26 +790,36 @@ def transfer_to_savings(self, amount, asset, memo, to=None, account=None): op = operations.TransferToSavings( **{ - "from": account, - "to": to, - "amount": '{:.{prec}f} {asset}'.format( - float(amount), - prec=3, - asset=asset), - "memo": memo, - } - ) + "from": + account, + "to": + to, + "amount": + '{:.{prec}f} {asset}'.format( + float(amount), prec=3, asset=asset), + "memo": + memo, + }) return self.finalizeOp(op, account, "active") - def transfer_from_savings(self, amount, asset, memo, request_id=None, to=None, account=None): + def transfer_from_savings(self, + amount, + asset, + memo, + request_id=None, + to=None, + account=None): """ Withdraw SBD or STEEM from 'savings' account. :param float amount: STEEM or SBD amount :param float asset: 'STEEM' or 'SBD' :param str memo: (optional) Memo - :param str request_id: (optional) identifier for tracking or cancelling the withdrawal - :param str to: (optional) the source account for the transfer if not ``default_account`` - :param str account: (optional) the source account for the transfer if not ``default_account`` + :param str request_id: (optional) identifier for tracking or + cancelling the withdrawal + :param str to: (optional) the source account for the transfer if + not ``default_account`` + :param str account: (optional) the source account for the transfer + if not ``default_account`` """ assert asset in ['STEEM', 'SBD'] @@ -743,35 +838,37 @@ def transfer_from_savings(self, amount, asset, memo, request_id=None, to=None, a op = operations.TransferFromSavings( **{ - "from": account, - "request_id": request_id, - "to": to, - "amount": '{:.{prec}f} {asset}'.format( - float(amount), - prec=3, - asset=asset), - "memo": memo, - } - ) + "from": + account, + "request_id": + request_id, + "to": + to, + "amount": + '{:.{prec}f} {asset}'.format( + float(amount), prec=3, asset=asset), + "memo": + memo, + }) return self.finalizeOp(op, account, "active") def transfer_from_savings_cancel(self, request_id, account=None): """ Cancel a withdrawal from 'savings' account. - :param str request_id: Identifier for tracking or cancelling the withdrawal - :param str account: (optional) the source account for the transfer if not ``default_account`` + :param str request_id: Identifier for tracking or cancelling + the withdrawal + :param str account: (optional) the source account for the transfer + if not ``default_account`` """ if not account: account = configStorage.get("default_account") if not account: raise ValueError("You need to provide an account") - op = operations.CancelTransferFromSavings( - **{ - "from": account, - "request_id": request_id, - } - ) + op = operations.CancelTransferFromSavings(**{ + "from": account, + "request_id": request_id, + }) return self.finalizeOp(op, account, "active") def claim_reward_balance(self, @@ -781,22 +878,27 @@ def claim_reward_balance(self, account=None): """ Claim reward balances. - By default, this will claim ``all`` outstanding balances. To bypass this behaviour, - set desired claim amount by setting any of `reward_steem`, `reward_sbd` or `reward_vests`. + By default, this will claim ``all`` outstanding balances. To bypass + this behaviour, set desired claim amount by setting any of + `reward_steem`, `reward_sbd` or `reward_vests`. Args: reward_steem (string): Amount of STEEM you would like to claim. reward_sbd (string): Amount of SBD you would like to claim. reward_vests (string): Amount of VESTS you would like to claim. - account (string): The source account for the claim if not ``default_account`` is used. + account (string): The source account for the claim if not + ``default_account`` is used. """ if not account: account = configStorage.get("default_account") if not account: raise ValueError("You need to provide an account") - # if no values were set by user, claim all outstanding balances on account - if none(int(first(x.split(' '))) for x in [reward_sbd, reward_steem, reward_vests]): + # if no values were set by user, claim all outstanding balances on + # account + if none( + int(first(x.split(' '))) + for x in [reward_sbd, reward_steem, reward_vests]): a = Account(account) reward_steem = a['reward_steem_balance'] reward_sbd = a['reward_sbd_balance'] @@ -808,17 +910,20 @@ def claim_reward_balance(self, "reward_steem": reward_steem, "reward_sbd": reward_sbd, "reward_vests": reward_vests, - } - ) + }) return self.finalizeOp(op, account, "posting") - def delegate_vesting_shares(self, to_account, vesting_shares, account=None): + def delegate_vesting_shares(self, to_account, vesting_shares, + account=None): """ Delegate SP to another account. Args: - to_account (string): Account we are delegating shares to (delegatee). - vesting_shares (string): Amount of VESTS to delegate eg. `10000 VESTS`. - account (string): The source account (delegator). If not specified, ``default_account`` is used. + to_account (string): Account we are delegating shares to + (delegatee). + vesting_shares (string): Amount of VESTS to delegate eg. `10000 + VESTS`. + account (string): The source account (delegator). If not specified, + ``default_account`` is used. """ if not account: account = configStorage.get("default_account") @@ -830,16 +935,20 @@ def delegate_vesting_shares(self, to_account, vesting_shares, account=None): "delegator": account, "delegatee": to_account, "vesting_shares": str(Amount(vesting_shares)), - } - ) + }) return self.finalizeOp(op, account, "active") - def witness_feed_publish(self, steem_usd_price, quote="1.000", account=None): + def witness_feed_publish(self, + steem_usd_price, + quote="1.000", + account=None): """ Publish a feed price as a witness. :param float steem_usd_price: Price of STEEM in USD (implied price) - :param float quote: (optional) Quote Price. Should be 1.000, unless we are adjusting the feed to support the peg. - :param str account: (optional) the source account for the transfer if not ``default_account`` + :param float quote: (optional) Quote Price. Should be 1.000, unless + we are adjusting the feed to support the peg. + :param str account: (optional) the source account for the transfer + if not ``default_account`` """ if not account: account = configStorage.get("default_account") @@ -853,8 +962,7 @@ def witness_feed_publish(self, steem_usd_price, quote="1.000", account=None): "base": "%s SBD" % steem_usd_price, "quote": "%s STEEM" % quote, } - } - ) + }) return self.finalizeOp(op, account, "active") def witness_update(self, signing_key, url, props, account=None): @@ -892,14 +1000,14 @@ def witness_update(self, signing_key, url, props, account=None): "props": props, "fee": "0.000 STEEM", "prefix": self.steemd.chain_params["prefix"] - } - ) + }) return self.finalizeOp(op, account, "active") def decode_memo(self, enc_memo): """ Try to decode an encrypted memo """ - assert enc_memo[0] == "#", "decode memo requires memos to start with '#'" + assert enc_memo[ + 0] == "#", "decode memo requires memos to start with '#'" keys = memo.involved_keys(enc_memo) wif = None for key in keys: @@ -918,10 +1026,10 @@ def interest(self, account): account = Account(account, steemd_instance=self.steemd) last_payment = fmt_time_string(account["sbd_last_interest_payment"]) next_payment = last_payment + timedelta(days=30) - interest_rate = self.steemd.get_dynamic_global_properties()["sbd_interest_rate"] / 100 # percent + interest_rate = self.steemd.get_dynamic_global_properties()[ + "sbd_interest_rate"] / 100 # percent interest_amount = (interest_rate / 100) * int( - int(account["sbd_seconds"]) / (60 * 60 * 24 * 356) - ) * 10 ** -3 + int(account["sbd_seconds"]) / (60 * 60 * 24 * 356)) * 10**-3 return { "interest": interest_amount, @@ -931,8 +1039,11 @@ def interest(self, account): "interest_rate": interest_rate, } - def set_withdraw_vesting_route(self, to, percentage=100, - account=None, auto_vest=False): + def set_withdraw_vesting_route(self, + to, + percentage=100, + account=None, + auto_vest=False): """ Set up a vesting withdraw route. When vesting shares are withdrawn, they will be routed to these accounts based on the specified weights. @@ -951,12 +1062,12 @@ def set_withdraw_vesting_route(self, to, percentage=100, raise ValueError("You need to provide an account") op = operations.SetWithdrawVestingRoute( - **{"from_account": account, - "to_account": to, - "percent": int(percentage * STEEMIT_1_PERCENT), - "auto_vest": auto_vest - } - ) + **{ + "from_account": account, + "to_account": to, + "percent": int(percentage * STEEMIT_1_PERCENT), + "auto_vest": auto_vest + }) return self.finalizeOp(op, account, "active") @@ -970,8 +1081,12 @@ def _test_weights_treshold(authority): if authority["weight_threshold"] > weights: raise ValueError("Threshold too restrictive!") - def allow(self, foreign, weight=None, permission="posting", - account=None, threshold=None): + def allow(self, + foreign, + weight=None, + permission="posting", + account=None, + threshold=None): """ Give additional access to an account by some other public key or account. @@ -994,8 +1109,7 @@ def allow(self, foreign, weight=None, permission="posting", if permission not in ["owner", "posting", "active"]: raise ValueError( - "Permission needs to be either 'owner', 'posting', or 'active" - ) + "Permission needs to be either 'owner', 'posting', or 'active") account = Account(account, steemd_instance=self.steemd) if not weight: weight = account[permission]["weight_threshold"] @@ -1003,39 +1117,37 @@ def allow(self, foreign, weight=None, permission="posting", authority = account[permission] try: pubkey = PublicKey(foreign) - authority["key_auths"].append([ - str(pubkey), - weight - ]) - except: + authority["key_auths"].append([str(pubkey), weight]) + except: # noqa FIXME(sneak) try: foreign_account = Account(foreign, steemd_instance=self.steemd) - authority["account_auths"].append([ - foreign_account["name"], - weight - ]) - except: + authority["account_auths"].append( + [foreign_account["name"], weight]) + except: # noqa FIXME(sneak) raise ValueError( - "Unknown foreign account or unvalid public key" - ) + "Unknown foreign account or unvalid public key") if threshold: authority["weight_threshold"] = threshold self._test_weights_treshold(authority) op = operations.AccountUpdate( - **{"account": account["name"], - permission: authority, - "memo_key": account["memo_key"], - "json_metadata": account["json_metadata"], - 'prefix': self.steemd.chain_params["prefix"]} - ) + **{ + "account": account["name"], + permission: authority, + "memo_key": account["memo_key"], + "json_metadata": account["json_metadata"], + 'prefix': self.steemd.chain_params["prefix"] + }) if permission == "owner": return self.finalizeOp(op, account["name"], "owner") else: return self.finalizeOp(op, account["name"], "active") - def disallow(self, foreign, permission="posting", - account=None, threshold=None): + def disallow(self, + foreign, + permission="posting", + account=None, + threshold=None): """ Remove additional access to an account by some other public key or account. @@ -1054,34 +1166,29 @@ def disallow(self, foreign, permission="posting", if permission not in ["owner", "posting", "active"]: raise ValueError( - "Permission needs to be either 'owner', 'posting', or 'active" - ) + "Permission needs to be either 'owner', 'posting', or 'active") account = Account(account, steemd_instance=self.steemd) authority = account[permission] try: - pubkey = PublicKey(foreign, prefix=self.steemd.chain_params["prefix"]) + pubkey = PublicKey( + foreign, prefix=self.steemd.chain_params["prefix"]) affected_items = list( - filter(lambda x: x[0] == str(pubkey), - authority["key_auths"])) - authority["key_auths"] = list(filter( - lambda x: x[0] != str(pubkey), - authority["key_auths"] - )) - except: + filter(lambda x: x[0] == str(pubkey), authority["key_auths"])) + authority["key_auths"] = list( + filter(lambda x: x[0] != str(pubkey), authority["key_auths"])) + except: # noqa FIXME(sneak) try: foreign_account = Account(foreign, steemd_instance=self.steemd) affected_items = list( filter(lambda x: x[0] == foreign_account["name"], authority["account_auths"])) - authority["account_auths"] = list(filter( - lambda x: x[0] != foreign_account["name"], - authority["account_auths"] - )) - except: + authority["account_auths"] = list( + filter(lambda x: x[0] != foreign_account["name"], + authority["account_auths"])) + except: # noqa FIXME(sneak) raise ValueError( - "Unknown foreign account or unvalid public key" - ) + "Unknown foreign account or unvalid public key") removed_weight = affected_items[0][1] @@ -1093,20 +1200,19 @@ def disallow(self, foreign, permission="posting", # authority) try: self._test_weights_treshold(authority) - except: - log.critical( - "The account's threshold will be reduced by %d" - % removed_weight - ) + except: # noqa FIXME(sneak) + log.critical("The account's threshold will be reduced by %d" % + removed_weight) authority["weight_threshold"] -= removed_weight self._test_weights_treshold(authority) op = operations.AccountUpdate( - **{"account": account["name"], - permission: authority, - "memo_key": account["memo_key"], - "json_metadata": account["json_metadata"]} - ) + **{ + "account": account["name"], + permission: authority, + "memo_key": account["memo_key"], + "json_metadata": account["json_metadata"] + }) if permission == "owner": return self.finalizeOp(op, account["name"], "owner") else: @@ -1130,10 +1236,11 @@ def update_memo_key(self, key, account=None): PublicKey(key) # raises exception if invalid account = Account(account, steemd_instance=self.steemd) op = operations.AccountUpdate( - **{"account": account["name"], - "memo_key": key, - "json_metadata": account["json_metadata"]} - ) + **{ + "account": account["name"], + "memo_key": key, + "json_metadata": account["json_metadata"] + }) return self.finalizeOp(op, account["name"], "active") def approve_witness(self, witness, account=None, approve=True): @@ -1150,11 +1257,11 @@ def approve_witness(self, witness, account=None, approve=True): if not account: raise ValueError("You need to provide an account") account = Account(account, steemd_instance=self.steemd) - op = operations.AccountWitnessVote( - **{"account": account["name"], - "witness": witness, - "approve": approve, - }) + op = operations.AccountWitnessVote(**{ + "account": account["name"], + "witness": witness, + "approve": approve, + }) return self.finalizeOp(op, account["name"], "active") def disapprove_witness(self, witness, account=None): @@ -1166,13 +1273,19 @@ def disapprove_witness(self, witness, account=None): :param str account: (optional) the account to allow access to (defaults to ``default_account``) """ - return self.approve_witness(witness=witness, account=account, approve=False) - - def custom_json(self, id, json, required_auths=[], required_posting_auths=[]): + return self.approve_witness( + witness=witness, account=account, approve=False) + + def custom_json(self, + id, + json, + required_auths=[], + required_posting_auths=[]): """ Create a custom json operation :param str id: identifier for the custom json (max length 32 bytes) - :param json json: the json data to put into the custom_json operation + :param json json: the json data to put into the custom_json + operation :param list required_auths: (optional) required auths :param list required_posting_auths: (optional) posting auths """ @@ -1184,10 +1297,12 @@ def custom_json(self, id, json, required_auths=[], required_posting_auths=[]): else: raise Exception("At least one account needs to be specified") op = operations.CustomJson( - **{"json": json, - "required_auths": required_auths, - "required_posting_auths": required_posting_auths, - "id": id}) + **{ + "json": json, + "required_auths": required_auths, + "required_posting_auths": required_posting_auths, + "id": id + }) return self.finalizeOp(op, account, "posting") def resteem(self, identifier, account=None): @@ -1202,20 +1317,22 @@ def resteem(self, identifier, account=None): if not account: raise ValueError("You need to provide an account") author, permlink = resolve_identifier(identifier) - json_body = ["reblog", {"account": account, - "author": author, - "permlink": permlink}] + json_body = [ + "reblog", { + "account": account, + "author": author, + "permlink": permlink + } + ] return self.custom_json( - id="follow", - json=json_body, - required_posting_auths=[account] - ) + id="follow", json=json_body, required_posting_auths=[account]) def unfollow(self, unfollow, what=["blog"], account=None): """ Unfollow another account's blog :param str unfollow: Follow this account - :param list what: List of states to follow (defaults to ``['blog']``) + :param list what: List of states to follow + (defaults to ``['blog']``) :param str account: (optional) the account to allow access to (defaults to ``default_account``) """ @@ -1227,7 +1344,8 @@ def follow(self, follow, what=["blog"], account=None): """ Follow another account's blog :param str follow: Follow this account - :param list what: List of states to follow (defaults to ``['blog']``) + :param list what: List of states to follow + (defaults to ``['blog']``) :param str account: (optional) the account to allow access to (defaults to ``default_account``) """ @@ -1236,19 +1354,21 @@ def follow(self, follow, what=["blog"], account=None): if not account: raise ValueError("You need to provide an account") - json_body = ['follow', {'follower': account, - 'following': follow, - 'what': what}] + json_body = [ + 'follow', { + 'follower': account, + 'following': follow, + 'what': what + } + ] return self.custom_json( - id="follow", - json=json_body, - required_posting_auths=[account] - ) + id="follow", json=json_body, required_posting_auths=[account]) def update_account_profile(self, profile, account=None): """ Update an account's meta data (json_meta) - :param dict json: The meta data to use (i.e. use Profile() from account.py) + :param dict json: The meta data to use (i.e. use Profile() from + account.py) :param str account: (optional) the account to allow access to (defaults to ``default_account``) """ @@ -1258,10 +1378,11 @@ def update_account_profile(self, profile, account=None): raise ValueError("You need to provide an account") account = Account(account, steemd_instance=self.steemd) op = operations.AccountUpdate( - **{"account": account["name"], - "memo_key": account["memo_key"], - "json_metadata": profile} - ) + **{ + "account": account["name"], + "memo_key": account["memo_key"], + "json_metadata": profile + }) return self.finalizeOp(op, account["name"], "active") def comment_options(self, identifier, options, account=None): @@ -1293,14 +1414,19 @@ def comment_options(self, identifier, options, account=None): default_max_payout = "1000000.000 SBD" op = operations.CommentOptions( **{ - "author": author, - "permlink": permlink, - "max_accepted_payout": options.get("max_accepted_payout", default_max_payout), - "percent_steem_dollars": options.get("percent_steem_dollars", 100) * STEEMIT_1_PERCENT, - "allow_votes": options.get("allow_votes", True), - "allow_curation_rewards": options.get("allow_curation_rewards", True), - } - ) + "author": + author, + "permlink": + permlink, + "max_accepted_payout": + options.get("max_accepted_payout", default_max_payout), + "percent_steem_dollars": + options.get("percent_steem_dollars", 100) * STEEMIT_1_PERCENT, + "allow_votes": + options.get("allow_votes", True), + "allow_curation_rewards": + options.get("allow_curation_rewards", True), + }) return self.finalizeOp(op, account["name"], "posting") diff --git a/steem/converter.py b/steem/converter.py index f0eb8c5..4dca0a6 100644 --- a/steem/converter.py +++ b/steem/converter.py @@ -8,7 +8,8 @@ class Converter(object): """ Converter simplifies the handling of different metrics of the blockchain - :param Steemd steemd_instance: Steemd() instance to use when accessing a RPC + :param Steemd steemd_instance: Steemd() instance to + use when accessing a RPC """ @@ -21,19 +22,16 @@ def sbd_median_price(self): """ Obtain the sbd price as derived from the median over all witness feeds. Return value will be SBD """ - return ( - Amount(self.steemd.get_feed_history()['current_median_history']['base']).amount / - Amount(self.steemd.get_feed_history()['current_median_history']['quote']).amount - ) + return (Amount(self.steemd.get_feed_history()['current_median_history'] + ['base']).amount / Amount(self.steemd.get_feed_history( + )['current_median_history']['quote']).amount) def steem_per_mvests(self): """ Obtain STEEM/MVESTS ratio """ info = self.steemd.get_dynamic_global_properties() - return ( - Amount(info["total_vesting_fund_steem"]).amount / - (Amount(info["total_vesting_shares"]).amount / 1e6) - ) + return (Amount(info["total_vesting_fund_steem"]).amount / + (Amount(info["total_vesting_shares"]).amount / 1e6)) def vests_to_sp(self, vests): """ Obtain SP from VESTS (not MVESTS!) @@ -92,9 +90,11 @@ def sbd_to_rshares(self, sbd_payout): total_reward_fund_steem = Amount(props['total_reward_fund_steem']) total_reward_shares2 = int(props['total_reward_shares2']) - post_rshares2 = (steem_payout / total_reward_fund_steem) * total_reward_shares2 + post_rshares2 = ( + steem_payout / total_reward_fund_steem) * total_reward_shares2 - rshares = math.sqrt(self.CONTENT_CONSTANT ** 2 + post_rshares2) - self.CONTENT_CONSTANT + rshares = math.sqrt( + self.CONTENT_CONSTANT**2 + post_rshares2) - self.CONTENT_CONSTANT return rshares def rshares_2_weight(self, rshares): @@ -102,5 +102,5 @@ def rshares_2_weight(self, rshares): :param number rshares: R-Shares """ - _max = 2 ** 64 - 1 + _max = 2**64 - 1 return (_max * rshares) / (2 * self.CONTENT_CONSTANT + rshares) diff --git a/steem/dex.py b/steem/dex.py index e94a6ae..964ee98 100644 --- a/steem/dex.py +++ b/steem/dex.py @@ -11,7 +11,8 @@ class Dex(object): """ This class allows to access calls specific for the internal exchange of STEEM. - :param Steemd steemd_instance: Steemd() instance to use when accessing a RPC + :param Steemd steemd_instance: Steemd() instance to use when + accessing a RPC """ assets = ["STEEM", "SBD"] @@ -23,20 +24,16 @@ def _get_asset(self, symbol): """ Return the properties of the assets tradeable on the network. - :param str symbol: Symbol to get the data for (i.e. STEEM, SBD, VESTS) + :param str symbol: Symbol to get the data for (i.e. STEEM, SBD, + VESTS) + """ if symbol == "STEEM": - return {"symbol": "STEEM", - "precision": 3 - } + return {"symbol": "STEEM", "precision": 3} elif symbol == "SBD": - return {"symbol": "SBD", - "precision": 3 - } + return {"symbol": "SBD", "precision": 3} elif symbol == "VESTS": - return {"symbol": "VESTS", - "precision": 6 - } + return {"symbol": "VESTS", "precision": 6} else: return None @@ -79,12 +76,14 @@ def get_ticker(self): """ t = self.steemd.get_ticker() - return {'highest_bid': float(t['highest_bid']), - 'latest': float(t["latest"]), - 'lowest_ask': float(t["lowest_ask"]), - 'percent_change': float(t["percent_change"]), - 'sbd_volume': Amount(t["sbd_volume"]), - 'steem_volume': Amount(t["steem_volume"])} + return { + 'highest_bid': float(t['highest_bid']), + 'latest': float(t["latest"]), + 'lowest_ask': float(t["lowest_ask"]), + 'percent_change': float(t["percent_change"]), + 'sbd_volume': Amount(t["sbd_volume"]), + 'steem_volume': Amount(t["steem_volume"]) + } def trade_history(self, time=1 * 60 * 60, limit=100): """ Returns the trade history for the internal market @@ -110,9 +109,14 @@ def market_history( ): """ Return the market history (filled orders). - :param int bucket_seconds: Bucket size in seconds (see `returnMarketHistoryBuckets()`) - :param int start_age: Age (in seconds) of the start of the window (default: 1h/3600) - :param int end_age: Age (in seconds) of the end of the window (default: now/0) + :param int bucket_seconds: Bucket size in seconds (see + `returnMarketHistoryBuckets()`) + + :param int start_age: Age (in seconds) of the start of the + window (default: 1h/3600) + + :param int end_age: Age (in seconds) of the end of the window + (default: now/0) Example: @@ -151,12 +155,22 @@ def buy(self, method will return the order creating (signed) transaction. :param number amount: Amount of ``quote`` to buy + :param str quote_symbol: STEEM, or SBD + :param float price: price denoted in ``base``/``quote`` - :param number expiration: (optional) expiration time of the order in seconds (defaults to 7 days) - :param bool killfill: flag that indicates if the order shall be killed if it is not filled (defaults to False) - :param str account: (optional) the source account for the transfer if not ``default_account`` - :param int order_id: (optional) a 32bit orderid for tracking of the created order (random by default) + + :param number expiration: (optional) expiration time of the + order in seconds (defaults to 7 days) + + :param bool killfill: flag that indicates if the order shall be + killed if it is not filled (defaults to False) + + :param str account: (optional) the source account for the + transfer if not ``default_account`` + + :param int order_id: (optional) a 32bit orderid for tracking of + the created order (random by default) Prices/Rates are denoted in 'base', i.e. the STEEM:SBD market is priced in SBD per STEEM. @@ -169,20 +183,25 @@ def buy(self, # We buy quote and pay with base quote, base = self._get_assets(quote=quote_symbol) - op = operations.LimitOrderCreate(**{ - "owner": account, - "orderid": order_id or random.getrandbits(32), - "amount_to_sell": '{:.{prec}f} {asset}'.format( - amount * rate, - prec=base["precision"], - asset=base["symbol"]), - "min_to_receive": '{:.{prec}f} {asset}'.format( - amount, - prec=quote["precision"], - asset=quote["symbol"]), - "fill_or_kill": killfill, - "expiration": transactions.fmt_time_from_now(expiration) - }) + op = operations.LimitOrderCreate( + **{ + "owner": + account, + "orderid": + order_id or random.getrandbits(32), + "amount_to_sell": + '{:.{prec}f} {asset}'.format( + amount * rate, + prec=base["precision"], + asset=base["symbol"]), + "min_to_receive": + '{:.{prec}f} {asset}'.format( + amount, prec=quote["precision"], asset=quote["symbol"]), + "fill_or_kill": + killfill, + "expiration": + transactions.fmt_time_from_now(expiration) + }) return self.steemd.commit.finalizeOp(op, account, "active") def sell(self, @@ -198,12 +217,22 @@ def sell(self, method will return the order creating (signed) transaction. :param number amount: Amount of ``quote`` to sell + :param str quote_symbol: STEEM, or SBD + :param float price: price denoted in ``base``/``quote`` - :param number expiration: (optional) expiration time of the order in seconds (defaults to 7 days) - :param bool killfill: flag that indicates if the order shall be killed if it is not filled (defaults to False) - :param str account: (optional) the source account for the transfer if not ``default_account`` - :param int orderid: (optional) a 32bit orderid for tracking of the created order (random by default) + + :param number expiration: (optional) expiration time of the + order in seconds (defaults to 7 days) + + :param bool killfill: flag that indicates if the order shall be + killed if it is not filled (defaults to False) + + :param str account: (optional) the source account for the + transfer if not ``default_account`` + + :param int orderid: (optional) a 32bit orderid for tracking of + the created order (random by default) Prices/Rates are denoted in 'base', i.e. the STEEM:SBD market is priced in SBD per STEEM. @@ -215,27 +244,35 @@ def sell(self, raise ValueError("You need to provide an account") # We buy quote and pay with base quote, base = self._get_assets(quote=quote_symbol) - op = operations.LimitOrderCreate(**{ - "owner": account, - "orderid": orderid or random.getrandbits(32), - "amount_to_sell": '{:.{prec}f} {asset}'.format( - amount, - prec=quote["precision"], - asset=quote["symbol"]), - "min_to_receive": '{:.{prec}f} {asset}'.format( - amount * rate, - prec=base["precision"], - asset=base["symbol"]), - "fill_or_kill": killfill, - "expiration": transactions.fmt_time_from_now(expiration) - }) + op = operations.LimitOrderCreate( + **{ + "owner": + account, + "orderid": + orderid or random.getrandbits(32), + "amount_to_sell": + '{:.{prec}f} {asset}'.format( + amount, prec=quote["precision"], asset=quote["symbol"]), + "min_to_receive": + '{:.{prec}f} {asset}'.format( + amount * rate, + prec=base["precision"], + asset=base["symbol"]), + "fill_or_kill": + killfill, + "expiration": + transactions.fmt_time_from_now(expiration) + }) return self.steemd.commit.finalizeOp(op, account, "active") def cancel(self, orderid, account=None): """ Cancels an order you have placed in a given market. :param int orderid: the 32bit orderid - :param str account: (optional) the source account for the transfer if not ``default_account`` + + :param str account: (optional) the source account for the + transfer if not ``default_account`` + """ if not account: if "default_account" in config: diff --git a/steem/instance.py b/steem/instance.py index 6b81905..31cb753 100644 --- a/steem/instance.py +++ b/steem/instance.py @@ -12,17 +12,19 @@ def get_config_node_list(): def shared_steemd_instance(): """ This method will initialize _shared_steemd_instance and return it. - The purpose of this method is to have offer single default Steem instance that can be reused by multiple classes. - """ + The purpose of this method is to have offer single default Steem + instance that can be reused by multiple classes. """ + global _shared_steemd_instance if not _shared_steemd_instance: - _shared_steemd_instance = stm.steemd.Steemd(nodes=get_config_node_list()) + _shared_steemd_instance = stm.steemd.Steemd( + nodes=get_config_node_list()) return _shared_steemd_instance def set_shared_steemd_instance(steemd_instance): - """ This method allows us to override default steem instance for all users of - _shared_steemd_instance. - """ + """ This method allows us to override default steem instance for all + users of _shared_steemd_instance. """ + global _shared_steemd_instance _shared_steemd_instance = steemd_instance diff --git a/steem/post.py b/steem/post.py index 0392838..da585b3 100644 --- a/steem/post.py +++ b/steem/post.py @@ -26,8 +26,12 @@ class Post(dict): abstraction layer for Comments in Steem Args: - post (str or dict): ``@author/permlink`` or raw ``comment`` as dictionary. + + post (str or dict): ``@author/permlink`` or raw ``comment`` as + dictionary. + steemd_instance (Steemd): Steemd node to connect to + """ def __init__(self, post, steemd_instance=None): @@ -41,9 +45,11 @@ def __init__(self, post, steemd_instance=None): if isinstance(post, str): # From identifier self.identifier = self.parse_identifier(post) - elif isinstance(post, dict) and "author" in post and "permlink" in post: + elif isinstance(post, + dict) and "author" in post and "permlink" in post: post["author"] = post["author"].replace('@', '') - self.identifier = construct_identifier(post["author"], post["permlink"]) + self.identifier = construct_identifier(post["author"], + post["permlink"]) else: raise ValueError("Post expects an identifier or a dict " "with author and permlink!") @@ -66,12 +72,10 @@ def refresh(self): self.patched = True # Parse Times - parse_times = ["active", - "cashout_time", - "created", - "last_payout", - "last_update", - "max_cashout_time"] + parse_times = [ + "active", "cashout_time", "created", "last_payout", "last_update", + "max_cashout_time" + ] for p in parse_times: post[p] = parse_time(post.get(p, "1970-01-01T00:00:00")) @@ -95,12 +99,11 @@ def refresh(self): post['community'] = '' if isinstance(post['json_metadata'], dict): if post["depth"] == 0: - post["tags"] = ( - post["parent_permlink"], - *get_in(post, ['json_metadata', 'tags'], default=[]) - ) + post["tags"] = (post["parent_permlink"], *get_in( + post, ['json_metadata', 'tags'], default=[])) - post['community'] = get_in(post, ['json_metadata', 'community'], default='') + post['community'] = get_in( + post, ['json_metadata', 'community'], default='') # If this post is a comment, retrieve the root comment self.root_identifier, self.category = self._get_root_identifier(post) @@ -133,17 +136,14 @@ def __repr__(self): def _get_root_identifier(self, post=None): if not post: post = self - m = re.match("/([^/]*)/@([^/]*)/([^#]*).*", - post.get("url", "")) + m = re.match("/([^/]*)/@([^/]*)/([^#]*).*", post.get("url", "")) if not m: return "", "" else: category = m.group(1) author = m.group(2) permlink = m.group(3) - return construct_identifier( - author, permlink - ), category + return construct_identifier(author, permlink), category def get_replies(self): """ Return **first-level** comments of the post. @@ -168,14 +168,15 @@ def get_all_replies(root_post=None, comments=list(), all_comments=list()): children = list(flatten([list(x.get_replies()) for x in comments])) if not children: return all_comments or comments - return Post.get_all_replies(comments=children, all_comments=comments + children) + return Post.get_all_replies( + comments=children, all_comments=comments + children) @property def reward(self): """Return a float value of estimated total SBD reward. """ return Amount(self.get("total_payout_value", "0 SBD")) + \ - Amount(self.get("pending_payout_value", "0 SBD")) + Amount(self.get("pending_payout_value", "0 SBD")) def time_elapsed(self): """Return a timedelta on how old the post is. @@ -193,16 +194,16 @@ def is_comment(self): return self['depth'] > 0 def curation_reward_pct(self): - """ If post is less than 30 minutes old, it will incur a curation reward penalty. - """ + """ If post is less than 30 minutes old, it will incur a curation + reward penalty. """ reward = (self.time_elapsed().seconds / 1800) * 100 if reward > 100: reward = 100 return reward def export(self): - """ This method returns a dictionary that is type-safe to store as JSON or in a database. - """ + """ This method returns a dictionary that is type-safe to store as + JSON or in a database. """ self.refresh() # Remove Steem instance object @@ -222,7 +223,8 @@ def decompose_amounts(item): def upvote(self, weight=+100, voter=None): """ Upvote the post - :param float weight: (optional) Weight for posting (-100.0 - +100.0) defaults to +100.0 + :param float weight: (optional) Weight for posting (-100.0 - + +100.0) defaults to +100.0 :param str voter: (optional) Voting account """ return self.vote(weight, voter=voter) @@ -230,7 +232,8 @@ def upvote(self, weight=+100, voter=None): def downvote(self, weight=-100, voter=None): """ Downvote the post - :param float weight: (optional) Weight for posting (-100.0 - +100.0) defaults to -100.0 + :param float weight: (optional) Weight for posting (-100.0 - + +100.0) defaults to -100.0 :param str voter: (optional) Voting account """ return self.vote(weight, voter=voter) @@ -273,9 +276,7 @@ def edit(self, body, meta=None, replace=False): return reply_identifier = construct_identifier( - original_post["parent_author"], - original_post["parent_permlink"] - ) + original_post["parent_author"], original_post["parent_permlink"]) new_meta = {} if meta: @@ -305,27 +306,31 @@ def reply(self, body, title="", author="", meta=None): :param json meta: JSON meta object that can be attached to the post. (optional) """ - return self.commit.post(title, - body, - json_metadata=meta, - author=author, - reply_identifier=self.identifier) + return self.commit.post( + title, + body, + json_metadata=meta, + author=author, + reply_identifier=self.identifier) def set_comment_options(self, options): op = CommentOptions( **{ - "author": self["author"], - "permlink": self["permlink"], + "author": + self["author"], + "permlink": + self["permlink"], "max_accepted_payout": - options.get("max_accepted_payout", str(self["max_accepted_payout"])), - "percent_steem_dollars": int( + options.get("max_accepted_payout", + str(self["max_accepted_payout"])), + "percent_steem_dollars": + int( options.get("percent_steem_dollars", - self["percent_steem_dollars"] / 100 - ) * 100), + self["percent_steem_dollars"] / 100) * 100), "allow_votes": - options.get("allow_votes", self["allow_votes"]), + options.get("allow_votes", self["allow_votes"]), "allow_curation_rewards": - options.get("allow_curation_rewards", self["allow_curation_rewards"]), - } - ) + options.get("allow_curation_rewards", self[ + "allow_curation_rewards"]), + }) return self.commit.finalizeOp(op, self["author"], "posting") diff --git a/steem/profile.py b/steem/profile.py index fd73475..cf27b6a 100644 --- a/steem/profile.py +++ b/steem/profile.py @@ -3,7 +3,6 @@ class DotDict(dict): - def __init__(self, *args): """ This class simplifies the use of "."-separated keys when defining a nested dictionary::: diff --git a/steem/steem.py b/steem/steem.py index f351602..58047fe 100644 --- a/steem/steem.py +++ b/steem/steem.py @@ -2,32 +2,46 @@ from .steemd import Steemd from steembase.exceptions import RPCError + class Steem: """ Connect to the Steem network. Args: - nodes (list): A list of Steem HTTP RPC nodes to connect to. If not provided, official Steemit nodes will be used. - debug (bool): Elevate logging level to `logging.DEBUG`. Defaults to `logging.INFO`. - no_broadcast (bool): If set to ``True``, committal actions like sending funds will have no effect (simulation only). + nodes (list): A list of Steem HTTP RPC nodes to connect to. If + not provided, official Steemit nodes will be used. + + debug (bool): Elevate logging level to `logging.DEBUG`. + Defaults to `logging.INFO`. + + no_broadcast (bool): If set to ``True``, committal actions like + sending funds will have no effect (simulation only). Optional Arguments (kwargs): Args: - keys (list): A list of wif keys. If provided, the Wallet will use these keys rather than the - ones found in BIP38 encrypted wallet. + + keys (list): A list of wif keys. If provided, the Wallet will + use these keys rather than the ones found in BIP38 encrypted + wallet. + unsigned (bool): (Defaults to False) Use this for offline signing. - expiration (int): (Defualts to 60) Size of window in seconds that the transaction - needs to be broadcasted in, before it expires. + expiration (int): (Defualts to 60) Size of window in seconds + that the transaction needs to be broadcasted in, before it + expires. Returns: - Steemd class instance. It can be used to execute commands against steem node. + + Steemd class instance. It can be used to execute commands + against steem node. Example: - If you would like to override the official Steemit nodes (default), you can pass your own. - When currently used node goes offline, ``Steemd`` will automatically fail-over to the next available node. + If you would like to override the official Steemit nodes + (default), you can pass your own. When currently used node goes + offline, ``Steemd`` will automatically fail-over to the next + available node. .. code-block:: python @@ -41,15 +55,9 @@ class Steem: """ def __init__(self, nodes=None, no_broadcast=False, **kwargs): - self.steemd = Steemd( - nodes=nodes, - **kwargs - ) + self.steemd = Steemd(nodes=nodes, **kwargs) self.commit = Commit( - steemd_instance=self.steemd, - no_broadcast=no_broadcast, - **kwargs - ) + steemd_instance=self.steemd, no_broadcast=no_broadcast, **kwargs) def __getattr__(self, item): """ Bind .commit, .steemd methods here as a convenience. """ @@ -70,10 +78,10 @@ def __init__(self, api_name="", exec_method=None): def __getattr__(self, method_name): return Steem.Method( - api_name=self.api_name, - method_name=method_name, - exec_method=self.exec_method, - ) + api_name=self.api_name, + method_name=method_name, + exec_method=self.exec_method, + ) class Method(object): def __init__(self, api_name="", method_name="", exec_method=None): @@ -85,10 +93,13 @@ def __init__(self, api_name="", method_name="", exec_method=None): def __call__(self, *args, **kwargs): if len(kwargs) > 0: if len(args) > 0: - raise RPCError("Cannot specify both args and kwargs in RPC") - return self.exec_method(self.method_name, kwargs=kwargs, api=self.api_name) + raise RPCError( + "Cannot specify both args and kwargs in RPC") + return self.exec_method( + self.method_name, kwargs=kwargs, api=self.api_name) return self.exec_method(self.method_name, *args, api=self.api_name) + if __name__ == '__main__': s = Steem() print(s.get_account_count()) diff --git a/steem/steemd.py b/steem/steemd.py index 3aa7178..f4d7d44 100644 --- a/steem/steemd.py +++ b/steem/steemd.py @@ -27,15 +27,21 @@ class Steemd(HttpClient): """ Connect to the Steem network. Args: - nodes (list): A list of Steem HTTP RPC nodes to connect to. If not provided, official Steemit nodes will be used. + + nodes (list): A list of Steem HTTP RPC nodes to connect to. If + not provided, official Steemit nodes will be used. Returns: - Steemd class instance. It can be used to execute commands against steem node. + + Steemd class instance. It can be used to execute commands + against steem node. Example: - If you would like to override the official Steemit nodes (default), you can pass your own. - When currently used node goes offline, ``Steemd`` will automatically fail-over to the next available node. + If you would like to override the official Steemit nodes + (default), you can pass your own. When currently used node goes + offline, ``Steemd`` will automatically fail-over to the next + available node. .. code-block:: python @@ -62,7 +68,9 @@ def chain_params(self): """ props = self.get_dynamic_global_properties() chain = props["current_supply"].split(" ")[1] - assert chain in known_chains, "The chain you are connecting to is not supported" + + assert chain in known_chains, "The chain you are connecting " + \ + "to is not supported" return known_chains.get(chain) def get_replies(self, author, skip_own=True): @@ -103,16 +111,19 @@ def get_posts(self, limit=10, sort="hot", category=None, start=None): identifier of the form ``@author/permlink`` """ - discussion_query = {"tag": category, - "limit": limit, - } + discussion_query = { + "tag": category, + "limit": limit, + } if start: author, permlink = resolve_identifier(start) discussion_query["start_author"] = author discussion_query["start_permlink"] = permlink - if sort not in ["trending", "created", "active", "cashout", - "payout", "votes", "children", "hot"]: + if sort not in [ + "trending", "created", "active", "cashout", "payout", "votes", + "children", "hot" + ]: raise Exception("Invalid choice of '--sort'!") func = getattr(self, "get_discussions_by_%s" % sort) @@ -141,11 +152,11 @@ def last_irreversible_block_num(self): @property def head_block_number(self): """ Newest block number. """ - return self.get_dynamic_global_properties()[ - 'head_block_number'] + return self.get_dynamic_global_properties()['head_block_number'] def get_account(self, account: str): - """ Lookup account information such as user profile, public keys, balances, etc. + """ Lookup account information such as user profile, public keys, + balances, etc. Args: account (str): STEEM username that we are looking up. @@ -169,9 +180,9 @@ def get_all_usernames(self, last_user=''): def _get_blocks(self, blocks: Union[List[int], Set[int]]): """ Fetch multiple blocks from steemd at once. - Warning: - This method does not ensure that all blocks are returned, or that the results are ordered. - You will probably want to use `steemd.get_blocks()` instead. + Warning: This method does not ensure that all blocks are returned, + or that the results are ordered. You will probably want to use + `steemd.get_blocks()` instead. Args: blocks (list): A list, or a set of block numbers. @@ -180,17 +191,23 @@ def _get_blocks(self, blocks: Union[List[int], Set[int]]): A generator with results. """ - results = self.call_multi_with_futures('get_block', blocks, max_workers=10) - return ({**x, 'block_num': int(x['block_id'][:8], base=16)} for x in results if x) + results = self.call_multi_with_futures( + 'get_block', blocks, max_workers=10) + return ({**x, 'block_num': int(x['block_id'][:8], base=16)} + for x in results if x) def get_blocks(self, block_nums: List[int]): """ Fetch multiple blocks from steemd at once, given a range. Args: - block_nums (list): A list of all block numbers we would like to tech. + + block_nums (list): A list of all block numbers we would like to + tech. Returns: + dict: An ensured and ordered list of all `get_block` results. + """ required = set(block_nums) available = set() @@ -210,8 +227,11 @@ def get_blocks_range(self, start: int, end: int): """ Fetch multiple blocks from steemd at once, given a range. Args: + start (int): The number of the block to start with - end (int): The number of the block at the end of the range. Not included in results. + + end (int): The number of the block at the end of the range. Not + included in results. Returns: dict: An ensured and ordered list of all `get_block` results. @@ -219,21 +239,6 @@ def get_blocks_range(self, start: int, end: int): """ return self.get_blocks(list(range(start, end))) - ################################ - # steemd api generated methods # - ################################ - # def set_subscribe_callback(self, callback: object, clear_filter: object): - # return self.call('set_subscribe_callback', callback, clear_filter, api='database_api') - # - # def set_pending_transaction_callback(self, callback: object): - # return self.call('set_pending_transaction_callback', callback, api='database_api') - # - # def set_block_applied_callback(self, callback: object): - # return self.call('set_block_applied_callback', callback, api='database_api') - # - # def cancel_all_subscriptions(self): - # return self.call('cancel_all_subscriptions', api='database_api') - def get_reward_fund(self, fund_name: str = 'post'): """ Get details for a reward fund. @@ -259,73 +264,107 @@ def get_reward_fund(self, fund_name: str = 'post'): """ return self.call('get_reward_fund', fund_name, api='database_api') - def get_expiring_vesting_delegations(self, account: str, start: PointInTime, limit: int): + def get_expiring_vesting_delegations(self, account: str, + start: PointInTime, limit: int): """ get_expiring_vesting_delegations """ - return self.call('get_expiring_vesting_delegations', account, start, limit, api='database_api') + return self.call( + 'get_expiring_vesting_delegations', + account, + start, + limit, + api='database_api') def get_trending_tags(self, after_tag: str, limit: int): """ get_trending_tags """ - return self.call('get_trending_tags', after_tag, limit, api='database_api') + return self.call( + 'get_trending_tags', after_tag, limit, api='database_api') def get_tags_used_by_author(self, account: str): """ get_tags_used_by_author """ - return self.call('get_tags_used_by_author', account, api='database_api') + return self.call( + 'get_tags_used_by_author', account, api='database_api') def get_discussions_by_trending(self, discussion_query: dict): """ get_discussions_by_trending """ - return self.call('get_discussions_by_trending', discussion_query, api='database_api') + return self.call( + 'get_discussions_by_trending', + discussion_query, + api='database_api') def get_comment_discussions_by_payout(self, discussion_query: dict): """ get_comment_discussions_by_payout """ - return self.call('get_comment_discussions_by_payout', discussion_query, api='database_api') + return self.call( + 'get_comment_discussions_by_payout', + discussion_query, + api='database_api') def get_post_discussions_by_payout(self, discussion_query: dict): """ get_post_discussions_by_payout """ - return self.call('get_post_discussions_by_payout', discussion_query, api='database_api') + return self.call( + 'get_post_discussions_by_payout', + discussion_query, + api='database_api') def get_discussions_by_created(self, discussion_query: dict): """ get_discussions_by_created """ - return self.call('get_discussions_by_created', discussion_query, api='database_api') + return self.call( + 'get_discussions_by_created', discussion_query, api='database_api') def get_discussions_by_active(self, discussion_query: dict): """ get_discussions_by_active """ - return self.call('get_discussions_by_active', discussion_query, api='database_api') + return self.call( + 'get_discussions_by_active', discussion_query, api='database_api') def get_discussions_by_cashout(self, discussion_query: dict): """ get_discussions_by_cashout """ - return self.call('get_discussions_by_cashout', discussion_query, api='database_api') + return self.call( + 'get_discussions_by_cashout', discussion_query, api='database_api') def get_discussions_by_payout(self, discussion_query: dict): """ get_discussions_by_payout """ - return self.call('get_discussions_by_payout', discussion_query, api='database_api') + return self.call( + 'get_discussions_by_payout', discussion_query, api='database_api') def get_discussions_by_votes(self, discussion_query: dict): """ get_discussions_by_votes """ - return self.call('get_discussions_by_votes', discussion_query, api='database_api') + return self.call( + 'get_discussions_by_votes', discussion_query, api='database_api') def get_discussions_by_children(self, discussion_query: dict): """ get_discussions_by_children """ - return self.call('get_discussions_by_children', discussion_query, api='database_api') + return self.call( + 'get_discussions_by_children', + discussion_query, + api='database_api') def get_discussions_by_hot(self, discussion_query: dict): """ get_discussions_by_hot """ - return self.call('get_discussions_by_hot', discussion_query, api='database_api') + return self.call( + 'get_discussions_by_hot', discussion_query, api='database_api') def get_discussions_by_feed(self, discussion_query: dict): """ get_discussions_by_feed """ - return self.call('get_discussions_by_feed', discussion_query, api='database_api') + return self.call( + 'get_discussions_by_feed', discussion_query, api='database_api') def get_discussions_by_blog(self, discussion_query: dict): """ get_discussions_by_blog """ - return self.call('get_discussions_by_blog', discussion_query, api='database_api') + return self.call( + 'get_discussions_by_blog', discussion_query, api='database_api') def get_discussions_by_comments(self, discussion_query: dict): """ get_discussions_by_comments """ - return self.call('get_discussions_by_comments', discussion_query, api='database_api') + return self.call( + 'get_discussions_by_comments', + discussion_query, + api='database_api') def get_discussions_by_promoted(self, discussion_query: dict): """ get_discussions_by_promoted """ - return self.call('get_discussions_by_promoted', discussion_query, api='database_api') + return self.call( + 'get_discussions_by_promoted', + discussion_query, + api='database_api') def get_block_header(self, block_num: int): """ Get block headers, given a block number. @@ -347,7 +386,9 @@ def get_block_header(self, block_num: int): {'extensions': [], 'previous': '0087a2372163ff5c5838b09589ce281d5a564f66', 'timestamp': '2017-01-29T02:47:33', - 'transaction_merkle_root': '4ddc419e531cccee6da660057d606d11aab9f3a5', + + 'transaction_merkle_root': + '4ddc419e531cccee6da660057d606d11aab9f3a5', 'witness': 'chainsquad.com'} """ return self.call('get_block_header', block_num, api='database_api') @@ -361,41 +402,13 @@ def get_block(self, block_num: int): Returns: dict: Block in a JSON compatible format. - Example: - - .. code-block:: python - - s.get_block(8888888) - - :: - - {'extensions': [], - 'previous': '0087a2372163ff5c5838b09589ce281d5a564f66', - 'timestamp': '2017-01-29T02:47:33', - 'transaction_merkle_root': '4ddc419e531cccee6da660057d606d11aab9f3a5', - 'transactions': [{'expiration': '2017-01-29T02:47:42', - 'extensions': [], - 'operations': [['comment', - {'author': 'hilarski', - 'body': 'https://media.giphy.com/media/RAx4Xwh1OPHji/giphy.gif', - 'json_metadata': '{"tags":["motocross"],"image":["https://media.giphy.com/media/RAx4Xwh1OPHji/giphy.gif"],"app":"steemit/0.1"}', - 'parent_author': 'b0y2k', - 'parent_permlink': 'ama-supercross-round-4-phoenix-2017', - 'permlink': 're-b0y2k-ama-supercross-round-4-phoenix-2017-20170129t024725575z', - 'title': ''}]], - 'ref_block_num': 41495, - 'ref_block_prefix': 2639073901, - 'signatures': ['2058b69f4c15f704a67a7b5a7996a9c9bbfd39c639f9db19b99ecad8328c4ce3610643f8d1b6424c352df120614cd535cd8f2772fce68814eeea50049684c37d69']}], - 'witness': 'chainsquad.com', - 'witness_signature': '1f115745e3f6fee95124164f4b57196c0eda2a700064faa97d0e037d3554ee2d5b618e6bfd457473783e8b8333724ba0bf93f0a4a7026e7925c8c4d2ba724152d4'} - - """ return self.call('get_block', block_num, api='database_api') def get_ops_in_block(self, block_num: int, virtual_only: bool): """ get_ops_in_block """ - return self.call('get_ops_in_block', block_num, virtual_only, api='database_api') + return self.call( + 'get_ops_in_block', block_num, virtual_only, api='database_api') def get_state(self, path: str): """ get_state """ @@ -426,7 +439,8 @@ def get_feed_history(self): :: - {'current_median_history': {'base': '0.093 SBD', 'quote': '1.010 STEEM'}, + {'current_median_history': + {'base': '0.093 SBD', 'quote': '1.010 STEEM'}, 'id': 0, 'price_history': [{'base': '0.092 SBD', 'quote': '1.010 STEEM'}, {'base': '0.093 SBD', 'quote': '1.020 STEEM'}, @@ -447,7 +461,8 @@ def get_current_median_history_price(self): {'base': '0.093 SBD', 'quote': '1.010 STEEM'} """ - return self.call('get_current_median_history_price', api='database_api') + return self.call( + 'get_current_median_history_price', api='database_api') def get_witness_schedule(self): """ get_witness_schedule """ @@ -467,25 +482,33 @@ def get_next_scheduled_hardfork(self): return self.call('get_next_scheduled_hardfork', api='database_api') def get_accounts(self, account_names: list): - """ Lookup account information such as user profile, public keys, balances, etc. + """ Lookup account information such as user profile, public keys, + balances, etc. + + This method is same as ``get_account``, but supports querying for + multiple accounts at the time. - This method is same as ``get_account``, but supports querying for multiple accounts at the time. """ return self.call('get_accounts', account_names, api='database_api') def get_account_references(self, account_id: int): """ get_account_references """ - return self.call('get_account_references', account_id, api='database_api') + return self.call( + 'get_account_references', account_id, api='database_api') def lookup_account_names(self, account_names: list): """ lookup_account_names """ - return self.call('lookup_account_names', account_names, api='database_api') + return self.call( + 'lookup_account_names', account_names, api='database_api') def lookup_accounts(self, after: Union[str, int], limit: int) -> List[str]: """Get a list of usernames from all registered accounts. Args: - after (str, int): Username to start with. If '', 0 or -1, it will start at beginning. + + after (str, int): Username to start with. If '', 0 or -1, it + will start at beginning. + limit (int): How many results to return. Returns: @@ -500,22 +523,30 @@ def get_account_count(self): def get_conversion_requests(self, account: str): """ get_conversion_requests """ - return self.call('get_conversion_requests', account, api='database_api') + return self.call( + 'get_conversion_requests', account, api='database_api') def get_account_history(self, account: str, index_from: int, limit: int): """ History of all operations for a given account. Args: + account (str): STEEM username that we are looking up. - index_from (int): The highest database index we take as a starting point. + + index_from (int): The highest database index we take as a + starting point. + limit (int): How many items are we interested in. Returns: + list: List of operations. Example: - To get the latest (newest) operations from a given user ``furion``, we should set the ``index_from`` to -1. - This is the same as saying `give me the highest index there is`. + + To get the latest (newest) operations from a given user + ``furion``, we should set the ``index_from`` to -1. This is the + same as saying `give me the highest index there is`. .. code-block :: python @@ -529,7 +560,7 @@ def get_account_history(self, account: str, index_from: int, limit: int): {'block': 9941972, 'op': ['vote', {'author': 'breezin', - 'permlink': 'raising-children-is-not-childsplay-pro-s-and-con-s-of-being-a-young-parent', + 'permlink': 'raising-childr....', 'voter': 'furion', 'weight': 900}], 'op_in_trx': 0, @@ -562,10 +593,13 @@ def get_account_history(self, account: str, index_from: int, limit: int): 'trx_in_block': 7, 'virtual_op': 0}]] - If we want to query for a particular range of indexes, we need to consider both `index_from` and `limit` fields. - Remember, `index_from` works backwards, so if we set it to 100, we will get items `100, 99, 98, 97...`. + If we want to query for a particular range of indexes, we need + to consider both `index_from` and `limit` fields. Remember, + `index_from` works backwards, so if we set it to 100, we will + get items `100, 99, 98, 97...`. - For example, if we'd like to get the first 100 operations the user did, we would write: + For example, if we'd like to get the first 100 operations the + user did, we would write: .. code-block:: python @@ -579,7 +613,12 @@ def get_account_history(self, account: str, index_from: int, limit: int): """ - return self.call('get_account_history', account, index_from, limit, api='database_api') + return self.call( + 'get_account_history', + account, + index_from, + limit, + api='database_api') def get_owner_history(self, account: str): """ get_owner_history """ @@ -591,23 +630,34 @@ def get_recovery_request(self, account: str): def get_escrow(self, from_account: str, escrow_id: int): """ get_escrow """ - return self.call('get_escrow', from_account, escrow_id, api='database_api') + return self.call( + 'get_escrow', from_account, escrow_id, api='database_api') def get_withdraw_routes(self, account: str, withdraw_route_type: str): """ get_withdraw_routes """ - return self.call('get_withdraw_routes', account, withdraw_route_type, api='database_api') + return self.call( + 'get_withdraw_routes', + account, + withdraw_route_type, + api='database_api') def get_account_bandwidth(self, account: str, bandwidth_type: object): """ get_account_bandwidth """ - return self.call('get_account_bandwidth', account, bandwidth_type, api='database_api') + return self.call( + 'get_account_bandwidth', + account, + bandwidth_type, + api='database_api') def get_savings_withdraw_from(self, account: str): """ get_savings_withdraw_from """ - return self.call('get_savings_withdraw_from', account, api='database_api') + return self.call( + 'get_savings_withdraw_from', account, api='database_api') def get_savings_withdraw_to(self, account: str): """ get_savings_withdraw_to """ - return self.call('get_savings_withdraw_to', account, api='database_api') + return self.call( + 'get_savings_withdraw_to', account, api='database_api') def get_order_book(self, limit: int): """ Get the internal market order book. @@ -631,22 +681,26 @@ def get_order_book(self, limit: int): :: {'asks': [{'created': '2017-03-06T21:29:54', - 'order_price': {'base': '513.571 STEEM', 'quote': '50.000 SBD'}, + 'order_price': + {'base': '513.571 STEEM', 'quote': '50.000 SBD'}, 'real_price': '0.09735752213423265', 'sbd': 50000, 'steem': 513571}, {'created': '2017-03-06T21:01:39', - 'order_price': {'base': '63.288 STEEM', 'quote': '6.204 SBD'}, + 'order_price': + {'base': '63.288 STEEM', 'quote': '6.204 SBD'}, 'real_price': '0.09802806219188472', 'sbd': 6204, 'steem': 63288}], 'bids': [{'created': '2017-03-06T21:29:51', - 'order_price': {'base': '50.000 SBD', 'quote': '516.503 STEEM'}, + 'order_price': + {'base': '50.000 SBD', 'quote': '516.503 STEEM'}, 'real_price': '0.09680485882947436', 'sbd': 50000, 'steem': 516503}, {'created': '2017-03-06T17:30:24', - 'order_price': {'base': '36.385 SBD', 'quote': '379.608 STEEM'}, + 'order_price': + {'base': '36.385 SBD', 'quote': '379.608 STEEM'}, 'real_price': '0.09584887568228277', 'sbd': 36385, 'steem': 379608}]} @@ -663,41 +717,55 @@ def get_liquidity_queue(self, start_account: str, limit: int): """ Get the liquidity queue. Warning: - This feature is currently not in use, and might be deprecated in the future. + + This feature is currently not in use, and might be deprecated + in the future. """ - return self.call('get_liquidity_queue', start_account, limit, api='database_api') + return self.call( + 'get_liquidity_queue', start_account, limit, api='database_api') def get_transaction_hex(self, signed_transaction: SignedTransaction): """ get_transaction_hex """ - return self.call('get_transaction_hex', signed_transaction, api='database_api') + return self.call( + 'get_transaction_hex', signed_transaction, api='database_api') def get_transaction(self, transaction_id: str): """ get_transaction """ return self.call('get_transaction', transaction_id, api='database_api') - def get_required_signatures(self, signed_transaction: SignedTransaction, available_keys: list): + def get_required_signatures(self, signed_transaction: SignedTransaction, + available_keys: list): """ get_required_signatures """ - return self.call('get_required_signatures', signed_transaction, available_keys, api='database_api') + return self.call( + 'get_required_signatures', + signed_transaction, + available_keys, + api='database_api') def get_potential_signatures(self, signed_transaction: SignedTransaction): """ get_potential_signatures """ - return self.call('get_potential_signatures', signed_transaction, api='database_api') + return self.call( + 'get_potential_signatures', signed_transaction, api='database_api') def verify_authority(self, signed_transaction: SignedTransaction): """ verify_authority """ - return self.call('verify_authority', signed_transaction, api='database_api') + return self.call( + 'verify_authority', signed_transaction, api='database_api') def verify_account_authority(self, account: str, keys: list): """ verify_account_authority """ - return self.call('verify_account_authority', account, keys, api='database_api') + return self.call( + 'verify_account_authority', account, keys, api='database_api') def get_active_votes(self, author: str, permlink: str): """ Get all votes for the given post. Args: author (str): OP's STEEM username. - permlink (str): Post identifier following the username. It looks like slug-ified title. + + permlink (str): Post identifier following the username. It + looks like slug-ified title. Returns: list: List of votes. @@ -705,7 +773,8 @@ def get_active_votes(self, author: str, permlink: str): Example: .. code-block:: python - s.get_active_votes('mynameisbrian', 'steemifying-idioms-there-s-no-use-crying-over-spilt-milk') + s.get_active_votes('mynameisbrian', + 'steemifying-idioms-there-s-no-use-crying-over-spilt-milk') Output: @@ -725,7 +794,8 @@ def get_active_votes(self, author: str, permlink: str): 'voter': 'flourish', 'weight': '2334690471157'}] """ - return self.call('get_active_votes', author, permlink, api='database_api') + return self.call( + 'get_active_votes', author, permlink, api='database_api') def get_account_votes(self, account: str): """ All votes the given account ever made. @@ -733,11 +803,10 @@ def get_account_votes(self, account: str): Returned votes are in the following format: :: - {'authorperm': 'alwaysfelicia/time-line-of-best-times-to-post-on-steemit-mystery-explained', - 'percent': 100, - 'rshares': 709227399, - 'time': '2016-08-07T16:06:24', - 'weight': '3241351576115042'}, + {'authorperm': + 'alwaysfelicia/time-line-of-best-time...', + 'percent': 100, 'rshares': 709227399, 'time': + '2016-08-07T16:06:24', 'weight': '3241351576115042'}, Args: @@ -756,21 +825,30 @@ def get_content(self, author: str, permlink: str): def get_content_replies(self, author: str, permlink: str): """ get_content_replies """ - return self.call('get_content_replies', author, permlink, api='database_api') + return self.call( + 'get_content_replies', author, permlink, api='database_api') - def get_discussions_by_author_before_date(self, - author: str, - start_permlink: str, - before_date: PointInTime, - limit: int): + def get_discussions_by_author_before_date( + self, author: str, start_permlink: str, before_date: PointInTime, + limit: int): """ get_discussions_by_author_before_date """ - return self.call('get_discussions_by_author_before_date', - author, start_permlink, before_date, limit, - api='database_api') - - def get_replies_by_last_update(self, account: str, start_permlink: str, limit: int): + return self.call( + 'get_discussions_by_author_before_date', + author, + start_permlink, + before_date, + limit, + api='database_api') + + def get_replies_by_last_update(self, account: str, start_permlink: str, + limit: int): """ get_replies_by_last_update """ - return self.call('get_replies_by_last_update', account, start_permlink, limit, api='database_api') + return self.call( + 'get_replies_by_last_update', + account, + start_permlink, + limit, + api='database_api') def get_witnesses(self, witness_ids: list): """ get_witnesses """ @@ -782,11 +860,13 @@ def get_witness_by_account(self, account: str): def get_witnesses_by_vote(self, from_account: str, limit: int): """ get_witnesses_by_vote """ - return self.call('get_witnesses_by_vote', from_account, limit, api='database_api') + return self.call( + 'get_witnesses_by_vote', from_account, limit, api='database_api') def lookup_witness_accounts(self, from_account: str, limit: int): """ lookup_witness_accounts """ - return self.call('lookup_witness_accounts', from_account, limit, api='database_api') + return self.call( + 'lookup_witness_accounts', from_account, limit, api='database_api') def get_witness_count(self): """ get_witness_count """ @@ -796,9 +876,15 @@ def get_active_witnesses(self): """ Get a list of currently active witnesses. """ return self.call('get_active_witnesses', api='database_api') - def get_vesting_delegations(self, account: str, from_account: str, limit: int): + def get_vesting_delegations(self, account: str, from_account: str, + limit: int): """ get_vesting_delegations """ - return self.call('get_vesting_delegations', account, from_account, limit, api='database_api') + return self.call( + 'get_vesting_delegations', + account, + from_account, + limit, + api='database_api') def login(self, username: str, password: str): """ login """ @@ -812,13 +898,27 @@ def get_version(self): """ Get steemd version of the node currently connected to. """ return self.call('get_version', api='login_api') - def get_followers(self, account: str, start_follower: str, follow_type: str, limit: int): + def get_followers(self, account: str, start_follower: str, + follow_type: str, limit: int): """ get_followers """ - return self.call('get_followers', account, start_follower, follow_type, limit, api='follow_api') - - def get_following(self, account: str, start_follower: str, follow_type: str, limit: int): + return self.call( + 'get_followers', + account, + start_follower, + follow_type, + limit, + api='follow_api') + + def get_following(self, account: str, start_follower: str, + follow_type: str, limit: int): """ get_following """ - return self.call('get_following', account, start_follower, follow_type, limit, api='follow_api') + return self.call( + 'get_following', + account, + start_follower, + follow_type, + limit, + api='follow_api') def get_follow_count(self, account: str): """ get_follow_count """ @@ -826,27 +926,33 @@ def get_follow_count(self, account: str): def get_feed_entries(self, account: str, entry_id: int, limit: int): """ get_feed_entries """ - return self.call('get_feed_entries', account, entry_id, limit, api='follow_api') + return self.call( + 'get_feed_entries', account, entry_id, limit, api='follow_api') def get_feed(self, account: str, entry_id: int, limit: int): """ get_feed """ - return self.call('get_feed', account, entry_id, limit, api='follow_api') + return self.call( + 'get_feed', account, entry_id, limit, api='follow_api') def get_blog_entries(self, account: str, entry_id: int, limit: int): """ get_blog_entries """ - return self.call('get_blog_entries', account, entry_id, limit, api='follow_api') + return self.call( + 'get_blog_entries', account, entry_id, limit, api='follow_api') def get_blog(self, account: str, entry_id: int, limit: int): """ get_blog """ - return self.call('get_blog', account, entry_id, limit, api='follow_api') + return self.call( + 'get_blog', account, entry_id, limit, api='follow_api') def get_account_reputations(self, account: str, limit: int): """ get_account_reputations """ - return self.call('get_account_reputations', account, limit, api='follow_api') + return self.call( + 'get_account_reputations', account, limit, api='follow_api') def get_reblogged_by(self, author: str, permlink: str): """ get_reblogged_by """ - return self.call('get_reblogged_by', author, permlink, api='follow_api') + return self.call( + 'get_reblogged_by', author, permlink, api='follow_api') def get_blog_authors(self, blog_account: str): """ get_blog_authors """ @@ -854,15 +960,18 @@ def get_blog_authors(self, blog_account: str): def broadcast_transaction(self, signed_transaction: SignedTransaction): """ broadcast_transaction """ - return self.call('broadcast_transaction', signed_transaction, api='network_broadcast_api') - - # def broadcast_transaction_with_callback(self, callback: object, signed_transaction: SignedTransaction): - # return self.call('broadcast_transaction_with_callback', callback, signed_transaction, - # api='network_broadcast_api') + return self.call( + 'broadcast_transaction', + signed_transaction, + api='network_broadcast_api') - def broadcast_transaction_synchronous(self, signed_transaction: SignedTransaction): + def broadcast_transaction_synchronous( + self, signed_transaction: SignedTransaction): """ broadcast_transaction_synchronous """ - return self.call('broadcast_transaction_synchronous', signed_transaction, api='network_broadcast_api') + return self.call( + 'broadcast_transaction_synchronous', + signed_transaction, + api='network_broadcast_api') def broadcast_block(self, block: Block): """ broadcast_block """ @@ -870,7 +979,8 @@ def broadcast_block(self, block: Block): def set_max_block_age(self, max_block_age: int): """ set_max_block_age """ - return self.call('set_max_block_age', max_block_age, api='network_broadcast_api') + return self.call( + 'set_max_block_age', max_block_age, api='network_broadcast_api') def get_ticker(self): """ Returns the market ticker for the internal SBD:STEEM market. """ @@ -880,30 +990,39 @@ def get_volume(self): """ Returns the market volume for the past 24 hours. """ return self.call('get_volume', api='market_history_api') - # def get_order_book(self, limit: int): - # return self.call('get_order_book', limit, api='market_history_api') - - def get_trade_history(self, start: PointInTime, end: PointInTime, limit: int): + def get_trade_history(self, start: PointInTime, end: PointInTime, + limit: int): """ Returns the trade history for the internal SBD:STEEM market. """ - return self.call('get_trade_history', start, end, limit, api='market_history_api') + return self.call( + 'get_trade_history', start, end, limit, api='market_history_api') def get_recent_trades(self, limit: int) -> List[Any]: - """ Returns the N most recent trades for the internal SBD:STEEM market. """ + """ Returns the N most recent trades for the internal SBD:STEEM + market. """ + return self.call('get_recent_trades', limit, api='market_history_api') - def get_market_history(self, bucket_seconds: int, start: PointInTime, end: PointInTime): + def get_market_history(self, bucket_seconds: int, start: PointInTime, + end: PointInTime): """ Returns the market history for the internal SBD:STEEM market. """ - return self.call('get_market_history', bucket_seconds, start, end, api='market_history_api') + return self.call( + 'get_market_history', + bucket_seconds, + start, + end, + api='market_history_api') def get_market_history_buckets(self): """ Returns the bucket seconds being tracked by the plugin. """ - return self.call('get_market_history_buckets', api='market_history_api') + return self.call( + 'get_market_history_buckets', api='market_history_api') def get_key_references(self, public_keys: List[str]): """ get_key_references """ if type(public_keys) == str: public_keys = [public_keys] - return self.call('get_key_references', public_keys, api='account_by_key_api') + return self.call( + 'get_key_references', public_keys, api='account_by_key_api') if __name__ == '__main__': diff --git a/steem/transactionbuilder.py b/steem/transactionbuilder.py index 8b3f9ed..e56bfdb 100644 --- a/steem/transactionbuilder.py +++ b/steem/transactionbuilder.py @@ -2,13 +2,11 @@ from steem.wallet import Wallet from steembase.account import PrivateKey -from steembase.exceptions import ( - InsufficientAuthorityError, - MissingKeyError, - InvalidKeyFormat -) +from steembase.exceptions import (InsufficientAuthorityError, MissingKeyError, + InvalidKeyFormat) from steembase.operations import Operation -from steembase.transactions import SignedTransaction, fmt_time_from_now, get_block_params +from steembase.transactions import SignedTransaction, fmt_time_from_now, \ + get_block_params from .account import Account from .instance import shared_steemd_instance @@ -21,7 +19,12 @@ class TransactionBuilder(dict): operations and signers. """ - def __init__(self, tx=None, steemd_instance=None, wallet_instance=None, no_broadcast=False, expiration=60): + def __init__(self, + tx=None, + steemd_instance=None, + wallet_instance=None, + no_broadcast=False, + expiration=60): self.steemd = steemd_instance or shared_steemd_instance() self.no_broadcast = no_broadcast self.expiration = expiration @@ -42,7 +45,8 @@ def appendOps(self, ops): self.constructTx() def appendSigner(self, account, permission): - assert permission in ["active", "owner", "posting"], "Invalid permission" + assert permission in ["active", "owner", + "posting"], "Invalid permission" account = Account(account, steemd_instance=self.steemd) required_treshold = account[permission]["weight_threshold"] @@ -59,7 +63,8 @@ def fetchkeys(account, level=0): if sum([x[1] for x in r]) < required_treshold: # go one level deeper for authority in account[permission]["account_auths"]: - auth_account = Account(authority[0], steemd_instance=self.steemd) + auth_account = Account( + authority[0], steemd_instance=self.steemd) r.extend(fetchkeys(auth_account, level + 1)) return r @@ -72,7 +77,7 @@ def appendWif(self, wif): try: PrivateKey(wif) self.wifs.append(wif) - except: + except: # noqa FIXME(sneak) raise InvalidKeyFormat def constructTx(self): @@ -86,8 +91,7 @@ def constructTx(self): ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, - operations=ops - ) + operations=ops) super(TransactionBuilder, self).__init__(tx.json()) def sign(self): @@ -101,7 +105,7 @@ def sign(self): """ try: signedtx = SignedTransaction(**self.json()) - except: + except: # noqa FIXME(sneak) raise ValueError("Invalid TransactionBuilder Format") if not any(self.wifs): @@ -142,25 +146,23 @@ def addSigningInformation(self, account, permission): # We add a required_authorities to be able to identify # how to sign later. This is an array, because we # may later want to allow multiple operations per tx - self.update({"required_authorities": { - account: authority - }}) + self.update({"required_authorities": {account: authority}}) for account_auth in authority["account_auths"]: - account_auth_account = Account(account_auth[0], steemd_instance=self.steemd) + account_auth_account = Account( + account_auth[0], steemd_instance=self.steemd) self["required_authorities"].update({ - account_auth[0]: account_auth_account.get(permission) + account_auth[0]: + account_auth_account.get(permission) }) # Try to resolve required signatures for offline signing - self["missing_signatures"] = [ - x[0] for x in authority["key_auths"] - ] + self["missing_signatures"] = [x[0] for x in authority["key_auths"]] # Add one recursion of keys from account_auths: for account_auth in authority["account_auths"]: - account_auth_account = Account(account_auth[0], steemd_instance=self.steemd) + account_auth_account = Account( + account_auth[0], steemd_instance=self.steemd) self["missing_signatures"].extend( - [x[0] for x in account_auth_account[permission]["key_auths"]] - ) + [x[0] for x in account_auth_account[permission]["key_auths"]]) self["blockchain"] = self.steemd.chain_params def json(self): diff --git a/steem/utils.py b/steem/utils.py index a086e73..1cf10d7 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -176,13 +176,15 @@ def detect_language(text): def is_comment(item): """Quick check whether an item is a comment (reply) to another post. - The item can be a Post object or just a raw comment object from the blockchain. + The item can be a Post object or just a raw comment object from the + blockchain. """ return item['permlink'][:3] == "re-" and item['parent_author'] def time_elapsed(posting_time): - """Takes a string time from a post or blockchain event, and returns a time delta from now. + """Takes a string time from a post or blockchain event, and returns a + time delta from now. """ if type(posting_time) == str: posting_time = parse_time(posting_time) @@ -190,7 +192,8 @@ def time_elapsed(posting_time): def parse_time(block_time): - """Take a string representation of time from the blockchain, and parse it into datetime object. + """Take a string representation of time from the blockchain, and parse + it into datetime object. """ return datetime.strptime(block_time, '%Y-%m-%dT%H:%M:%S') @@ -222,14 +225,14 @@ def remove_from_dict(obj, remove_keys=list()): def construct_identifier(*args, username_prefix='@'): - """ Create a post identifier from comment/post object or arguments. - + """ Create a post identifier from comment/post object or arguments. + Examples: - - :: - + + :: construct_identifier('username', 'permlink') - construct_identifier({'author': 'username', 'permlink': 'permlink'}) + construct_identifier({'author': 'username', + 'permlink': 'permlink'}) """ if len(args) == 1: op = args[0] @@ -237,7 +240,8 @@ def construct_identifier(*args, username_prefix='@'): elif len(args) == 2: author, permlink = args else: - raise ValueError('construct_identifier() received unparsable arguments') + raise ValueError( + 'construct_identifier() received unparsable arguments') fields = dict(prefix=username_prefix, author=author, permlink=permlink) return "{prefix}{author}/{permlink}".format(**fields) @@ -303,7 +307,8 @@ def fmt_time_from_now(secs=0): :rtype: str """ - return datetime.utcfromtimestamp(time.time() + int(secs)).strftime('%Y-%m-%dT%H:%M:%S') + return datetime.utcfromtimestamp(time.time() + int(secs)).strftime( + '%Y-%m-%dT%H:%M:%S') def env_unlocked(): @@ -348,4 +353,4 @@ def strfdelta(tdelta, fmt): def is_valid_account_name(name): - return re.match('^[a-z][a-z0-9\-.]{2,15}$', name) \ No newline at end of file + return re.match('^[a-z][a-z0-9\-.]{2,15}$', name) diff --git a/steem/wallet.py b/steem/wallet.py index 0414982..e8bfcbf 100644 --- a/steem/wallet.py +++ b/steem/wallet.py @@ -4,10 +4,7 @@ from steem.instance import shared_steemd_instance from steembase import bip38 from steembase.account import PrivateKey -from steembase.exceptions import ( - InvalidWifError, - WalletExists -) +from steembase.exceptions import (InvalidWifError, WalletExists) from .account import Account @@ -20,7 +17,9 @@ class Wallet: or uses a SQLite database managed by storage.py. :param Steem rpc: RPC connection to a Steem node - :param array,dict,string keys: Predefine the wif keys to shortcut the wallet database + + :param array,dict,string keys: Predefine the wif keys to shortcut + the wallet database Three wallet operation modes are possible: @@ -64,8 +63,7 @@ def __init__(self, steemd_instance=None, **kwargs): """ If no keys are provided manually we load the SQLite keyStorage """ - from steembase.storage import (keyStorage, - MasterPassword) + from steembase.storage import (keyStorage, MasterPassword) self.MasterPassword = MasterPassword self.keyStorage = keyStorage @@ -73,7 +71,8 @@ def setKeys(self, loadkeys): """ This method is strictly only for in memory keys that are passed to Wallet/Steem with the ``keys`` argument """ - log.debug("Force setting of private keys. Not using the wallet database!") + log.debug( + "Force setting of private keys. Not using the wallet database!") if isinstance(loadkeys, dict): Wallet.keyMap = loadkeys loadkeys = list(loadkeys.values()) @@ -83,7 +82,7 @@ def setKeys(self, loadkeys): for wif in loadkeys: try: key = PrivateKey(wif) - except: + except: # noqa FIXME(sneak) raise InvalidWifError Wallet.keys[format(key.pubkey, self.prefix)] = str(key) @@ -93,8 +92,8 @@ def unlock(self, pwd=None): if not self.created(): self.newWallet() - if (self.masterpassword is None and - self.configStorage[self.MasterPassword.config_key]): + if (self.masterpassword is None + and self.configStorage[self.MasterPassword.config_key]): if pwd is None: pwd = self.getPassword() masterpwd = self.MasterPassword(pwd) @@ -149,7 +148,8 @@ def encrypt_wif(self, wif): """ Encrypt a wif key """ self.unlock() - return format(bip38.encrypt(PrivateKey(wif), self.masterpassword), "encwif") + return format( + bip38.encrypt(PrivateKey(wif), self.masterpassword), "encwif") def decrypt_wif(self, encwif): """ decrypt a wif key @@ -158,7 +158,7 @@ def decrypt_wif(self, encwif): # Try to decode as wif PrivateKey(encwif) return encwif - except: + except: # noqa FIXME(sneak) pass self.unlock() return format(bip38.decrypt(encwif, self.masterpassword), "wif") @@ -175,17 +175,13 @@ def getPassword(self, confirm=False, text='Passphrase: '): while True: pw = self.getPassword(confirm=False) if not pw: - print( - "You cannot chosen an empty password! " + - "If you want to automate the use of the libs, " + - "please use the `UNLOCK` environmental variable!" - ) + print("You cannot chosen an empty password! " + + "If you want to automate the use of the libs, " + + "please use the `UNLOCK` environmental variable!") continue else: pwck = self.getPassword( - confirm=False, - text="Confirm Passphrase: " - ) + confirm=False, text="Confirm Passphrase: ") if pw == pwck: return pw else: @@ -197,13 +193,17 @@ def getPassword(self, confirm=False, text='Passphrase: '): def addPrivateKey(self, wif): """ Add a private key to the wallet database """ - # it could be either graphenebase or pistonbase so we can't check the type directly + + # it could be either graphenebase or pistonbase so we can't check + # the type directly + if isinstance(wif, PrivateKey) or isinstance(wif, PrivateKey): wif = str(wif) try: pub = format(PrivateKey(wif).pubkey, self.prefix) - except: - raise InvalidWifError("Invalid Private Key Format. Please use WIF!") + except: # noqa FIXME(sneak) + raise InvalidWifError( + "Invalid Private Key Format. Please use WIF!") if self.keyStorage: # Test if wallet exists @@ -229,7 +229,8 @@ def getPrivateKeyForPublicKey(self, pub): if not self.created(): self.newWallet() - return self.decrypt_wif(self.keyStorage.getPrivateKeyForPublicKey(pub)) + return self.decrypt_wif( + self.keyStorage.getPrivateKeyForPublicKey(pub)) def removePrivateKeyFromPublicKey(self, pub): """ Remove a key from the wallet database @@ -319,7 +320,8 @@ def getAccountFromPublicKey(self, pub): # FIXME, this only returns the first associated key. # If the key is used by multiple accounts, this # will surely lead to undesired behavior - names = self.steemd.call('get_key_references', [pub], api="account_by_key_api")[0] + names = self.steemd.call( + 'get_key_references', [pub], api="account_by_key_api")[0] if not names: return None else: @@ -330,21 +332,19 @@ def getAccount(self, pub): """ name = self.getAccountFromPublicKey(pub) if not name: - return {"name": None, - "type": None, - "pubkey": pub - } + return {"name": None, "type": None, "pubkey": pub} else: try: account = Account(name) - except: + except: # noqa FIXME(sneak) return keyType = self.getKeyType(account, pub) - return {"name": name, - "account": account, - "type": keyType, - "pubkey": pub - } + return { + "name": name, + "account": account, + "type": keyType, + "pubkey": pub + } def getKeyType(self, account, pub): """ Get key type @@ -380,10 +380,12 @@ def getAccountsWithPermissions(self): continue type = account["type"] if name not in r: - r[name] = {"posting": False, - "owner": False, - "active": False, - "memo": False} + r[name] = { + "posting": False, + "owner": False, + "active": False, + "memo": False + } r[name][type] = True return r diff --git a/steem/witness.py b/steem/witness.py index 0e8a11c..ad8f932 100644 --- a/steem/witness.py +++ b/steem/witness.py @@ -7,9 +7,11 @@ class Witness(dict): """ Read data about a witness in the chain :param str witness: Name of the witness - :param Steemd steemd_instance: Steemd() instance to use when accessing a RPC + :param Steemd steemd_instance: Steemd() instance to use when + accessing a RPC """ + def __init__(self, witness, steemd_instance=None): self.steemd = steemd_instance or shared_steemd_instance() self.witness_name = witness diff --git a/steembase/account.py b/steembase/account.py index a7b9a2e..1b2e34e 100644 --- a/steembase/account.py +++ b/steembase/account.py @@ -25,9 +25,7 @@ def get_private(self): """ Derive private key from the brain key and the current sequence number """ - a = bytes(self.account + - self.role + - self.password, 'utf8') + a = bytes(self.account + self.role + self.password, 'utf8') s = hashlib.sha256(a).digest() return PrivateKey(hexlify(s).decode('ascii')) @@ -80,7 +78,8 @@ def next_sequence(self): return self def normalize(self, brainkey): - """ Correct formating with single whitespace syntax and no trailing space """ + """ Correct formating with single whitespace syntax and no trailing + space """ return " ".join(re.compile("[\t\n\v\f\r ]+").split(brainkey)) def get_brainkey(self): @@ -115,7 +114,7 @@ def suggest(self): assert len(dict_lines) == 49744 for j in range(0, word_count): num = int.from_bytes(os.urandom(2), byteorder="little") - rndMult = num / 2 ** 16 # returns float between 0..1 (inclusive) + rndMult = num / 2**16 # returns float between 0..1 (inclusive) wIdx = round(len(dict_lines) * rndMult) brainkey[j] = dict_lines[wIdx] return " ".join(brainkey).upper() @@ -231,10 +230,12 @@ def _derive_y_from_x(self, x, is_even): def compressed(self): """ Derive compressed public key """ order = ecdsa.SECP256k1.generator.order() - p = ecdsa.VerifyingKey.from_string(bytes(self), curve=ecdsa.SECP256k1).pubkey.point + p = ecdsa.VerifyingKey.from_string( + bytes(self), curve=ecdsa.SECP256k1).pubkey.point x_str = ecdsa.util.number_to_string(p.x(), order) # y_str = ecdsa.util.number_to_string(p.y(), order) - compressed = hexlify(bytes(chr(2 + (p.y() & 1)), 'ascii') + x_str).decode('ascii') + compressed = hexlify( + bytes(chr(2 + (p.y() & 1)), 'ascii') + x_str).decode('ascii') return (compressed) def unCompressed(self): @@ -252,7 +253,8 @@ def unCompressed(self): def point(self): """ Return the point for the public key """ string = unhexlify(self.unCompressed()) - return ecdsa.VerifyingKey.from_string(string[1:], curve=ecdsa.SECP256k1).pubkey.point + return ecdsa.VerifyingKey.from_string( + string[1:], curve=ecdsa.SECP256k1).pubkey.point def __repr__(self): """ Gives the hex representation of the Graphene public key. """ @@ -265,7 +267,8 @@ def __str__(self): return format(self._pk, self.prefix) def __format__(self, _format): - """ Formats the instance of:doc:`Base58 ` according to ``_format`` """ + """ Formats the instance of:doc:`Base58 ` according + to ``_format`` """ return format(self._pk, _format) def __bytes__(self): @@ -308,19 +311,25 @@ def __init__(self, wif=None, prefix="STM"): # compress pubkeys only self._pubkeyhex, self._pubkeyuncompressedhex = self.compressedpubkey() self.pubkey = PublicKey(self._pubkeyhex, prefix=prefix) - self.uncompressed = PublicKey(self._pubkeyuncompressedhex, prefix=prefix) - self.uncompressed.address = Address(pubkey=self._pubkeyuncompressedhex, prefix=prefix) + self.uncompressed = PublicKey( + self._pubkeyuncompressedhex, prefix=prefix) + self.uncompressed.address = Address( + pubkey=self._pubkeyuncompressedhex, prefix=prefix) self.address = Address(pubkey=self._pubkeyhex, prefix=prefix) def compressedpubkey(self): """ Derive uncompressed public key """ secret = unhexlify(repr(self._wif)) - order = ecdsa.SigningKey.from_string(secret, curve=ecdsa.SECP256k1).curve.generator.order() - p = ecdsa.SigningKey.from_string(secret, curve=ecdsa.SECP256k1).verifying_key.pubkey.point + order = ecdsa.SigningKey.from_string( + secret, curve=ecdsa.SECP256k1).curve.generator.order() + p = ecdsa.SigningKey.from_string( + secret, curve=ecdsa.SECP256k1).verifying_key.pubkey.point x_str = ecdsa.util.number_to_string(p.x(), order) y_str = ecdsa.util.number_to_string(p.y(), order) - compressed = hexlify(bytes(chr(2 + (p.y() & 1)), 'ascii') + x_str).decode('ascii') - uncompressed = hexlify(bytes(chr(4), 'ascii') + x_str + y_str).decode('ascii') + compressed = hexlify( + bytes(chr(2 + (p.y() & 1)), 'ascii') + x_str).decode('ascii') + uncompressed = hexlify(bytes(chr(4), 'ascii') + x_str + y_str).decode( + 'ascii') return [compressed, uncompressed] def __format__(self, _format): diff --git a/steembase/base58.py b/steembase/base58.py index e1e8772..99149eb 100644 --- a/steembase/base58.py +++ b/steembase/base58.py @@ -4,11 +4,9 @@ import string import logging log = logging.getLogger(__name__) - """ This class and the methods require python3 """ # FIXME this library needs to support both 2 and 3 assert sys.version_info[0] == 3, "graphenelib requires python3" - """ Default Prefix """ PREFIX = "STM" @@ -22,26 +20,37 @@ class Base58(object): """Base58 base class This class serves as an abstraction layer to deal with base58 encoded - strings and their corresponding hex and binary representation throughout the - library. + strings and their corresponding hex and binary representation + throughout the library. + + :param data: Data to initialize object, e.g. pubkey data, address data, + ... - :param data: Data to initialize object, e.g. pubkey data, address data, ... :type data: hex, wif, bip38 encrypted wif, base58 string - :param str prefix: Prefix to use for Address/PubKey strings (defaults to ``GPH``) + + :param str prefix: Prefix to use for Address/PubKey strings (defaults + to ``GPH``) + :return: Base58 object initialized with ``data`` + :rtype: Base58 + :raises ValueError: if data cannot be decoded * ``bytes(Base58)``: Returns the raw data * ``str(Base58)``: Returns the readable ``Base58CheckEncoded`` data. * ``repr(Base58)``: Gives the hex representation of the data. - * ``format(Base58,_format)`` Formats the instance according to ``_format``: + + * ``format(Base58,_format)`` Formats the instance according to + ``_format``: + * ``"btc"``: prefixed with ``0x80``. Yields a valid btc address * ``"wif"``: prefixed with ``0x00``. Yields a valid wif key * ``"bts"``: prefixed with ``BTS`` * etc. """ + def __init__(self, data, prefix=PREFIX): self._prefix = prefix if all(c in string.hexdigits for c in data): @@ -170,7 +179,7 @@ def base58CheckDecode(s): s = unhexlify(base58decode(s)) dec = hexlify(s[:-4]).decode('ascii') checksum = doublesha256(dec)[:4] - assert(s[-4:] == checksum) + assert (s[-4:] == checksum) return dec[2:] @@ -184,5 +193,5 @@ def gphBase58CheckDecode(s): s = unhexlify(base58decode(s)) dec = hexlify(s[:-4]).decode('ascii') checksum = ripemd160(dec)[:4] - assert(s[-4:] == checksum) + assert (s[-4:] == checksum) return dec diff --git a/steembase/bip38.py b/steembase/bip38.py index 050d826..223fa74 100644 --- a/steembase/bip38.py +++ b/steembase/bip38.py @@ -24,9 +24,7 @@ SCRYPT_MODULE = "pylibscrypt" except ImportError: - raise ImportError( - "Missing dependency: scrypt or pylibscrypt" - ) + raise ImportError("Missing dependency: scrypt or pylibscrypt") log.debug("Using scrypt module: %s" % SCRYPT_MODULE) @@ -66,8 +64,8 @@ def encrypt(privkey, passphrase): encrypted_half1 = _encrypt_xor(privkeyhex[:32], derived_half1[:16], aes) encrypted_half2 = _encrypt_xor(privkeyhex[32:], derived_half1[16:], aes) " flag byte is forced 0xc0 because Graphene only uses compressed keys " - payload = (b'\x01' + b'\x42' + b'\xc0' + - salt + encrypted_half1 + encrypted_half2) + payload = ( + b'\x01' + b'\x42' + b'\xc0' + salt + encrypted_half1 + encrypted_half2) " Checksum " checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4] privatkey = hexlify(payload + checksum).decode('ascii') @@ -81,7 +79,8 @@ def decrypt(encrypted_privkey, passphrase): :param str passphrase: UTF-8 encoded passphrase for decryption :return: BIP0038 non-ec-multiply decrypted key :rtype: Base58 - :raises SaltException: if checksum verification failed (e.g. wrong password) + :raises SaltException: if checksum verification failed (e.g. wrong + password) """ @@ -106,8 +105,8 @@ def decrypt(encrypted_privkey, passphrase): decryptedhalf2 = aes.decrypt(encryptedhalf2) decryptedhalf1 = aes.decrypt(encryptedhalf1) privraw = decryptedhalf1 + decryptedhalf2 - privraw = ('%064x' % (int(hexlify(privraw), 16) ^ - int(hexlify(derivedhalf1), 16))) + privraw = ('%064x' % + (int(hexlify(privraw), 16) ^ int(hexlify(derivedhalf1), 16))) wif = Base58(privraw) """ Verify Salt """ privkey = PrivateKey(format(wif, "wif")) @@ -115,5 +114,6 @@ def decrypt(encrypted_privkey, passphrase): a = bytes(addr, 'ascii') saltverify = hashlib.sha256(hashlib.sha256(a).digest()).digest()[0:4] if saltverify != salt: - raise SaltException('checksum verification failed! Password may be incorrect.') + raise SaltException( + 'checksum verification failed! Password may be incorrect.') return wif diff --git a/steembase/chains.py b/steembase/chains.py index b3d4bd7..4e68b4e 100644 --- a/steembase/chains.py +++ b/steembase/chains.py @@ -9,10 +9,15 @@ "vests_symbol": "VESTS", }, "TEST": { - "chain_id": "9afbce9f2416520733bacb370315d32b6b2c43d6097576df1c1222859d91eecc", - "prefix": "TST", - "steem_symbol": "TESTS", - "sbd_symbol": "TBD", - "vests_symbol": "VESTS", + "chain_id": + "9afbce9f2416520733bacb370315d32b6b2c43d6097576df1c1222859d91eecc", + "prefix": + "TST", + "steem_symbol": + "TESTS", + "sbd_symbol": + "TBD", + "vests_symbol": + "VESTS", }, } diff --git a/steembase/exceptions.py b/steembase/exceptions.py index 806b2f0..15af068 100644 --- a/steembase/exceptions.py +++ b/steembase/exceptions.py @@ -6,11 +6,9 @@ def decodeRPCErrorMsg(e): python Exception class """ found = re.search( - ( - "(10 assert_exception: Assert Exception\n|" - "3030000 tx_missing_posting_auth)" - ".*: (.*)\n" - ), + ("(10 assert_exception: Assert Exception\n|" + "3030000 tx_missing_posting_auth)" + ".*: (.*)\n"), str(e), flags=re.M) if found: diff --git a/steembase/http_client.py b/steembase/http_client.py index 4c86a6d..7887eac 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -29,7 +29,9 @@ class HttpClient(object): .. code-block:: python from steem.http_client import HttpClient - rpc = HttpClient(['https://steemd-node1.com', 'https://steemd-node2.com']) + + rpc = HttpClient(['https://steemd-node1.com', + 'https://steemd-node2.com']) any call available to that port can be issued using the instance via the syntax ``rpc.call('command', *parameters)``. @@ -60,7 +62,7 @@ def __init__(self, nodes, **kwargs): if tcp_keepalive: socket_options = HTTPConnection.default_socket_options + \ - [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), ] + [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), ] else: socket_options = HTTPConnection.default_socket_options @@ -93,7 +95,11 @@ def next_node(self): """ Switch to the next available node. This method will change base URL of our requests. - Use it when the current node goes down to change to a fallback node. """ + + Use it when the current node goes down to change to a fallback + node. + + """ self.set_node(next(self.nodes)) def set_node(self, node_url): @@ -110,23 +116,36 @@ def json_rpc_body(name, *args, api=None, as_json=True, _id=0, kwargs=None): """ Build request body for steemd RPC requests. Args: - name (str): Name of a method we are trying to call. (ie: `get_accounts`) + + name (str): Name of a method we are trying to call. (ie: + `get_accounts`) + args: A list of arguments belonging to the calling method. + api (None, str): If api is provided (ie: `follow_api`), we generate a body that uses `call` method appropriately. - as_json (bool): Should this function return json as dictionary or string. - _id (int): This is an arbitrary number that can be used for request/response tracking in multi-threaded - scenarios. + + as_json (bool): Should this function return json as dictionary + or string. + + _id (int): This is an arbitrary number that can be used for + request/response tracking in multi-threaded scenarios. Returns: - (dict,str): If `as_json` is set to `True`, we get json formatted as a string. + + (dict,str): If `as_json` is set to `True`, we get json + formatted as a string. + Otherwise, a Python dictionary is returned. + """ headers = {"jsonrpc": "2.0", "id": _id} if kwargs is not None: - body_dict = {**headers, "method": "call", "params": [api, name, kwargs]} + body_dict = {**headers, "method": "call", + "params": [api, name, kwargs]} elif api: - body_dict = {**headers, "method": "call", "params": [api, name, args]} + body_dict = {**headers, "method": "call", + "params": [api, name, args]} else: body_dict = {**headers, "method": name, "params": args} if as_json: @@ -134,23 +153,28 @@ def json_rpc_body(name, *args, api=None, as_json=True, _id=0, kwargs=None): else: return body_dict - def call(self, name, *args, api=None, return_with_args=None, _ret_cnt=0, kwargs=None): + def call(self, + name, + *args, + api=None, + return_with_args=None, + _ret_cnt=0, + kwargs=None): """ Call a remote procedure in steemd. Warnings: - This command will auto-retry in case of node failure, as well as handle - node fail-over, unless we are broadcasting a transaction. - In latter case, the exception is **re-raised**. + + This command will auto-retry in case of node failure, as well + as handle node fail-over, unless we are broadcasting a + transaction. In latter case, the exception is **re-raised**. + """ body = HttpClient.json_rpc_body(name, *args, api=api, kwargs=kwargs) response = None try: response = self.request(body=body) - except (MaxRetryError, - ConnectionResetError, - ReadTimeoutError, - RemoteDisconnected, - ProtocolError) as e: + except (MaxRetryError, ConnectionResetError, ReadTimeoutError, + RemoteDisconnected, ProtocolError) as e: # if we broadcasted a transaction, always raise # this is to prevent potential for double spend scenario if api == 'network_broadcast_api': @@ -158,15 +182,19 @@ def call(self, name, *args, api=None, return_with_args=None, _ret_cnt=0, kwargs= # try switching nodes before giving up if _ret_cnt > 2: - time.sleep(_ret_cnt) # we should wait only a short period before trying the next node, but still slowly increase backoff + # we should wait only a short period before trying + # the next node, but still slowly increase backoff + time.sleep(_ret_cnt) if _ret_cnt > 10: raise e self.next_node() logging.debug('Switched node to %s due to exception: %s' % (self.hostname, e.__class__.__name__)) - return self.call(name, *args, - return_with_args=return_with_args, - _ret_cnt=_ret_cnt + 1) + return self.call( + name, + *args, + return_with_args=return_with_args, + _ret_cnt=_ret_cnt + 1) except Exception as e: if self.re_raise: raise e @@ -178,8 +206,8 @@ def call(self, name, *args, api=None, return_with_args=None, _ret_cnt=0, kwargs= args=args, return_with_args=return_with_args) else: - if response.status not in tuple( - [*response.REDIRECT_STATUSES, 200]): + if response.status not in tuple([*response.REDIRECT_STATUSES, + 200]): logger.info('non 200 response:%s', response.status) return self._return( @@ -215,14 +243,17 @@ def _return(self, response=None, args=None, return_with_args=None): else: return result - def call_multi_with_futures(self, name, params, api=None, max_workers=None): + def call_multi_with_futures(self, name, params, api=None, + max_workers=None): with concurrent.futures.ThreadPoolExecutor( max_workers=max_workers) as executor: # Start the load operations and mark each future with its URL def ensure_list(parameter): - return parameter if type(parameter) in (list, tuple, set) else [parameter] + return parameter if type(parameter) in (list, tuple, + set) else [parameter] - futures = (executor.submit(self.call, name, *ensure_list(param), api=api) + futures = (executor.submit( + self.call, name, *ensure_list(param), api=api) for param in params) for future in concurrent.futures.as_completed(futures): yield future.result() diff --git a/steembase/memo.py b/steembase/memo.py index cf28dcc..8ba79d8 100644 --- a/steembase/memo.py +++ b/steembase/memo.py @@ -144,7 +144,7 @@ def decode_memo(priv, message): message = aes.decrypt(unhexlify(bytes(message, 'ascii'))) try: return _unpad(message.decode('utf8'), 16) - except: + except: # noqa FIXME(sneak) raise ValueError(message) diff --git a/steembase/operations.py b/steembase/operations.py index ab33598..7461023 100644 --- a/steembase/operations.py +++ b/steembase/operations.py @@ -6,10 +6,9 @@ from .account import PublicKey from .operationids import operations -from .types import ( - Int16, Uint16, Uint32, Uint64, - String, Bytes, Array, PointInTime, Bool, - Optional, Map, Id, JsonObj, StaticVariant) +from .types import (Int16, Uint16, Uint32, Uint64, String, Bytes, Array, + PointInTime, Bool, Optional, Map, Id, JsonObj, + StaticVariant) default_prefix = "STM" @@ -32,12 +31,14 @@ def __init__(self, op): if self.opId is None: raise ValueError("Unknown operation") - # convert method name like feed_publish to class name like FeedPublish + # convert method name like feed_publish to class + # name like FeedPublish self.name = self.to_class_name(name) try: klass = self.get_class(self.name) - except: - raise NotImplementedError("Unimplemented Operation %s" % self.name) + except: # noqa FIXME(sneak) + raise NotImplementedError( + "Unimplemented Operation %s" % self.name) else: self.op = klass(op[1]) else: @@ -56,12 +57,14 @@ def get_operation_name_for_id(_id): @staticmethod def to_class_name(method_name): - """ Take a name of a method, like feed_publish and turn it into class name like FeedPublish. """ + """ Take a name of a method, like feed_publish and turn it into + class name like FeedPublish. """ return ''.join(map(str.title, method_name.split('_'))) @staticmethod def to_method_name(class_name): - """ Take a name of a class, like FeedPublish and turn it into method name like feed_publish. """ + """ Take a name of a class, like FeedPublish and turn it into + method name like feed_publish. """ words = re.findall('[A-Z][^A-Z]*', class_name) return '_'.join(map(str.lower, words)) @@ -75,10 +78,9 @@ def __bytes__(self): return bytes(Id(self.opId)) + bytes(self.op) def __str__(self): - return json.dumps([ - self.get_operation_name_for_id(self.opId), - self.op.json() - ]) + return json.dumps( + [self.get_operation_name_for_id(self.opId), + self.op.json()]) class GrapheneObject(object): @@ -153,19 +155,17 @@ def __init__(self, *args, **kwargs): reverse=False, ) - accountAuths = Map([ - [String(e[0]), Uint16(e[1])] - for e in kwargs["account_auths"] - ]) - keyAuths = Map([ - [PublicKey(e[0], prefix=prefix), Uint16(e[1])] - for e in kwargs["key_auths"] - ]) - super().__init__(OrderedDict([ - ('weight_threshold', Uint32(int(kwargs["weight_threshold"]))), - ('account_auths', accountAuths), - ('key_auths', keyAuths), - ])) + accountAuths = Map([[String(e[0]), Uint16(e[1])] + for e in kwargs["account_auths"]]) + keyAuths = Map([[PublicKey(e[0], prefix=prefix), + Uint16(e[1])] for e in kwargs["key_auths"]]) + super().__init__( + OrderedDict([ + ('weight_threshold', Uint32( + int(kwargs["weight_threshold"]))), + ('account_auths', accountAuths), + ('key_auths', keyAuths), + ])) class Memo(GrapheneObject): @@ -178,13 +178,14 @@ def __init__(self, *args, **kwargs): if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__(OrderedDict([ - ('from', PublicKey(kwargs["from"], prefix=prefix)), - ('to', PublicKey(kwargs["to"], prefix=prefix)), - ('nonce', Uint64(int(kwargs["nonce"]))), - ('check', Uint32(int(kwargs["check"]))), - ('encrypted', Bytes(kwargs["encrypted"])), - ])) + super().__init__( + OrderedDict([ + ('from', PublicKey(kwargs["from"], prefix=prefix)), + ('to', PublicKey(kwargs["to"], prefix=prefix)), + ('nonce', Uint64(int(kwargs["nonce"]))), + ('check', Uint32(int(kwargs["check"]))), + ('encrypted', Bytes(kwargs["encrypted"])), + ])) class Vote(GrapheneObject): @@ -194,12 +195,13 @@ def __init__(self, *args, **kwargs): else: if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__(OrderedDict([ - ('voter', String(kwargs["voter"])), - ('author', String(kwargs["author"])), - ('permlink', String(kwargs["permlink"])), - ('weight', Int16(kwargs["weight"])), - ])) + super().__init__( + OrderedDict([ + ('voter', String(kwargs["voter"])), + ('author', String(kwargs["author"])), + ('permlink', String(kwargs["permlink"])), + ('weight', Int16(kwargs["weight"])), + ])) class Comment(GrapheneObject): @@ -211,21 +213,22 @@ def __init__(self, *args, **kwargs): kwargs = args[0] meta = "" if "json_metadata" in kwargs and kwargs["json_metadata"]: - if (isinstance(kwargs["json_metadata"], dict) or - isinstance(kwargs["json_metadata"], list)): + if (isinstance(kwargs["json_metadata"], dict) + or isinstance(kwargs["json_metadata"], list)): meta = json.dumps(kwargs["json_metadata"]) else: meta = kwargs["json_metadata"] - super().__init__(OrderedDict([ - ('parent_author', String(kwargs["parent_author"])), - ('parent_permlink', String(kwargs["parent_permlink"])), - ('author', String(kwargs["author"])), - ('permlink', String(kwargs["permlink"])), - ('title', String(kwargs["title"])), - ('body', String(kwargs["body"])), - ('json_metadata', String(meta)), - ])) + super().__init__( + OrderedDict([ + ('parent_author', String(kwargs["parent_author"])), + ('parent_permlink', String(kwargs["parent_permlink"])), + ('author', String(kwargs["author"])), + ('permlink', String(kwargs["permlink"])), + ('title', String(kwargs["title"])), + ('body', String(kwargs["body"])), + ('json_metadata', String(meta)), + ])) class Amount: @@ -241,19 +244,12 @@ def __init__(self, d): def __bytes__(self): # padding asset = self.asset + "\x00" * (7 - len(self.asset)) - amount = round(float(self.amount) * 10 ** self.precision) - return ( - struct.pack(" 32: raise Exception("'id' too long") - super().__init__(OrderedDict([ - ('required_auths', - Array([String(o) for o in kwargs["required_auths"]])), - ('required_posting_auths', - Array([String(o) for o in kwargs["required_posting_auths"]])), - ('id', String(kwargs["id"])), - ('json', String(js)), - ])) + super().__init__( + OrderedDict([ + ('required_auths', + Array([String(o) for o in kwargs["required_auths"]])), + ('required_posting_auths', + Array([ + String(o) for o in kwargs["required_posting_auths"] + ])), + ('id', String(kwargs["id"])), + ('json', String(js)), + ])) class CommentOptions(GrapheneObject): @@ -698,15 +731,19 @@ def __init__(self, *args, **kwargs): ext = CommentOptionExtensions(ext_obj) extensions = Array([ext]) - super().__init__(OrderedDict([ - ('author', String(kwargs["author"])), - ('permlink', String(kwargs["permlink"])), - ('max_accepted_payout', Amount(kwargs["max_accepted_payout"])), - ('percent_steem_dollars', Uint16(int(kwargs["percent_steem_dollars"]))), - ('allow_votes', Bool(bool(kwargs["allow_votes"]))), - ('allow_curation_rewards', Bool(bool(kwargs["allow_curation_rewards"]))), - ('extensions', extensions), - ])) + super().__init__( + OrderedDict([ + ('author', String(kwargs["author"])), + ('permlink', String(kwargs["permlink"])), + ('max_accepted_payout', + Amount(kwargs["max_accepted_payout"])), + ('percent_steem_dollars', + Uint16(int(kwargs["percent_steem_dollars"]))), + ('allow_votes', Bool(bool(kwargs["allow_votes"]))), + ('allow_curation_rewards', + Bool(bool(kwargs["allow_curation_rewards"]))), + ('extensions', extensions), + ])) def isArgsThisClass(self, args): diff --git a/steembase/storage.py b/steembase/storage.py index d167e46..252f460 100644 --- a/steembase/storage.py +++ b/steembase/storage.py @@ -22,19 +22,6 @@ class DataDir(object): """ This class ensures that the user's data is stored in its OS preotected user directory: - **OSX:** - - * `~/Library/Application Support/` - - **Windows:** - - * `C:\Documents and Settings\\Application Data\Local Settings\\` - * `C:\Documents and Settings\\Application Data\\` - - **Linux:** - - * `~/.local/share/` - Furthermore, it offers an interface to generated backups in the `backups/` directory every now and then. """ @@ -69,10 +56,9 @@ def sqlite3_backup(self, dbfile, backupdir): """ if not os.path.isdir(backupdir): os.mkdir(backupdir) - backup_file = os.path.join( - backupdir, - os.path.basename(self.storageDatabase) + - datetime.now().strftime("-" + timeformat)) + backup_file = os.path.join(backupdir, + os.path.basename(self.storageDatabase) + + datetime.now().strftime("-" + timeformat)) connection = sqlite3.connect(self.sqlDataBaseFile) cursor = connection.cursor() # Lock database before making a backup @@ -117,8 +103,7 @@ def exists_table(self): """ Check if the database table exists """ query = ("SELECT name FROM sqlite_master " + - "WHERE type='table' AND name=?", - (self.__tablename__,)) + "WHERE type='table' AND name=?", (self.__tablename__, )) connection = sqlite3.connect(self.sqlDataBaseFile) cursor = connection.cursor() cursor.execute(*query) @@ -128,10 +113,8 @@ def create_table(self): """ Create the new table in the SQLite database """ query = ('CREATE TABLE %s (' % self.__tablename__ + - 'id INTEGER PRIMARY KEY AUTOINCREMENT,' + - 'pub STRING(256),' + - 'wif STRING(256)' + - ')') + 'id INTEGER PRIMARY KEY AUTOINCREMENT,' + 'pub STRING(256),' + + 'wif STRING(256)' + ')') connection = sqlite3.connect(self.sqlDataBaseFile) cursor = connection.cursor() cursor.execute(query) @@ -155,9 +138,8 @@ def getPrivateKeyForPublicKey(self, pub): The encryption scheme is BIP38 """ - query = ("SELECT wif from %s " % (self.__tablename__) + - "WHERE pub=?", - (pub,)) + query = ("SELECT wif from %s " % (self.__tablename__) + "WHERE pub=?", + (pub, )) connection = sqlite3.connect(self.sqlDataBaseFile) cursor = connection.cursor() cursor.execute(*query) @@ -173,8 +155,7 @@ def updateWif(self, pub, wif): :param str pub: Public key :param str wif: Private key """ - query = ("UPDATE %s " % self.__tablename__ + - "SET wif=? WHERE pub=?", + query = ("UPDATE %s " % self.__tablename__ + "SET wif=? WHERE pub=?", (wif, pub)) connection = sqlite3.connect(self.sqlDataBaseFile) cursor = connection.cursor() @@ -191,8 +172,7 @@ def add(self, wif, pub): if self.getPrivateKeyForPublicKey(pub): raise ValueError("Key already in storage") query = ('INSERT INTO %s (pub, wif) ' % self.__tablename__ + - 'VALUES (?, ?)', - (pub, wif)) + 'VALUES (?, ?)', (pub, wif)) connection = sqlite3.connect(self.sqlDataBaseFile) cursor = connection.cursor() cursor.execute(*query) @@ -203,9 +183,8 @@ def delete(self, pub): :param str pub: Public key """ - query = ("DELETE FROM %s " % (self.__tablename__) + - "WHERE pub=?", - (pub,)) + query = ("DELETE FROM %s " % (self.__tablename__) + "WHERE pub=?", + (pub, )) connection = sqlite3.connect(self.sqlDataBaseFile) cursor = connection.cursor() cursor.execute(*query) @@ -236,8 +215,7 @@ def exists_table(self): """ Check if the database table exists """ query = ("SELECT name FROM sqlite_master " + - "WHERE type='table' AND name=?", - (self.__tablename__,)) + "WHERE type='table' AND name=?", (self.__tablename__, )) connection = sqlite3.connect(self.sqlDataBaseFile) cursor = connection.cursor() cursor.execute(*query) @@ -247,10 +225,8 @@ def create_table(self): """ Create the new table in the SQLite database """ query = ('CREATE TABLE %s (' % self.__tablename__ + - 'id INTEGER PRIMARY KEY AUTOINCREMENT,' + - 'key STRING(256),' + - 'value STRING(256)' + - ')') + 'id INTEGER PRIMARY KEY AUTOINCREMENT,' + 'key STRING(256),' + + 'value STRING(256)' + ')') connection = sqlite3.connect(self.sqlDataBaseFile) cursor = connection.cursor() cursor.execute(query) @@ -259,28 +235,23 @@ def create_table(self): def checkBackup(self): """ Backup the SQL database every 7 days """ - if ("lastBackup" not in configStorage or - configStorage["lastBackup"] == ""): + if ("lastBackup" not in configStorage + or configStorage["lastBackup"] == ""): print("No backup has been created yet!") self.refreshBackup() try: - if ( - datetime.now() - - datetime.strptime(configStorage["lastBackup"], - timeformat) - ).days > 7: + if (datetime.now() - datetime.strptime(configStorage["lastBackup"], + timeformat)).days > 7: print("Backups older than 7 days!") self.refreshBackup() - except: + except: # noqa FIXME(sneak) self.refreshBackup() def _haveKey(self, key): """ Is the key `key` available int he configuration? """ - query = ("SELECT value FROM %s " % (self.__tablename__) + - "WHERE key=?", - (key,) - ) + query = ("SELECT value FROM %s " % + (self.__tablename__) + "WHERE key=?", (key, )) connection = sqlite3.connect(self.sqlDataBaseFile) cursor = connection.cursor() cursor.execute(*query) @@ -290,10 +261,8 @@ def __getitem__(self, key): """ This method behaves differently from regular `dict` in that it returns `None` if a key is not found! """ - query = ("SELECT value FROM %s " % (self.__tablename__) + - "WHERE key=?", - (key,) - ) + query = ("SELECT value FROM %s " % + (self.__tablename__) + "WHERE key=?", (key, )) connection = sqlite3.connect(self.sqlDataBaseFile) cursor = connection.cursor() cursor.execute(*query) @@ -322,13 +291,12 @@ def __contains__(self, key): def __setitem__(self, key, value): if self._haveKey(key): - query = ("UPDATE %s " % self.__tablename__ + - "SET value=? WHERE key=?", - (value, key)) + query = ( + "UPDATE %s " % self.__tablename__ + "SET value=? WHERE key=?", + (value, key)) else: query = ("INSERT INTO %s " % self.__tablename__ + - "(key, value) VALUES (?, ?)", - (key, value)) + "(key, value) VALUES (?, ?)", (key, value)) connection = sqlite3.connect(self.sqlDataBaseFile) cursor = connection.cursor() cursor.execute(*query) @@ -337,9 +305,8 @@ def __setitem__(self, key, value): def delete(self, key): """ Delete a key from the configuration store """ - query = ("DELETE FROM %s " % (self.__tablename__) + - "WHERE key=?", - (key,)) + query = ("DELETE FROM %s " % (self.__tablename__) + "WHERE key=?", + (key, )) connection = sqlite3.connect(self.sqlDataBaseFile) cursor = connection.cursor() cursor.execute(*query) @@ -376,7 +343,8 @@ class MasterPassword(object): password = "" decrypted_master = "" - #: This key identifies the encrypted master password stored in the confiration + #: This key identifies the encrypted master password + # stored in the confiration config_key = "encrypted_master_password" def __init__(self, password): @@ -407,7 +375,7 @@ def decryptEncryptedMaster(self): checksum, encrypted_master = configStorage[self.config_key].split("$") try: decrypted_master = aes.decrypt(encrypted_master) - except: + except: # noqa FIXME(sneak) raise WrongMasterPasswordException if checksum != self.deriveChecksum(decrypted_master): raise WrongMasterPasswordException @@ -423,8 +391,8 @@ def newMaster(self): """ Generate a new random masterpassword """ # make sure to not overwrite an existing key - if (self.config_key in configStorage and - configStorage[self.config_key]): + if (self.config_key in configStorage + and configStorage[self.config_key]): return self.decrypted_master = hexlify(os.urandom(32)).decode("ascii") @@ -440,8 +408,9 @@ def getEncryptedMaster(self): if not self.decrypted_master: raise Exception("master not decrypted") aes = AESCipher(self.password) - return "{}${}".format(self.deriveChecksum(self.decrypted_master), - aes.encrypt(self.decrypted_master)) + return "{}${}".format( + self.deriveChecksum(self.decrypted_master), + aes.encrypt(self.decrypted_master)) def changePassword(self, newpassword): """ Change the password diff --git a/steembase/transactions.py b/steembase/transactions.py index ffdaf07..7070538 100644 --- a/steembase/transactions.py +++ b/steembase/transactions.py @@ -27,7 +27,7 @@ USE_SECP256K1 = True log.debug("Loaded secp256k1 binding.") -except: +except: # noqa FIXME(sneak) USE_SECP256K1 = False log.debug("To speed up transactions signing install \n" " pip install secp256k1") @@ -38,7 +38,8 @@ class SignedTransaction(GrapheneObject): signature :param num refNum: parameter ref_block_num (see ``getBlockParams``) - :param num refPrefix: parameter ref_block_prefix (see ``getBlockParams``) + :param num refPrefix: parameter ref_block_prefix (see + ``getBlockParams``) :param str expiration: expiration date :param Array operations: array of operations """ @@ -56,22 +57,28 @@ def __init__(self, *args, **kwargs): if "signatures" not in kwargs: kwargs["signatures"] = Array([]) else: - kwargs["signatures"] = Array([Signature(unhexlify(a)) for a in kwargs["signatures"]]) + kwargs["signatures"] = Array( + [Signature(unhexlify(a)) for a in kwargs["signatures"]]) if "operations" in kwargs: - if all([not isinstance(a, Operation) for a in kwargs["operations"]]): - kwargs['operations'] = Array([Operation(a) for a in kwargs["operations"]]) + if all([ + not isinstance(a, Operation) + for a in kwargs["operations"] + ]): + kwargs['operations'] = Array( + [Operation(a) for a in kwargs["operations"]]) else: kwargs['operations'] = Array(kwargs["operations"]) - super().__init__(OrderedDict([ - ('ref_block_num', Uint16(kwargs['ref_block_num'])), - ('ref_block_prefix', Uint32(kwargs['ref_block_prefix'])), - ('expiration', PointInTime(kwargs['expiration'])), - ('operations', kwargs['operations']), - ('extensions', kwargs['extensions']), - ('signatures', kwargs['signatures']), - ])) + super().__init__( + OrderedDict([ + ('ref_block_num', Uint16(kwargs['ref_block_num'])), + ('ref_block_prefix', Uint32(kwargs['ref_block_prefix'])), + ('expiration', PointInTime(kwargs['expiration'])), + ('operations', kwargs['operations']), + ('extensions', kwargs['extensions']), + ('signatures', kwargs['signatures']), + ])) def recoverPubkeyParameter(self, digest, signature, pubkey): """ Use to derive a number that allows to easily recover the @@ -80,13 +87,14 @@ def recoverPubkeyParameter(self, digest, signature, pubkey): for i in range(0, 4): if USE_SECP256K1: sig = pubkey.ecdsa_recoverable_deserialize(signature, i) - p = secp256k1.PublicKey(pubkey.ecdsa_recover(self.message, sig)) + p = secp256k1.PublicKey( + pubkey.ecdsa_recover(self.message, sig)) if p.serialize() == pubkey.serialize(): return i else: p = self.recover_public_key(digest, signature, i) - if (p.to_string() == pubkey.to_string() or - self.compressedPubkey(p) == pubkey.to_string()): + if (p.to_string() == pubkey.to_string() + or self.compressedPubkey(p) == pubkey.to_string()): return i return None @@ -107,10 +115,12 @@ def compressedPubkey(self, pk): x_str = ecdsa.util.number_to_string(p.x(), order) return bytes(chr(2 + (p.y() & 1)), 'ascii') + x_str + # FIXME(sneak) this should be reviewed for correctness def recover_public_key(self, digest, signature, i): """ Recover the public key from the the signature """ - # See http: //www.secg.org/download/aid-780/sec1-v2.pdf section 4.1.6 primarily + # See http: //www.secg.org/download/aid-780/sec1-v2.pdf + # section 4.1.6 primarily curve = ecdsa.SECP256k1.curve G = ecdsa.SECP256k1.generator order = ecdsa.SECP256k1.order @@ -118,8 +128,12 @@ def recover_public_key(self, digest, signature, i): r, s = ecdsa.util.sigdecode_string(signature, order) # 1.1 x = r + (i // 2) * order - # 1.3. This actually calculates for either effectively 02||X or 03||X depending on 'k' instead of always for 02||X as specified. - # This substitutes for the lack of reversing R later on. -R actually is defined to be just flipping the y-coordinate in the elliptic curve. + # 1.3. This actually calculates for either effectively + # 02||X or 03||X depending on 'k' instead of always + # for 02||X as specified. + # This substitutes for the lack of reversing R later on. + # -R actually is defined to be just flipping the y-coordinate + # in the elliptic curve. alpha = ((x * x * x) + (curve.a() * x) + curve.b()) % curve.p() beta = ecdsa.numbertheory.square_root_mod_prime(alpha, curve.p()) y = beta if (beta - yp) % 2 == 0 else curve.p() - beta @@ -128,10 +142,13 @@ def recover_public_key(self, digest, signature, i): # 1.5 Compute e e = ecdsa.util.string_to_number(digest) # 1.6 Compute Q = r^-1(sR - eG) - Q = ecdsa.numbertheory.inverse_mod(r, order) * (s * R + (-e % order) * G) - # Not strictly necessary, but let's verify the message for paranoia's sake. - if not ecdsa.VerifyingKey.from_public_point(Q, curve=ecdsa.SECP256k1).verify_digest(signature, digest, - sigdecode=ecdsa.util.sigdecode_string): + Q = ecdsa.numbertheory.inverse_mod(r, order) * (s * R + + (-e % order) * G) + # Not strictly necessary, but let's verify the message for + # paranoia's sake. + if not ecdsa.VerifyingKey.from_public_point( + Q, curve=ecdsa.SECP256k1).verify_digest( + signature, digest, sigdecode=ecdsa.util.sigdecode_string): return None return ecdsa.VerifyingKey.from_public_point(Q, curve=ecdsa.SECP256k1) @@ -179,30 +196,31 @@ def verify(self, pubkeys=[], chain=None): for signature in signatures: sig = bytes(signature)[1:] - recoverParameter = (bytes(signature)[0]) - 4 - 27 # recover parameter only + recoverParameter = ( + bytes(signature)[0]) - 4 - 27 # recover parameter only if USE_SECP256K1: - ALL_FLAGS = secp256k1.lib.SECP256K1_CONTEXT_VERIFY | secp256k1.lib.SECP256K1_CONTEXT_SIGN + ALL_FLAGS = secp256k1.lib.SECP256K1_CONTEXT_VERIFY | \ + secp256k1.lib.SECP256K1_CONTEXT_SIGN # Placeholder pub = secp256k1.PublicKey(flags=ALL_FLAGS) # Recover raw signature sig = pub.ecdsa_recoverable_deserialize(sig, recoverParameter) # Recover PublicKey - verifyPub = secp256k1.PublicKey(pub.ecdsa_recover(bytes(self.message), sig)) + verifyPub = secp256k1.PublicKey( + pub.ecdsa_recover(bytes(self.message), sig)) # Convert recoverable sig to normal sig normalSig = verifyPub.ecdsa_recoverable_convert(sig) # Verify verifyPub.ecdsa_verify(bytes(self.message), normalSig) - phex = hexlify(verifyPub.serialize(compressed=True)).decode('ascii') + phex = hexlify( + verifyPub.serialize(compressed=True)).decode('ascii') pubKeysFound.append(phex) else: p = self.recover_public_key(self.digest, sig, recoverParameter) # Will throw an exception of not valid p.verify_digest( - sig, - self.digest, - sigdecode=ecdsa.util.sigdecode_string - ) + sig, self.digest, sigdecode=ecdsa.util.sigdecode_string) phex = hexlify(self.compressedPubkey(p)).decode('ascii') pubKeysFound.append(phex) @@ -218,11 +236,12 @@ def verify(self, pubkeys=[], chain=None): return pubKeysFound def _is_canonical(self, sig): - return (not (sig[0] & 0x80) and - not (sig[0] == 0 and not (sig[1] & 0x80)) and - not (sig[32] & 0x80) and - not (sig[32] == 0 and not (sig[33] & 0x80))) + return (not (sig[0] & 0x80) + and not (sig[0] == 0 and not (sig[1] & 0x80)) + and not (sig[32] & 0x80) + and not (sig[32] == 0 and not (sig[33] & 0x80))) + # FIXME(sneak) audit this function def sign(self, wifkeys, chain=None): """ Sign the transaction with the provided private keys. @@ -236,7 +255,10 @@ def sign(self, wifkeys, chain=None): # Get Unique private keys self.privkeys = [] - [self.privkeys.append(item) for item in wifkeys if item not in self.privkeys] + [ + self.privkeys.append(item) for item in wifkeys + if item not in self.privkeys + ] # Sign the message with every private key given! sigs = [] @@ -249,15 +271,11 @@ def sign(self, wifkeys, chain=None): while True: ndata[0] += 1 privkey = secp256k1.PrivateKey(p, raw=True) - sig = secp256k1.ffi.new('secp256k1_ecdsa_recoverable_signature *') + sig = secp256k1.ffi.new( + 'secp256k1_ecdsa_recoverable_signature *') signed = secp256k1.lib.secp256k1_ecdsa_sign_recoverable( - privkey.ctx, - sig, - self.digest, - privkey.private_key, - secp256k1.ffi.NULL, - ndata - ) + privkey.ctx, sig, self.digest, privkey.private_key, + secp256k1.ffi.NULL, ndata) assert signed == 1 signature, i = privkey.ecdsa_recoverable_serialize(sig) if self._is_canonical(signature): @@ -270,30 +288,30 @@ def sign(self, wifkeys, chain=None): while 1: cnt += 1 if not cnt % 20: - log.info("Still searching for a canonical signature. Tried %d times already!" % cnt) + log.info("Still searching for a canonical signature. " + "Tried %d times already!" % cnt) # Deterministic k - # k = ecdsa.rfc6979.generate_k( sk.curve.generator.order(), sk.privkey.secret_multiplier, hashlib.sha256, hashlib.sha256( - self.digest + - struct.pack("d", time.time()) # use the local time to randomize the signature + self.digest + struct.pack("d", time.time( + )) # use the local time to randomize the signature ).digest()) # Sign message # sigder = sk.sign_digest( - self.digest, - sigencode=ecdsa.util.sigencode_der, - k=k) + self.digest, sigencode=ecdsa.util.sigencode_der, k=k) # Reformating of signature # - r, s = ecdsa.util.sigdecode_der(sigder, sk.curve.generator.order()) - signature = ecdsa.util.sigencode_string(r, s, sk.curve.generator.order()) + r, s = ecdsa.util.sigdecode_der(sigder, + sk.curve.generator.order()) + signature = ecdsa.util.sigencode_string( + r, s, sk.curve.generator.order()) # Make sure signature is canonical! # @@ -302,7 +320,8 @@ def sign(self, wifkeys, chain=None): if lenR is 32 and lenS is 32: # Derive the recovery parameter # - i = self.recoverPubkeyParameter(self.digest, signature, sk.get_verifying_key()) + i = self.recoverPubkeyParameter( + self.digest, signature, sk.get_verifying_key()) i += 4 # compressed i += 27 # compact break @@ -329,7 +348,8 @@ def get_block_params(steem): props = steem.get_dynamic_global_properties() ref_block_num = props["head_block_number"] - 3 & 0xFFFF ref_block = steem.get_block(props["head_block_number"] - 2) - ref_block_prefix = struct.unpack_from(" Date: Sat, 27 Jan 2018 07:10:07 -0600 Subject: [PATCH 027/166] moved console scripts to module, no more bin/ --- Makefile | 7 +++---- bin/piston | 7 ------- bin/steempy | 7 ------- bin/steemtail | 49 ------------------------------------------------- setup.py | 6 +++++- steem/cli.py | 44 +++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 49 insertions(+), 71 deletions(-) delete mode 100755 bin/piston delete mode 100755 bin/steempy delete mode 100755 bin/steemtail diff --git a/Makefile b/Makefile index 656b1f7..5fda0df 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PROJECT := $(shell basename $(shell pwd)) -MODULES := steem steembase tests +PYTHON_FILES := steem steembase tests setup.py default: docker_test @@ -20,9 +20,8 @@ test: clean pipenv run py.test fmt: - pipenv run yapf --recursive --in-place --style pep8 $(MODULES) - #pipenv run autopep8 --recursive --in-place $(MODULES) - pipenv run pycodestyle $(MODULES) + pipenv run yapf --recursive --in-place --style pep8 $(PYTHON_FILES) + pipenv run pycodestyle $(PYTHON_FILES) init: pip3 install --upgrade pip diff --git a/bin/piston b/bin/piston deleted file mode 100755 index eaeb58f..0000000 --- a/bin/piston +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env python3.6 - -import steem -import steem.cli - -if __name__ == '__main__': - steem.cli.legacy() diff --git a/bin/steempy b/bin/steempy deleted file mode 100755 index eaeb58f..0000000 --- a/bin/steempy +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env python3.6 - -import steem -import steem.cli - -if __name__ == '__main__': - steem.cli.legacy() diff --git a/bin/steemtail b/bin/steemtail deleted file mode 100755 index fcd8192..0000000 --- a/bin/steemtail +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3.6 - -import steem -import sys -import argparse -import pprint -import json -from steem.blockchain import Blockchain - - -def main(): - parser = argparse.ArgumentParser( - description="UNIX tail(1)-like tool for the steem blockchain") - parser.add_argument( - '-f', - '--follow', - help='Constantly stream output to stdout', - action='store_true') - parser.add_argument( - '-n', '--lines', type=int, default=10, help='How many ops to show') - parser.add_argument( - '-j', - '--json', - help='Output as JSON instead of human-readable pretty-printed format', - action='store_true') - args = parser.parse_args(sys.argv[1:]) - - b = Blockchain() - stream = b.reliable_stream() - - op_count = 0 - if args.json: - if not args.follow: sys.stdout.write('[') - for op in stream: - if args.json: - sys.stdout.write('%s' % json.dumps(op)) - else: - pprint.pprint(op) - op_count += 1 - if not args.follow: - if op_count > args.lines: - if args.json: sys.stdout.write(']') - return - else: - if args.json: sys.stdout.write(',') - - -if __name__ == '__main__': - main() diff --git a/setup.py b/setup.py index bac44da..971ca14 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,11 @@ tests_require=test_requirements, install_requires=requirements, entry_points={ - 'console_scripts': ['steempy=steem.cli:legacy'], + 'console_scripts': [ + 'piston=steem.cli:legacyentry', + 'steempy=steem.cli:legacyentry', + 'steemtail=steem.cli:steemtailentry', + ], }, classifiers=[ 'Intended Audience :: Developers', diff --git a/steem/cli.py b/steem/cli.py index 5f15d38..37965b4 100644 --- a/steem/cli.py +++ b/steem/cli.py @@ -28,7 +28,7 @@ ] -def legacy(): +def legacyentry(): """ Piston like cli application. This will be re-written as a @click app in the future. @@ -1415,5 +1415,43 @@ def print_json(tx): print(tx) -if __name__ == '__main__': - pass +# this is another console script entrypoint +# also this function sucks and should be taken out back and shot +def steemtailentry(): + parser = argparse.ArgumentParser( + description="UNIX tail(1)-like tool for the steem blockchain") + parser.add_argument( + '-f', + '--follow', + help='Constantly stream output to stdout', + action='store_true') + parser.add_argument( + '-n', '--lines', type=int, default=10, help='How many ops to show') + parser.add_argument( + '-j', + '--json', + help='Output as JSON instead of human-readable pretty-printed format', + action='store_true') + args = parser.parse_args(sys.argv[1:]) + + b = Blockchain() + stream = b.reliable_stream() + + op_count = 0 + if args.json: + if not args.follow: + sys.stdout.write('[') + for op in stream: + if args.json: + sys.stdout.write('%s' % json.dumps(op)) + else: + pprint.pprint(op) + op_count += 1 + if not args.follow: + if op_count > args.lines: + if args.json: + sys.stdout.write(']') + return + else: + if args.json: + sys.stdout.write(',') From 28578848690abc48101cea41409c7b2c6b8fe04b Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 07:11:09 -0600 Subject: [PATCH 028/166] have circle run tests with docker --- circle.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 circle.yml diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..9129a4a --- /dev/null +++ b/circle.yml @@ -0,0 +1,11 @@ +machine: + services: + - docker + +dependencies: + override: + - echo "Ignore CircleCI detected dependencies" + +test: + override: + - docker build -t steemit/steem-python . From dd1b796cf66bad55cd730d805b00a7303837edb6 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 07:30:25 -0600 Subject: [PATCH 029/166] clean up import formatting --- steem/commit.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/steem/commit.py b/steem/commit.py index 1718faa..44b087d 100644 --- a/steem/commit.py +++ b/steem/commit.py @@ -2,28 +2,27 @@ import logging import random import re -from datetime import datetime, timedelta - import voluptuous as vo +from datetime import datetime, timedelta from funcy.colls import none from funcy.flow import silent from funcy.seqs import first from steembase import memo from steembase import operations from steembase.account import PrivateKey, PublicKey -from steembase.exceptions import ( - AccountExistsException, - MissingKeyError, -) +from steembase.exceptions import AccountExistsException, MissingKeyError from steembase.storage import configStorage - from .account import Account from .amount import Amount from .converter import Converter from .instance import shared_steemd_instance from .transactionbuilder import TransactionBuilder -from .utils import derive_permlink, resolve_identifier, \ - fmt_time_string, keep_in_dict +from .utils import ( + derive_permlink, + fmt_time_string, + keep_in_dict, + resolve_identifier, +) from .wallet import Wallet log = logging.getLogger(__name__) From ace774391c2a36f3a18eab3079de4f17fca1bcd6 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 07:30:25 -0600 Subject: [PATCH 030/166] clean up import formatting --- steem/commit.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/steem/commit.py b/steem/commit.py index 1718faa..44b087d 100644 --- a/steem/commit.py +++ b/steem/commit.py @@ -2,28 +2,27 @@ import logging import random import re -from datetime import datetime, timedelta - import voluptuous as vo +from datetime import datetime, timedelta from funcy.colls import none from funcy.flow import silent from funcy.seqs import first from steembase import memo from steembase import operations from steembase.account import PrivateKey, PublicKey -from steembase.exceptions import ( - AccountExistsException, - MissingKeyError, -) +from steembase.exceptions import AccountExistsException, MissingKeyError from steembase.storage import configStorage - from .account import Account from .amount import Amount from .converter import Converter from .instance import shared_steemd_instance from .transactionbuilder import TransactionBuilder -from .utils import derive_permlink, resolve_identifier, \ - fmt_time_string, keep_in_dict +from .utils import ( + derive_permlink, + fmt_time_string, + keep_in_dict, + resolve_identifier, +) from .wallet import Wallet log = logging.getLogger(__name__) From f13147bbb5f3838d4145d495520aaaf654686e22 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 07:38:41 -0600 Subject: [PATCH 031/166] clean up imports --- steem/cli.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/steem/cli.py b/steem/cli.py index 37965b4..7a28fb1 100644 --- a/steem/cli.py +++ b/steem/cli.py @@ -2,11 +2,12 @@ import json import logging import os +import pkg_resources +import pprint import re +import steem as stm import sys -import pkg_resources -import steem as stm from prettytable import PrettyTable from steembase.storage import configStorage from steembase.account import PrivateKey From b58164af013ff9f97a8335ff62d89f39f40a04df Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 07:53:53 -0600 Subject: [PATCH 032/166] update readme, improve `steemtail` cli script --- README.md | 30 +++++++++++++++++++++++++----- steem/cli.py | 9 ++++++--- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a0fd8b2..c81ebf3 100644 --- a/README.md +++ b/README.md @@ -5,19 +5,39 @@ BIP38 encrypted wallet and a practical CLI utility called `steempy`. Currently only python3 is supported. Python2 support is planned. -## Installation +# Installation -You can install `steem-python` with `pip3`: +## Easy installation with pip3 ``` pip3 install --user steem ``` -## Documentation +You can also use pipenv: + +## Or you can use Pipenv, the new hotness + +``` +git clone https://github.com/steemit/steem-python.git +cd steem-python +pipenv install --three --dev +pipenv install . +``` + +# CLI tools + +The library comes with a few console scripts. + +* `steempy`: + * rudimentary blockchain CLI (needs some TLC) +* `steemtail`: + * useful for e.g. `steemtail -f -j | jq --unbuffered --sort-keys .` + +# Documentation Documentation is available at **http://steem.readthedocs.io** -## TODO +# TODO * fix parts that were copied from python-graphenelib that only support python3 to support python2 as well @@ -25,6 +45,6 @@ Documentation is available at **http://steem.readthedocs.io** * 100% documentation coverage * migrate to click CLI library -## Notice +# Notice This library is *under development*. Beware. diff --git a/steem/cli.py b/steem/cli.py index 7a28fb1..790c1c2 100644 --- a/steem/cli.py +++ b/steem/cli.py @@ -1435,16 +1435,19 @@ def steemtailentry(): action='store_true') args = parser.parse_args(sys.argv[1:]) - b = Blockchain() - stream = b.reliable_stream() + # FIXME(sneak) this always returns + # '0000000000000000000000000000000000000000' for trx_id :( op_count = 0 if args.json: if not args.follow: sys.stdout.write('[') - for op in stream: + for op in Blockchain().reliable_stream(): if args.json: sys.stdout.write('%s' % json.dumps(op)) + if args.follow: + sys.stdout.write("\n") # for human eyeballs + sys.stdout.flush() # flush after each op if live mode else: pprint.pprint(op) op_count += 1 From 5cca5eda2bbf97529524c496df2ae607ac5e9024 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 07:55:48 -0600 Subject: [PATCH 033/166] more README updates --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c81ebf3..d94c6c7 100644 --- a/README.md +++ b/README.md @@ -37,12 +37,23 @@ The library comes with a few console scripts. Documentation is available at **http://steem.readthedocs.io** +# Tests + +Some tests are included. They can be run via either docker or vagrant, +for reproducibility, e.g.: + +* `docker build .` + +or + +* `vagrant up` + # TODO * fix parts that were copied from python-graphenelib that only support python3 to support python2 as well -* more unit-tests -* 100% documentation coverage +* more unit tests +* 100% documentation coverage, consistent documentation * migrate to click CLI library # Notice From 7c915a4caf20feba1f290d19718f27f0bc5d4df3 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 08:11:17 -0600 Subject: [PATCH 034/166] updated readme with installation instructions --- README.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d94c6c7..452c18d 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,13 @@ Currently only python3 is supported. Python2 support is planned. # Installation -## Easy installation with pip3 +## Installation with pipenv (recommended) -``` -pip3 install --user steem -``` +Install pipenv first if you don't have it: -You can also use pipenv: +`pip3 install --upgrade --user pipenv` -## Or you can use Pipenv, the new hotness +Then, install steem-python using it: ``` git clone https://github.com/steemit/steem-python.git @@ -24,12 +22,20 @@ pipenv install --three --dev pipenv install . ``` -# CLI tools +## Installation with pip3 + +``` +git clone https://github.com/steemit/steem-python.git +cd steem-python +pip3 install --user . +``` + +# CLI tools bundled The library comes with a few console scripts. * `steempy`: - * rudimentary blockchain CLI (needs some TLC) + * rudimentary blockchain CLI (needs some TLC and more TLAs) * `steemtail`: * useful for e.g. `steemtail -f -j | jq --unbuffered --sort-keys .` From e2ea225626fe38498ddd5c472fcad979f8128d7b Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sat, 27 Jan 2018 08:27:06 -0600 Subject: [PATCH 035/166] formatting --- steem/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/steem/cli.py b/steem/cli.py index 790c1c2..c43d525 100644 --- a/steem/cli.py +++ b/steem/cli.py @@ -1446,8 +1446,8 @@ def steemtailentry(): if args.json: sys.stdout.write('%s' % json.dumps(op)) if args.follow: - sys.stdout.write("\n") # for human eyeballs - sys.stdout.flush() # flush after each op if live mode + sys.stdout.write("\n") # for human eyeballs + sys.stdout.flush() # flush after each op if live mode else: pprint.pprint(op) op_count += 1 From 2b8353fed9bd7c758365101297ca3c6d31b0b367 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Sun, 28 Jan 2018 07:02:28 -0600 Subject: [PATCH 036/166] add openssl note to install instructions for osx --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 452c18d..463fa13 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,16 @@ cd steem-python pip3 install --user . ``` +## Homebrew Build Prereqs + +If you're on a mac, you may need to do the following first: + +``` +brew install openssl +export CFLAGS="-I$(brew --prefix openssl)/include $CFLAGS" +export LDFLAGS="-L$(brew --prefix openssl)/lib $LDFLAGS" +``` + # CLI tools bundled The library comes with a few console scripts. From fe959ce59916ac38acae0a8f4aeef30a37fa5a36 Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 29 Jan 2018 13:21:22 -0600 Subject: [PATCH 037/166] More python 2 support. --- steem/blockchain.py | 10 +- steem/steemd.py | 187 ++++++++++++++++++------------------ steem/transactionbuilder.py | 2 +- steem/utils.py | 4 +- steem/wallet.py | 2 +- 5 files changed, 102 insertions(+), 103 deletions(-) diff --git a/steem/blockchain.py b/steem/blockchain.py index c21eb9f..eefc460 100644 --- a/steem/blockchain.py +++ b/steem/blockchain.py @@ -215,14 +215,16 @@ def get_reliable_ops_in_block(_client, _block_num): yield get_reliable_ops_in_block(_reliable_client, head_block) else: - yield from get_reliable_ops_in_block( - _reliable_client, head_block) + for reliable_ops in get_reliable_ops_in_block( + _reliable_client, head_block): + yield reliable_ops + sleep_interval = sleep_interval / 2 time.sleep(sleep_interval) start_block = head_block + 1 - def stream(self, filter_by = list(), *args, **kwargs): + def stream(self, filter_by=list(), *args, **kwargs): """ Yield a stream of operations, starting with current head block. Args: @@ -259,7 +261,7 @@ def stream(self, filter_by = list(), *args, **kwargs): }) def history(self, - filter_by = list(), + filter_by=list(), start_block=1, end_block=None, raw_output=False, diff --git a/steem/steemd.py b/steem/steemd.py index f4d7d44..bec8738 100644 --- a/steem/steemd.py +++ b/steem/steemd.py @@ -1,6 +1,5 @@ # coding=utf-8 import logging -from typing import List, Any, Union, Set from funcy.seqs import first from steembase.chains import known_chains @@ -154,7 +153,7 @@ def head_block_number(self): """ Newest block number. """ return self.get_dynamic_global_properties()['head_block_number'] - def get_account(self, account: str): + def get_account(self, account): """ Lookup account information such as user profile, public keys, balances, etc. @@ -177,7 +176,7 @@ def get_all_usernames(self, last_user=''): return usernames - def _get_blocks(self, blocks: Union[List[int], Set[int]]): + def _get_blocks(self, blocks): """ Fetch multiple blocks from steemd at once. Warning: This method does not ensure that all blocks are returned, @@ -193,10 +192,10 @@ def _get_blocks(self, blocks: Union[List[int], Set[int]]): """ results = self.call_multi_with_futures( 'get_block', blocks, max_workers=10) - return ({**x, 'block_num': int(x['block_id'][:8], base=16)} + return (x.update({'block_num'(x['block_id'][:8], base=16)}) for x in results if x) - def get_blocks(self, block_nums: List[int]): + def get_blocks(self, block_nums): """ Fetch multiple blocks from steemd at once, given a range. Args: @@ -223,7 +222,7 @@ def get_blocks(self, block_nums: List[int]): return [blocks[x] for x in block_nums] - def get_blocks_range(self, start: int, end: int): + def get_blocks_range(self, start, end): """ Fetch multiple blocks from steemd at once, given a range. Args: @@ -239,7 +238,7 @@ def get_blocks_range(self, start: int, end: int): """ return self.get_blocks(list(range(start, end))) - def get_reward_fund(self, fund_name: str = 'post'): + def get_reward_fund(self, fund_name='post'): """ Get details for a reward fund. Right now the only pool available is 'post'. @@ -264,8 +263,8 @@ def get_reward_fund(self, fund_name: str = 'post'): """ return self.call('get_reward_fund', fund_name, api='database_api') - def get_expiring_vesting_delegations(self, account: str, - start: PointInTime, limit: int): + def get_expiring_vesting_delegations(self, account, + start, limit): """ get_expiring_vesting_delegations """ return self.call( 'get_expiring_vesting_delegations', @@ -274,106 +273,106 @@ def get_expiring_vesting_delegations(self, account: str, limit, api='database_api') - def get_trending_tags(self, after_tag: str, limit: int): + def get_trending_tags(self, after_tag, limit): """ get_trending_tags """ return self.call( 'get_trending_tags', after_tag, limit, api='database_api') - def get_tags_used_by_author(self, account: str): + def get_tags_used_by_author(self, account): """ get_tags_used_by_author """ return self.call( 'get_tags_used_by_author', account, api='database_api') - def get_discussions_by_trending(self, discussion_query: dict): + def get_discussions_by_trending(self, discussion_query): """ get_discussions_by_trending """ return self.call( 'get_discussions_by_trending', discussion_query, api='database_api') - def get_comment_discussions_by_payout(self, discussion_query: dict): + def get_comment_discussions_by_payout(self, discussion_query): """ get_comment_discussions_by_payout """ return self.call( 'get_comment_discussions_by_payout', discussion_query, api='database_api') - def get_post_discussions_by_payout(self, discussion_query: dict): + def get_post_discussions_by_payout(self, discussion_query): """ get_post_discussions_by_payout """ return self.call( 'get_post_discussions_by_payout', discussion_query, api='database_api') - def get_discussions_by_created(self, discussion_query: dict): + def get_discussions_by_created(self, discussion_query): """ get_discussions_by_created """ return self.call( 'get_discussions_by_created', discussion_query, api='database_api') - def get_discussions_by_active(self, discussion_query: dict): + def get_discussions_by_active(self, discussion_query): """ get_discussions_by_active """ return self.call( 'get_discussions_by_active', discussion_query, api='database_api') - def get_discussions_by_cashout(self, discussion_query: dict): + def get_discussions_by_cashout(self, discussion_query): """ get_discussions_by_cashout """ return self.call( 'get_discussions_by_cashout', discussion_query, api='database_api') - def get_discussions_by_payout(self, discussion_query: dict): + def get_discussions_by_payout(self, discussion_query): """ get_discussions_by_payout """ return self.call( 'get_discussions_by_payout', discussion_query, api='database_api') - def get_discussions_by_votes(self, discussion_query: dict): + def get_discussions_by_votes(self, discussion_query): """ get_discussions_by_votes """ return self.call( 'get_discussions_by_votes', discussion_query, api='database_api') - def get_discussions_by_children(self, discussion_query: dict): + def get_discussions_by_children(self, discussion_query): """ get_discussions_by_children """ return self.call( 'get_discussions_by_children', discussion_query, api='database_api') - def get_discussions_by_hot(self, discussion_query: dict): + def get_discussions_by_hot(self, discussion_query): """ get_discussions_by_hot """ return self.call( 'get_discussions_by_hot', discussion_query, api='database_api') - def get_discussions_by_feed(self, discussion_query: dict): + def get_discussions_by_feed(self, discussion_query): """ get_discussions_by_feed """ return self.call( 'get_discussions_by_feed', discussion_query, api='database_api') - def get_discussions_by_blog(self, discussion_query: dict): + def get_discussions_by_blog(self, discussion_query): """ get_discussions_by_blog """ return self.call( 'get_discussions_by_blog', discussion_query, api='database_api') - def get_discussions_by_comments(self, discussion_query: dict): + def get_discussions_by_comments(self, discussion_query): """ get_discussions_by_comments """ return self.call( 'get_discussions_by_comments', discussion_query, api='database_api') - def get_discussions_by_promoted(self, discussion_query: dict): + def get_discussions_by_promoted(self, discussion_query): """ get_discussions_by_promoted """ return self.call( 'get_discussions_by_promoted', discussion_query, api='database_api') - def get_block_header(self, block_num: int): + def get_block_header(self, block_num): """ Get block headers, given a block number. Args: - block_num (int): Block number. + block_num (int) number. Returns: - dict: Block headers in a JSON compatible format. + dict headers in a JSON compatible format. Example: @@ -393,24 +392,24 @@ def get_block_header(self, block_num: int): """ return self.call('get_block_header', block_num, api='database_api') - def get_block(self, block_num: int): + def get_block(self, block_num): """ Get the full block, transactions and all, given a block number. Args: - block_num (int): Block number. + block_num (int) number. Returns: - dict: Block in a JSON compatible format. + dict in a JSON compatible format. """ return self.call('get_block', block_num, api='database_api') - def get_ops_in_block(self, block_num: int, virtual_only: bool): + def get_ops_in_block(self, block_num, virtual_only): """ get_ops_in_block """ return self.call( 'get_ops_in_block', block_num, virtual_only, api='database_api') - def get_state(self, path: str): + def get_state(self, path): """ get_state """ return self.call('get_state', path, api='database_api') @@ -481,7 +480,7 @@ def get_next_scheduled_hardfork(self): """ get_next_scheduled_hardfork """ return self.call('get_next_scheduled_hardfork', api='database_api') - def get_accounts(self, account_names: list): + def get_accounts(self, account_names): """ Lookup account information such as user profile, public keys, balances, etc. @@ -491,17 +490,17 @@ def get_accounts(self, account_names: list): """ return self.call('get_accounts', account_names, api='database_api') - def get_account_references(self, account_id: int): + def get_account_references(self, account_id): """ get_account_references """ return self.call( 'get_account_references', account_id, api='database_api') - def lookup_account_names(self, account_names: list): + def lookup_account_names(self, account_names): """ lookup_account_names """ return self.call( 'lookup_account_names', account_names, api='database_api') - def lookup_accounts(self, after: Union[str, int], limit: int) -> List[str]: + def lookup_accounts(self, after, limit): """Get a list of usernames from all registered accounts. Args: @@ -521,12 +520,12 @@ def get_account_count(self): """ How many accounts are currently registered on STEEM? """ return self.call('get_account_count', api='database_api') - def get_conversion_requests(self, account: str): + def get_conversion_requests(self, account): """ get_conversion_requests """ return self.call( 'get_conversion_requests', account, api='database_api') - def get_account_history(self, account: str, index_from: int, limit: int): + def get_account_history(self, account, index_from, limit): """ History of all operations for a given account. Args: @@ -620,20 +619,20 @@ def get_account_history(self, account: str, index_from: int, limit: int): limit, api='database_api') - def get_owner_history(self, account: str): + def get_owner_history(self, account): """ get_owner_history """ return self.call('get_owner_history', account, api='database_api') - def get_recovery_request(self, account: str): + def get_recovery_request(self, account): """ get_recovery_request """ return self.call('get_recovery_request', account, api='database_api') - def get_escrow(self, from_account: str, escrow_id: int): + def get_escrow(self, from_account, escrow_id): """ get_escrow """ return self.call( 'get_escrow', from_account, escrow_id, api='database_api') - def get_withdraw_routes(self, account: str, withdraw_route_type: str): + def get_withdraw_routes(self, account, withdraw_route_type): """ get_withdraw_routes """ return self.call( 'get_withdraw_routes', @@ -641,7 +640,7 @@ def get_withdraw_routes(self, account: str, withdraw_route_type: str): withdraw_route_type, api='database_api') - def get_account_bandwidth(self, account: str, bandwidth_type: object): + def get_account_bandwidth(self, account, bandwidth_type): """ get_account_bandwidth """ return self.call( 'get_account_bandwidth', @@ -649,17 +648,17 @@ def get_account_bandwidth(self, account: str, bandwidth_type: object): bandwidth_type, api='database_api') - def get_savings_withdraw_from(self, account: str): + def get_savings_withdraw_from(self, account): """ get_savings_withdraw_from """ return self.call( 'get_savings_withdraw_from', account, api='database_api') - def get_savings_withdraw_to(self, account: str): + def get_savings_withdraw_to(self, account): """ get_savings_withdraw_to """ return self.call( 'get_savings_withdraw_to', account, api='database_api') - def get_order_book(self, limit: int): + def get_order_book(self, limit): """ Get the internal market order book. This method will return both bids and asks. @@ -709,11 +708,11 @@ def get_order_book(self, limit: int): """ return self.call('get_order_book', limit, api='database_api') - def get_open_orders(self, account: str): + def get_open_orders(self, account): """ get_open_orders """ return self.call('get_open_orders', account, api='database_api') - def get_liquidity_queue(self, start_account: str, limit: int): + def get_liquidity_queue(self, start_account, limit): """ Get the liquidity queue. Warning: @@ -725,17 +724,17 @@ def get_liquidity_queue(self, start_account: str, limit: int): return self.call( 'get_liquidity_queue', start_account, limit, api='database_api') - def get_transaction_hex(self, signed_transaction: SignedTransaction): + def get_transaction_hex(self, signed_transaction): """ get_transaction_hex """ return self.call( 'get_transaction_hex', signed_transaction, api='database_api') - def get_transaction(self, transaction_id: str): + def get_transaction(self, transaction_id): """ get_transaction """ return self.call('get_transaction', transaction_id, api='database_api') - def get_required_signatures(self, signed_transaction: SignedTransaction, - available_keys: list): + def get_required_signatures(self, signed_transaction, + available_keys): """ get_required_signatures """ return self.call( 'get_required_signatures', @@ -743,22 +742,22 @@ def get_required_signatures(self, signed_transaction: SignedTransaction, available_keys, api='database_api') - def get_potential_signatures(self, signed_transaction: SignedTransaction): + def get_potential_signatures(self, signed_transaction): """ get_potential_signatures """ return self.call( 'get_potential_signatures', signed_transaction, api='database_api') - def verify_authority(self, signed_transaction: SignedTransaction): + def verify_authority(self, signed_transaction): """ verify_authority """ return self.call( 'verify_authority', signed_transaction, api='database_api') - def verify_account_authority(self, account: str, keys: list): + def verify_account_authority(self, account, keys): """ verify_account_authority """ return self.call( 'verify_account_authority', account, keys, api='database_api') - def get_active_votes(self, author: str, permlink: str): + def get_active_votes(self, author, permlink): """ Get all votes for the given post. Args: @@ -797,7 +796,7 @@ def get_active_votes(self, author: str, permlink: str): return self.call( 'get_active_votes', author, permlink, api='database_api') - def get_account_votes(self, account: str): + def get_account_votes(self, account): """ All votes the given account ever made. Returned votes are in the following format: @@ -819,18 +818,18 @@ def get_account_votes(self, account: str): """ return self.call('get_account_votes', account, api='database_api') - def get_content(self, author: str, permlink: str): + def get_content(self, author, permlink): """ get_content """ return self.call('get_content', author, permlink, api='database_api') - def get_content_replies(self, author: str, permlink: str): + def get_content_replies(self, author, permlink): """ get_content_replies """ return self.call( 'get_content_replies', author, permlink, api='database_api') def get_discussions_by_author_before_date( - self, author: str, start_permlink: str, before_date: PointInTime, - limit: int): + self, author, start_permlink, before_date, + limit): """ get_discussions_by_author_before_date """ return self.call( 'get_discussions_by_author_before_date', @@ -840,8 +839,8 @@ def get_discussions_by_author_before_date( limit, api='database_api') - def get_replies_by_last_update(self, account: str, start_permlink: str, - limit: int): + def get_replies_by_last_update(self, account, start_permlink, + limit): """ get_replies_by_last_update """ return self.call( 'get_replies_by_last_update', @@ -850,20 +849,20 @@ def get_replies_by_last_update(self, account: str, start_permlink: str, limit, api='database_api') - def get_witnesses(self, witness_ids: list): + def get_witnesses(self, witness_ids): """ get_witnesses """ return self.call('get_witnesses', witness_ids, api='database_api') - def get_witness_by_account(self, account: str): + def get_witness_by_account(self, account): """ get_witness_by_account """ return self.call('get_witness_by_account', account, api='database_api') - def get_witnesses_by_vote(self, from_account: str, limit: int): + def get_witnesses_by_vote(self, from_account, limit): """ get_witnesses_by_vote """ return self.call( 'get_witnesses_by_vote', from_account, limit, api='database_api') - def lookup_witness_accounts(self, from_account: str, limit: int): + def lookup_witness_accounts(self, from_account, limit): """ lookup_witness_accounts """ return self.call( 'lookup_witness_accounts', from_account, limit, api='database_api') @@ -876,8 +875,8 @@ def get_active_witnesses(self): """ Get a list of currently active witnesses. """ return self.call('get_active_witnesses', api='database_api') - def get_vesting_delegations(self, account: str, from_account: str, - limit: int): + def get_vesting_delegations(self, account, from_account, + limit): """ get_vesting_delegations """ return self.call( 'get_vesting_delegations', @@ -886,11 +885,11 @@ def get_vesting_delegations(self, account: str, from_account: str, limit, api='database_api') - def login(self, username: str, password: str): + def login(self, username, password): """ login """ return self.call('login', username, password, api='login_api') - def get_api_by_name(self, api_name: str): + def get_api_by_name(self, api_name): """ get_api_by_name """ return self.call('get_api_by_name', api_name, api='login_api') @@ -898,8 +897,8 @@ def get_version(self): """ Get steemd version of the node currently connected to. """ return self.call('get_version', api='login_api') - def get_followers(self, account: str, start_follower: str, - follow_type: str, limit: int): + def get_followers(self, account, start_follower, + follow_type, limit): """ get_followers """ return self.call( 'get_followers', @@ -909,8 +908,8 @@ def get_followers(self, account: str, start_follower: str, limit, api='follow_api') - def get_following(self, account: str, start_follower: str, - follow_type: str, limit: int): + def get_following(self, account, start_follower, + follow_type, limit): """ get_following """ return self.call( 'get_following', @@ -920,45 +919,45 @@ def get_following(self, account: str, start_follower: str, limit, api='follow_api') - def get_follow_count(self, account: str): + def get_follow_count(self, account): """ get_follow_count """ return self.call('get_follow_count', account, api='follow_api') - def get_feed_entries(self, account: str, entry_id: int, limit: int): + def get_feed_entries(self, account, entry_id, limit): """ get_feed_entries """ return self.call( 'get_feed_entries', account, entry_id, limit, api='follow_api') - def get_feed(self, account: str, entry_id: int, limit: int): + def get_feed(self, account, entry_id, limit): """ get_feed """ return self.call( 'get_feed', account, entry_id, limit, api='follow_api') - def get_blog_entries(self, account: str, entry_id: int, limit: int): + def get_blog_entries(self, account, entry_id, limit): """ get_blog_entries """ return self.call( 'get_blog_entries', account, entry_id, limit, api='follow_api') - def get_blog(self, account: str, entry_id: int, limit: int): + def get_blog(self, account, entry_id, limit): """ get_blog """ return self.call( 'get_blog', account, entry_id, limit, api='follow_api') - def get_account_reputations(self, account: str, limit: int): + def get_account_reputations(self, account, limit): """ get_account_reputations """ return self.call( 'get_account_reputations', account, limit, api='follow_api') - def get_reblogged_by(self, author: str, permlink: str): + def get_reblogged_by(self, author, permlink): """ get_reblogged_by """ return self.call( 'get_reblogged_by', author, permlink, api='follow_api') - def get_blog_authors(self, blog_account: str): + def get_blog_authors(self, blog_account): """ get_blog_authors """ return self.call('get_blog_authors', blog_account, api='follow_api') - def broadcast_transaction(self, signed_transaction: SignedTransaction): + def broadcast_transaction(self, signed_transaction): """ broadcast_transaction """ return self.call( 'broadcast_transaction', @@ -966,18 +965,18 @@ def broadcast_transaction(self, signed_transaction: SignedTransaction): api='network_broadcast_api') def broadcast_transaction_synchronous( - self, signed_transaction: SignedTransaction): + self, signed_transaction): """ broadcast_transaction_synchronous """ return self.call( 'broadcast_transaction_synchronous', signed_transaction, api='network_broadcast_api') - def broadcast_block(self, block: Block): + def broadcast_block(self, block): """ broadcast_block """ return self.call('broadcast_block', block, api='network_broadcast_api') - def set_max_block_age(self, max_block_age: int): + def set_max_block_age(self, max_block_age): """ set_max_block_age """ return self.call( 'set_max_block_age', max_block_age, api='network_broadcast_api') @@ -990,20 +989,20 @@ def get_volume(self): """ Returns the market volume for the past 24 hours. """ return self.call('get_volume', api='market_history_api') - def get_trade_history(self, start: PointInTime, end: PointInTime, - limit: int): + def get_trade_history(self, start, end, + limit): """ Returns the trade history for the internal SBD:STEEM market. """ return self.call( 'get_trade_history', start, end, limit, api='market_history_api') - def get_recent_trades(self, limit: int) -> List[Any]: + def get_recent_trades(self, limit): """ Returns the N most recent trades for the internal SBD:STEEM market. """ return self.call('get_recent_trades', limit, api='market_history_api') - def get_market_history(self, bucket_seconds: int, start: PointInTime, - end: PointInTime): + def get_market_history(self, bucket_seconds, start, + end): """ Returns the market history for the internal SBD:STEEM market. """ return self.call( 'get_market_history', @@ -1017,7 +1016,7 @@ def get_market_history_buckets(self): return self.call( 'get_market_history_buckets', api='market_history_api') - def get_key_references(self, public_keys: List[str]): + def get_key_references(self, public_keys): """ get_key_references """ if type(public_keys) == str: public_keys = [public_keys] diff --git a/steem/transactionbuilder.py b/steem/transactionbuilder.py index e56bfdb..61e1061 100644 --- a/steem/transactionbuilder.py +++ b/steem/transactionbuilder.py @@ -1,6 +1,6 @@ import logging -from steem.wallet import Wallet +from .wallet import Wallet from steembase.account import PrivateKey from steembase.exceptions import (InsufficientAuthorityError, MissingKeyError, InvalidKeyFormat) diff --git a/steem/utils.py b/steem/utils.py index 2311ea2..75d2d8b 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -5,8 +5,6 @@ import re import time from datetime import datetime -from json import JSONDecodeError -from urllib.parse import urlparse import w3lib.url from langdetect import DetectorFactory, detect @@ -224,7 +222,7 @@ def remove_from_dict(obj, remove_keys=list()): return {k: v for k, v in items if k not in remove_keys} -def construct_identifier(*args, username_prefix='@'): +def construct_identifier(username_prefix='@', *args): """ Create a post identifier from comment/post object or arguments. Examples: diff --git a/steem/wallet.py b/steem/wallet.py index e8bfcbf..4980ea9 100644 --- a/steem/wallet.py +++ b/steem/wallet.py @@ -1,7 +1,7 @@ import logging import os -from steem.instance import shared_steemd_instance +from .instance import shared_steemd_instance from steembase import bip38 from steembase.account import PrivateKey from steembase.exceptions import (InvalidWifError, WalletExists) From 640b35d24c83ac4134b25df23b3044bbec0a12ed Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 29 Jan 2018 15:19:06 -0600 Subject: [PATCH 038/166] trying to import Steem no longer causes any syntax errors. --- steem/post.py | 4 ++-- steembase/http_client.py | 22 ++++++++++------------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/steem/post.py b/steem/post.py index da585b3..8a74b3f 100644 --- a/steem/post.py +++ b/steem/post.py @@ -99,8 +99,8 @@ def refresh(self): post['community'] = '' if isinstance(post['json_metadata'], dict): if post["depth"] == 0: - post["tags"] = (post["parent_permlink"], *get_in( - post, ['json_metadata', 'tags'], default=[])) + post["tags"] = (post["parent_permlink"]) + post["tags"].append(*get_in(post, ['json_metadata', 'tags'], default=[])) post['community'] = get_in( post, ['json_metadata', 'community'], default='') diff --git a/steembase/http_client.py b/steembase/http_client.py index 7887eac..5f41ca9 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -5,9 +5,7 @@ import socket import time from functools import partial -from http.client import RemoteDisconnected from itertools import cycle -from urllib.parse import urlparse import certifi import urllib3 @@ -112,7 +110,7 @@ def hostname(self): return urlparse(self.url).hostname @staticmethod - def json_rpc_body(name, *args, api=None, as_json=True, _id=0, kwargs=None): + def json_rpc_body(name, api=None, as_json=True, _id=0, kwargs=None, *args): """ Build request body for steemd RPC requests. Args: @@ -141,13 +139,13 @@ def json_rpc_body(name, *args, api=None, as_json=True, _id=0, kwargs=None): """ headers = {"jsonrpc": "2.0", "id": _id} if kwargs is not None: - body_dict = {**headers, "method": "call", - "params": [api, name, kwargs]} + body_dict = headers.update({"method": "call", + "params": [api, name, kwargs]}) elif api: - body_dict = {**headers, "method": "call", - "params": [api, name, args]} + body_dict = headers.update({"method": "call", + "params": [api, name, args]}) else: - body_dict = {**headers, "method": name, "params": args} + body_dict = headers.update({"method": name, "params": args}) if as_json: return json.dumps(body_dict, ensure_ascii=False).encode('utf8') else: @@ -155,11 +153,11 @@ def json_rpc_body(name, *args, api=None, as_json=True, _id=0, kwargs=None): def call(self, name, - *args, api=None, return_with_args=None, _ret_cnt=0, - kwargs=None): + kwargs=None, + *args): """ Call a remote procedure in steemd. Warnings: @@ -206,8 +204,8 @@ def call(self, args=args, return_with_args=return_with_args) else: - if response.status not in tuple([*response.REDIRECT_STATUSES, - 200]): + redirectStatuses = list(*response.REDIRECT_STATUSES).append(200) + if response.status not in tuple(*redirectStatuses): logger.info('non 200 response:%s', response.status) return self._return( From ccee57b9d6b3ba41a4f70e509f5fa0d1f237dc5b Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 29 Jan 2018 21:20:14 -0600 Subject: [PATCH 039/166] Another small python3-ism removed. --- steem/blog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/steem/blog.py b/steem/blog.py index eda426e..7b034ad 100644 --- a/steem/blog.py +++ b/steem/blog.py @@ -41,7 +41,7 @@ class Blog: """ def __init__(self, - account_name: str, + account_name, comments_only=False, steemd_instance=None): self.steem = steemd_instance or shared_steemd_instance() From 4cd5fb0069be05e2731b32e9bbd73436d50ac648 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Wed, 31 Jan 2018 01:55:05 -0600 Subject: [PATCH 040/166] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 463fa13..88bf148 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ cd steem-python pip3 install --user . ``` +Note that installation using `pip3` requires that the `pipenv` module be installed to parse the requirements out of the `Pipfile` so that pip3 can do the install. If you get an error about `pipenv` not being found, you can resolve it with a `pip3 install --upgrade --user pipenv`, then the install with `pip3` will work as usual. + ## Homebrew Build Prereqs If you're on a mac, you may need to do the following first: From 86a35b42dd0d54a1256114c6514002c3fe8dd557 Mon Sep 17 00:00:00 2001 From: John White Date: Wed, 31 Jan 2018 14:41:54 -0600 Subject: [PATCH 041/166] 41 passing tests. --- steem/blockchain.py | 2 +- steem/utils.py | 2 ++ steembase/account.py | 4 ++-- steembase/base58.py | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/steem/blockchain.py b/steem/blockchain.py index eefc460..f0a0a74 100644 --- a/steem/blockchain.py +++ b/steem/blockchain.py @@ -299,7 +299,7 @@ def replay(self, **kwargs): def hash_op(event): """ This method generates a hash of blockchain operation. """ data = json.dumps(event, sort_keys=True) - return hashlib.sha1(bytes(data, 'utf-8')).hexdigest() + return hashlib.sha1(bytes(data).encode('utf-8')).hexdigest() def get_all_usernames(self, *args, **kwargs): """ Fetch the full list of STEEM usernames. """ diff --git a/steem/utils.py b/steem/utils.py index 75d2d8b..a3e80d9 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -232,6 +232,8 @@ def construct_identifier(username_prefix='@', *args): construct_identifier({'author': 'username', 'permlink': 'permlink'}) """ + print(len(args)) + print(args) if len(args) == 1: op = args[0] author, permlink = op['author'], op['permlink'] diff --git a/steembase/account.py b/steembase/account.py index 1b2e34e..7a87f51 100644 --- a/steembase/account.py +++ b/steembase/account.py @@ -327,8 +327,8 @@ def compressedpubkey(self): x_str = ecdsa.util.number_to_string(p.x(), order) y_str = ecdsa.util.number_to_string(p.y(), order) compressed = hexlify( - bytes(chr(2 + (p.y() & 1)), 'ascii') + x_str).decode('ascii') - uncompressed = hexlify(bytes(chr(4), 'ascii') + x_str + y_str).decode( + chr(2 + (p.y() & 1)).encode('ascii') + x_str).decode('ascii') + uncompressed = hexlify(chr(4).encode('ascii') + x_str + y_str).decode( 'ascii') return [compressed, uncompressed] diff --git a/steembase/base58.py b/steembase/base58.py index d08bc1b..fc7d013 100644 --- a/steembase/base58.py +++ b/steembase/base58.py @@ -117,7 +117,7 @@ def __bytes__(self): def base58decode(base58_str): - base58_text = bytes(base58_str, "ascii") + base58_text = base58_str.encode("ascii") n = 0 leading_zeroes_count = 0 for b in base58_text: @@ -135,7 +135,7 @@ def base58decode(base58_str): def base58encode(hexstring): - byteseq = bytes(unhexlify(bytes(hexstring, 'ascii'))) + byteseq = bytes(unhexlify(hexstring.encode('ascii'))) n = 0 leading_zeroes_count = 0 for c in byteseq: From 9afc65a465455b50b35b29a886f1148d12df50e9 Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 12 Feb 2018 23:18:34 -0600 Subject: [PATCH 042/166] Backporting serialization to python2. --- steem/utils.py | 6 +++--- steembase/account.py | 11 +++++++++-- steembase/base58.py | 7 +++++-- steembase/bip38.py | 11 +++++++++-- steembase/http_client.py | 9 +++++++-- 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/steem/utils.py b/steem/utils.py index a3e80d9..1840717 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -222,7 +222,7 @@ def remove_from_dict(obj, remove_keys=list()): return {k: v for k, v in items if k not in remove_keys} -def construct_identifier(username_prefix='@', *args): +def construct_identifier(*args): """ Create a post identifier from comment/post object or arguments. Examples: @@ -232,8 +232,8 @@ def construct_identifier(username_prefix='@', *args): construct_identifier({'author': 'username', 'permlink': 'permlink'}) """ - print(len(args)) - print(args) + username_prefix = '@' + if len(args) == 1: op = args[0] author, permlink = op['author'], op['permlink'] diff --git a/steembase/account.py b/steembase/account.py index 7a87f51..fdfa83b 100644 --- a/steembase/account.py +++ b/steembase/account.py @@ -1,6 +1,7 @@ import hashlib import os import re +import sys from binascii import hexlify, unhexlify import ecdsa @@ -25,7 +26,10 @@ def get_private(self): """ Derive private key from the brain key and the current sequence number """ - a = bytes(self.account + self.role + self.password, 'utf8') + if sys.version > '3': + a = bytes(self.account + self.role + self.password, 'utf8') + else: + a = bytes(self.account + self.role + self.password).encode('utf8') s = hashlib.sha256(a).digest() return PrivateKey(hexlify(s).decode('ascii')) @@ -91,7 +95,10 @@ def get_private(self): number """ encoded = "%s %d" % (self.brainkey, self.sequence) - a = bytes(encoded, 'ascii') + if sys.version > '3': + a = bytes(encoded, 'ascii') + else: + a = bytes(encoded).encode('ascii') s = hashlib.sha256(hashlib.sha512(a).digest()).digest() return PrivateKey(hexlify(s).decode('ascii')) diff --git a/steembase/base58.py b/steembase/base58.py index fc7d013..67e191d 100644 --- a/steembase/base58.py +++ b/steembase/base58.py @@ -117,7 +117,7 @@ def __bytes__(self): def base58decode(base58_str): - base58_text = base58_str.encode("ascii") + base58_text = base58_str.encode('ascii') n = 0 leading_zeroes_count = 0 for b in base58_text: @@ -135,7 +135,10 @@ def base58decode(base58_str): def base58encode(hexstring): - byteseq = bytes(unhexlify(hexstring.encode('ascii'))) + if sys.version > '3': + byteseq = bytes(unhexlify(bytes(hexstring, 'ascii'))) + else: + byteseq = bytearray.fromhex(hexstring) n = 0 leading_zeroes_count = 0 for c in byteseq: diff --git a/steembase/bip38.py b/steembase/bip38.py index 223fa74..b0c3de9 100644 --- a/steembase/bip38.py +++ b/steembase/bip38.py @@ -1,5 +1,6 @@ import hashlib import logging +import sys from binascii import hexlify, unhexlify from .account import PrivateKey @@ -51,7 +52,10 @@ def encrypt(privkey, passphrase): """ privkeyhex = repr(privkey) # hex addr = format(privkey.uncompressed.address, "BTC") - a = bytes(addr, 'ascii') + if sys.version > '3': + a = bytes(addr, 'ascii') + else: + a = bytes(addr).encode('ascii') salt = hashlib.sha256(hashlib.sha256(a).digest()).digest()[0:4] if SCRYPT_MODULE == "scrypt": key = scrypt.hash(passphrase, salt, 16384, 8, 8) @@ -111,7 +115,10 @@ def decrypt(encrypted_privkey, passphrase): """ Verify Salt """ privkey = PrivateKey(format(wif, "wif")) addr = format(privkey.uncompressed.address, "BTC") - a = bytes(addr, 'ascii') + if sys.version > '3': + a = bytes(addr, 'ascii') + else: + a = bytes(addr).encode('ascii') saltverify = hashlib.sha256(hashlib.sha256(a).digest()).digest()[0:4] if saltverify != salt: raise SaltException( diff --git a/steembase/http_client.py b/steembase/http_client.py index 5f41ca9..c403959 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -137,6 +137,7 @@ def json_rpc_body(name, api=None, as_json=True, _id=0, kwargs=None, *args): Otherwise, a Python dictionary is returned. """ + print(name) headers = {"jsonrpc": "2.0", "id": _id} if kwargs is not None: body_dict = headers.update({"method": "call", @@ -149,6 +150,7 @@ def json_rpc_body(name, api=None, as_json=True, _id=0, kwargs=None, *args): if as_json: return json.dumps(body_dict, ensure_ascii=False).encode('utf8') else: + print(body_dict) return body_dict def call(self, @@ -169,6 +171,7 @@ def call(self, """ body = HttpClient.json_rpc_body(name, *args, api=api, kwargs=kwargs) response = None + print(args) try: response = self.request(body=body) except (MaxRetryError, ConnectionResetError, ReadTimeoutError, @@ -204,8 +207,9 @@ def call(self, args=args, return_with_args=return_with_args) else: - redirectStatuses = list(*response.REDIRECT_STATUSES).append(200) - if response.status not in tuple(*redirectStatuses): + # redirectStatuses = list(response.REDIRECT_STATUSES) + # redirectStatuses.append(200) + if response.status not in tuple([*response.REDIRECT_STATUSES, 200]): logger.info('non 200 response:%s', response.status) return self._return( @@ -226,6 +230,7 @@ def _return(self, response=None, args=None, return_with_args=None): result = None else: if 'error' in response_json: + print(response_json) error = response_json['error'] if self.re_raise: From 02e960634bce838cbb34b02c3d5a4171bf2bac5f Mon Sep 17 00:00:00 2001 From: John White Date: Tue, 13 Feb 2018 13:17:33 -0600 Subject: [PATCH 043/166] Midway through debugging problems with rpc command building due to syntactic changes for python2. --- steembase/http_client.py | 45 ++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index c403959..07c1e5c 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -4,6 +4,7 @@ import logging import socket import time +import sys from functools import partial from itertools import cycle @@ -13,6 +14,14 @@ from urllib3.connection import HTTPConnection from urllib3.exceptions import MaxRetryError, ReadTimeoutError, ProtocolError +if sys.version > '3': + from urllib.parse import urlparse + from http.client import RemoteDisconnected +else: + from urlparse import urlparse + from httplib import HTTPException + + logger = logging.getLogger(__name__) @@ -110,7 +119,7 @@ def hostname(self): return urlparse(self.url).hostname @staticmethod - def json_rpc_body(name, api=None, as_json=True, _id=0, kwargs=None, *args): + def json_rpc_body(name, api=None, as_json=True, _id=0, *args, **kwargs): """ Build request body for steemd RPC requests. Args: @@ -137,14 +146,14 @@ def json_rpc_body(name, api=None, as_json=True, _id=0, kwargs=None, *args): Otherwise, a Python dictionary is returned. """ - print(name) + print(kwargs) headers = {"jsonrpc": "2.0", "id": _id} if kwargs is not None: body_dict = headers.update({"method": "call", - "params": [api, name, kwargs]}) + "params": [api, name, kwargs]}) elif api: body_dict = headers.update({"method": "call", - "params": [api, name, args]}) + "params": [api, name, args]}) else: body_dict = headers.update({"method": name, "params": args}) if as_json: @@ -158,8 +167,8 @@ def call(self, api=None, return_with_args=None, _ret_cnt=0, - kwargs=None, - *args): + *args, + **kwargs): """ Call a remote procedure in steemd. Warnings: @@ -169,13 +178,19 @@ def call(self, transaction. In latter case, the exception is **re-raised**. """ - body = HttpClient.json_rpc_body(name, *args, api=api, kwargs=kwargs) + body = HttpClient.json_rpc_body(name, api=api, *args, kwargs=kwargs) response = None print(args) try: response = self.request(body=body) - except (MaxRetryError, ConnectionResetError, ReadTimeoutError, - RemoteDisconnected, ProtocolError) as e: + errorList = 0 + if sys.version > '3': + errorList = {MaxRetryError, ReadTimeoutError, ProtocolError, + RemoteDisconnected, ConnectionResetError} + else: + errorList = {MaxRetryError, ReadTimeoutError, ProtocolError, + HTTPException} + except (errorList) as e: # if we broadcasted a transaction, always raise # this is to prevent potential for double spend scenario if api == 'network_broadcast_api': @@ -193,9 +208,9 @@ def call(self, (self.hostname, e.__class__.__name__)) return self.call( name, - *args, return_with_args=return_with_args, - _ret_cnt=_ret_cnt + 1) + _ret_cnt=_ret_cnt + 1, + *args) except Exception as e: if self.re_raise: raise e @@ -207,9 +222,9 @@ def call(self, args=args, return_with_args=return_with_args) else: - # redirectStatuses = list(response.REDIRECT_STATUSES) - # redirectStatuses.append(200) - if response.status not in tuple([*response.REDIRECT_STATUSES, 200]): + redirectStatuses = list(response.REDIRECT_STATUSES) + redirectStatuses.append(200) + if response.status not in tuple([redirectStatuses]): logger.info('non 200 response:%s', response.status) return self._return( @@ -257,6 +272,6 @@ def ensure_list(parameter): futures = (executor.submit( self.call, name, *ensure_list(param), api=api) - for param in params) + for param in params) for future in concurrent.futures.as_completed(futures): yield future.result() From a7799674ba1d7e1330037dcfe27766d024ec8662 Mon Sep 17 00:00:00 2001 From: John White Date: Tue, 13 Feb 2018 16:02:16 -0600 Subject: [PATCH 044/166] 62 passing tests. --- Pipfile | 15 +-- Pipfile.lock | 232 +++++++++++++++++++++++++++++++-------- steem/blockchain.py | 6 +- steem/post.py | 1 + steembase/http_client.py | 50 ++++++--- 5 files changed, 235 insertions(+), 69 deletions(-) diff --git a/Pipfile b/Pipfile index 1e00e91..d86b437 100644 --- a/Pipfile +++ b/Pipfile @@ -6,6 +6,7 @@ name = "pypi" [packages] + appdirs = "*" certifi = "*" ecdsa = "*" @@ -15,22 +16,22 @@ prettytable = "*" pycrypto = "*" scrypt = "*" toolz = "*" -urllib3 = "*" +"urllib3" = "*" voluptuous = "*" -w3lib = "*" +"w3lib" = "*" +"pytest-pep8" = "*" [dev-packages] + ipython = "*" -pep8 = "*" +"pep8" = "*" pytest = "*" -pytest-pep8 = "*" +"pytest-pep8" = "*" pytest-console-scripts = "*" pytest-pylint = "*" recommonmark = "*" yapf = "*" -autopep8 = "*" - - +"autopep8" = "*" #[requires] #python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock index 547188d..8ef7e06 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d10840e543ef7e1f9c9bde62f64e00e3cd65f345cb20740ecf2972d2ee76d911" + "sha256": "fe2d9e17c4f05b531909f6c4d17633b68efd407ba4e7e75878662f115a7cbeba" }, "host-environment-markers": { "implementation_name": "cpython", @@ -27,6 +27,13 @@ ] }, "default": { + "apipkg": { + "hashes": [ + "sha256:65d2aa68b28e7d31233bb2ba8eb31cda40e4671f8ac2d6b241e358c9652a74b9", + "sha256:2e38399dbe842891fe85392601aab8f40a8f4cc5a9053c326de35a1cc0297ac6" + ], + "version": "==1.4" + }, "appdirs": { "hashes": [ "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e", @@ -34,6 +41,13 @@ ], "version": "==1.4.3" }, + "attrs": { + "hashes": [ + "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450", + "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9" + ], + "version": "==17.4.0" + }, "certifi": { "hashes": [ "sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296", @@ -48,11 +62,26 @@ ], "version": "==0.13" }, + "execnet": { + "hashes": [ + "sha256:fc155a6b553c66c838d1a22dba1dc9f5f505c43285a878c6f74a79c024750b83", + "sha256:a7a84d5fa07a089186a329528f127c9d73b9de57f1a1131b82bb5320ee651f6a" + ], + "version": "==1.5.0" + }, + "funcsigs": { + "hashes": [ + "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", + "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" + ], + "markers": "python_version < '3.0'", + "version": "==1.0.2" + }, "funcy": { "hashes": [ - "sha256:215c95e81142d831a0a71a84777268ce852b57e87582c743c662514b85be6bcd" + "sha256:c38ef134d34c767b9d631de453e19f710ac0575b9463d55e30f4f93274e089e4" ], - "version": "==1.10" + "version": "==1.10.1" }, "langdetect": { "hashes": [ @@ -60,6 +89,19 @@ ], "version": "==1.0.7" }, + "pep8": { + "hashes": [ + "sha256:b22cfae5db09833bb9bd7c8463b53e1a9c9b39f12e304a8d0bba729c501827ee", + "sha256:fe249b52e20498e59e0b5c5256aa52ee99fc295b26ec9eaa85776ffdb9fe6374" + ], + "version": "==1.7.1" + }, + "pluggy": { + "hashes": [ + "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff" + ], + "version": "==0.6.0" + }, "prettytable": { "hashes": [ "sha256:853c116513625c738dc3ce1aee148b5b5757a86727e67eff6502c7ca59d43c36", @@ -68,30 +110,54 @@ ], "version": "==0.7.2" }, + "py": { + "hashes": [ + "sha256:8cca5c229d225f8c1e3085be4fcf306090b00850fefad892f9d96c7b6e2f310f", + "sha256:ca18943e28235417756316bfada6cd96b23ce60dd532642690dcfdaba988a76d" + ], + "version": "==1.5.2" + }, "pycrypto": { "hashes": [ "sha256:f2ce1e989b272cfcb677616763e0a2e7ec659effa67a88aa92b3a65528f60a3c" ], "version": "==2.6.1" }, - "scrypt": { + "pytest": { "hashes": [ - "sha256:dc9abe69799ca423b938a06ddc33e5873e493ffcd68dbb9ba48396979b210d39", - "sha256:4e3cd639cc83e3f2c2241a01ebef9487c48b1ddf2d78f430d82d8f9ef60c6271", - "sha256:ae2fd88756fb4d98ccc5e2639af55eeb80863b3f9f6e0f539e5ce050964cdd5e", - "sha256:60e8c96a287ab892d9c7e1523d157ccfbdbe66da0c31738c8ed5732c2eea6a23", - "sha256:c0f90cabb8f6eaec05de5ce9aa9a9b67dc63d644e6b803beb1c43ae9b9452b65", - "sha256:c37a1f8440d7c621d9f23f3c1f2a28848bc50fefbca581fd7a1b01583a083c07", - "sha256:3422d11652cd12550540675e9fb54a1de6d60f3cbfedfb067284ef028589e2ee", - "sha256:d4a5a4f53450b8ef629bbf1ee4be6105c69936e49b3d8bc621ac2287f0c86020", - "sha256:6109a4df8c88f851df18a1a451e533dcc47e17cfe0e4561f4e08a82669ddc942", - "sha256:136f7d1caf596c5ee1fc7eab223605e956c3f61f77090fd9ea4e3e57a2040b78", - "sha256:c8909a2089fd1199781aa7ce2cb66b8866d40a9f9e1fba082e067ed9524d87e9", - "sha256:4333b67f190e5eaddc8800aefe33abd7e81b589bbe84a84be66872a4955dad6e", - "sha256:aeab005a8ae43a6e5d0165ce433a15955b151045d2bbfc52a8c96f100f825323", - "sha256:bb141584fa0ebdfb8a2a1fc7ddcf119ee18b1b9cd0fb3e4df9615760648b9d49" + "sha256:95fa025cd6deb5d937e04e368a00552332b58cae23f63b76c8c540ff1733ab6d", + "sha256:6074ea3b9c999bd6d0df5fa9d12dd95ccd23550df2a582f5f5b848331d2e82ca" ], - "version": "==0.8.0" + "version": "==3.4.0" + }, + "pytest-cache": { + "hashes": [ + "sha256:be7468edd4d3d83f1e844959fd6e3fd28e77a481440a7118d430130ea31b07a9" + ], + "version": "==1.0" + }, + "pytest-pep8": { + "hashes": [ + "sha256:032ef7e5fa3ac30f4458c73e05bb67b0f036a8a5cb418a534b3170f89f120318" + ], + "version": "==1.0.6" + }, + "scrypt": { + "hashes": [ + "sha256:dc40f0e1a357a49ca62f30f2fc09e92e02c062a656f27949b436b2ba8002d9e1", + "sha256:a343c302b3e99dcb7fcbe57aa7919ed761f1568f854291ccebe1b5e6e2c9e509", + "sha256:a124719c686f2b5957e392465147fb3fd6077e7c643e9538cab1ee631eb01dde", + "sha256:bc131f74a688fa09993c518ca666a2ebd4268b207e039cbab03a034228140d3e", + "sha256:232acdbc3434d2de55def8d5dbf1bc4b9bfc50da7c5741df2a6eebc4e18d3720", + "sha256:85919f023148cd9fb01d75ad4e3e061928c298fa6249a0cd6cd469c4b947595e", + "sha256:971db040d3963ebe4b919a203fe10d7d6659951d3644066314330983dc175ed4", + "sha256:475ac80239b3d788ae71a09c3019ca915e149aaa339adcdd1c9eef121293dc88", + "sha256:4ad7188f2e42dbee2ff1cd72e3da40b170ba41847effbf0d726444f62ae60f3a", + "sha256:18ccbc63d87c6f89b753194194bb37aeaf1abc517e4b989461d115c1d93ce128", + "sha256:c23daecee405cb036845917295c76f8d747fc890158df40cb304b4b3c3640079", + "sha256:f8239b2d47fa1d40bc27efd231dc7083695d10c1c2ac51a99380360741e0362d" + ], + "version": "==0.8.6" }, "six": { "hashes": [ @@ -140,7 +206,6 @@ "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" ], - "markers": "sys_platform == 'darwin'", "version": "==0.1.0" }, "astroid": { @@ -163,12 +228,27 @@ ], "version": "==1.3.4" }, + "backports.functools-lru-cache": { + "hashes": [ + "sha256:f0b0e4eba956de51238e17573b7087e852dfe9854afd2e9c873f73fc0ca0a6dd", + "sha256:9d98697f088eb1b0fa451391f91afb5e3ebde16bbdb272819fd091151fda4f1a" + ], + "markers": "python_version < '3.4'", + "version": "==1.5" + }, "commonmark": { "hashes": [ "sha256:34d73ec8085923c023930dfc0bcd1c4286e28a2a82de094bb72fabcc0281cbe5" ], "version": "==0.5.4" }, + "configparser": { + "hashes": [ + "sha256:5308b47021bc2340965c371f0f058cc6971a04502638d4244225c49d80db273a" + ], + "markers": "python_version == '2.7'", + "version": "==3.5.0" + }, "decorator": { "hashes": [ "sha256:94d1d8905f5010d74bbbd86c30471255661a14187c45f8d7f3e5aa8540fdb2e5", @@ -184,6 +264,16 @@ ], "version": "==0.14" }, + "enum34": { + "hashes": [ + "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", + "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", + "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1", + "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850" + ], + "markers": "python_version == '2.7'", + "version": "==1.1.6" + }, "execnet": { "hashes": [ "sha256:fc155a6b553c66c838d1a22dba1dc9f5f505c43285a878c6f74a79c024750b83", @@ -191,6 +281,21 @@ ], "version": "==1.5.0" }, + "funcsigs": { + "hashes": [ + "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", + "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" + ], + "markers": "python_version < '3.0'", + "version": "==1.0.2" + }, + "futures": { + "hashes": [ + "sha256:c4884a65654a7c45435063e14ae85280eb1f111d94e542396717ba9828c4337f", + "sha256:51ecb45f0add83c806c68e4b06106f90db260585b25ef2abfcda0bd95c0132fd" + ], + "version": "==3.1.1" + }, "ipython": { "hashes": [ "sha256:fcc6d46f08c3c4de7b15ae1c426e15be1b7932bcda9d83ce1a4304e8c1129df3", @@ -207,10 +312,11 @@ }, "isort": { "hashes": [ - "sha256:cd5d3fc2c16006b567a17193edf4ed9830d9454cbeb5a42ac80b36ea00c23db4", - "sha256:79f46172d3a4e2e53e7016e663cc7a8b538bec525c36675fcfd2767df30b3983" + "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497", + "sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af", + "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8" ], - "version": "==4.2.15" + "version": "==4.3.4" }, "jedi": { "hashes": [ @@ -274,6 +380,14 @@ ], "version": "==0.1.1" }, + "pathlib2": { + "hashes": [ + "sha256:db3e43032d23787d3e9aec8c7ef1e0d2c3c589d5f303477661ebda2ca6d4bfba", + "sha256:d32550b75a818b289bd4c1f96b60c89957811da205afcceab75bc8b4857ea5b3" + ], + "markers": "python_version in '2.6 2.7 3.2 3.3'", + "version": "==2.3.0" + }, "pbr": { "hashes": [ "sha256:60c25b7dfd054ef9bb0ae327af949dd4676aa09ac3a9471cdc871d8a9213f9ac", @@ -290,11 +404,10 @@ }, "pexpect": { "hashes": [ - "sha256:144939a072a46d32f6e5ecc866509e1d613276781f7182148a08df52eaa7b022", - "sha256:8e287b171dbaf249d0b06b5f2e88cb7e694651d2d0b8c15bccb83170d3c55575" + "sha256:6ff881b07aff0cb8ec02055670443f784434395f90c3285d2ae470f921ade52a", + "sha256:67b85a1565968e3d5b5e7c9283caddc90c3947a2625bed1905be27bd5a03e47d" ], - "markers": "sys_platform != 'win32'", - "version": "==4.3.1" + "version": "==4.4.0" }, "pickleshare": { "hashes": [ @@ -354,10 +467,10 @@ }, "pytest": { "hashes": [ - "sha256:b84878865558194630c6147f44bdaef27222a9f153bbd4a08908b16bf285e0b1", - "sha256:53548280ede7818f4dc2ad96608b9f08ae2cc2ca3874f2ceb6f97e3583f25bc4" + "sha256:95fa025cd6deb5d937e04e368a00552332b58cae23f63b76c8c540ff1733ab6d", + "sha256:6074ea3b9c999bd6d0df5fa9d12dd95ccd23550df2a582f5f5b848331d2e82ca" ], - "version": "==3.3.2" + "version": "==3.4.0" }, "pytest-cache": { "hashes": [ @@ -367,9 +480,9 @@ }, "pytest-console-scripts": { "hashes": [ - "sha256:75188d816f7398956aee48dbff4ce6759d64fbf617f760c8c4cec77608afb8a3" + "sha256:0a658015805bb4746f86fe829cca0e1958c5a8f4944c695c3c998309e58a5cbc" ], - "version": "==0.1.3" + "version": "==0.1.4" }, "pytest-pep8": { "hashes": [ @@ -379,15 +492,15 @@ }, "pytest-pylint": { "hashes": [ - "sha256:9b8ca25823b2f39e89d8170453f5282e57b973395060e838ced5f8c09271ca65", - "sha256:2efaf761472637df9a8f4a3f4fac37f8ce433d70957c5f5767c4be322a42a3d2", - "sha256:9f38725b22967a56724115c9df0a93dda37fea71dd5495fb1354b82e3d938d0d", - "sha256:85da6403c69eb715b9703df640818f337603f2cac947f932b033588851aaaf16", - "sha256:b85763dc36757bfb736b07fecb4f67a0892dcb00868e01f150c7424f608bd62e", - "sha256:ec63f7c4c05331654ab54fda8e68b8a11512009d506a8e35ee9b6d40a359356d", - "sha256:2bb26948f0355d14b274742153a6b4daa51e6d60481143bfd7f025699a27210d" + "sha256:aa41ed2e8671e72bc40ab33f43232fdac712a316e177b4bfc23c6e685bd6b5cc", + "sha256:644f50217834099886c951bc5d034938ae305c530aa45d8866ecacd3b50d268b", + "sha256:9c9a3c13e1f15e674a7503a921fe1332239016718e3496e4c83990f266a79bc6", + "sha256:de36a9edfc0c3cf46412634d308b97ef78e953edaf45a150feaf372ceac18c4a", + "sha256:a9975080aff6030edb344ec9ea7297ce9e0c623ae06b406f4e9ebf8773975b6d", + "sha256:879aa6445bcb11840f3bc96265aa1a89168ab22e6f4a1b4a94200d99d86ca4be", + "sha256:41d6f223ae5e0a0fbb0056e826ecdb8046be2a828eba55c0d4f66cbfd7d27168" ], - "version": "==0.7.1" + "version": "==0.8.0" }, "recommonmark": { "hashes": [ @@ -396,12 +509,37 @@ ], "version": "==0.4.0" }, + "scandir": { + "hashes": [ + "sha256:f39dd5affde2860fb28176d2233f318ccca97c55019407ee8172b3fba0b211db", + "sha256:7729b8444c5f5187649ff58501e7c2ad22b84d7dc28f738f64c5b615913fec22", + "sha256:b6cb611a18a828146a178362a36a2c6557c51c596ded4314cb516dd8c947b4ce", + "sha256:d985e36eb3effebb20434e6cd7495440b4ba676a22f3ec61e9fff9f3e2995238", + "sha256:24f32112c483ac6c4a40b62f1282e61ecca7977153b66a0d26a9583a716dcb64", + "sha256:b55a091b91f9e6c9c7129889b2f58df329530172a99172de9e784545342a45e6", + "sha256:f91418e82edb5a43b020fa15e30a41d730b71c5957536749366bf63cc05427b1", + "sha256:6c80092f8fe3e62c3da3110067589c6661c722b0889906d2974e5150f1314523", + "sha256:96dfc553f50946deb6d1cd762bac5cf122832c4aa253c885ca357ef53dd8d072", + "sha256:8e3ca5925cc13787aeafbf08f055a8066c091fc20bfa8783235b916cf047afbe", + "sha256:b2d55be869c4f716084a19b1e16932f0769711316ba62de941320bf2be84763d" + ], + "markers": "python_version < '3.5'", + "version": "==1.7" + }, "simplegeneric": { "hashes": [ "sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173" ], "version": "==0.8.1" }, + "singledispatch": { + "hashes": [ + "sha256:833b46966687b3de7f438c761ac475213e53b306740f1abfaa86e1d1aae56aa8", + "sha256:5b06af87df13818d14f08a028e42f566640aef80805c3b50c5056b086e3c2b9c" + ], + "markers": "python_version < '3.4'", + "version": "==3.4.0.3" + }, "six": { "hashes": [ "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", @@ -416,6 +554,14 @@ ], "version": "==4.3.2" }, + "typing": { + "hashes": [ + "sha256:b2c689d54e1144bbcfd191b0832980a21c2dbcf7b5ff7a66248a60c90e951eb8", + "sha256:3a887b021a77b292e151afb75323dea88a7bc1b3dfa92176cff8e44c8b68bddf", + "sha256:d400a9344254803a2368533e4533a4200d21eb7b6b729c173bc38201a74db3f2" + ], + "version": "==3.6.4" + }, "wcwidth": { "hashes": [ "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c", @@ -431,10 +577,10 @@ }, "yapf": { "hashes": [ - "sha256:a0bbc8ed274609f9c7575a5d69056fa393e26a778b3e070a72f4998b8e90c3cd", - "sha256:bd19f246be7193ad2acdc04702b92315f1ae28d49c82f6671afdeefe9d32f468" + "sha256:0ba4765012218b67a8c67da5c045d1fb9171d786ffe8dce6c8d1e11cbbaba8eb", + "sha256:7f5efdb7edf0318b91e53721d934580a77153e24a222f52f6e1c3b7629aead43" ], - "version": "==0.20.1" + "version": "==0.20.2" } } } diff --git a/steem/blockchain.py b/steem/blockchain.py index f0a0a74..403b17a 100644 --- a/steem/blockchain.py +++ b/steem/blockchain.py @@ -2,6 +2,7 @@ import json import time import warnings +import sys from .instance import shared_steemd_instance, stm from .utils import parse_time @@ -299,7 +300,10 @@ def replay(self, **kwargs): def hash_op(event): """ This method generates a hash of blockchain operation. """ data = json.dumps(event, sort_keys=True) - return hashlib.sha1(bytes(data).encode('utf-8')).hexdigest() + if sys.version > '3': + return hashlib.sha1(bytes(data, 'utf-8')).hexdigest() + else: + return hashlib.sha1(bytes(data).encode('utf-8')).hexdigest() def get_all_usernames(self, *args, **kwargs): """ Fetch the full list of STEEM usernames. """ diff --git a/steem/post.py b/steem/post.py index 074c860..0e6f215 100644 --- a/steem/post.py +++ b/steem/post.py @@ -100,6 +100,7 @@ def refresh(self): if isinstance(post['json_metadata'], dict): if post["depth"] == 0: post["tags"] = (post["parent_permlink"]) + print(post["tags"]) post["tags"].append(*get_in(post, ['json_metadata', 'tags'], default=[])) post['community'] = get_in( diff --git a/steembase/http_client.py b/steembase/http_client.py index 07c1e5c..4a0d3f5 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -21,7 +21,6 @@ from urlparse import urlparse from httplib import HTTPException - logger = logging.getLogger(__name__) @@ -69,7 +68,7 @@ def __init__(self, nodes, **kwargs): if tcp_keepalive: socket_options = HTTPConnection.default_socket_options + \ - [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), ] + [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), ] else: socket_options = HTTPConnection.default_socket_options @@ -119,7 +118,7 @@ def hostname(self): return urlparse(self.url).hostname @staticmethod - def json_rpc_body(name, api=None, as_json=True, _id=0, *args, **kwargs): + def json_rpc_body(name, *args, **kwargs): """ Build request body for steemd RPC requests. Args: @@ -146,27 +145,37 @@ def json_rpc_body(name, api=None, as_json=True, _id=0, *args, **kwargs): Otherwise, a Python dictionary is returned. """ - print(kwargs) + api = kwargs.get('kwargs').pop('api', None) + as_json = kwargs.get('kwargs').pop('as_json', True) + _id = kwargs.get('kwargs').pop('_id', 0) + + if len(kwargs) == 1 and 'kwargs' in kwargs: + kwargs = None + headers = {"jsonrpc": "2.0", "id": _id} if kwargs is not None: - body_dict = headers.update({"method": "call", - "params": [api, name, kwargs]}) + + body_dict = dict(headers) + body_dict.update({"method": "call", + "params": [api, name, kwargs]}) elif api: - body_dict = headers.update({"method": "call", - "params": [api, name, args]}) + + body_dict = dict(headers) + body_dict.update({"method": "call", + "params": [api, name, args]}) + else: - body_dict = headers.update({"method": name, "params": args}) + + body_dict = dict(headers) + body_dict.update({"method": name, "params": args}) + if as_json: return json.dumps(body_dict, ensure_ascii=False).encode('utf8') else: - print(body_dict) return body_dict def call(self, name, - api=None, - return_with_args=None, - _ret_cnt=0, *args, **kwargs): """ Call a remote procedure in steemd. @@ -178,18 +187,23 @@ def call(self, transaction. In latter case, the exception is **re-raised**. """ - body = HttpClient.json_rpc_body(name, api=api, *args, kwargs=kwargs) + + api = kwargs.get('api', None) + return_with_args = kwargs.get('return_with_args', None) + _ret_cnt = kwargs.get('_ret_cnt', 0) + + body = HttpClient.json_rpc_body(name, *args, kwargs=kwargs) response = None - print(args) + try: response = self.request(body=body) errorList = 0 if sys.version > '3': errorList = {MaxRetryError, ReadTimeoutError, ProtocolError, - RemoteDisconnected, ConnectionResetError} + RemoteDisconnected, ConnectionResetError} else: errorList = {MaxRetryError, ReadTimeoutError, ProtocolError, - HTTPException} + HTTPException} except (errorList) as e: # if we broadcasted a transaction, always raise # this is to prevent potential for double spend scenario @@ -224,7 +238,7 @@ def call(self, else: redirectStatuses = list(response.REDIRECT_STATUSES) redirectStatuses.append(200) - if response.status not in tuple([redirectStatuses]): + if response.status not in tuple(redirectStatuses): logger.info('non 200 response:%s', response.status) return self._return( From 9ba14c19298f7350efe1274f1e3c16f8aceee80f Mon Sep 17 00:00:00 2001 From: John White Date: Wed, 14 Feb 2018 13:01:13 -0600 Subject: [PATCH 045/166] All tests passing. --- steem/account.py | 3 ++- steem/post.py | 6 ++---- steem/steemd.py | 5 ++++- steem/utils.py | 7 +++++++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/steem/account.py b/steem/account.py index 99cddb9..55bfbc6 100644 --- a/steem/account.py +++ b/steem/account.py @@ -299,10 +299,11 @@ def construct_op(account_name): 'type': op_type, }) _id = Blockchain.hash_op(immutable) - return immutable.update({ + immutable.update({ '_id': _id, 'index': index, }) + return immutable if filter_by is None: yield construct_op(self.name) diff --git a/steem/post.py b/steem/post.py index 0e6f215..7bf9ea4 100644 --- a/steem/post.py +++ b/steem/post.py @@ -99,10 +99,8 @@ def refresh(self): post['community'] = '' if isinstance(post['json_metadata'], dict): if post["depth"] == 0: - post["tags"] = (post["parent_permlink"]) - print(post["tags"]) - post["tags"].append(*get_in(post, ['json_metadata', 'tags'], default=[])) - + tags = get_in(post, ['json_metadata', 'tags'], default=[]) + post["tags"] = (post["parent_permlink"], tags) post['community'] = get_in( post, ['json_metadata', 'community'], default='') diff --git a/steem/steemd.py b/steem/steemd.py index bec8738..53959a7 100644 --- a/steem/steemd.py +++ b/steem/steemd.py @@ -12,6 +12,7 @@ from .blockchain import Blockchain from .post import Post from .utils import resolve_identifier +from .utils import compose_dictionary logger = logging.getLogger(__name__) @@ -192,7 +193,7 @@ def _get_blocks(self, blocks): """ results = self.call_multi_with_futures( 'get_block', blocks, max_workers=10) - return (x.update({'block_num'(x['block_id'][:8], base=16)}) + return (compose_dictionary(x, block_num=int(x['block_id'][:8], base=16)) for x in results if x) def get_blocks(self, block_nums): @@ -215,6 +216,8 @@ def get_blocks(self, block_nums): while missing: for block in self._get_blocks(missing): + print("Missing") + print(block) blocks[block['block_num']] = block available = set(blocks.keys()) diff --git a/steem/utils.py b/steem/utils.py index 1840717..cd2fbf4 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -354,3 +354,10 @@ def strfdelta(tdelta, fmt): def is_valid_account_name(name): return re.match('^[a-z][a-z0-9\-.]{2,15}$', name) + + +def compose_dictionary(dictionary, **kwargs): + for pairs in kwargs.items(): + dictionary[pairs[0]] = pairs[1] + + return dictionary From 7b6ca426418f7341c8d7ad08350d8f3c2e6f476b Mon Sep 17 00:00:00 2001 From: John White Date: Wed, 14 Feb 2018 15:52:40 -0600 Subject: [PATCH 046/166] More fixes for python2 compat. All tests are passing again in python3, currently facing an issue with memo.py. Could potentially be due to unintended side effects in base58.py. --- Pipfile | 1 + Pipfile.lock | 61 +++++++++++++++++++-------------------- steem/amount.py | 3 ++ steem/instance.py | 9 ++++-- steem/utils.py | 4 +++ steembase/http_client.py | 3 +- steembase/memo.py | 8 ++++- steembase/operations.py | 58 ++++++++++++++++++------------------- steembase/transactions.py | 2 +- steembase/types.py | 4 +-- 10 files changed, 84 insertions(+), 69 deletions(-) diff --git a/Pipfile b/Pipfile index d86b437..29ea21a 100644 --- a/Pipfile +++ b/Pipfile @@ -20,6 +20,7 @@ toolz = "*" voluptuous = "*" "w3lib" = "*" "pytest-pep8" = "*" +"futures" = {version = "*", markers="python_version < '3.0.0'"} [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 8ef7e06..a7c06c3 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,19 +1,19 @@ { "_meta": { "hash": { - "sha256": "fe2d9e17c4f05b531909f6c4d17633b68efd407ba4e7e75878662f115a7cbeba" + "sha256": "f3b8df34e618448650982c10e58820f7d2f629272e4c590da28878d3e536f50b" }, "host-environment-markers": { "implementation_name": "cpython", - "implementation_version": "3.6.4", + "implementation_version": "0", "os_name": "posix", "platform_machine": "x86_64", "platform_python_implementation": "CPython", "platform_release": "17.3.0", "platform_system": "Darwin", "platform_version": "Darwin Kernel Version 17.3.0: Thu Nov 9 18:09:22 PST 2017; root:xnu-4570.31.3~1/RELEASE_X86_64", - "python_full_version": "3.6.4", - "python_version": "3.6", + "python_full_version": "2.7.10", + "python_version": "2.7", "sys_platform": "darwin" }, "pipfile-spec": 6, @@ -83,6 +83,14 @@ ], "version": "==1.10.1" }, + "futures": { + "hashes": [ + "sha256:ec0a6cb848cc212002b9828c3e34c675e0c9ff6741dc445cab6fdd4e1085d1f1", + "sha256:9ec02aa7d674acb8618afb127e27fde7fc68994c0437ad759fa094a574adb265" + ], + "markers": "python_version < '3.0.0'", + "version": "==3.2.0" + }, "langdetect": { "hashes": [ "sha256:91a170d5f0ade380db809b3ba67f08e95fe6c6c8641f96d67a51ff7e98a9bf30" @@ -206,6 +214,7 @@ "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" ], + "markers": "sys_platform == 'darwin'", "version": "==0.1.0" }, "astroid": { @@ -236,6 +245,14 @@ "markers": "python_version < '3.4'", "version": "==1.5" }, + "backports.shutil-get-terminal-size": { + "hashes": [ + "sha256:0975ba55054c15e346944b38956a4c9cbee9009391e41b86c68990effb8c1f64", + "sha256:713e7a8228ae80341c70586d1cc0a8caa5207346927e23d09dcbcaf18eadec80" + ], + "markers": "python_version == '2.7'", + "version": "==1.0.0" + }, "commonmark": { "hashes": [ "sha256:34d73ec8085923c023930dfc0bcd1c4286e28a2a82de094bb72fabcc0281cbe5" @@ -291,17 +308,18 @@ }, "futures": { "hashes": [ - "sha256:c4884a65654a7c45435063e14ae85280eb1f111d94e542396717ba9828c4337f", - "sha256:51ecb45f0add83c806c68e4b06106f90db260585b25ef2abfcda0bd95c0132fd" + "sha256:ec0a6cb848cc212002b9828c3e34c675e0c9ff6741dc445cab6fdd4e1085d1f1", + "sha256:9ec02aa7d674acb8618afb127e27fde7fc68994c0437ad759fa094a574adb265" ], - "version": "==3.1.1" + "version": "==3.2.0" }, "ipython": { "hashes": [ - "sha256:fcc6d46f08c3c4de7b15ae1c426e15be1b7932bcda9d83ce1a4304e8c1129df3", - "sha256:51c158a6c8b899898d1c91c6b51a34110196815cc905f9be0fa5878e19355608" + "sha256:578e2f3d779ed130a3cfefc09b2eb965a81457f6a31a25cd38e0bab622d4777d", + "sha256:185ef2093dbac6d7250fe9ed4d4dd0f18f10d0a6ac6169f3eeb2ff0663d96b92", + "sha256:66469e894d1f09d14a1f23b971a410af131daa9ad2a19922082e02e0ddfd150f" ], - "version": "==6.2.1" + "version": "==5.5.0" }, "ipython-genutils": { "hashes": [ @@ -318,13 +336,6 @@ ], "version": "==4.3.4" }, - "jedi": { - "hashes": [ - "sha256:d795f2c2e659f5ea39a839e5230d70a0b045d0daee7ca2403568d8f348d0ad89", - "sha256:d6e799d04d1ade9459ed0f20de47c32f2285438956a677d083d3c98def59fa97" - ], - "version": "==0.11.1" - }, "lazy-object-proxy": { "hashes": [ "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019", @@ -373,13 +384,6 @@ ], "version": "==2.0.0" }, - "parso": { - "hashes": [ - "sha256:a7bb86fe0844304869d1c08e8bd0e52be931228483025c422917411ab82d628a", - "sha256:5815f3fe254e5665f3c5d6f54f086c2502035cb631a91341591b5a564203cffb" - ], - "version": "==0.1.1" - }, "pathlib2": { "hashes": [ "sha256:db3e43032d23787d3e9aec8c7ef1e0d2c3c589d5f303477661ebda2ca6d4bfba", @@ -407,6 +411,7 @@ "sha256:6ff881b07aff0cb8ec02055670443f784434395f90c3285d2ae470f921ade52a", "sha256:67b85a1565968e3d5b5e7c9283caddc90c3947a2625bed1905be27bd5a03e47d" ], + "markers": "sys_platform != 'win32'", "version": "==4.4.0" }, "pickleshare": { @@ -554,14 +559,6 @@ ], "version": "==4.3.2" }, - "typing": { - "hashes": [ - "sha256:b2c689d54e1144bbcfd191b0832980a21c2dbcf7b5ff7a66248a60c90e951eb8", - "sha256:3a887b021a77b292e151afb75323dea88a7bc1b3dfa92176cff8e44c8b68bddf", - "sha256:d400a9344254803a2368533e4533a4200d21eb7b6b729c173bc38201a74db3f2" - ], - "version": "==3.6.4" - }, "wcwidth": { "hashes": [ "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c", diff --git a/steem/amount.py b/steem/amount.py index 0bdff2a..51146c4 100644 --- a/steem/amount.py +++ b/steem/amount.py @@ -7,11 +7,14 @@ class Amount(dict): """ def __init__(self, amount_string="0 SBD"): + print(type(amount_string)) if isinstance(amount_string, Amount): self["amount"] = amount_string["amount"] self["asset"] = amount_string["asset"] elif isinstance(amount_string, str): self["amount"], self["asset"] = amount_string.split(" ") + elif isinstance(amount_string, unicode): + self["amount"], self["asset"] = amount_string.split(" ") else: raise ValueError( "Need an instance of 'Amount' or a string with amount " + diff --git a/steem/instance.py b/steem/instance.py index 31cb753..85a620d 100644 --- a/steem/instance.py +++ b/steem/instance.py @@ -1,4 +1,5 @@ import steem as stm +import sys _shared_steemd_instance = None @@ -17,8 +18,12 @@ def shared_steemd_instance(): global _shared_steemd_instance if not _shared_steemd_instance: - _shared_steemd_instance = stm.steemd.Steemd( - nodes=get_config_node_list()) + if sys.version > '3.0': + _shared_steemd_instance = stm.steemd.Steemd( + nodes=get_config_node_list()) + else: + _shared_steemd_instance = stm.Steemd( + nodes=get_config_node_list()) return _shared_steemd_instance diff --git a/steem/utils.py b/steem/utils.py index cd2fbf4..f084c38 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -4,6 +4,7 @@ import os import re import time +import sys from datetime import datetime import w3lib.url @@ -11,6 +12,9 @@ from langdetect.lang_detect_exception import LangDetectException from toolz import update_in, assoc +if sys.version < '3.0': + from urlparse import urlparse + logger = logging.getLogger(__name__) # https://github.com/matiasb/python-unidiff/blob/master/unidiff/constants.py#L37 diff --git a/steembase/http_client.py b/steembase/http_client.py index 4a0d3f5..da35194 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -1,5 +1,4 @@ # coding=utf-8 -import concurrent.futures import json import logging import socket @@ -7,7 +6,7 @@ import sys from functools import partial from itertools import cycle - +import concurrent.futures import certifi import urllib3 from steembase.exceptions import RPCError diff --git a/steembase/memo.py b/steembase/memo.py index 8ba79d8..49948b8 100644 --- a/steembase/memo.py +++ b/steembase/memo.py @@ -1,6 +1,7 @@ import hashlib import struct from binascii import hexlify, unhexlify +import sys from .operations import Memo from Crypto.Cipher import AES @@ -80,7 +81,11 @@ def encode_memo(priv, pub, nonce, message, **kwargs): from steembase import transactions shared_secret = get_shared_secret(priv, pub) aes, check = init_aes(shared_secret, nonce) - raw = bytes(message, 'utf8') + raw = 0 + if sys.version > '3.0': + raw = bytes(message, 'utf8') + else: + raw = bytes(message).encode('utf8') " Padding " BS = 16 if len(raw) % BS: @@ -115,6 +120,7 @@ def decode_memo(priv, message): """ " decode structure " raw = base58decode(message[1:]) + print(raw) from_key = PublicKey(raw[:66]) raw = raw[66:] to_key = PublicKey(raw[:66]) diff --git a/steembase/operations.py b/steembase/operations.py index 7461023..fadb310 100644 --- a/steembase/operations.py +++ b/steembase/operations.py @@ -159,7 +159,7 @@ def __init__(self, *args, **kwargs): for e in kwargs["account_auths"]]) keyAuths = Map([[PublicKey(e[0], prefix=prefix), Uint16(e[1])] for e in kwargs["key_auths"]]) - super().__init__( + super(Permission, self).__init__( OrderedDict([ ('weight_threshold', Uint32( int(kwargs["weight_threshold"]))), @@ -178,7 +178,7 @@ def __init__(self, *args, **kwargs): if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__( + super(Memo, self).__init__( OrderedDict([ ('from', PublicKey(kwargs["from"], prefix=prefix)), ('to', PublicKey(kwargs["to"], prefix=prefix)), @@ -195,7 +195,7 @@ def __init__(self, *args, **kwargs): else: if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__( + super(Vote, self).__init__( OrderedDict([ ('voter', String(kwargs["voter"])), ('author', String(kwargs["author"])), @@ -219,7 +219,7 @@ def __init__(self, *args, **kwargs): else: meta = kwargs["json_metadata"] - super().__init__( + super(Comment, self).__init__( OrderedDict([ ('parent_author', String(kwargs["parent_author"])), ('parent_permlink', String(kwargs["parent_permlink"])), @@ -260,7 +260,7 @@ def __init__(self, *args, **kwargs): if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__( + super(ExchangeRate, self).__init__( OrderedDict([ ('base', Amount(kwargs["base"])), ('quote', Amount(kwargs["quote"])), @@ -275,7 +275,7 @@ def __init__(self, *args, **kwargs): if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__( + super(WitnessProps, self).__init__( OrderedDict([ ('account_creation_fee', Amount(kwargs["account_creation_fee"])), @@ -292,7 +292,7 @@ def __init__(self, *args, **kwargs): else: if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__( + super(Beneficiary, self).__init__( OrderedDict([ ('account', String(kwargs["account"])), ('weight', Int16(kwargs["weight"])), @@ -301,7 +301,7 @@ def __init__(self, *args, **kwargs): class Beneficiaries(GrapheneObject): def __init__(self, kwargs): - super().__init__( + super(Beneficiaries, self).__init__( OrderedDict([ ('beneficiaries', Array([Beneficiary(o) for o in kwargs["beneficiaries"]])), @@ -331,7 +331,7 @@ def __init__(self, o): data = Beneficiaries(data) else: raise Exception("Unknown CommentOptionExtension") - super().__init__(data, type_id) + StaticVariant.__init__(self, data, type_id) ######################################################## @@ -357,7 +357,7 @@ def __init__(self, *args, **kwargs): meta = json.dumps(kwargs["json_metadata"]) else: meta = kwargs["json_metadata"] - super().__init__( + super(AccountCreate, self).__init__( OrderedDict([ ('fee', Amount(kwargs["fee"])), ('creator', String(kwargs["creator"])), @@ -388,7 +388,7 @@ def __init__(self, *args, **kwargs): meta = json.dumps(kwargs["json_metadata"]) else: meta = kwargs["json_metadata"] - super().__init__( + super(AccountCreateWithDelegation, self).__init__( OrderedDict([ ('fee', Amount(kwargs["fee"])), ('delegation', Amount(kwargs["delegation"])), @@ -428,7 +428,7 @@ def __init__(self, *args, **kwargs): kwargs["posting"], prefix=prefix) if "posting" in kwargs else None - super().__init__( + super(AccountUpdate, self).__init__( OrderedDict([ ('account', String(kwargs["account"])), ('owner', Optional(owner)), @@ -448,7 +448,7 @@ def __init__(self, *args, **kwargs): kwargs = args[0] if "memo" not in kwargs: kwargs["memo"] = "" - super().__init__( + super(Transfer, self).__init__( OrderedDict([ ('from', String(kwargs["from"])), ('to', String(kwargs["to"])), @@ -464,7 +464,7 @@ def __init__(self, *args, **kwargs): else: if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__( + super(TransferToVesting, self).__init__( OrderedDict([ ('from', String(kwargs["from"])), ('to', String(kwargs["to"])), @@ -479,7 +479,7 @@ def __init__(self, *args, **kwargs): else: if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__( + super(WithdrawVesting, self).__init__( OrderedDict([ ('account', String(kwargs["account"])), ('vesting_shares', Amount(kwargs["vesting_shares"])), @@ -495,7 +495,7 @@ def __init__(self, *args, **kwargs): kwargs = args[0] if "memo" not in kwargs: kwargs["memo"] = "" - super().__init__( + super(TransferToSavings, self).__init__( OrderedDict([ ('from', String(kwargs["from"])), ('to', String(kwargs["to"])), @@ -514,7 +514,7 @@ def __init__(self, *args, **kwargs): if "memo" not in kwargs: kwargs["memo"] = "" - super().__init__( + super(TransferFromSavings, self).__init__( OrderedDict([ ('from', String(kwargs["from"])), ('request_id', Uint32(int(kwargs["request_id"]))), @@ -531,7 +531,7 @@ def __init__(self, *args, **kwargs): else: if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__( + super(CancelTransferFromSavings, self).__init__( OrderedDict([ ('from', String(kwargs["from"])), ('request_id', Uint32(int(kwargs["request_id"]))), @@ -545,7 +545,7 @@ def __init__(self, *args, **kwargs): else: if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__( + super(ClaimRewardBalance, self).__init__( OrderedDict([ ('account', String(kwargs["account"])), ('reward_steem', Amount(kwargs["reward_steem"])), @@ -561,7 +561,7 @@ def __init__(self, *args, **kwargs): else: if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__( + super(DelegateVestingShares, self).__init__( OrderedDict([ ('delegator', String(kwargs["delegator"])), ('delegatee', String(kwargs["delegatee"])), @@ -576,7 +576,7 @@ def __init__(self, *args, **kwargs): else: if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__( + super(LimitOrderCreate, self).__init__( OrderedDict([ ('owner', String(kwargs["owner"])), ('orderid', Uint32(int(kwargs["orderid"]))), @@ -594,7 +594,7 @@ def __init__(self, *args, **kwargs): else: if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__( + super(LimitOrderCancel, self).__init__( OrderedDict([ ('owner', String(kwargs["owner"])), ('orderid', Uint32(int(kwargs["orderid"]))), @@ -608,7 +608,7 @@ def __init__(self, *args, **kwargs): else: if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__( + super(SetWithdrawVestingRoute, self).__init__( OrderedDict([ ('from_account', String(kwargs["from_account"])), ('to_account', String(kwargs["to_account"])), @@ -624,7 +624,7 @@ def __init__(self, *args, **kwargs): else: if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__( + super(Convert, self).__init__( OrderedDict([ ('owner', String(kwargs["owner"])), ('requestid', Uint32(kwargs["requestid"])), @@ -639,7 +639,7 @@ def __init__(self, *args, **kwargs): else: if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__( + super(FeedPublish, self).__init__( OrderedDict([ ('publisher', String(kwargs["publisher"])), ('exchange_rate', ExchangeRate(kwargs["exchange_rate"])), @@ -659,7 +659,7 @@ def __init__(self, *args, **kwargs): kwargs[ "block_signing_key"] = \ "STM1111111111111111111111111111111114T1Anm" - super().__init__( + super(WitnessUpdate, self).__init__( OrderedDict([ ('owner', String(kwargs["owner"])), ('url', String(kwargs["url"])), @@ -677,7 +677,7 @@ def __init__(self, *args, **kwargs): else: if len(args) == 1 and len(kwargs) == 0: kwargs = args[0] - super().__init__( + super(AccountWitnessVote, self).__init__( OrderedDict([ ('account', String(kwargs["account"])), ('witness', String(kwargs["witness"])), @@ -702,7 +702,7 @@ def __init__(self, *args, **kwargs): if len(kwargs["id"]) > 32: raise Exception("'id' too long") - super().__init__( + super(CustomJson, self).__init__( OrderedDict([ ('required_auths', Array([String(o) for o in kwargs["required_auths"]])), @@ -731,7 +731,7 @@ def __init__(self, *args, **kwargs): ext = CommentOptionExtensions(ext_obj) extensions = Array([ext]) - super().__init__( + super(CommentOptions, self).__init__( OrderedDict([ ('author', String(kwargs["author"])), ('permlink', String(kwargs["permlink"])), diff --git a/steembase/transactions.py b/steembase/transactions.py index 7070538..641c58a 100644 --- a/steembase/transactions.py +++ b/steembase/transactions.py @@ -70,7 +70,7 @@ def __init__(self, *args, **kwargs): else: kwargs['operations'] = Array(kwargs["operations"]) - super().__init__( + super(SignedTransaction, self).__init__( OrderedDict([ ('ref_block_num', Uint16(kwargs['ref_block_num'])), ('ref_block_prefix', Uint32(kwargs['ref_block_prefix'])), diff --git a/steembase/types.py b/steembase/types.py index 1dc8e3d..fc6044b 100644 --- a/steembase/types.py +++ b/steembase/types.py @@ -262,7 +262,7 @@ def __str__(self): class Bool(Uint8): # Bool = Uint8 def __init__(self, d): - super().__init__(d) + Uint8.__init__(self, d) def __str__(self): return True if self.data else False @@ -270,7 +270,7 @@ def __str__(self): class Set(Array): # Set = Array def __init__(self, d): - super().__init__(d) + Array.__init__(self, d) class FixedArray: From 7b38785f91a625f7c2f48d1ef99a87959f7e9103 Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 15 Feb 2018 16:02:29 -0600 Subject: [PATCH 047/166] More progress on python 2.7 support. --- steem/amount.py | 1 - steem/steemd.py | 2 -- steem/utils.py | 1 + steembase/base58.py | 26 ++++++++++++++++++++++---- steembase/http_client.py | 1 - steembase/memo.py | 39 +++++++++++++++++++++++++-------------- steembase/types.py | 2 +- 7 files changed, 49 insertions(+), 23 deletions(-) diff --git a/steem/amount.py b/steem/amount.py index 51146c4..34b9ef5 100644 --- a/steem/amount.py +++ b/steem/amount.py @@ -7,7 +7,6 @@ class Amount(dict): """ def __init__(self, amount_string="0 SBD"): - print(type(amount_string)) if isinstance(amount_string, Amount): self["amount"] = amount_string["amount"] self["asset"] = amount_string["asset"] diff --git a/steem/steemd.py b/steem/steemd.py index 53959a7..edd2b1e 100644 --- a/steem/steemd.py +++ b/steem/steemd.py @@ -216,8 +216,6 @@ def get_blocks(self, block_nums): while missing: for block in self._get_blocks(missing): - print("Missing") - print(block) blocks[block['block_num']] = block available = set(blocks.keys()) diff --git a/steem/utils.py b/steem/utils.py index f084c38..3e0efba 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -365,3 +365,4 @@ def compose_dictionary(dictionary, **kwargs): dictionary[pairs[0]] = pairs[1] return dictionary + diff --git a/steembase/base58.py b/steembase/base58.py index 67e191d..83daf89 100644 --- a/steembase/base58.py +++ b/steembase/base58.py @@ -102,7 +102,7 @@ def __str__(self): """ return gphBase58CheckEncode(self._hex) - def __bytes__(self): + def to_bytes(self): """ Return raw bytes :return: Raw bytes of instance @@ -136,9 +136,12 @@ def base58decode(base58_str): def base58encode(hexstring): if sys.version > '3': - byteseq = bytes(unhexlify(bytes(hexstring, 'ascii'))) + byteseq = bytes(hexstring, 'ascii') + byteseq = unhexlify(byteseq) + byteseq = bytes(byteseq) else: - byteseq = bytearray.fromhex(hexstring) + byteseq = bytesAsIntegerArrayFromHexString(bytes(hexstring)) + n = 0 leading_zeroes_count = 0 for c in byteseq: @@ -152,8 +155,8 @@ def base58encode(hexstring): n = div else: res.insert(0, BASE58_ALPHABET[n]) - return (BASE58_ALPHABET[0:1] * leading_zeroes_count + res).decode('ascii') + return (BASE58_ALPHABET[0:1] * leading_zeroes_count + res).decode('ascii') def ripemd160(s): ripemd160 = hashlib.new('ripemd160') @@ -200,3 +203,18 @@ def gphBase58CheckDecode(s): checksum = ripemd160(dec)[:4] assert (s[-4:] == checksum) return dec + + +def bytesAsIntegerArrayFromHexString(hexstring): + if len(hexstring) % 2: + raise ValueError("hexstring must be even length") + + byteArray = [] + hexIterator = iter(hexstring) + + for c in hexIterator: + hexDigit = c + next(hexIterator) + byteArray.append(int(hexDigit, 16)) + + return byteArray + diff --git a/steembase/http_client.py b/steembase/http_client.py index da35194..719c468 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -258,7 +258,6 @@ def _return(self, response=None, args=None, return_with_args=None): result = None else: if 'error' in response_json: - print(response_json) error = response_json['error'] if self.re_raise: diff --git a/steembase/memo.py b/steembase/memo.py index 49948b8..aac44c5 100644 --- a/steembase/memo.py +++ b/steembase/memo.py @@ -2,6 +2,8 @@ import struct from binascii import hexlify, unhexlify import sys +from collections import OrderedDict +import json from .operations import Memo from Crypto.Cipher import AES @@ -81,11 +83,11 @@ def encode_memo(priv, pub, nonce, message, **kwargs): from steembase import transactions shared_secret = get_shared_secret(priv, pub) aes, check = init_aes(shared_secret, nonce) - raw = 0 if sys.version > '3.0': raw = bytes(message, 'utf8') else: - raw = bytes(message).encode('utf8') + raw = bytes(message) + " Padding " BS = 16 if len(raw) % BS: @@ -93,18 +95,22 @@ def encode_memo(priv, pub, nonce, message, **kwargs): " Encryption " cipher = hexlify(aes.encrypt(raw)).decode('ascii') prefix = kwargs.pop("prefix", default_prefix) - s = { - "from": format(priv.pubkey, prefix), - "to": format(pub, prefix), - "nonce": nonce, - "check": check, - "encrypted": cipher, - "from_priv": repr(priv), - "to_pub": repr(pub), - "shared_secret": shared_secret, - } + s = OrderedDict([ + ("from",format(priv.pubkey, prefix)), + ("to", format(pub, prefix)), + ("nonce", nonce), + ("check", check), + ("encrypted", cipher), + ("from_priv", repr(priv)), + ("to_pub", repr(pub)), + ("shared_secret", shared_secret), + ]) + tx = Memo(**s) - return "#" + base58encode(hexlify(bytes(tx)).decode("ascii")) + print(tx.data) + print('\n\n') + print(tx) + return "#" + base58encode(hexlify(bytes(tx.data)).decode("ascii")) def decode_memo(priv, message): @@ -132,8 +138,10 @@ def decode_memo(priv, message): cipher = raw if repr(to_key) == repr(priv.pubkey): + print('first') shared_secret = get_shared_secret(priv, from_key) elif repr(from_key) == repr(priv.pubkey): + print('second') shared_secret = get_shared_secret(priv, to_key) else: raise ValueError("Incorrect PrivateKey") @@ -147,7 +155,10 @@ def decode_memo(priv, message): " Encryption " # remove the varint prefix (FIXME, long messages!) message = cipher[2:] - message = aes.decrypt(unhexlify(bytes(message, 'ascii'))) + if sys.version > '3.0': + message = aes.decrypt(unhexlify(bytes(message, 'ascii'))) + else: + message = aes.decrypt(unhexlify(bytes(message))) try: return _unpad(message.decode('utf8'), 16) except: # noqa FIXME(sneak) diff --git a/steembase/types.py b/steembase/types.py index fc6044b..91c767f 100644 --- a/steembase/types.py +++ b/steembase/types.py @@ -270,7 +270,7 @@ def __str__(self): class Set(Array): # Set = Array def __init__(self, d): - Array.__init__(self, d) + Array.__init__(d) class FixedArray: From 67202fc14caddc1a21865247d2b4889a1aa2aa94 Mon Sep 17 00:00:00 2001 From: John White Date: Fri, 16 Feb 2018 10:13:08 -0600 Subject: [PATCH 048/166] In-progress trying to unify handling of bytes method across 2.7 and 3.6. --- steem/blockchain.py | 7 ++---- steem/utils.py | 15 +++++++++++ steembase/account.py | 22 +++++++--------- steembase/base58.py | 7 +++--- steembase/memo.py | 18 +++++-------- steembase/operations.py | 6 ++--- steembase/storage.py | 2 +- steembase/transactions.py | 15 +++++------ steembase/types.py | 42 +++++++++++++++---------------- tests/steem/test_transactions.py | 43 ++++++++++++++++---------------- 10 files changed, 91 insertions(+), 86 deletions(-) diff --git a/steem/blockchain.py b/steem/blockchain.py index 403b17a..dcd4200 100644 --- a/steem/blockchain.py +++ b/steem/blockchain.py @@ -5,7 +5,7 @@ import sys from .instance import shared_steemd_instance, stm -from .utils import parse_time +from .utils import parse_time, future_bytes import logging logger = logging.getLogger(__name__) @@ -300,10 +300,7 @@ def replay(self, **kwargs): def hash_op(event): """ This method generates a hash of blockchain operation. """ data = json.dumps(event, sort_keys=True) - if sys.version > '3': - return hashlib.sha1(bytes(data, 'utf-8')).hexdigest() - else: - return hashlib.sha1(bytes(data).encode('utf-8')).hexdigest() + return hashlib.sha1(future_bytes(data, 'utf-8')).hexdigest() def get_all_usernames(self, *args, **kwargs): """ Fetch the full list of STEEM usernames. """ diff --git a/steem/utils.py b/steem/utils.py index 3e0efba..15b7ab8 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -366,3 +366,18 @@ def compose_dictionary(dictionary, **kwargs): return dictionary + +def future_bytes(item, encoding=None): + if hasattr(item, 'to_bytes'): + return item.to_bytes() + else: + if sys.version > '3.0': + if encoding: + return bytes(item, encoding) + else: + return bytes(item) + else: + if encoding: + return bytes(item).encode(encoding) + else: + return bytes(item) diff --git a/steembase/account.py b/steembase/account.py index fdfa83b..66111a2 100644 --- a/steembase/account.py +++ b/steembase/account.py @@ -3,6 +3,7 @@ import re import sys from binascii import hexlify, unhexlify +from steem.utils import future_bytes import ecdsa @@ -26,10 +27,7 @@ def get_private(self): """ Derive private key from the brain key and the current sequence number """ - if sys.version > '3': - a = bytes(self.account + self.role + self.password, 'utf8') - else: - a = bytes(self.account + self.role + self.password).encode('utf8') + a = future_bytes(self.account + self.role + self.password, 'utf8') s = hashlib.sha256(a).digest() return PrivateKey(hexlify(s).decode('ascii')) @@ -95,10 +93,8 @@ def get_private(self): number """ encoded = "%s %d" % (self.brainkey, self.sequence) - if sys.version > '3': - a = bytes(encoded, 'ascii') - else: - a = bytes(encoded).encode('ascii') + a = future_bytes(encoded, 'ascii') + s = hashlib.sha256(hashlib.sha512(a).digest()).digest() return PrivateKey(hexlify(s).decode('ascii')) @@ -190,7 +186,7 @@ def __format__(self, _format): else: return format(self._address, _format) - def __bytes__(self): + def to_bytes(self): """ Returns the raw content of the ``Base58CheckEncoded`` address """ if self._address is None: return bytes(self.derivesha512address()) @@ -238,11 +234,11 @@ def compressed(self): """ Derive compressed public key """ order = ecdsa.SECP256k1.generator.order() p = ecdsa.VerifyingKey.from_string( - bytes(self), curve=ecdsa.SECP256k1).pubkey.point + future_bytes(self), curve=ecdsa.SECP256k1).pubkey.point x_str = ecdsa.util.number_to_string(p.x(), order) # y_str = ecdsa.util.number_to_string(p.y(), order) compressed = hexlify( - bytes(chr(2 + (p.y() & 1)), 'ascii') + x_str).decode('ascii') + future_bytes(chr(2 + (p.y() & 1)), 'ascii') + x_str).decode('ascii') return (compressed) def unCompressed(self): @@ -278,7 +274,7 @@ def __format__(self, _format): to ``_format`` """ return format(self._pk, _format) - def __bytes__(self): + def to_bytes(self): """ Returns the raw public key (has length 33)""" return bytes(self._pk) @@ -355,6 +351,6 @@ def __str__(self): """ return format(self._wif, "WIF") - def __bytes__(self): + def to_bytes(self): """ Returns the raw private key """ return bytes(self._wif) diff --git a/steembase/base58.py b/steembase/base58.py index 83daf89..c5f6753 100644 --- a/steembase/base58.py +++ b/steembase/base58.py @@ -3,6 +3,7 @@ import sys import string import logging +from steem.utils import future_bytes log = logging.getLogger(__name__) """ This class and the methods require python3 """ # FIXME this library needs to support both 2 and 3 @@ -136,11 +137,11 @@ def base58decode(base58_str): def base58encode(hexstring): if sys.version > '3': - byteseq = bytes(hexstring, 'ascii') + byteseq = future_bytes(hexstring, 'ascii') byteseq = unhexlify(byteseq) - byteseq = bytes(byteseq) + byteseq = future_bytes(byteseq) else: - byteseq = bytesAsIntegerArrayFromHexString(bytes(hexstring)) + byteseq = bytesAsIntegerArrayFromHexString(future_bytes(hexstring)) n = 0 leading_zeroes_count = 0 diff --git a/steembase/memo.py b/steembase/memo.py index aac44c5..3600c5a 100644 --- a/steembase/memo.py +++ b/steembase/memo.py @@ -63,8 +63,8 @@ def _pad(s, BS): def _unpad(s, BS): - count = int(struct.unpack('B', bytes(s[-1], 'ascii'))[0]) - if bytes(s[-count::], 'ascii') == count * struct.pack('B', count): + count = int(struct.unpack('B', future_bytes(s[-1], 'ascii'))[0]) + if future_bytes(s[-count::], 'ascii') == count * struct.pack('B', count): return s[:-count] return s @@ -83,10 +83,7 @@ def encode_memo(priv, pub, nonce, message, **kwargs): from steembase import transactions shared_secret = get_shared_secret(priv, pub) aes, check = init_aes(shared_secret, nonce) - if sys.version > '3.0': - raw = bytes(message, 'utf8') - else: - raw = bytes(message) + raw = future_bytes(message) " Padding " BS = 16 @@ -109,8 +106,8 @@ def encode_memo(priv, pub, nonce, message, **kwargs): tx = Memo(**s) print(tx.data) print('\n\n') - print(tx) - return "#" + base58encode(hexlify(bytes(tx.data)).decode("ascii")) + print(tx.to_bytes()) + return "#" + base58encode(hexlify(future_bytes(tx.data)).decode("ascii")) def decode_memo(priv, message): @@ -155,10 +152,7 @@ def decode_memo(priv, message): " Encryption " # remove the varint prefix (FIXME, long messages!) message = cipher[2:] - if sys.version > '3.0': - message = aes.decrypt(unhexlify(bytes(message, 'ascii'))) - else: - message = aes.decrypt(unhexlify(bytes(message))) + message = aes.decrypt(unhexlify(future_bytes(message, 'ascii'))) try: return _unpad(message.decode('utf8'), 16) except: # noqa FIXME(sneak) diff --git a/steembase/operations.py b/steembase/operations.py index fadb310..5be0e1d 100644 --- a/steembase/operations.py +++ b/steembase/operations.py @@ -74,7 +74,7 @@ def get_class(class_name): module = importlib.import_module('steembase.operations') return getattr(module, class_name) - def __bytes__(self): + def to_bytes(self): return bytes(Id(self.opId)) + bytes(self.op) def __str__(self): @@ -97,7 +97,7 @@ class GrapheneObject(object): def __init__(self, data=None): self.data = data - def __bytes__(self): + def to_bytes(self): if self.data is None: return bytes() b = b"" @@ -241,7 +241,7 @@ def __init__(self, d): else: raise Exception("Asset unknown") - def __bytes__(self): + def to_bytes(self): # padding asset = self.asset + "\x00" * (7 - len(self.asset)) amount = round(float(self.amount) * 10**self.precision) diff --git a/steembase/storage.py b/steembase/storage.py index 252f460..d47c38d 100644 --- a/steembase/storage.py +++ b/steembase/storage.py @@ -399,7 +399,7 @@ def newMaster(self): def deriveChecksum(self, s): """ Derive the checksum """ - checksum = hashlib.sha256(bytes(s, "ascii")).hexdigest() + checksum = hashlib.sha256(future_bytes(s, "ascii")).hexdigest() return checksum[:4] def getEncryptedMaster(self): diff --git a/steembase/transactions.py b/steembase/transactions.py index 641c58a..374efce 100644 --- a/steembase/transactions.py +++ b/steembase/transactions.py @@ -8,6 +8,7 @@ import ecdsa +from steem.utils import future_bytes from .account import PrivateKey, PublicKey from .chains import known_chains from .operations import Operation, GrapheneObject, isArgsThisClass @@ -113,7 +114,7 @@ def compressedPubkey(self, pk): order = pk.curve.generator.order() p = pk.pubkey.point x_str = ecdsa.util.number_to_string(p.x(), order) - return bytes(chr(2 + (p.y() & 1)), 'ascii') + x_str + return future_bytes(chr(2 + (p.y() & 1)), 'ascii') + x_str # FIXME(sneak) this should be reviewed for correctness def recover_public_key(self, digest, signature, i): @@ -180,7 +181,7 @@ def deriveDigest(self, chain): # Get message to sign # bytes(self) will give the wire formated data according to # GrapheneObject and the data given in __init__() - self.message = unhexlify(self.chainid) + bytes(self) + self.message = unhexlify(self.chainid) + future_bytes(self) self.digest = hashlib.sha256(self.message).digest() # restore signatures @@ -195,9 +196,9 @@ def verify(self, pubkeys=[], chain=None): pubKeysFound = [] for signature in signatures: - sig = bytes(signature)[1:] + sig = future_bytes(signature)[1:] recoverParameter = ( - bytes(signature)[0]) - 4 - 27 # recover parameter only + future_bytes(signature)[0]) - 4 - 27 # recover parameter only if USE_SECP256K1: ALL_FLAGS = secp256k1.lib.SECP256K1_CONTEXT_VERIFY | \ @@ -208,11 +209,11 @@ def verify(self, pubkeys=[], chain=None): sig = pub.ecdsa_recoverable_deserialize(sig, recoverParameter) # Recover PublicKey verifyPub = secp256k1.PublicKey( - pub.ecdsa_recover(bytes(self.message), sig)) + pub.ecdsa_recover(future_bytes(self.message), sig)) # Convert recoverable sig to normal sig normalSig = verifyPub.ecdsa_recoverable_convert(sig) # Verify - verifyPub.ecdsa_verify(bytes(self.message), normalSig) + verifyPub.ecdsa_verify(future_bytes(self.message), normalSig) phex = hexlify( verifyPub.serialize(compressed=True)).decode('ascii') pubKeysFound.append(phex) @@ -263,7 +264,7 @@ def sign(self, wifkeys, chain=None): # Sign the message with every private key given! sigs = [] for wif in self.privkeys: - p = bytes(PrivateKey(wif)) + p = future_bytes(PrivateKey(wif)) i = 0 if USE_SECP256K1: ndata = secp256k1.ffi.new("const int *ndata") diff --git a/steembase/types.py b/steembase/types.py index 91c767f..7fbbafa 100644 --- a/steembase/types.py +++ b/steembase/types.py @@ -77,7 +77,7 @@ class Uint8: def __init__(self, d): self.data = d - def __bytes__(self): + def to_bytes(self): return struct.pack(" Date: Fri, 16 Feb 2018 10:23:04 -0600 Subject: [PATCH 049/166] Added pytest-cov plugin. --- Pipfile | 5 +++-- Pipfile.lock | 59 +++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/Pipfile b/Pipfile index 29ea21a..e79536a 100644 --- a/Pipfile +++ b/Pipfile @@ -19,8 +19,7 @@ toolz = "*" "urllib3" = "*" voluptuous = "*" "w3lib" = "*" -"pytest-pep8" = "*" -"futures" = {version = "*", markers="python_version < '3.0.0'"} +futures = {version = "*", markers="python_version < '3.0.0'"} [dev-packages] @@ -34,5 +33,7 @@ pytest-pylint = "*" recommonmark = "*" yapf = "*" "autopep8" = "*" +pytest-cov = "*" +"pytest-pep8" = "*" #[requires] #python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock index a7c06c3..db5d326 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f3b8df34e618448650982c10e58820f7d2f629272e4c590da28878d3e536f50b" + "sha256": "400262b6fc3e62a76e26cedacbedcfea2e881da6b20689eb913406cc123c586a" }, "host-environment-markers": { "implementation_name": "cpython", @@ -55,6 +55,47 @@ ], "version": "==2018.1.18" }, + "coverage": { + "hashes": [ + "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", + "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", + "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80", + "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", + "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", + "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", + "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", + "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", + "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", + "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", + "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", + "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", + "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", + "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", + "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", + "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", + "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", + "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", + "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", + "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", + "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", + "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", + "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", + "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", + "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", + "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", + "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", + "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", + "sha256:ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91", + "sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2", + "sha256:b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d", + "sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a", + "sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4", + "sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd", + "sha256:e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77", + "sha256:f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e" + ], + "version": "==4.5.1" + }, "ecdsa": { "hashes": [ "sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c", @@ -144,6 +185,13 @@ ], "version": "==1.0" }, + "pytest-cov": { + "hashes": [ + "sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec", + "sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d" + ], + "version": "==2.5.1" + }, "pytest-pep8": { "hashes": [ "sha256:032ef7e5fa3ac30f4458c73e05bb67b0f036a8a5cb418a534b3170f89f120318" @@ -189,9 +237,10 @@ }, "voluptuous": { "hashes": [ - "sha256:7a7466f8dc3666a292d186d1d871a47bf2120836ccb900d5ba904674957a2396" + "sha256:6bbbe83a509d948230e9f921ba2e75173590be988eeb446dbe7a54268bef4da8", + "sha256:af7315c9fa99e0bfd195a21106c82c81619b42f0bd9b6e287b797c6b6b6a9918" ], - "version": "==0.10.5" + "version": "==0.11.1" }, "w3lib": { "hashes": [ @@ -242,7 +291,7 @@ "sha256:f0b0e4eba956de51238e17573b7087e852dfe9854afd2e9c873f73fc0ca0a6dd", "sha256:9d98697f088eb1b0fa451391f91afb5e3ebde16bbdb272819fd091151fda4f1a" ], - "markers": "python_version < '3.4'", + "markers": "python_version == '2.7'", "version": "==1.5" }, "backports.shutil-get-terminal-size": { @@ -303,7 +352,7 @@ "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" ], - "markers": "python_version < '3.0'", + "markers": "python_version < '3.3'", "version": "==1.0.2" }, "futures": { From 830cc4539c8375dc6b3e359e59a9c4a154150fa5 Mon Sep 17 00:00:00 2001 From: John White Date: Fri, 16 Feb 2018 10:51:29 -0600 Subject: [PATCH 050/166] Small fix in Pipfile. --- Pipfile | 1 - steembase/types.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Pipfile b/Pipfile index e79536a..0b35e4b 100644 --- a/Pipfile +++ b/Pipfile @@ -34,6 +34,5 @@ recommonmark = "*" yapf = "*" "autopep8" = "*" pytest-cov = "*" -"pytest-pep8" = "*" #[requires] #python_version = "3.6" diff --git a/steembase/types.py b/steembase/types.py index 7fbbafa..6f38e48 100644 --- a/steembase/types.py +++ b/steembase/types.py @@ -3,6 +3,7 @@ import time from binascii import hexlify, unhexlify from calendar import timegm +from steem.utils import future_bytes object_type = { "dynamic_global_property": 0, @@ -270,7 +271,7 @@ def __str__(self): class Set(Array): # Set = Array def __init__(self, d): - Array.__init__(d) + Array.__init__(self, d) class FixedArray: From 4c727e54f0129d8a6abfc1c48b9241f6a119b953 Mon Sep 17 00:00:00 2001 From: John White Date: Fri, 16 Feb 2018 15:27:17 -0600 Subject: [PATCH 051/166] Mostly fixed bytes handling, still trying to discern the issues in memo.py. --- Pipfile | 1 + Pipfile.lock | 100 ++++++++++++++++++++-------------------- steem/utils.py | 11 +++-- steembase/account.py | 8 ++-- steembase/bip38.py | 15 ++---- steembase/memo.py | 18 ++++---- steembase/operations.py | 9 ++-- steembase/types.py | 18 ++++---- 8 files changed, 89 insertions(+), 91 deletions(-) diff --git a/Pipfile b/Pipfile index 0b35e4b..8836a7b 100644 --- a/Pipfile +++ b/Pipfile @@ -20,6 +20,7 @@ toolz = "*" voluptuous = "*" "w3lib" = "*" futures = {version = "*", markers="python_version < '3.0.0'"} +"pytest-pep8" = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index db5d326..9bca9a8 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "400262b6fc3e62a76e26cedacbedcfea2e881da6b20689eb913406cc123c586a" + "sha256": "ded82e4047de6bf6a5027166a8618d228df337f22d35b6c48c3a68cbe8151e2d" }, "host-environment-markers": { "implementation_name": "cpython", @@ -55,47 +55,6 @@ ], "version": "==2018.1.18" }, - "coverage": { - "hashes": [ - "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", - "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", - "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80", - "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", - "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", - "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", - "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", - "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", - "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", - "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", - "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", - "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", - "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", - "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", - "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", - "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", - "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", - "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", - "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", - "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", - "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", - "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", - "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", - "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", - "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", - "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", - "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", - "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", - "sha256:ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91", - "sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2", - "sha256:b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d", - "sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a", - "sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4", - "sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd", - "sha256:e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77", - "sha256:f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e" - ], - "version": "==4.5.1" - }, "ecdsa": { "hashes": [ "sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c", @@ -185,13 +144,6 @@ ], "version": "==1.0" }, - "pytest-cov": { - "hashes": [ - "sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec", - "sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d" - ], - "version": "==2.5.1" - }, "pytest-pep8": { "hashes": [ "sha256:032ef7e5fa3ac30f4458c73e05bb67b0f036a8a5cb418a534b3170f89f120318" @@ -315,6 +267,47 @@ "markers": "python_version == '2.7'", "version": "==3.5.0" }, + "coverage": { + "hashes": [ + "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", + "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", + "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80", + "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", + "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", + "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", + "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", + "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", + "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", + "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", + "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", + "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", + "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", + "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", + "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", + "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", + "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", + "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", + "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", + "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", + "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", + "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", + "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", + "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", + "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", + "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", + "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", + "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", + "sha256:ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91", + "sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2", + "sha256:b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d", + "sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a", + "sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4", + "sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd", + "sha256:e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77", + "sha256:f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e" + ], + "version": "==4.5.1" + }, "decorator": { "hashes": [ "sha256:94d1d8905f5010d74bbbd86c30471255661a14187c45f8d7f3e5aa8540fdb2e5", @@ -438,7 +431,7 @@ "sha256:db3e43032d23787d3e9aec8c7ef1e0d2c3c589d5f303477661ebda2ca6d4bfba", "sha256:d32550b75a818b289bd4c1f96b60c89957811da205afcceab75bc8b4857ea5b3" ], - "markers": "python_version in '2.6 2.7 3.2 3.3'", + "markers": "python_version == '2.7' or python_version == '3.3'", "version": "==2.3.0" }, "pbr": { @@ -538,6 +531,13 @@ ], "version": "==0.1.4" }, + "pytest-cov": { + "hashes": [ + "sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec", + "sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d" + ], + "version": "==2.5.1" + }, "pytest-pep8": { "hashes": [ "sha256:032ef7e5fa3ac30f4458c73e05bb67b0f036a8a5cb418a534b3170f89f120318" diff --git a/steem/utils.py b/steem/utils.py index 15b7ab8..8255278 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -368,14 +368,17 @@ def compose_dictionary(dictionary, **kwargs): def future_bytes(item, encoding=None): - if hasattr(item, 'to_bytes'): - return item.to_bytes() - else: - if sys.version > '3.0': + if sys.version > '3.0': + if hasattr(item, 'to_bytes'): + return item.to_bytes() + else: if encoding: return bytes(item, encoding) else: return bytes(item) + else: + if hasattr(item, 'to_bytes'): + return item.to_bytes() else: if encoding: return bytes(item).encode(encoding) diff --git a/steembase/account.py b/steembase/account.py index 66111a2..3de014d 100644 --- a/steembase/account.py +++ b/steembase/account.py @@ -189,9 +189,9 @@ def __format__(self, _format): def to_bytes(self): """ Returns the raw content of the ``Base58CheckEncoded`` address """ if self._address is None: - return bytes(self.derivesha512address()) + return future_bytes(self.derivesha512address()) else: - return bytes(self._address) + return future_bytes(self._address) class PublicKey(object): @@ -276,7 +276,7 @@ def __format__(self, _format): def to_bytes(self): """ Returns the raw public key (has length 33)""" - return bytes(self._pk) + return future_bytes(self._pk) class PrivateKey(object): @@ -353,4 +353,4 @@ def __str__(self): def to_bytes(self): """ Returns the raw private key """ - return bytes(self._wif) + return future_bytes(self._wif) diff --git a/steembase/bip38.py b/steembase/bip38.py index b0c3de9..3d2fa85 100644 --- a/steembase/bip38.py +++ b/steembase/bip38.py @@ -5,6 +5,7 @@ from .account import PrivateKey from .base58 import Base58, base58decode +from steem.utils import future_bytes log = logging.getLogger(__name__) @@ -52,15 +53,12 @@ def encrypt(privkey, passphrase): """ privkeyhex = repr(privkey) # hex addr = format(privkey.uncompressed.address, "BTC") - if sys.version > '3': - a = bytes(addr, 'ascii') - else: - a = bytes(addr).encode('ascii') + a = future_bytes(addr, 'ascii') salt = hashlib.sha256(hashlib.sha256(a).digest()).digest()[0:4] if SCRYPT_MODULE == "scrypt": key = scrypt.hash(passphrase, salt, 16384, 8, 8) elif SCRYPT_MODULE == "pylibscrypt": - key = scrypt.scrypt(bytes(passphrase, "utf-8"), salt, 16384, 8, 8) + key = scrypt.scrypt(future_bytes(passphrase, "utf-8"), salt, 16384, 8, 8) else: raise ValueError("No scrypt module loaded") (derived_half1, derived_half2) = (key[:32], key[32:]) @@ -98,7 +96,7 @@ def decrypt(encrypted_privkey, passphrase): if SCRYPT_MODULE == "scrypt": key = scrypt.hash(passphrase, salt, 16384, 8, 8) elif SCRYPT_MODULE == "pylibscrypt": - key = scrypt.scrypt(bytes(passphrase, "utf-8"), salt, 16384, 8, 8) + key = scrypt.scrypt(future_bytes(passphrase, "utf-8"), salt, 16384, 8, 8) else: raise ValueError("No scrypt module loaded") derivedhalf1 = key[0:32] @@ -115,10 +113,7 @@ def decrypt(encrypted_privkey, passphrase): """ Verify Salt """ privkey = PrivateKey(format(wif, "wif")) addr = format(privkey.uncompressed.address, "BTC") - if sys.version > '3': - a = bytes(addr, 'ascii') - else: - a = bytes(addr).encode('ascii') + a = future_bytes(addr, 'ascii') saltverify = hashlib.sha256(hashlib.sha256(a).digest()).digest()[0:4] if saltverify != salt: raise SaltException( diff --git a/steembase/memo.py b/steembase/memo.py index 3600c5a..511c60f 100644 --- a/steembase/memo.py +++ b/steembase/memo.py @@ -1,14 +1,16 @@ import hashlib +import json import struct -from binascii import hexlify, unhexlify import sys +from binascii import hexlify, unhexlify from collections import OrderedDict -import json -from .operations import Memo from Crypto.Cipher import AES + +from .operations import Memo from .base58 import base58encode, base58decode from .account import PrivateKey, PublicKey +from steem.utils import future_bytes default_prefix = "STM" @@ -83,7 +85,7 @@ def encode_memo(priv, pub, nonce, message, **kwargs): from steembase import transactions shared_secret = get_shared_secret(priv, pub) aes, check = init_aes(shared_secret, nonce) - raw = future_bytes(message) + raw = future_bytes(message, 'utf8') " Padding " BS = 16 @@ -102,11 +104,8 @@ def encode_memo(priv, pub, nonce, message, **kwargs): ("to_pub", repr(pub)), ("shared_secret", shared_secret), ]) - tx = Memo(**s) - print(tx.data) - print('\n\n') - print(tx.to_bytes()) + return "#" + base58encode(hexlify(future_bytes(tx.data)).decode("ascii")) @@ -123,6 +122,7 @@ def decode_memo(priv, message): """ " decode structure " raw = base58decode(message[1:]) + print('\n\n base58 decoded raw data') print(raw) from_key = PublicKey(raw[:66]) raw = raw[66:] @@ -135,10 +135,8 @@ def decode_memo(priv, message): cipher = raw if repr(to_key) == repr(priv.pubkey): - print('first') shared_secret = get_shared_secret(priv, from_key) elif repr(from_key) == repr(priv.pubkey): - print('second') shared_secret = get_shared_secret(priv, to_key) else: raise ValueError("Incorrect PrivateKey") diff --git a/steembase/operations.py b/steembase/operations.py index 5be0e1d..5c0031f 100644 --- a/steembase/operations.py +++ b/steembase/operations.py @@ -4,6 +4,7 @@ import struct from collections import OrderedDict +from steem.utils import future_bytes from .account import PublicKey from .operationids import operations from .types import (Int16, Uint16, Uint32, Uint64, String, Bytes, Array, @@ -75,7 +76,7 @@ def get_class(class_name): return getattr(module, class_name) def to_bytes(self): - return bytes(Id(self.opId)) + bytes(self.op) + return future_bytes(Id(self.opId)) + future_bytes(self.op) def __str__(self): return json.dumps( @@ -103,9 +104,9 @@ def to_bytes(self): b = b"" for name, value in self.data.items(): if isinstance(value, str): - b += bytes(value, 'utf-8') + b += future_bytes(value, 'utf-8') else: - b += bytes(value) + b += future_bytes(value) return b def __json__(self): @@ -246,7 +247,7 @@ def to_bytes(self): asset = self.asset + "\x00" * (7 - len(self.asset)) amount = round(float(self.amount) * 10**self.precision) return (struct.pack(" Date: Fri, 16 Feb 2018 15:40:58 -0600 Subject: [PATCH 052/166] Removed some erroneous print statements. --- steembase/memo.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/steembase/memo.py b/steembase/memo.py index 511c60f..2c37674 100644 --- a/steembase/memo.py +++ b/steembase/memo.py @@ -106,7 +106,7 @@ def encode_memo(priv, pub, nonce, message, **kwargs): ]) tx = Memo(**s) - return "#" + base58encode(hexlify(future_bytes(tx.data)).decode("ascii")) + return "#" + base58encode(hexlify(future_bytes(tx)).decode("ascii")) def decode_memo(priv, message): @@ -122,8 +122,6 @@ def decode_memo(priv, message): """ " decode structure " raw = base58decode(message[1:]) - print('\n\n base58 decoded raw data') - print(raw) from_key = PublicKey(raw[:66]) raw = raw[66:] to_key = PublicKey(raw[:66]) From 78312dfbec00b7eab59007892ca35f99cf732de8 Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 19 Feb 2018 10:30:12 -0600 Subject: [PATCH 053/166] Gate memo encoding and decoding on Python 2. --- steembase/memo.py | 6 ++++++ steembase/types.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/steembase/memo.py b/steembase/memo.py index 2c37674..7380e28 100644 --- a/steembase/memo.py +++ b/steembase/memo.py @@ -72,6 +72,9 @@ def _unpad(s, BS): def encode_memo(priv, pub, nonce, message, **kwargs): + + if sys.version < '3.0': + raise NotImplementedError("Memo encoding not currently supported in Python 2.") """ Encode a message with a shared secret between Alice and Bob :param PrivateKey priv: Private Key (of Alice) @@ -110,6 +113,9 @@ def encode_memo(priv, pub, nonce, message, **kwargs): def decode_memo(priv, message): + + if sys.version < '3.0': + raise NotImplementedError("Memo encoding not currently supported in Python 2.") """ Decode a message with a shared secret between Alice and Bob :param PrivateKey priv: Private Key (of Bob) diff --git a/steembase/types.py b/steembase/types.py index d0f4363..d766e95 100644 --- a/steembase/types.py +++ b/steembase/types.py @@ -184,7 +184,7 @@ def unicodify(self): r.append("u%04x" % o) else: r.append(s) - return bytes("".join(r), "utf-8") + return future_bytes("".join(r), "utf-8") class Bytes: From 72e942d87848eae5784f55d01f5ea166cf06bddd Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 22 Feb 2018 11:58:14 -0600 Subject: [PATCH 054/166] Added a to_chr method to handle the difference between Python 3.6 chr() and 2.7 unichr(). --- steem/utils.py | 30 ++++++++++++++++-------------- steembase/account.py | 9 ++++----- steembase/base58.py | 21 +++------------------ steembase/http_client.py | 11 +---------- steembase/memo.py | 8 ++++---- steembase/transactions.py | 15 ++++++++++----- steembase/types.py | 5 +++-- tests/steem/test_transactions.py | 4 ++-- 8 files changed, 43 insertions(+), 60 deletions(-) diff --git a/steem/utils.py b/steem/utils.py index 8255278..e41f7e7 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -7,6 +7,9 @@ import sys from datetime import datetime +import future +from builtins import bytes + import w3lib.url from langdetect import DetectorFactory, detect from langdetect.lang_detect_exception import LangDetectException @@ -368,19 +371,18 @@ def compose_dictionary(dictionary, **kwargs): def future_bytes(item, encoding=None): - if sys.version > '3.0': - if hasattr(item, 'to_bytes'): - return item.to_bytes() - else: - if encoding: - return bytes(item, encoding) - else: - return bytes(item) + + if hasattr(item, 'to_bytes'): + return item.to_bytes() else: - if hasattr(item, 'to_bytes'): - return item.to_bytes() + if encoding: + return bytes(item, encoding) else: - if encoding: - return bytes(item).encode(encoding) - else: - return bytes(item) + return bytes(item) + + +def to_chr(item): + if sys.version >= '3.0': + return chr(item) + else: + return unichr(item) diff --git a/steembase/account.py b/steembase/account.py index 3de014d..ed7ccf8 100644 --- a/steembase/account.py +++ b/steembase/account.py @@ -1,9 +1,8 @@ import hashlib import os import re -import sys from binascii import hexlify, unhexlify -from steem.utils import future_bytes +from steem.utils import future_bytes, to_chr import ecdsa @@ -238,7 +237,7 @@ def compressed(self): x_str = ecdsa.util.number_to_string(p.x(), order) # y_str = ecdsa.util.number_to_string(p.y(), order) compressed = hexlify( - future_bytes(chr(2 + (p.y() & 1)), 'ascii') + x_str).decode('ascii') + future_bytes(to_chr(2 + (p.y() & 1)), 'ascii') + x_str).decode('ascii') return (compressed) def unCompressed(self): @@ -330,8 +329,8 @@ def compressedpubkey(self): x_str = ecdsa.util.number_to_string(p.x(), order) y_str = ecdsa.util.number_to_string(p.y(), order) compressed = hexlify( - chr(2 + (p.y() & 1)).encode('ascii') + x_str).decode('ascii') - uncompressed = hexlify(chr(4).encode('ascii') + x_str + y_str).decode( + to_chr(2 + (p.y() & 1)).encode('ascii') + x_str).decode('ascii') + uncompressed = hexlify(to_chr(4).encode('ascii') + x_str + y_str).decode( 'ascii') return [compressed, uncompressed] diff --git a/steembase/base58.py b/steembase/base58.py index c5f6753..dd4ab42 100644 --- a/steembase/base58.py +++ b/steembase/base58.py @@ -136,12 +136,9 @@ def base58decode(base58_str): def base58encode(hexstring): - if sys.version > '3': - byteseq = future_bytes(hexstring, 'ascii') - byteseq = unhexlify(byteseq) - byteseq = future_bytes(byteseq) - else: - byteseq = bytesAsIntegerArrayFromHexString(future_bytes(hexstring)) + byteseq = future_bytes(hexstring, 'ascii') + byteseq = unhexlify(byteseq) + byteseq = future_bytes(byteseq) n = 0 leading_zeroes_count = 0 @@ -206,16 +203,4 @@ def gphBase58CheckDecode(s): return dec -def bytesAsIntegerArrayFromHexString(hexstring): - if len(hexstring) % 2: - raise ValueError("hexstring must be even length") - - byteArray = [] - hexIterator = iter(hexstring) - - for c in hexIterator: - hexDigit = c + next(hexIterator) - byteArray.append(int(hexDigit, 16)) - - return byteArray diff --git a/steembase/http_client.py b/steembase/http_client.py index 719c468..dc06ac1 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -15,10 +15,8 @@ if sys.version > '3': from urllib.parse import urlparse - from http.client import RemoteDisconnected else: from urlparse import urlparse - from httplib import HTTPException logger = logging.getLogger(__name__) @@ -196,14 +194,7 @@ def call(self, try: response = self.request(body=body) - errorList = 0 - if sys.version > '3': - errorList = {MaxRetryError, ReadTimeoutError, ProtocolError, - RemoteDisconnected, ConnectionResetError} - else: - errorList = {MaxRetryError, ReadTimeoutError, ProtocolError, - HTTPException} - except (errorList) as e: + except (MaxRetryError, ReadTimeoutError, ProtocolError) as e: # if we broadcasted a transaction, always raise # this is to prevent potential for double spend scenario if api == 'network_broadcast_api': diff --git a/steembase/memo.py b/steembase/memo.py index 7380e28..c1893a5 100644 --- a/steembase/memo.py +++ b/steembase/memo.py @@ -73,8 +73,8 @@ def _unpad(s, BS): def encode_memo(priv, pub, nonce, message, **kwargs): - if sys.version < '3.0': - raise NotImplementedError("Memo encoding not currently supported in Python 2.") + # if sys.version < '3.0': + # raise NotImplementedError("Memo encoding not currently supported in Python 2.") """ Encode a message with a shared secret between Alice and Bob :param PrivateKey priv: Private Key (of Alice) @@ -114,8 +114,8 @@ def encode_memo(priv, pub, nonce, message, **kwargs): def decode_memo(priv, message): - if sys.version < '3.0': - raise NotImplementedError("Memo encoding not currently supported in Python 2.") + # if sys.version < '3.0': + # raise NotImplementedError("Memo encoding not currently supported in Python 2.") """ Decode a message with a shared secret between Alice and Bob :param PrivateKey priv: Private Key (of Bob) diff --git a/steembase/transactions.py b/steembase/transactions.py index 374efce..2b679d6 100644 --- a/steembase/transactions.py +++ b/steembase/transactions.py @@ -2,13 +2,15 @@ import logging import struct import time +import array +import sys from binascii import hexlify, unhexlify from collections import OrderedDict from datetime import datetime import ecdsa -from steem.utils import future_bytes +from steem.utils import future_bytes, to_chr from .account import PrivateKey, PublicKey from .chains import known_chains from .operations import Operation, GrapheneObject, isArgsThisClass @@ -114,7 +116,7 @@ def compressedPubkey(self, pk): order = pk.curve.generator.order() p = pk.pubkey.point x_str = ecdsa.util.number_to_string(p.x(), order) - return future_bytes(chr(2 + (p.y() & 1)), 'ascii') + x_str + return future_bytes(to_chr(2 + (p.y() & 1)), 'ascii') + x_str # FIXME(sneak) this should be reviewed for correctness def recover_public_key(self, digest, signature, i): @@ -179,7 +181,7 @@ def deriveDigest(self, chain): self.data["signatures"] = [] # Get message to sign - # bytes(self) will give the wire formated data according to + # bytes(self) will give the wire formatted data according to # GrapheneObject and the data given in __init__() self.message = unhexlify(self.chainid) + future_bytes(self) self.digest = hashlib.sha256(self.message).digest() @@ -197,8 +199,10 @@ def verify(self, pubkeys=[], chain=None): for signature in signatures: sig = future_bytes(signature)[1:] - recoverParameter = ( - future_bytes(signature)[0]) - 4 - 27 # recover parameter only + if sys.version > '3.0': + recoverParameter = (future_bytes(signature)[0]) - 4 - 27 # recover parameter only + else: + recoverParameter = ord((future_bytes(signature)[0])) - 4 - 27 if USE_SECP256K1: ALL_FLAGS = secp256k1.lib.SECP256K1_CONTEXT_VERIFY | \ @@ -316,6 +320,7 @@ def sign(self, wifkeys, chain=None): # Make sure signature is canonical! # + sigder = array.array('B', sigder) lenR = sigder[3] lenS = sigder[5 + lenR] if lenR is 32 and lenS is 32: diff --git a/steembase/types.py b/steembase/types.py index d766e95..f56edab 100644 --- a/steembase/types.py +++ b/steembase/types.py @@ -1,6 +1,7 @@ import json import struct import time +import array from binascii import hexlify, unhexlify from calendar import timegm from steem.utils import future_bytes @@ -35,9 +36,9 @@ def varint(n): """ data = b'' while n >= 0x80: - data += bytes([(n & 0x7f) | 0x80]) + data += future_bytes([(n & 0x7f) | 0x80]) n >>= 7 - data += bytes([n]) + data += future_bytes([n]) return data diff --git a/tests/steem/test_transactions.py b/tests/steem/test_transactions.py index 8e9f9e1..217ee91 100644 --- a/tests/steem/test_transactions.py +++ b/tests/steem/test_transactions.py @@ -5,7 +5,7 @@ from steembase.transactions import SignedTransaction from steembase import operations from collections import OrderedDict -from steem.utils import future_bytes +from steem.utils import future_bytes, to_chr import steem as stm wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" @@ -466,7 +466,7 @@ def test_utf8tests(self): "author": "a", "permlink": "a", "title": "-", - "body": "".join([chr(i) for i in range(0, 2048)]), + "body": "".join([to_chr(i) for i in range(0, 2048)]), "json_metadata": {} }) ops = [operations.Operation(op)] From 18b0783f9fde7c9346b204e8ad5dd68f1e165034 Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 22 Feb 2018 12:58:19 -0600 Subject: [PATCH 055/166] Cleanup for merge to master. --- setup.py | 7 +++---- steem/instance.py | 2 +- steembase/base58.py | 4 ---- steembase/http_client.py | 2 +- steembase/memo.py | 4 ---- steembase/transactions.py | 2 +- 6 files changed, 6 insertions(+), 15 deletions(-) diff --git a/setup.py b/setup.py index 971ca14..8fe2837 100644 --- a/setup.py +++ b/setup.py @@ -4,9 +4,6 @@ from setuptools import setup, find_packages import sys -if sys.version_info < (3, 0): - sys.exit('Sorry, python2 is not yet supported. Please use python3.') - pfile = Project(chdir=False).parsed_pipfile requirements = convert_deps_to_pip(pfile['packages'], r=False) test_requirements = convert_deps_to_pip(pfile['dev-packages'], r=False) @@ -38,7 +35,9 @@ classifiers=[ 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', 'Programming Language :: Python :: 3', + 'Natural Language :: English', + 'Programming Language :: Python :: 2.7' + 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Python Modules', diff --git a/steem/instance.py b/steem/instance.py index 85a620d..7935aed 100644 --- a/steem/instance.py +++ b/steem/instance.py @@ -18,7 +18,7 @@ def shared_steemd_instance(): global _shared_steemd_instance if not _shared_steemd_instance: - if sys.version > '3.0': + if sys.version >= '3.0': _shared_steemd_instance = stm.steemd.Steemd( nodes=get_config_node_list()) else: diff --git a/steembase/base58.py b/steembase/base58.py index dd4ab42..a6bb45d 100644 --- a/steembase/base58.py +++ b/steembase/base58.py @@ -5,10 +5,6 @@ import logging from steem.utils import future_bytes log = logging.getLogger(__name__) -""" This class and the methods require python3 """ -# FIXME this library needs to support both 2 and 3 - -#assert sys.version_info[0] == 3, "graphenelib requires python3" """ Default Prefix """ PREFIX = "STM" diff --git a/steembase/http_client.py b/steembase/http_client.py index dc06ac1..3d1f459 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -13,7 +13,7 @@ from urllib3.connection import HTTPConnection from urllib3.exceptions import MaxRetryError, ReadTimeoutError, ProtocolError -if sys.version > '3': +if sys.version >= '3.0': from urllib.parse import urlparse else: from urlparse import urlparse diff --git a/steembase/memo.py b/steembase/memo.py index c1893a5..e947fd9 100644 --- a/steembase/memo.py +++ b/steembase/memo.py @@ -73,8 +73,6 @@ def _unpad(s, BS): def encode_memo(priv, pub, nonce, message, **kwargs): - # if sys.version < '3.0': - # raise NotImplementedError("Memo encoding not currently supported in Python 2.") """ Encode a message with a shared secret between Alice and Bob :param PrivateKey priv: Private Key (of Alice) @@ -114,8 +112,6 @@ def encode_memo(priv, pub, nonce, message, **kwargs): def decode_memo(priv, message): - # if sys.version < '3.0': - # raise NotImplementedError("Memo encoding not currently supported in Python 2.") """ Decode a message with a shared secret between Alice and Bob :param PrivateKey priv: Private Key (of Bob) diff --git a/steembase/transactions.py b/steembase/transactions.py index 2b679d6..d360525 100644 --- a/steembase/transactions.py +++ b/steembase/transactions.py @@ -199,7 +199,7 @@ def verify(self, pubkeys=[], chain=None): for signature in signatures: sig = future_bytes(signature)[1:] - if sys.version > '3.0': + if sys.version >= '3.0': recoverParameter = (future_bytes(signature)[0]) - 4 - 27 # recover parameter only else: recoverParameter = ord((future_bytes(signature)[0])) - 4 - 27 From 1f4573c7b8105120d47455e8bd153d7b9dd7dfc6 Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 22 Feb 2018 13:19:01 -0600 Subject: [PATCH 056/166] File formatting. --- steem/account.py | 30 ++++++------ steem/blockchain.py | 2 +- steem/cli.py | 34 +++++++------- steem/commit.py | 93 +++++++++++++++++++------------------ steem/converter.py | 10 ++-- steem/dex.py | 40 ++++++++-------- steem/post.py | 22 ++++----- steem/steemd.py | 6 +-- steem/transactionbuilder.py | 2 +- steem/utils.py | 5 +- steembase/account.py | 2 +- steembase/base58.py | 5 +- steembase/bip38.py | 2 +- steembase/chains.py | 10 ++-- steembase/memo.py | 4 +- steembase/operations.py | 4 +- steembase/storage.py | 14 +++--- steembase/transactions.py | 8 ++-- 18 files changed, 146 insertions(+), 147 deletions(-) diff --git a/steem/account.py b/steem/account.py index 55bfbc6..ea98c0c 100644 --- a/steem/account.py +++ b/steem/account.py @@ -96,11 +96,11 @@ def get_balances(self): totals = { 'STEEM': - sum([available['STEEM'], savings['STEEM'], rewards['STEEM']]), + sum([available['STEEM'], savings['STEEM'], rewards['STEEM']]), 'SBD': - sum([available['SBD'], savings['SBD'], rewards['SBD']]), + sum([available['SBD'], savings['SBD'], rewards['SBD']]), 'VESTS': - sum([available['VESTS'], rewards['VESTS']]), + sum([available['VESTS'], rewards['VESTS']]), } total = walk_values(rpartial(round, 3), totals) @@ -331,13 +331,13 @@ def history(self, i = start_index while i < max_index + batch_size: for account_history in self.get_account_history( - index=i, - limit=batch_size, - start=i - batch_size, - stop=max_index, - order=1, - filter_by=filter_by, - raw_output=raw_output, + index=i, + limit=batch_size, + start=i - batch_size, + stop=max_index, + order=1, + filter_by=filter_by, + raw_output=raw_output, ): yield account_history i += (batch_size + 1) @@ -357,11 +357,11 @@ def history_reverse(self, if i - batch_size < 0: batch_size = i for account_history in self.get_account_history( - index=i, - limit=batch_size, - order=-1, - filter_by=filter_by, - raw_output=raw_output, + index=i, + limit=batch_size, + order=-1, + filter_by=filter_by, + raw_output=raw_output, ): yield account_history i -= (batch_size + 1) diff --git a/steem/blockchain.py b/steem/blockchain.py index dcd4200..7ca1d49 100644 --- a/steem/blockchain.py +++ b/steem/blockchain.py @@ -2,12 +2,12 @@ import json import time import warnings -import sys from .instance import shared_steemd_instance, stm from .utils import parse_time, future_bytes import logging + logger = logging.getLogger(__name__) diff --git a/steem/cli.py b/steem/cli.py index c43d525..cb396ea 100644 --- a/steem/cli.py +++ b/steem/cli.py @@ -106,7 +106,7 @@ def legacyentry(): nargs='*', type=str, help='General information about the blockchain, a block, an account' - ' name, a post, a public key, ...') + ' name, a post, a public key, ...') """ Command "changewalletpassphrase" """ @@ -123,7 +123,7 @@ def legacyentry(): nargs='*', type=str, help='private key to import into the wallet (unsafe, unless you ' + - 'delete your shell history)') + 'delete your shell history)') addkey.set_defaults(command="addkey") parsewif = subparsers.add_parser( @@ -176,7 +176,7 @@ def legacyentry(): 'post', type=str, help='@author/permlink-identifier of the post to upvote ' + - 'to (e.g. @xeroc/python-steem-0-1)') + 'to (e.g. @xeroc/python-steem-0-1)') parser_upvote.add_argument( '--account', type=str, @@ -203,7 +203,7 @@ def legacyentry(): 'post', type=str, help='@author/permlink-identifier of the post to downvote ' + - 'to (e.g. @xeroc/python-steem-0-1)') + 'to (e.g. @xeroc/python-steem-0-1)') parser_downvote.add_argument( '--weight', type=float, @@ -360,7 +360,7 @@ def legacyentry(): type=str, nargs="?", help='The account or key that will be allowed to interact with ' + - 'your account') + 'your account') parser_allow.add_argument( '--permission', type=str, @@ -397,7 +397,7 @@ def legacyentry(): 'foreign_account', type=str, help='The account or key whose allowance to interact as your ' + - 'account will be removed') + 'account will be removed') parser_disallow.add_argument( '--permission', type=str, @@ -497,7 +497,7 @@ def legacyentry(): type=str, required=False, help='Load transaction from file. If "-", read from ' + - 'stdin (defaults to "-")') + 'stdin (defaults to "-")') """ Command "broadcast" """ @@ -509,7 +509,7 @@ def legacyentry(): type=str, required=False, help='Load transaction from file. If "-", read from ' + - 'stdin (defaults to "-")') + 'stdin (defaults to "-")') """ Command "orderbook" """ @@ -591,7 +591,7 @@ def legacyentry(): required=False, default=configStorage["default_account"], help='Resteem as this user (requires to have the ' + - 'key installed in the wallet)') + 'key installed in the wallet)') """ Command "follow" """ @@ -645,7 +645,7 @@ def legacyentry(): required=False, default=configStorage["default_account"], help='setprofile as this user (requires to have the key ' + - 'installed in the wallet)') + 'installed in the wallet)') parser_setprofile_a = parser_setprofile.add_argument_group( 'Multiple keys at once') parser_setprofile_a.add_argument( @@ -668,7 +668,7 @@ def legacyentry(): required=False, default=configStorage["default_account"], help='delprofile as this user (requires to have the ' + - 'key installed in the wallet)') + 'key installed in the wallet)') parser_delprofile.add_argument( 'variable', type=str, nargs='*', help='Variable to set') """ @@ -797,8 +797,8 @@ def legacyentry(): info = blockchain.info() median_price = steem.get_current_median_history_price() steem_per_mvest = ( - Amount(info["total_vesting_fund_steem"]).amount / - (Amount(info["total_vesting_shares"]).amount / 1e6)) + Amount(info["total_vesting_fund_steem"]).amount / + (Amount(info["total_vesting_shares"]).amount / 1e6)) price = (Amount(median_price["base"]).amount / Amount( median_price["quote"]).amount) for key in info: @@ -1157,7 +1157,7 @@ def legacyentry(): posting_key = PasswordKey(args.account, password, role="posting") posting_pubkey = format(posting_key.get_public_key(), "STM") if posting_pubkey in [ - x[0] for x in account["posting"]["key_auths"] + x[0] for x in account["posting"]["key_auths"] ]: print("Importing posting key!") posting_privkey = posting_key.get_private_key() @@ -1300,11 +1300,11 @@ def legacyentry(): elif args.command == "witnesscreate": props = { "account_creation_fee": - str(Amount("%f STEEM" % args.account_creation_fee)), + str(Amount("%f STEEM" % args.account_creation_fee)), "maximum_block_size": - args.maximum_block_size, + args.maximum_block_size, "sbd_interest_rate": - int(args.sbd_interest_rate * 100) + int(args.sbd_interest_rate * 100) } print_json( steem.commit.witness_update( diff --git a/steem/commit.py b/steem/commit.py index 44b087d..6dc17c2 100644 --- a/steem/commit.py +++ b/steem/commit.py @@ -30,6 +30,7 @@ STEEMIT_100_PERCENT = 10000 STEEMIT_1_PERCENT = (STEEMIT_100_PERCENT / 100) + # TODO # account_witness_proxy [active] # account_update [owner, active] @@ -310,9 +311,9 @@ def post(self, # or just simply vo.Schema([{'account': str, 'weight': int}]) schema = vo.Schema([{ vo.Required('account'): - vo.All(str, vo.Length(max=16)), + vo.All(str, vo.Length(max=16)), vo.Required('weight', default=10000): - vo.All(int, vo.Range(min=1, max=10000)) + vo.All(int, vo.Range(min=1, max=10000)) }]) schema(beneficiaries) options['beneficiaries'] = beneficiaries @@ -321,21 +322,21 @@ def post(self, comment_op = operations.CommentOptions( **{ "author": - author, + author, "permlink": - permlink, + permlink, "max_accepted_payout": - options.get("max_accepted_payout", default_max_payout), + options.get("max_accepted_payout", default_max_payout), "percent_steem_dollars": - int(options.get("percent_steem_dollars", 10000)), + int(options.get("percent_steem_dollars", 10000)), "allow_votes": - options.get("allow_votes", True), + options.get("allow_votes", True), "allow_curation_rewards": - options.get("allow_curation_rewards", True), + options.get("allow_curation_rewards", True), "extensions": - options.get("extensions", []), + options.get("extensions", []), "beneficiaries": - options.get("beneficiaries"), + options.get("beneficiaries"), }) ops.append(comment_op) @@ -662,14 +663,14 @@ def transfer(self, to, amount, asset, memo="", account=None): op = operations.Transfer( **{ "from": - account, + account, "to": - to, + to, "amount": - '{:.{prec}f} {asset}'.format( - float(amount), prec=3, asset=asset), + '{:.{prec}f} {asset}'.format( + float(amount), prec=3, asset=asset), "memo": - memo + memo }) return self.finalizeOp(op, account, "active") @@ -691,10 +692,10 @@ def withdraw_vesting(self, amount, account=None): op = operations.WithdrawVesting( **{ "account": - account, + account, "vesting_shares": - '{:.{prec}f} {asset}'.format( - float(amount), prec=6, asset="VESTS"), + '{:.{prec}f} {asset}'.format( + float(amount), prec=6, asset="VESTS"), }) return self.finalizeOp(op, account, "active") @@ -722,12 +723,12 @@ def transfer_to_vesting(self, amount, to=None, account=None): op = operations.TransferToVesting( **{ "from": - account, + account, "to": - to, + to, "amount": - '{:.{prec}f} {asset}'.format( - float(amount), prec=3, asset='STEEM') + '{:.{prec}f} {asset}'.format( + float(amount), prec=3, asset='STEEM') }) return self.finalizeOp(op, account, "active") @@ -756,12 +757,12 @@ def convert(self, amount, account=None, request_id=None): op = operations.Convert( **{ "owner": - account, + account, "requestid": - request_id, + request_id, "amount": - '{:.{prec}f} {asset}'.format( - float(amount), prec=3, asset='SBD') + '{:.{prec}f} {asset}'.format( + float(amount), prec=3, asset='SBD') }) return self.finalizeOp(op, account, "active") @@ -790,14 +791,14 @@ def transfer_to_savings(self, amount, asset, memo, to=None, account=None): op = operations.TransferToSavings( **{ "from": - account, + account, "to": - to, + to, "amount": - '{:.{prec}f} {asset}'.format( - float(amount), prec=3, asset=asset), + '{:.{prec}f} {asset}'.format( + float(amount), prec=3, asset=asset), "memo": - memo, + memo, }) return self.finalizeOp(op, account, "active") @@ -838,16 +839,16 @@ def transfer_from_savings(self, op = operations.TransferFromSavings( **{ "from": - account, + account, "request_id": - request_id, + request_id, "to": - to, + to, "amount": - '{:.{prec}f} {asset}'.format( - float(amount), prec=3, asset=asset), + '{:.{prec}f} {asset}'.format( + float(amount), prec=3, asset=asset), "memo": - memo, + memo, }) return self.finalizeOp(op, account, "active") @@ -1006,7 +1007,7 @@ def decode_memo(self, enc_memo): """ Try to decode an encrypted memo """ assert enc_memo[ - 0] == "#", "decode memo requires memos to start with '#'" + 0] == "#", "decode memo requires memos to start with '#'" keys = memo.involved_keys(enc_memo) wif = None for key in keys: @@ -1026,9 +1027,9 @@ def interest(self, account): last_payment = fmt_time_string(account["sbd_last_interest_payment"]) next_payment = last_payment + timedelta(days=30) interest_rate = self.steemd.get_dynamic_global_properties()[ - "sbd_interest_rate"] / 100 # percent + "sbd_interest_rate"] / 100 # percent interest_amount = (interest_rate / 100) * int( - int(account["sbd_seconds"]) / (60 * 60 * 24 * 356)) * 10**-3 + int(account["sbd_seconds"]) / (60 * 60 * 24 * 356)) * 10 ** -3 return { "interest": interest_amount, @@ -1414,17 +1415,17 @@ def comment_options(self, identifier, options, account=None): op = operations.CommentOptions( **{ "author": - author, + author, "permlink": - permlink, + permlink, "max_accepted_payout": - options.get("max_accepted_payout", default_max_payout), + options.get("max_accepted_payout", default_max_payout), "percent_steem_dollars": - options.get("percent_steem_dollars", 100) * STEEMIT_1_PERCENT, + options.get("percent_steem_dollars", 100) * STEEMIT_1_PERCENT, "allow_votes": - options.get("allow_votes", True), + options.get("allow_votes", True), "allow_curation_rewards": - options.get("allow_curation_rewards", True), + options.get("allow_curation_rewards", True), }) return self.finalizeOp(op, account["name"], "posting") diff --git a/steem/converter.py b/steem/converter.py index 4c34d24..d3813d4 100644 --- a/steem/converter.py +++ b/steem/converter.py @@ -24,7 +24,7 @@ def sbd_median_price(self): """ return (Amount(self.steemd.get_feed_history()['current_median_history'] ['base']).amount / Amount(self.steemd.get_feed_history( - )['current_median_history']['quote']).amount) + )['current_median_history']['quote']).amount) def steem_per_mvests(self): """ Obtain STEEM/MVESTS ratio @@ -62,7 +62,7 @@ def sp_to_rshares(self, sp, voting_power=10000, vote_pct=10000): # determine voting power used used_power = int((voting_power * vote_pct) / 10000); - max_vote_denom = props['vote_power_reserve_rate'] * (5*60*60*24) / (60*60*24); + max_vote_denom = props['vote_power_reserve_rate'] * (5 * 60 * 60 * 24) / (60 * 60 * 24); used_power = int((used_power + max_vote_denom - 1) / max_vote_denom) # calculate vote rshares @@ -98,10 +98,10 @@ def sbd_to_rshares(self, sbd_payout): total_reward_shares2 = int(props['total_reward_shares2']) post_rshares2 = ( - steem_payout / total_reward_fund_steem) * total_reward_shares2 + steem_payout / total_reward_fund_steem) * total_reward_shares2 rshares = math.sqrt( - self.CONTENT_CONSTANT**2 + post_rshares2) - self.CONTENT_CONSTANT + self.CONTENT_CONSTANT ** 2 + post_rshares2) - self.CONTENT_CONSTANT return rshares def rshares_2_weight(self, rshares): @@ -109,5 +109,5 @@ def rshares_2_weight(self, rshares): :param number rshares: R-Shares """ - _max = 2**64 - 1 + _max = 2 ** 64 - 1 return (_max * rshares) / (2 * self.CONTENT_CONSTANT + rshares) diff --git a/steem/dex.py b/steem/dex.py index 964ee98..ccf7803 100644 --- a/steem/dex.py +++ b/steem/dex.py @@ -186,21 +186,21 @@ def buy(self, op = operations.LimitOrderCreate( **{ "owner": - account, + account, "orderid": - order_id or random.getrandbits(32), + order_id or random.getrandbits(32), "amount_to_sell": - '{:.{prec}f} {asset}'.format( - amount * rate, - prec=base["precision"], - asset=base["symbol"]), + '{:.{prec}f} {asset}'.format( + amount * rate, + prec=base["precision"], + asset=base["symbol"]), "min_to_receive": - '{:.{prec}f} {asset}'.format( - amount, prec=quote["precision"], asset=quote["symbol"]), + '{:.{prec}f} {asset}'.format( + amount, prec=quote["precision"], asset=quote["symbol"]), "fill_or_kill": - killfill, + killfill, "expiration": - transactions.fmt_time_from_now(expiration) + transactions.fmt_time_from_now(expiration) }) return self.steemd.commit.finalizeOp(op, account, "active") @@ -247,21 +247,21 @@ def sell(self, op = operations.LimitOrderCreate( **{ "owner": - account, + account, "orderid": - orderid or random.getrandbits(32), + orderid or random.getrandbits(32), "amount_to_sell": - '{:.{prec}f} {asset}'.format( - amount, prec=quote["precision"], asset=quote["symbol"]), + '{:.{prec}f} {asset}'.format( + amount, prec=quote["precision"], asset=quote["symbol"]), "min_to_receive": - '{:.{prec}f} {asset}'.format( - amount * rate, - prec=base["precision"], - asset=base["symbol"]), + '{:.{prec}f} {asset}'.format( + amount * rate, + prec=base["precision"], + asset=base["symbol"]), "fill_or_kill": - killfill, + killfill, "expiration": - transactions.fmt_time_from_now(expiration) + transactions.fmt_time_from_now(expiration) }) return self.steemd.commit.finalizeOp(op, account, "active") diff --git a/steem/post.py b/steem/post.py index 7bf9ea4..2f25c8f 100644 --- a/steem/post.py +++ b/steem/post.py @@ -175,7 +175,7 @@ def reward(self): """Return a float value of estimated total SBD reward. """ return Amount(self.get("total_payout_value", "0 SBD")) + \ - Amount(self.get("pending_payout_value", "0 SBD")) + Amount(self.get("pending_payout_value", "0 SBD")) def time_elapsed(self): """Return a timedelta on how old the post is. @@ -316,20 +316,20 @@ def set_comment_options(self, options): op = CommentOptions( **{ "author": - self["author"], + self["author"], "permlink": - self["permlink"], + self["permlink"], "max_accepted_payout": - options.get("max_accepted_payout", - str(self["max_accepted_payout"])), + options.get("max_accepted_payout", + str(self["max_accepted_payout"])), "percent_steem_dollars": - int( - options.get("percent_steem_dollars", - self["percent_steem_dollars"] / 100) * 100), + int( + options.get("percent_steem_dollars", + self["percent_steem_dollars"] / 100) * 100), "allow_votes": - options.get("allow_votes", self["allow_votes"]), + options.get("allow_votes", self["allow_votes"]), "allow_curation_rewards": - options.get("allow_curation_rewards", self[ - "allow_curation_rewards"]), + options.get("allow_curation_rewards", self[ + "allow_curation_rewards"]), }) return self.commit.finalizeOp(op, self["author"], "posting") diff --git a/steem/steemd.py b/steem/steemd.py index edd2b1e..6d40f1c 100644 --- a/steem/steemd.py +++ b/steem/steemd.py @@ -70,7 +70,7 @@ def chain_params(self): chain = props["current_supply"].split(" ")[1] assert chain in known_chains, "The chain you are connecting " + \ - "to is not supported" + "to is not supported" return known_chains.get(chain) def get_replies(self, author, skip_own=True): @@ -121,8 +121,8 @@ def get_posts(self, limit=10, sort="hot", category=None, start=None): discussion_query["start_permlink"] = permlink if sort not in [ - "trending", "created", "active", "cashout", "payout", "votes", - "children", "hot" + "trending", "created", "active", "cashout", "payout", "votes", + "children", "hot" ]: raise Exception("Invalid choice of '--sort'!") diff --git a/steem/transactionbuilder.py b/steem/transactionbuilder.py index 61e1061..d9796f4 100644 --- a/steem/transactionbuilder.py +++ b/steem/transactionbuilder.py @@ -152,7 +152,7 @@ def addSigningInformation(self, account, permission): account_auth[0], steemd_instance=self.steemd) self["required_authorities"].update({ account_auth[0]: - account_auth_account.get(permission) + account_auth_account.get(permission) }) # Try to resolve required signatures for offline signing diff --git a/steem/utils.py b/steem/utils.py index e41f7e7..cf7e626 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -15,7 +15,9 @@ from langdetect.lang_detect_exception import LangDetectException from toolz import update_in, assoc -if sys.version < '3.0': +if sys.version >= '3.0': + from urllib.parse import urlparse +else: from urlparse import urlparse logger = logging.getLogger(__name__) @@ -371,7 +373,6 @@ def compose_dictionary(dictionary, **kwargs): def future_bytes(item, encoding=None): - if hasattr(item, 'to_bytes'): return item.to_bytes() else: diff --git a/steembase/account.py b/steembase/account.py index ed7ccf8..201ab3e 100644 --- a/steembase/account.py +++ b/steembase/account.py @@ -116,7 +116,7 @@ def suggest(self): assert len(dict_lines) == 49744 for j in range(0, word_count): num = int.from_bytes(os.urandom(2), byteorder="little") - rndMult = num / 2**16 # returns float between 0..1 (inclusive) + rndMult = num / 2 ** 16 # returns float between 0..1 (inclusive) wIdx = round(len(dict_lines) * rndMult) brainkey[j] = dict_lines[wIdx] return " ".join(brainkey).upper() diff --git a/steembase/base58.py b/steembase/base58.py index a6bb45d..ec1e4e4 100644 --- a/steembase/base58.py +++ b/steembase/base58.py @@ -4,6 +4,7 @@ import string import logging from steem.utils import future_bytes + log = logging.getLogger(__name__) """ Default Prefix """ @@ -152,6 +153,7 @@ def base58encode(hexstring): return (BASE58_ALPHABET[0:1] * leading_zeroes_count + res).decode('ascii') + def ripemd160(s): ripemd160 = hashlib.new('ripemd160') ripemd160.update(unhexlify(s)) @@ -197,6 +199,3 @@ def gphBase58CheckDecode(s): checksum = ripemd160(dec)[:4] assert (s[-4:] == checksum) return dec - - - diff --git a/steembase/bip38.py b/steembase/bip38.py index 3d2fa85..dde43be 100644 --- a/steembase/bip38.py +++ b/steembase/bip38.py @@ -67,7 +67,7 @@ def encrypt(privkey, passphrase): encrypted_half2 = _encrypt_xor(privkeyhex[32:], derived_half1[16:], aes) " flag byte is forced 0xc0 because Graphene only uses compressed keys " payload = ( - b'\x01' + b'\x42' + b'\xc0' + salt + encrypted_half1 + encrypted_half2) + b'\x01' + b'\x42' + b'\xc0' + salt + encrypted_half1 + encrypted_half2) " Checksum " checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4] privatkey = hexlify(payload + checksum).decode('ascii') diff --git a/steembase/chains.py b/steembase/chains.py index 4e68b4e..a15e266 100644 --- a/steembase/chains.py +++ b/steembase/chains.py @@ -10,14 +10,14 @@ }, "TEST": { "chain_id": - "9afbce9f2416520733bacb370315d32b6b2c43d6097576df1c1222859d91eecc", + "9afbce9f2416520733bacb370315d32b6b2c43d6097576df1c1222859d91eecc", "prefix": - "TST", + "TST", "steem_symbol": - "TESTS", + "TESTS", "sbd_symbol": - "TBD", + "TBD", "vests_symbol": - "VESTS", + "VESTS", }, } diff --git a/steembase/memo.py b/steembase/memo.py index e947fd9..bced108 100644 --- a/steembase/memo.py +++ b/steembase/memo.py @@ -72,7 +72,6 @@ def _unpad(s, BS): def encode_memo(priv, pub, nonce, message, **kwargs): - """ Encode a message with a shared secret between Alice and Bob :param PrivateKey priv: Private Key (of Alice) @@ -96,7 +95,7 @@ def encode_memo(priv, pub, nonce, message, **kwargs): cipher = hexlify(aes.encrypt(raw)).decode('ascii') prefix = kwargs.pop("prefix", default_prefix) s = OrderedDict([ - ("from",format(priv.pubkey, prefix)), + ("from", format(priv.pubkey, prefix)), ("to", format(pub, prefix)), ("nonce", nonce), ("check", check), @@ -111,7 +110,6 @@ def encode_memo(priv, pub, nonce, message, **kwargs): def decode_memo(priv, message): - """ Decode a message with a shared secret between Alice and Bob :param PrivateKey priv: Private Key (of Bob) diff --git a/steembase/operations.py b/steembase/operations.py index 5c0031f..daf4c25 100644 --- a/steembase/operations.py +++ b/steembase/operations.py @@ -245,7 +245,7 @@ def __init__(self, d): def to_bytes(self): # padding asset = self.asset + "\x00" * (7 - len(self.asset)) - amount = round(float(self.amount) * 10**self.precision) + amount = round(float(self.amount) * 10 ** self.precision) return (struct.pack(" Date: Thu, 22 Feb 2018 14:26:18 -0600 Subject: [PATCH 057/166] Updates to pipfile and pipfile.lock. --- Pipfile | 5 +- Pipfile.lock | 141 +++++++++++++++------------------------------------ 2 files changed, 43 insertions(+), 103 deletions(-) diff --git a/Pipfile b/Pipfile index 8836a7b..87e0805 100644 --- a/Pipfile +++ b/Pipfile @@ -20,7 +20,6 @@ toolz = "*" voluptuous = "*" "w3lib" = "*" futures = {version = "*", markers="python_version < '3.0.0'"} -"pytest-pep8" = "*" [dev-packages] @@ -34,6 +33,4 @@ pytest-pylint = "*" recommonmark = "*" yapf = "*" "autopep8" = "*" -pytest-cov = "*" -#[requires] -#python_version = "3.6" +pytest-cov = "*" \ No newline at end of file diff --git a/Pipfile.lock b/Pipfile.lock index 9bca9a8..eae93a8 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,19 +1,19 @@ { "_meta": { "hash": { - "sha256": "ded82e4047de6bf6a5027166a8618d228df337f22d35b6c48c3a68cbe8151e2d" + "sha256": "e13fa20ab9ecd6d2f6d6e839adf3f998b27358fca2b8e8f4484058ae8402e36d" }, "host-environment-markers": { "implementation_name": "cpython", - "implementation_version": "0", + "implementation_version": "3.6.4", "os_name": "posix", "platform_machine": "x86_64", "platform_python_implementation": "CPython", "platform_release": "17.3.0", "platform_system": "Darwin", "platform_version": "Darwin Kernel Version 17.3.0: Thu Nov 9 18:09:22 PST 2017; root:xnu-4570.31.3~1/RELEASE_X86_64", - "python_full_version": "2.7.10", - "python_version": "2.7", + "python_full_version": "3.6.4", + "python_version": "3.6", "sys_platform": "darwin" }, "pipfile-spec": 6, @@ -27,13 +27,6 @@ ] }, "default": { - "apipkg": { - "hashes": [ - "sha256:65d2aa68b28e7d31233bb2ba8eb31cda40e4671f8ac2d6b241e358c9652a74b9", - "sha256:2e38399dbe842891fe85392601aab8f40a8f4cc5a9053c326de35a1cc0297ac6" - ], - "version": "==1.4" - }, "appdirs": { "hashes": [ "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e", @@ -41,13 +34,6 @@ ], "version": "==1.4.3" }, - "attrs": { - "hashes": [ - "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450", - "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9" - ], - "version": "==17.4.0" - }, "certifi": { "hashes": [ "sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296", @@ -62,21 +48,6 @@ ], "version": "==0.13" }, - "execnet": { - "hashes": [ - "sha256:fc155a6b553c66c838d1a22dba1dc9f5f505c43285a878c6f74a79c024750b83", - "sha256:a7a84d5fa07a089186a329528f127c9d73b9de57f1a1131b82bb5320ee651f6a" - ], - "version": "==1.5.0" - }, - "funcsigs": { - "hashes": [ - "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", - "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" - ], - "markers": "python_version < '3.0'", - "version": "==1.0.2" - }, "funcy": { "hashes": [ "sha256:c38ef134d34c767b9d631de453e19f710ac0575b9463d55e30f4f93274e089e4" @@ -85,11 +56,11 @@ }, "futures": { "hashes": [ - "sha256:ec0a6cb848cc212002b9828c3e34c675e0c9ff6741dc445cab6fdd4e1085d1f1", - "sha256:9ec02aa7d674acb8618afb127e27fde7fc68994c0437ad759fa094a574adb265" + "sha256:c4884a65654a7c45435063e14ae85280eb1f111d94e542396717ba9828c4337f", + "sha256:51ecb45f0add83c806c68e4b06106f90db260585b25ef2abfcda0bd95c0132fd" ], "markers": "python_version < '3.0.0'", - "version": "==3.2.0" + "version": "==3.1.1" }, "langdetect": { "hashes": [ @@ -97,19 +68,6 @@ ], "version": "==1.0.7" }, - "pep8": { - "hashes": [ - "sha256:b22cfae5db09833bb9bd7c8463b53e1a9c9b39f12e304a8d0bba729c501827ee", - "sha256:fe249b52e20498e59e0b5c5256aa52ee99fc295b26ec9eaa85776ffdb9fe6374" - ], - "version": "==1.7.1" - }, - "pluggy": { - "hashes": [ - "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff" - ], - "version": "==0.6.0" - }, "prettytable": { "hashes": [ "sha256:853c116513625c738dc3ce1aee148b5b5757a86727e67eff6502c7ca59d43c36", @@ -118,38 +76,12 @@ ], "version": "==0.7.2" }, - "py": { - "hashes": [ - "sha256:8cca5c229d225f8c1e3085be4fcf306090b00850fefad892f9d96c7b6e2f310f", - "sha256:ca18943e28235417756316bfada6cd96b23ce60dd532642690dcfdaba988a76d" - ], - "version": "==1.5.2" - }, "pycrypto": { "hashes": [ "sha256:f2ce1e989b272cfcb677616763e0a2e7ec659effa67a88aa92b3a65528f60a3c" ], "version": "==2.6.1" }, - "pytest": { - "hashes": [ - "sha256:95fa025cd6deb5d937e04e368a00552332b58cae23f63b76c8c540ff1733ab6d", - "sha256:6074ea3b9c999bd6d0df5fa9d12dd95ccd23550df2a582f5f5b848331d2e82ca" - ], - "version": "==3.4.0" - }, - "pytest-cache": { - "hashes": [ - "sha256:be7468edd4d3d83f1e844959fd6e3fd28e77a481440a7118d430130ea31b07a9" - ], - "version": "==1.0" - }, - "pytest-pep8": { - "hashes": [ - "sha256:032ef7e5fa3ac30f4458c73e05bb67b0f036a8a5cb418a534b3170f89f120318" - ], - "version": "==1.0.6" - }, "scrypt": { "hashes": [ "sha256:dc40f0e1a357a49ca62f30f2fc09e92e02c062a656f27949b436b2ba8002d9e1", @@ -215,7 +147,6 @@ "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" ], - "markers": "sys_platform == 'darwin'", "version": "==0.1.0" }, "astroid": { @@ -243,17 +174,9 @@ "sha256:f0b0e4eba956de51238e17573b7087e852dfe9854afd2e9c873f73fc0ca0a6dd", "sha256:9d98697f088eb1b0fa451391f91afb5e3ebde16bbdb272819fd091151fda4f1a" ], - "markers": "python_version == '2.7'", + "markers": "python_version < '3.4'", "version": "==1.5" }, - "backports.shutil-get-terminal-size": { - "hashes": [ - "sha256:0975ba55054c15e346944b38956a4c9cbee9009391e41b86c68990effb8c1f64", - "sha256:713e7a8228ae80341c70586d1cc0a8caa5207346927e23d09dcbcaf18eadec80" - ], - "markers": "python_version == '2.7'", - "version": "==1.0.0" - }, "commonmark": { "hashes": [ "sha256:34d73ec8085923c023930dfc0bcd1c4286e28a2a82de094bb72fabcc0281cbe5" @@ -330,7 +253,7 @@ "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1", "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850" ], - "markers": "python_version == '2.7'", + "markers": "python_version < '3.4'", "version": "==1.1.6" }, "execnet": { @@ -345,23 +268,22 @@ "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" ], - "markers": "python_version < '3.3'", + "markers": "python_version < '3.0'", "version": "==1.0.2" }, "futures": { "hashes": [ - "sha256:ec0a6cb848cc212002b9828c3e34c675e0c9ff6741dc445cab6fdd4e1085d1f1", - "sha256:9ec02aa7d674acb8618afb127e27fde7fc68994c0437ad759fa094a574adb265" + "sha256:c4884a65654a7c45435063e14ae85280eb1f111d94e542396717ba9828c4337f", + "sha256:51ecb45f0add83c806c68e4b06106f90db260585b25ef2abfcda0bd95c0132fd" ], - "version": "==3.2.0" + "version": "==3.1.1" }, "ipython": { "hashes": [ - "sha256:578e2f3d779ed130a3cfefc09b2eb965a81457f6a31a25cd38e0bab622d4777d", - "sha256:185ef2093dbac6d7250fe9ed4d4dd0f18f10d0a6ac6169f3eeb2ff0663d96b92", - "sha256:66469e894d1f09d14a1f23b971a410af131daa9ad2a19922082e02e0ddfd150f" + "sha256:fcc6d46f08c3c4de7b15ae1c426e15be1b7932bcda9d83ce1a4304e8c1129df3", + "sha256:51c158a6c8b899898d1c91c6b51a34110196815cc905f9be0fa5878e19355608" ], - "version": "==5.5.0" + "version": "==6.2.1" }, "ipython-genutils": { "hashes": [ @@ -378,6 +300,13 @@ ], "version": "==4.3.4" }, + "jedi": { + "hashes": [ + "sha256:d795f2c2e659f5ea39a839e5230d70a0b045d0daee7ca2403568d8f348d0ad89", + "sha256:d6e799d04d1ade9459ed0f20de47c32f2285438956a677d083d3c98def59fa97" + ], + "version": "==0.11.1" + }, "lazy-object-proxy": { "hashes": [ "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019", @@ -426,12 +355,19 @@ ], "version": "==2.0.0" }, + "parso": { + "hashes": [ + "sha256:a7bb86fe0844304869d1c08e8bd0e52be931228483025c422917411ab82d628a", + "sha256:5815f3fe254e5665f3c5d6f54f086c2502035cb631a91341591b5a564203cffb" + ], + "version": "==0.1.1" + }, "pathlib2": { "hashes": [ "sha256:db3e43032d23787d3e9aec8c7ef1e0d2c3c589d5f303477661ebda2ca6d4bfba", "sha256:d32550b75a818b289bd4c1f96b60c89957811da205afcceab75bc8b4857ea5b3" ], - "markers": "python_version == '2.7' or python_version == '3.3'", + "markers": "python_version in '2.6 2.7 3.2 3.3'", "version": "==2.3.0" }, "pbr": { @@ -453,7 +389,6 @@ "sha256:6ff881b07aff0cb8ec02055670443f784434395f90c3285d2ae470f921ade52a", "sha256:67b85a1565968e3d5b5e7c9283caddc90c3947a2625bed1905be27bd5a03e47d" ], - "markers": "sys_platform != 'win32'", "version": "==4.4.0" }, "pickleshare": { @@ -514,10 +449,10 @@ }, "pytest": { "hashes": [ - "sha256:95fa025cd6deb5d937e04e368a00552332b58cae23f63b76c8c540ff1733ab6d", - "sha256:6074ea3b9c999bd6d0df5fa9d12dd95ccd23550df2a582f5f5b848331d2e82ca" + "sha256:8970e25181e15ab14ae895599a0a0e0ade7d1f1c4c8ca1072ce16f25526a184d", + "sha256:9ddcb879c8cc859d2540204b5399011f842e5e8823674bf429f70ada281b3cc6" ], - "version": "==3.4.0" + "version": "==3.4.1" }, "pytest-cache": { "hashes": [ @@ -608,6 +543,14 @@ ], "version": "==4.3.2" }, + "typing": { + "hashes": [ + "sha256:b2c689d54e1144bbcfd191b0832980a21c2dbcf7b5ff7a66248a60c90e951eb8", + "sha256:3a887b021a77b292e151afb75323dea88a7bc1b3dfa92176cff8e44c8b68bddf", + "sha256:d400a9344254803a2368533e4533a4200d21eb7b6b729c173bc38201a74db3f2" + ], + "version": "==3.6.4" + }, "wcwidth": { "hashes": [ "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c", From 38b6d0f95b5442a1921f01288bcfef5e66c18319 Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 22 Feb 2018 14:46:37 -0600 Subject: [PATCH 058/166] Another small pipfile update. --- Pipfile | 1 + Pipfile.lock | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Pipfile b/Pipfile index 87e0805..e9497c2 100644 --- a/Pipfile +++ b/Pipfile @@ -20,6 +20,7 @@ toolz = "*" voluptuous = "*" "w3lib" = "*" futures = {version = "*", markers="python_version < '3.0.0'"} +future = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index eae93a8..0df0510 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e13fa20ab9ecd6d2f6d6e839adf3f998b27358fca2b8e8f4484058ae8402e36d" + "sha256": "828134b2d25dc03cfcc818ccf373004bd56a11ae642837506d59670a1325cc26" }, "host-environment-markers": { "implementation_name": "cpython", @@ -54,6 +54,12 @@ ], "version": "==1.10.1" }, + "future": { + "hashes": [ + "sha256:e39ced1ab767b5936646cedba8bcce582398233d6a627067d4c6a454c90cfedb" + ], + "version": "==0.16.0" + }, "futures": { "hashes": [ "sha256:c4884a65654a7c45435063e14ae85280eb1f111d94e542396717ba9828c4337f", @@ -174,7 +180,7 @@ "sha256:f0b0e4eba956de51238e17573b7087e852dfe9854afd2e9c873f73fc0ca0a6dd", "sha256:9d98697f088eb1b0fa451391f91afb5e3ebde16bbdb272819fd091151fda4f1a" ], - "markers": "python_version < '3.4'", + "markers": "python_version == '2.7'", "version": "==1.5" }, "commonmark": { @@ -253,7 +259,7 @@ "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1", "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850" ], - "markers": "python_version < '3.4'", + "markers": "python_version == '2.7'", "version": "==1.1.6" }, "execnet": { @@ -268,7 +274,7 @@ "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" ], - "markers": "python_version < '3.0'", + "markers": "python_version < '3.3'", "version": "==1.0.2" }, "futures": { From 797faf7b4be24ba1a86e10e53d55ca29bb419b4e Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 22 Feb 2018 16:37:45 -0600 Subject: [PATCH 059/166] Updates to Readme.md. --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 463fa13..6e0d5a7 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ `steem-python` is the official Steem library for Python. It comes with a BIP38 encrypted wallet and a practical CLI utility called `steempy`. -Currently only python3 is supported. Python2 support is planned. +This library works on both Python 2.7 and 3.6. # Installation @@ -18,7 +18,7 @@ Then, install steem-python using it: ``` git clone https://github.com/steemit/steem-python.git cd steem-python -pipenv install --three --dev +pipenv install --three --dev # use --two instead of --three for Python 2.7 pipenv install . ``` @@ -66,8 +66,6 @@ or # TODO -* fix parts that were copied from python-graphenelib that only support - python3 to support python2 as well * more unit tests * 100% documentation coverage, consistent documentation * migrate to click CLI library From 514c0f3168d08248a9f4016e7bf8028d049869cc Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 26 Feb 2018 10:46:54 -0600 Subject: [PATCH 060/166] Refactored 'to_bytes' to '__bytes__'. --- steem/utils.py | 4 ++-- steembase/account.py | 6 +++--- steembase/base58.py | 2 +- steembase/operations.py | 6 +++--- steembase/types.py | 40 ++++++++++++++++++++-------------------- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/steem/utils.py b/steem/utils.py index cf7e626..20c47b1 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -373,8 +373,8 @@ def compose_dictionary(dictionary, **kwargs): def future_bytes(item, encoding=None): - if hasattr(item, 'to_bytes'): - return item.to_bytes() + if hasattr(item, '__bytes__'): + return item.__bytes__() else: if encoding: return bytes(item, encoding) diff --git a/steembase/account.py b/steembase/account.py index 201ab3e..2960e5a 100644 --- a/steembase/account.py +++ b/steembase/account.py @@ -185,7 +185,7 @@ def __format__(self, _format): else: return format(self._address, _format) - def to_bytes(self): + def __bytes__(self): """ Returns the raw content of the ``Base58CheckEncoded`` address """ if self._address is None: return future_bytes(self.derivesha512address()) @@ -273,7 +273,7 @@ def __format__(self, _format): to ``_format`` """ return format(self._pk, _format) - def to_bytes(self): + def __bytes__(self): """ Returns the raw public key (has length 33)""" return future_bytes(self._pk) @@ -350,6 +350,6 @@ def __str__(self): """ return format(self._wif, "WIF") - def to_bytes(self): + def __bytes__(self): """ Returns the raw private key """ return future_bytes(self._wif) diff --git a/steembase/base58.py b/steembase/base58.py index ec1e4e4..bff484d 100644 --- a/steembase/base58.py +++ b/steembase/base58.py @@ -100,7 +100,7 @@ def __str__(self): """ return gphBase58CheckEncode(self._hex) - def to_bytes(self): + def __bytes__(self): """ Return raw bytes :return: Raw bytes of instance diff --git a/steembase/operations.py b/steembase/operations.py index daf4c25..88c5a6a 100644 --- a/steembase/operations.py +++ b/steembase/operations.py @@ -75,7 +75,7 @@ def get_class(class_name): module = importlib.import_module('steembase.operations') return getattr(module, class_name) - def to_bytes(self): + def __bytes__(self): return future_bytes(Id(self.opId)) + future_bytes(self.op) def __str__(self): @@ -98,7 +98,7 @@ class GrapheneObject(object): def __init__(self, data=None): self.data = data - def to_bytes(self): + def __bytes__(self): if self.data is None: return bytes() b = b"" @@ -242,7 +242,7 @@ def __init__(self, d): else: raise Exception("Asset unknown") - def to_bytes(self): + def __bytes__(self): # padding asset = self.asset + "\x00" * (7 - len(self.asset)) amount = round(float(self.amount) * 10 ** self.precision) diff --git a/steembase/types.py b/steembase/types.py index f56edab..443937b 100644 --- a/steembase/types.py +++ b/steembase/types.py @@ -79,7 +79,7 @@ class Uint8: def __init__(self, d): self.data = d - def to_bytes(self): + def __bytes__(self): return struct.pack(" Date: Mon, 26 Feb 2018 11:20:11 -0600 Subject: [PATCH 061/166] Added documentation to compat methods, and renamed them to have compat in the name for comprehension. --- steem/utils.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/steem/utils.py b/steem/utils.py index 20c47b1..2491719 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -366,6 +366,14 @@ def is_valid_account_name(name): def compose_dictionary(dictionary, **kwargs): + """ + This method allows us the one line dictionary composition that is offered by the ** dictionary unpacking + available in 3.6. + + :param dictionary: the dictionary to add the kwargs elements to. + :param kwargs: a set of key/value pairs to add to `dictionary`. + :return: the composed dictionary. + """ for pairs in kwargs.items(): dictionary[pairs[0]] = pairs[1] @@ -373,6 +381,39 @@ def compose_dictionary(dictionary, **kwargs): def future_bytes(item, encoding=None): + """ + This method is required because Python 2.7 `bytes` is simply an alias for `str`. Without this method, + code execution would look something like: + + class clazz(object): + + def __bytes__(self): + return bytes(5) + + + Python 2.7: + + c = clazz() + bytes(c) + >>'<__main__.clazz object at 0x105171a90>' + + In this example, when `bytes(c)` is invoked, the interpreter then calls `str(c)`, and prints the above string. + the method `__bytes__` is never invoked. + + Python 3.6: + c = clazz() + bytes(c) + >>b'\x00\x00\x00\x00\x00' + + This is the expected and necessary behavior across both platforms. + + w/ future_bytes method, we will ensure that the correct bytes method is always invoked, avoiding the `str` alias in + 2.7. + + :param item: this is the object who's bytes method needs to be invoked + :param encoding: optional encoding parameter to handle the Python 3.6 two argument 'bytes' method. + :return: a bytes object that functions the same across 3.6 and 2.7 + """ if hasattr(item, '__bytes__'): return item.__bytes__() else: @@ -383,6 +424,15 @@ def future_bytes(item, encoding=None): def to_chr(item): + """ + This is necessary to maintain compatibility across Python 2.7 and 3.6. + In 3.6, 'chr' handles any unicode character, whereas in 2.7, `chr` only handles + ASCII characters. Thankfully, the Python 2.7 method `unichr` provides the same + functionality as 3.6 `chr`. + + :param item: a length 1 string who's `chr` method needs to be invoked + :return: the unichr code point of the single character string, item + """ if sys.version >= '3.0': return chr(item) else: From 56985df4cc0923a14a98fa34e31b5b65be3ab4db Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 26 Feb 2018 14:12:07 -0600 Subject: [PATCH 062/166] Refactored compat methods. --- steem/blockchain.py | 4 +-- steem/steemd.py | 4 +-- steem/utils.py | 6 ++--- steembase/account.py | 22 +++++++-------- steembase/base58.py | 6 ++--- steembase/bip38.py | 10 +++---- steembase/memo.py | 12 ++++----- steembase/operations.py | 10 +++---- steembase/transactions.py | 18 ++++++------- steembase/types.py | 28 +++++++++---------- tests/steem/test_transactions.py | 46 ++++++++++++++++---------------- 11 files changed, 83 insertions(+), 83 deletions(-) diff --git a/steem/blockchain.py b/steem/blockchain.py index 7ca1d49..3d5f076 100644 --- a/steem/blockchain.py +++ b/steem/blockchain.py @@ -4,7 +4,7 @@ import warnings from .instance import shared_steemd_instance, stm -from .utils import parse_time, future_bytes +from .utils import parse_time, compat_bytes import logging @@ -300,7 +300,7 @@ def replay(self, **kwargs): def hash_op(event): """ This method generates a hash of blockchain operation. """ data = json.dumps(event, sort_keys=True) - return hashlib.sha1(future_bytes(data, 'utf-8')).hexdigest() + return hashlib.sha1(compat_bytes(data, 'utf-8')).hexdigest() def get_all_usernames(self, *args, **kwargs): """ Fetch the full list of STEEM usernames. """ diff --git a/steem/steemd.py b/steem/steemd.py index 6d40f1c..31746b5 100644 --- a/steem/steemd.py +++ b/steem/steemd.py @@ -12,7 +12,7 @@ from .blockchain import Blockchain from .post import Post from .utils import resolve_identifier -from .utils import compose_dictionary +from .utils import compat_compose_dictionary logger = logging.getLogger(__name__) @@ -193,7 +193,7 @@ def _get_blocks(self, blocks): """ results = self.call_multi_with_futures( 'get_block', blocks, max_workers=10) - return (compose_dictionary(x, block_num=int(x['block_id'][:8], base=16)) + return (compat_compose_dictionary(x, block_num=int(x['block_id'][:8], base=16)) for x in results if x) def get_blocks(self, block_nums): diff --git a/steem/utils.py b/steem/utils.py index 2491719..ead7cc5 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -365,7 +365,7 @@ def is_valid_account_name(name): return re.match('^[a-z][a-z0-9\-.]{2,15}$', name) -def compose_dictionary(dictionary, **kwargs): +def compat_compose_dictionary(dictionary, **kwargs): """ This method allows us the one line dictionary composition that is offered by the ** dictionary unpacking available in 3.6. @@ -380,7 +380,7 @@ def compose_dictionary(dictionary, **kwargs): return dictionary -def future_bytes(item, encoding=None): +def compat_bytes(item, encoding=None): """ This method is required because Python 2.7 `bytes` is simply an alias for `str`. Without this method, code execution would look something like: @@ -423,7 +423,7 @@ def __bytes__(self): return bytes(item) -def to_chr(item): +def compat_chr(item): """ This is necessary to maintain compatibility across Python 2.7 and 3.6. In 3.6, 'chr' handles any unicode character, whereas in 2.7, `chr` only handles diff --git a/steembase/account.py b/steembase/account.py index 2960e5a..0144fdb 100644 --- a/steembase/account.py +++ b/steembase/account.py @@ -2,7 +2,7 @@ import os import re from binascii import hexlify, unhexlify -from steem.utils import future_bytes, to_chr +from steem.utils import compat_bytes, compat_chr import ecdsa @@ -26,7 +26,7 @@ def get_private(self): """ Derive private key from the brain key and the current sequence number """ - a = future_bytes(self.account + self.role + self.password, 'utf8') + a = compat_bytes(self.account + self.role + self.password, 'utf8') s = hashlib.sha256(a).digest() return PrivateKey(hexlify(s).decode('ascii')) @@ -92,7 +92,7 @@ def get_private(self): number """ encoded = "%s %d" % (self.brainkey, self.sequence) - a = future_bytes(encoded, 'ascii') + a = compat_bytes(encoded, 'ascii') s = hashlib.sha256(hashlib.sha512(a).digest()).digest() return PrivateKey(hexlify(s).decode('ascii')) @@ -188,9 +188,9 @@ def __format__(self, _format): def __bytes__(self): """ Returns the raw content of the ``Base58CheckEncoded`` address """ if self._address is None: - return future_bytes(self.derivesha512address()) + return compat_bytes(self.derivesha512address()) else: - return future_bytes(self._address) + return compat_bytes(self._address) class PublicKey(object): @@ -233,11 +233,11 @@ def compressed(self): """ Derive compressed public key """ order = ecdsa.SECP256k1.generator.order() p = ecdsa.VerifyingKey.from_string( - future_bytes(self), curve=ecdsa.SECP256k1).pubkey.point + compat_bytes(self), curve=ecdsa.SECP256k1).pubkey.point x_str = ecdsa.util.number_to_string(p.x(), order) # y_str = ecdsa.util.number_to_string(p.y(), order) compressed = hexlify( - future_bytes(to_chr(2 + (p.y() & 1)), 'ascii') + x_str).decode('ascii') + compat_bytes(compat_chr(2 + (p.y() & 1)), 'ascii') + x_str).decode('ascii') return (compressed) def unCompressed(self): @@ -275,7 +275,7 @@ def __format__(self, _format): def __bytes__(self): """ Returns the raw public key (has length 33)""" - return future_bytes(self._pk) + return compat_bytes(self._pk) class PrivateKey(object): @@ -329,8 +329,8 @@ def compressedpubkey(self): x_str = ecdsa.util.number_to_string(p.x(), order) y_str = ecdsa.util.number_to_string(p.y(), order) compressed = hexlify( - to_chr(2 + (p.y() & 1)).encode('ascii') + x_str).decode('ascii') - uncompressed = hexlify(to_chr(4).encode('ascii') + x_str + y_str).decode( + compat_chr(2 + (p.y() & 1)).encode('ascii') + x_str).decode('ascii') + uncompressed = hexlify(compat_chr(4).encode('ascii') + x_str + y_str).decode( 'ascii') return [compressed, uncompressed] @@ -352,4 +352,4 @@ def __str__(self): def __bytes__(self): """ Returns the raw private key """ - return future_bytes(self._wif) + return compat_bytes(self._wif) diff --git a/steembase/base58.py b/steembase/base58.py index bff484d..193ec11 100644 --- a/steembase/base58.py +++ b/steembase/base58.py @@ -3,7 +3,7 @@ import sys import string import logging -from steem.utils import future_bytes +from steem.utils import compat_bytes log = logging.getLogger(__name__) @@ -133,9 +133,9 @@ def base58decode(base58_str): def base58encode(hexstring): - byteseq = future_bytes(hexstring, 'ascii') + byteseq = compat_bytes(hexstring, 'ascii') byteseq = unhexlify(byteseq) - byteseq = future_bytes(byteseq) + byteseq = compat_bytes(byteseq) n = 0 leading_zeroes_count = 0 diff --git a/steembase/bip38.py b/steembase/bip38.py index dde43be..939c626 100644 --- a/steembase/bip38.py +++ b/steembase/bip38.py @@ -5,7 +5,7 @@ from .account import PrivateKey from .base58 import Base58, base58decode -from steem.utils import future_bytes +from steem.utils import compat_bytes log = logging.getLogger(__name__) @@ -53,12 +53,12 @@ def encrypt(privkey, passphrase): """ privkeyhex = repr(privkey) # hex addr = format(privkey.uncompressed.address, "BTC") - a = future_bytes(addr, 'ascii') + a = compat_bytes(addr, 'ascii') salt = hashlib.sha256(hashlib.sha256(a).digest()).digest()[0:4] if SCRYPT_MODULE == "scrypt": key = scrypt.hash(passphrase, salt, 16384, 8, 8) elif SCRYPT_MODULE == "pylibscrypt": - key = scrypt.scrypt(future_bytes(passphrase, "utf-8"), salt, 16384, 8, 8) + key = scrypt.scrypt(compat_bytes(passphrase, "utf-8"), salt, 16384, 8, 8) else: raise ValueError("No scrypt module loaded") (derived_half1, derived_half2) = (key[:32], key[32:]) @@ -96,7 +96,7 @@ def decrypt(encrypted_privkey, passphrase): if SCRYPT_MODULE == "scrypt": key = scrypt.hash(passphrase, salt, 16384, 8, 8) elif SCRYPT_MODULE == "pylibscrypt": - key = scrypt.scrypt(future_bytes(passphrase, "utf-8"), salt, 16384, 8, 8) + key = scrypt.scrypt(compat_bytes(passphrase, "utf-8"), salt, 16384, 8, 8) else: raise ValueError("No scrypt module loaded") derivedhalf1 = key[0:32] @@ -113,7 +113,7 @@ def decrypt(encrypted_privkey, passphrase): """ Verify Salt """ privkey = PrivateKey(format(wif, "wif")) addr = format(privkey.uncompressed.address, "BTC") - a = future_bytes(addr, 'ascii') + a = compat_bytes(addr, 'ascii') saltverify = hashlib.sha256(hashlib.sha256(a).digest()).digest()[0:4] if saltverify != salt: raise SaltException( diff --git a/steembase/memo.py b/steembase/memo.py index bced108..ab6434e 100644 --- a/steembase/memo.py +++ b/steembase/memo.py @@ -10,7 +10,7 @@ from .operations import Memo from .base58 import base58encode, base58decode from .account import PrivateKey, PublicKey -from steem.utils import future_bytes +from steem.utils import compat_bytes default_prefix = "STM" @@ -65,8 +65,8 @@ def _pad(s, BS): def _unpad(s, BS): - count = int(struct.unpack('B', future_bytes(s[-1], 'ascii'))[0]) - if future_bytes(s[-count::], 'ascii') == count * struct.pack('B', count): + count = int(struct.unpack('B', compat_bytes(s[-1], 'ascii'))[0]) + if compat_bytes(s[-count::], 'ascii') == count * struct.pack('B', count): return s[:-count] return s @@ -85,7 +85,7 @@ def encode_memo(priv, pub, nonce, message, **kwargs): from steembase import transactions shared_secret = get_shared_secret(priv, pub) aes, check = init_aes(shared_secret, nonce) - raw = future_bytes(message, 'utf8') + raw = compat_bytes(message, 'utf8') " Padding " BS = 16 @@ -106,7 +106,7 @@ def encode_memo(priv, pub, nonce, message, **kwargs): ]) tx = Memo(**s) - return "#" + base58encode(hexlify(future_bytes(tx)).decode("ascii")) + return "#" + base58encode(hexlify(compat_bytes(tx)).decode("ascii")) def decode_memo(priv, message): @@ -148,7 +148,7 @@ def decode_memo(priv, message): " Encryption " # remove the varint prefix (FIXME, long messages!) message = cipher[2:] - message = aes.decrypt(unhexlify(future_bytes(message, 'ascii'))) + message = aes.decrypt(unhexlify(compat_bytes(message, 'ascii'))) try: return _unpad(message.decode('utf8'), 16) except: # noqa FIXME(sneak) diff --git a/steembase/operations.py b/steembase/operations.py index 88c5a6a..28a32e7 100644 --- a/steembase/operations.py +++ b/steembase/operations.py @@ -4,7 +4,7 @@ import struct from collections import OrderedDict -from steem.utils import future_bytes +from steem.utils import compat_bytes from .account import PublicKey from .operationids import operations from .types import (Int16, Uint16, Uint32, Uint64, String, Bytes, Array, @@ -76,7 +76,7 @@ def get_class(class_name): return getattr(module, class_name) def __bytes__(self): - return future_bytes(Id(self.opId)) + future_bytes(self.op) + return compat_bytes(Id(self.opId)) + compat_bytes(self.op) def __str__(self): return json.dumps( @@ -104,9 +104,9 @@ def __bytes__(self): b = b"" for name, value in self.data.items(): if isinstance(value, str): - b += future_bytes(value, 'utf-8') + b += compat_bytes(value, 'utf-8') else: - b += future_bytes(value) + b += compat_bytes(value) return b def __json__(self): @@ -247,7 +247,7 @@ def __bytes__(self): asset = self.asset + "\x00" * (7 - len(self.asset)) amount = round(float(self.amount) * 10 ** self.precision) return (struct.pack("= '3.0': - recoverParameter = (future_bytes(signature)[0]) - 4 - 27 # recover parameter only + recoverParameter = (compat_bytes(signature)[0]) - 4 - 27 # recover parameter only else: - recoverParameter = ord((future_bytes(signature)[0])) - 4 - 27 + recoverParameter = ord((compat_bytes(signature)[0])) - 4 - 27 if USE_SECP256K1: ALL_FLAGS = secp256k1.lib.SECP256K1_CONTEXT_VERIFY | \ @@ -213,11 +213,11 @@ def verify(self, pubkeys=[], chain=None): sig = pub.ecdsa_recoverable_deserialize(sig, recoverParameter) # Recover PublicKey verifyPub = secp256k1.PublicKey( - pub.ecdsa_recover(future_bytes(self.message), sig)) + pub.ecdsa_recover(compat_bytes(self.message), sig)) # Convert recoverable sig to normal sig normalSig = verifyPub.ecdsa_recoverable_convert(sig) # Verify - verifyPub.ecdsa_verify(future_bytes(self.message), normalSig) + verifyPub.ecdsa_verify(compat_bytes(self.message), normalSig) phex = hexlify( verifyPub.serialize(compressed=True)).decode('ascii') pubKeysFound.append(phex) @@ -268,7 +268,7 @@ def sign(self, wifkeys, chain=None): # Sign the message with every private key given! sigs = [] for wif in self.privkeys: - p = future_bytes(PrivateKey(wif)) + p = compat_bytes(PrivateKey(wif)) i = 0 if USE_SECP256K1: ndata = secp256k1.ffi.new("const int *ndata") diff --git a/steembase/types.py b/steembase/types.py index 443937b..1d6757e 100644 --- a/steembase/types.py +++ b/steembase/types.py @@ -4,7 +4,7 @@ import array from binascii import hexlify, unhexlify from calendar import timegm -from steem.utils import future_bytes +from steem.utils import compat_bytes object_type = { "dynamic_global_property": 0, @@ -36,9 +36,9 @@ def varint(n): """ data = b'' while n >= 0x80: - data += future_bytes([(n & 0x7f) | 0x80]) + data += compat_bytes([(n & 0x7f) | 0x80]) n >>= 7 - data += future_bytes([n]) + data += compat_bytes([n]) return data @@ -185,7 +185,7 @@ def unicodify(self): r.append("u%04x" % o) else: r.append(s) - return future_bytes("".join(r), "utf-8") + return compat_bytes("".join(r), "utf-8") class Bytes: @@ -198,7 +198,7 @@ def __init__(self, d, length=None): def __bytes__(self): # FIXME constraint data to self.length - d = unhexlify(future_bytes(self.data, 'utf-8')) + d = unhexlify(compat_bytes(self.data, 'utf-8')) return varint(len(d)) + d def __str__(self): @@ -222,7 +222,7 @@ def __init__(self, d): self.length = Varint32(len(self.data)) def __bytes__(self): - return future_bytes(self.length) + b"".join([future_bytes(a) for a in self.data]) + return compat_bytes(self.length) + b"".join([compat_bytes(a) for a in self.data]) def __str__(self): r = [] @@ -292,10 +292,10 @@ def __init__(self, d): def __bytes__(self): if not self.data: - return future_bytes(Bool(0)) + return compat_bytes(Bool(0)) else: - return future_bytes(Bool(1)) + future_bytes(self.data) if future_bytes( - self.data) else future_bytes(Bool(0)) + return compat_bytes(Bool(1)) + compat_bytes(self.data) if compat_bytes( + self.data) else compat_bytes(Bool(0)) def __str__(self): return str(self.data) @@ -303,7 +303,7 @@ def __str__(self): def isempty(self): if not self.data: return True - return not bool(future_bytes(self.data)) + return not bool(compat_bytes(self.data)) class StaticVariant: @@ -312,7 +312,7 @@ def __init__(self, d, type_id): self.type_id = type_id def __bytes__(self): - return varint(self.type_id) + future_bytes(self.data) + return varint(self.type_id) + compat_bytes(self.data) def __str__(self): return json.dumps([self.type_id, self.data.json()]) @@ -326,7 +326,7 @@ def __bytes__(self): b = b"" b += varint(len(self.data)) for e in self.data: - b += future_bytes(e[0]) + future_bytes(e[1]) + b += compat_bytes(e[0]) + compat_bytes(e[1]) return b def __str__(self): @@ -341,7 +341,7 @@ def __init__(self, d): self.data = Varint32(d) def __bytes__(self): - return future_bytes(self.data) + return compat_bytes(self.data) def __str__(self): return str(self.data) @@ -382,7 +382,7 @@ def __init__(self, object_str, type_verify=None): raise Exception("Object id is invalid") def __bytes__(self): - return future_bytes(self.instance) # only yield instance + return compat_bytes(self.instance) # only yield instance def __str__(self): return self.Id diff --git a/tests/steem/test_transactions.py b/tests/steem/test_transactions.py index 217ee91..44f84f8 100644 --- a/tests/steem/test_transactions.py +++ b/tests/steem/test_transactions.py @@ -5,7 +5,7 @@ from steembase.transactions import SignedTransaction from steembase import operations from collections import OrderedDict -from steem.utils import future_bytes, to_chr +from steem.utils import compat_bytes, compat_chr import steem as stm wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" @@ -41,7 +41,7 @@ def test_Comment(self): operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c80457010107666f6f6261726107666f6f626172620" "7666f6f6261726307666f6f6261726407666f6f6261726507666f6f62" @@ -68,7 +68,7 @@ def test_Vote(self): tx.verify([PrivateKey(wif).pubkey], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c80457010007666f6f6261726107666f6f62617263" "07666f6f62617264e8030001202e09123f732a438ef6d6138484d7ad" @@ -137,7 +137,7 @@ def test_create_account(self): operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c804570109102700000000000003535445454d000" "0057865726f63086673616661617366010000000002026f6231b8ed" @@ -171,7 +171,7 @@ def test_Transfer(self): operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c80457010203666f6f046261617206b201000000" "000003535445454d000004466f6f6f00012025416c234dd5ff15d8" @@ -194,7 +194,7 @@ def test_Transfer_to_vesting(self): operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c80457010303666f6f046261617206b201000000" "000003535445454d00000001203a34cd45fb4a2585514614be2c1" @@ -215,7 +215,7 @@ def test_withdraw_vesting(self): operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ( "f68585abf4dce7c80457010403666f6f00e1f5050000000006564553545300000" @@ -240,7 +240,7 @@ def test_Transfer_to_savings(self): operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ( "f68585abf4dce7c804570120087465737475736572087465737475736572e8030" @@ -266,7 +266,7 @@ def test_Transfer_from_savings(self): operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ( "f68585abf4dce7c804570121087465737475736572292300000774657374736" @@ -288,7 +288,7 @@ def test_Cancel_transfer_from_savings(self): operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ( "f68585abf4dce7c8045701220774657375736572292300000001200942474f672" @@ -314,7 +314,7 @@ def test_order_create(self): operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c8045701050000000000000000000000000003535" "445454d0000000000000000000003535445454d0000007f46685800" "011f28a2fc52dcfc19378c5977917b158dfab93e7760259aab7ecdb" @@ -376,7 +376,7 @@ def test_account_update(self): operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c80457010a0973747265656d69616e01010000" "00000202bbcf38855c9ae9d55704ee50ff56552af1242266c105" "44a75b61005e17fa78a601000389d28937022880a7f0c7deaa6f" @@ -407,7 +407,7 @@ def test_order_cancel(self): operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c804570106003cac20000001206c9888d0c2c3" "1dba1302566f524dfac01a15760b93a8726241a7ae6ba00edfd" "e5b83edaf94a4bd35c2957ded6023576dcbe936338fb9d340e2" @@ -430,7 +430,7 @@ def test_set_route(self): operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c804570114057865726f63057865726f63e803" "0000011f12d2b8f93f9528f31979e0e1f59a6d45346a88c02ab2" "c4115b10c9e273fc1e99621af0c2188598c84762b7e99ca63f6b" @@ -451,7 +451,7 @@ def test_convert(self): operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c804570108057865726f6343529d8ba0860100000" "00000035342440000000000011f3d22eb66e5cddcc90f5d6ca0bd7a" "43e0ab811ecd480022af8a847c45eac720b342188d55643d8cb1711" @@ -466,7 +466,7 @@ def test_utf8tests(self): "author": "a", "permlink": "a", "title": "-", - "body": "".join([to_chr(i) for i in range(0, 2048)]), + "body": "".join([compat_chr(i) for i in range(0, 2048)]), "json_metadata": {} }) ops = [operations.Operation(op)] @@ -477,7 +477,7 @@ def test_utf8tests(self): operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c804570101000001610161012dec1f75303030307" "5303030317530303032753030303375303030347530303035753030" "3036753030303762090a7530303062660d753030306575303030667" @@ -648,7 +648,7 @@ def test_feed_publish(self): expiration=expiration, operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c804570107057865726f63e803000000000" "00003534244000000001b1000000000000003535445454d00" "000001203847a02aa76964cacfb41565c23286cc64b18f6bb" @@ -680,7 +680,7 @@ def test_witness_update(self): expiration=expiration, operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c80457010b057865726f6308666f6f6f6f6261" "720314aa202c9158990b3ec51a1aa49b2ab5d300c97b391df3be" "b34bb74f3c62699e102700000000000003535445454d000047f4" @@ -703,7 +703,7 @@ def test_witness_vote(self): expiration=expiration, operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c80457010c057865726f630a636" "861696e73717561640100011f16b43411e11f4739" "4c1624a3c4d3cf4daba700b8690f494e6add7ad9b" @@ -735,7 +735,7 @@ def test_custom_json(self): expiration=expiration, operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c8045701120001057865726f6306666f6c6c" "6f777f5b227265626c6f67222c207b226163636f756e74223a" "20227865726f63222c2022617574686f72223a202263686169" @@ -777,7 +777,7 @@ def test_comment_options(self): expiration=expiration, operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - txWire = hexlify(future_bytes(tx)).decode("ascii") + txWire = hexlify(compat_bytes(tx)).decode("ascii") compare = ("f68585abf4dce7c804570113057865726f6306706973746f6e" "00ca9a3b000000000353424400000000102701010100020a67" "6f6f642d6b61726d61d007046e756c6c881300011f59634e65" @@ -806,7 +806,7 @@ def compareConstructedTX(self): expiration=expiration, operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) - tx_wire = hexlify(future_bytes(tx)).decode("ascii") + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") # todo rpc = self.steem.commit.wallet From e9d328b43378fa3959b4d828e0e333cdc08b6584 Mon Sep 17 00:00:00 2001 From: John White Date: Sat, 3 Mar 2018 17:51:50 -0600 Subject: [PATCH 063/166] Updates per comments. --- steem/blockchain.py | 4 +++- steem/cli.py | 4 ++-- steem/post.py | 6 +++--- steem/utils.py | 12 ++++++------ steembase/transactions.py | 5 ++++- tests/steem/test_utils.py | 2 +- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/steem/blockchain.py b/steem/blockchain.py index 3d5f076..2a6d71c 100644 --- a/steem/blockchain.py +++ b/steem/blockchain.py @@ -253,13 +253,15 @@ def stream(self, filter_by=list(), *args, **kwargs): if kwargs.get('raw_output'): yield event else: - yield op.update({ + updated_op = op.copy() + updated_op.update({ "_id": self.hash_op(event), "type": op_type, "timestamp": parse_time(event.get("timestamp")), "block_num": event.get("block"), "trx_id": event.get("trx_id"), }) + yield updated_op def history(self, filter_by=list(), diff --git a/steem/cli.py b/steem/cli.py index cb396ea..b79b7b1 100644 --- a/steem/cli.py +++ b/steem/cli.py @@ -1366,11 +1366,11 @@ def format_operation_details(op, memos=False): if op[0] == "vote": return "%s: %s" % ( op[1]["voter"], - construct_identifier(op[1]["author"], op[1]["permlink"])) + construct_identifier('@', op[1]["author"], op[1]["permlink"])) elif op[0] == "comment": return "%s: %s" % ( op[1]["author"], - construct_identifier(op[1]["author"], op[1]["permlink"])) + construct_identifier('@', op[1]["author"], op[1]["permlink"])) elif op[0] == "transfer": str_ = "%s -> %s %s" % ( op[1]["from"], diff --git a/steem/post.py b/steem/post.py index 2f25c8f..13daf96 100644 --- a/steem/post.py +++ b/steem/post.py @@ -48,7 +48,7 @@ def __init__(self, post, steemd_instance=None): elif isinstance(post, dict) and "author" in post and "permlink" in post: post["author"] = post["author"].replace('@', '') - self.identifier = construct_identifier(post["author"], + self.identifier = construct_identifier('@', post["author"], post["permlink"]) else: raise ValueError("Post expects an identifier or a dict " @@ -142,7 +142,7 @@ def _get_root_identifier(self, post=None): category = m.group(1) author = m.group(2) permlink = m.group(3) - return construct_identifier(author, permlink), category + return construct_identifier('@', author, permlink), category def get_replies(self): """ Return **first-level** comments of the post. @@ -274,7 +274,7 @@ def edit(self, body, meta=None, replace=False): log.info("No changes made! Skipping ...") return - reply_identifier = construct_identifier( + reply_identifier = construct_identifier('@', original_post["parent_author"], original_post["parent_permlink"]) new_meta = {} diff --git a/steem/utils.py b/steem/utils.py index ead7cc5..452522e 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -231,17 +231,16 @@ def remove_from_dict(obj, remove_keys=list()): return {k: v for k, v in items if k not in remove_keys} -def construct_identifier(*args): +def construct_identifier(username_prefix='@', *args): """ Create a post identifier from comment/post object or arguments. Examples: :: - construct_identifier('username', 'permlink') - construct_identifier({'author': 'username', + construct_identifier('@', 'username', 'permlink') + construct_identifier('@', {'author': 'username', 'permlink': 'permlink'}) """ - username_prefix = '@' if len(args) == 1: op = args[0] @@ -374,10 +373,11 @@ def compat_compose_dictionary(dictionary, **kwargs): :param kwargs: a set of key/value pairs to add to `dictionary`. :return: the composed dictionary. """ + composed_dict = dictionary.copy() for pairs in kwargs.items(): - dictionary[pairs[0]] = pairs[1] + composed_dict[pairs[0]] = pairs[1] - return dictionary + return composed_dict def compat_bytes(item, encoding=None): diff --git a/steembase/transactions.py b/steembase/transactions.py index 80058e1..df79641 100644 --- a/steembase/transactions.py +++ b/steembase/transactions.py @@ -318,9 +318,12 @@ def sign(self, wifkeys, chain=None): signature = ecdsa.util.sigencode_string( r, s, sk.curve.generator.order()) + # This line allows us to convert a 2.7 byte array(which is just binary) to an array of byte values. + # We can then use the elements in sigder as integers, as in the following two lines. + sigder = array.array('B', sigder) + # Make sure signature is canonical! # - sigder = array.array('B', sigder) lenR = sigder[3] lenS = sigder[5 + lenR] if lenR is 32 and lenS is 32: diff --git a/tests/steem/test_utils.py b/tests/steem/test_utils.py index 7e3442f..097da8a 100644 --- a/tests/steem/test_utils.py +++ b/tests/steem/test_utils.py @@ -11,7 +11,7 @@ class Testcases(unittest.TestCase): def test_constructIdentifier(self): - self.assertEqual(construct_identifier("A", "B"), "@A/B") + self.assertEqual(construct_identifier('@', "A", "B"), "@A/B") def test_sanitizePermlink(self): self.assertEqual(sanitize_permlink("aAf_0.12"), "aaf-0-12") From 70eefa089cd76db80733308b5176f6832b2975b3 Mon Sep 17 00:00:00 2001 From: John White Date: Sun, 4 Mar 2018 17:02:27 -0600 Subject: [PATCH 064/166] Updated handling of kwargs in json_rpc_body. --- steembase/http_client.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index 3d1f459..d3e65d0 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -142,15 +142,12 @@ def json_rpc_body(name, *args, **kwargs): Otherwise, a Python dictionary is returned. """ - api = kwargs.get('kwargs').pop('api', None) - as_json = kwargs.get('kwargs').pop('as_json', True) - _id = kwargs.get('kwargs').pop('_id', 0) - - if len(kwargs) == 1 and 'kwargs' in kwargs: - kwargs = None + api = kwargs.pop('api', None) + as_json = kwargs.pop('as_json', True) + _id = kwargs.pop('_id', 0) headers = {"jsonrpc": "2.0", "id": _id} - if kwargs is not None: + if kwargs is not None and len(kwargs) > 0: body_dict = dict(headers) body_dict.update({"method": "call", @@ -189,7 +186,7 @@ def call(self, return_with_args = kwargs.get('return_with_args', None) _ret_cnt = kwargs.get('_ret_cnt', 0) - body = HttpClient.json_rpc_body(name, *args, kwargs=kwargs) + body = HttpClient.json_rpc_body(name, *args, **kwargs) response = None try: From 440aafa7f3b6767724a6235b0c5f1e97f8336043 Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 5 Mar 2018 12:32:00 -0600 Subject: [PATCH 065/166] Fixed error catching in http_client.py and comment tags processing. --- steem/post.py | 6 ++++-- steembase/http_client.py | 13 +++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/steem/post.py b/steem/post.py index 13daf96..14cd2dc 100644 --- a/steem/post.py +++ b/steem/post.py @@ -99,8 +99,10 @@ def refresh(self): post['community'] = '' if isinstance(post['json_metadata'], dict): if post["depth"] == 0: - tags = get_in(post, ['json_metadata', 'tags'], default=[]) - post["tags"] = (post["parent_permlink"], tags) + tags = [post["parent_permlink"]] + tags += get_in(post, ['json_metadata', 'tags'], default=[]) + post["tags"] = set(tags) + post['community'] = get_in( post, ['json_metadata', 'community'], default='') diff --git a/steembase/http_client.py b/steembase/http_client.py index d3e65d0..c010b0f 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -14,9 +14,10 @@ from urllib3.exceptions import MaxRetryError, ReadTimeoutError, ProtocolError if sys.version >= '3.0': - from urllib.parse import urlparse + from http.client import RemoteDisconnected else: from urlparse import urlparse + from httplib import HTTPException logger = logging.getLogger(__name__) @@ -189,9 +190,17 @@ def call(self, body = HttpClient.json_rpc_body(name, *args, **kwargs) response = None + if sys.version > '3.0': + errorList = (MaxRetryError, ReadTimeoutError, ProtocolError, + RemoteDisconnected, ConnectionResetError,) + else: + errorList = (MaxRetryError, ReadTimeoutError, ProtocolError, + HTTPException,) + try: + raise HTTPException response = self.request(body=body) - except (MaxRetryError, ReadTimeoutError, ProtocolError) as e: + except (errorList) as e: # if we broadcasted a transaction, always raise # this is to prevent potential for double spend scenario if api == 'network_broadcast_api': From 860b580b94407119f27912e6bb33f9a3b52ec87e Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 5 Mar 2018 12:50:55 -0600 Subject: [PATCH 066/166] Small fix. --- steembase/http_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index c010b0f..3b55831 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -198,7 +198,6 @@ def call(self, HTTPException,) try: - raise HTTPException response = self.request(body=body) except (errorList) as e: # if we broadcasted a transaction, always raise From 1db648b5dcfa74d25b29a5a2db423fa2d4fd125e Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 5 Mar 2018 15:26:37 -0600 Subject: [PATCH 067/166] Added a missing import to http_client.py and simplified syntax in compat_compose_dictionary. --- steem/utils.py | 3 +-- steembase/http_client.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/steem/utils.py b/steem/utils.py index 452522e..cf2b50e 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -374,8 +374,7 @@ def compat_compose_dictionary(dictionary, **kwargs): :return: the composed dictionary. """ composed_dict = dictionary.copy() - for pairs in kwargs.items(): - composed_dict[pairs[0]] = pairs[1] + composed_dict.update(kwargs) return composed_dict diff --git a/steembase/http_client.py b/steembase/http_client.py index 3b55831..18bca89 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -15,6 +15,7 @@ if sys.version >= '3.0': from http.client import RemoteDisconnected + from urllib.parse import urlparse else: from urlparse import urlparse from httplib import HTTPException From 5790fa4bc6bf8360a08b2defb8afdc64121fcc84 Mon Sep 17 00:00:00 2001 From: John White Date: Tue, 6 Mar 2018 14:06:30 -0600 Subject: [PATCH 068/166] Small update to construct_identifier object. --- steem/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/steem/utils.py b/steem/utils.py index cf2b50e..dceb4e5 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -241,6 +241,10 @@ def construct_identifier(username_prefix='@', *args): construct_identifier('@', {'author': 'username', 'permlink': 'permlink'}) """ + # https://github.com/steemit/steem-python/issues/165 + # this is assert here will be removed with the above issue + # addressed. + assert(username_prefix == '@') if len(args) == 1: op = args[0] From 434921b767b063f8fb254159f805409925586d46 Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 12 Mar 2018 12:31:38 -0500 Subject: [PATCH 069/166] Added circleci 2.0 shell. --- .circleci/config.yml | 48 ++++ Dockerfile | 34 --- Pipfile | 37 --- Pipfile.lock | 581 ------------------------------------------- circle.yml | 11 - setup.cfg | 4 +- setup.py | 186 +++++++++++--- 7 files changed, 204 insertions(+), 697 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 Dockerfile delete mode 100644 Pipfile delete mode 100644 Pipfile.lock delete mode 100644 circle.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..b88413e --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,48 @@ +version: 2 +jobs: + build: + docker: + - image: circleci/python:3.6 + steps: + - checkout + - run: python setup.py install + test27: + docker: + - image: circleci/python:2.7 + steps: + - checkout + - run: python setup.py test + test33: + docker: + - image: circleci/python:3.3 + steps: + - checkout + - run: python setup.py test + test34: + docker: + - image: circleci/python:3.4 + steps: + - checkout + - run: python setup.py test + test35: + docker: + - image: circleci/python:3.5 + steps: + - checkout + - run: python setup.py test + test36: + docker: + - image: circleci/python:3.6 + steps: + - checkout + - run: python setup.py test + +workflows: + version: 2 + test_all_envs: + jobs: + - test27 + - test33 + - test34 + - test35 + - test36 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index b5ab315..0000000 --- a/Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -FROM phusion/baseimage:0.9.19 - -# Standard stuff -ENV LANG en_US.UTF-8 -ENV LC_ALL en_US.UTF-8 - -# Stuff for building steem-python -ARG BUILD_ROOT=/build - -# Now we install the essentials -RUN \ - apt-get update && \ - apt-get install -y python3-pip libssl-dev build-essential - -# This updates the distro-provided pip -RUN pip3 install --upgrade pip - -RUN mkdir ${BUILD_ROOT} - -COPY Makefile ${BUILD_ROOT}/ -COPY Pipfile ${BUILD_ROOT}/ -COPY Pipfile.lock ${BUILD_ROOT}/ - -WORKDIR ${BUILD_ROOT} - -RUN pip3 install --upgrade pip && \ - pip3 install --upgrade pipenv && \ - pipenv install --three --dev && \ - pipenv install . - -COPY . ${BUILD_ROOT} - -# run tests -RUN pipenv run py.test diff --git a/Pipfile b/Pipfile deleted file mode 100644 index e9497c2..0000000 --- a/Pipfile +++ /dev/null @@ -1,37 +0,0 @@ -[[source]] - -url = "https://pypi.python.org/simple" -verify_ssl = true -name = "pypi" - - -[packages] - -appdirs = "*" -certifi = "*" -ecdsa = "*" -funcy = "*" -langdetect = "*" -prettytable = "*" -pycrypto = "*" -scrypt = "*" -toolz = "*" -"urllib3" = "*" -voluptuous = "*" -"w3lib" = "*" -futures = {version = "*", markers="python_version < '3.0.0'"} -future = "*" - - -[dev-packages] - -ipython = "*" -"pep8" = "*" -pytest = "*" -"pytest-pep8" = "*" -pytest-console-scripts = "*" -pytest-pylint = "*" -recommonmark = "*" -yapf = "*" -"autopep8" = "*" -pytest-cov = "*" \ No newline at end of file diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 0df0510..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,581 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "828134b2d25dc03cfcc818ccf373004bd56a11ae642837506d59670a1325cc26" - }, - "host-environment-markers": { - "implementation_name": "cpython", - "implementation_version": "3.6.4", - "os_name": "posix", - "platform_machine": "x86_64", - "platform_python_implementation": "CPython", - "platform_release": "17.3.0", - "platform_system": "Darwin", - "platform_version": "Darwin Kernel Version 17.3.0: Thu Nov 9 18:09:22 PST 2017; root:xnu-4570.31.3~1/RELEASE_X86_64", - "python_full_version": "3.6.4", - "python_version": "3.6", - "sys_platform": "darwin" - }, - "pipfile-spec": 6, - "requires": {}, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "appdirs": { - "hashes": [ - "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e", - "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92" - ], - "version": "==1.4.3" - }, - "certifi": { - "hashes": [ - "sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296", - "sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d" - ], - "version": "==2018.1.18" - }, - "ecdsa": { - "hashes": [ - "sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c", - "sha256:64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa" - ], - "version": "==0.13" - }, - "funcy": { - "hashes": [ - "sha256:c38ef134d34c767b9d631de453e19f710ac0575b9463d55e30f4f93274e089e4" - ], - "version": "==1.10.1" - }, - "future": { - "hashes": [ - "sha256:e39ced1ab767b5936646cedba8bcce582398233d6a627067d4c6a454c90cfedb" - ], - "version": "==0.16.0" - }, - "futures": { - "hashes": [ - "sha256:c4884a65654a7c45435063e14ae85280eb1f111d94e542396717ba9828c4337f", - "sha256:51ecb45f0add83c806c68e4b06106f90db260585b25ef2abfcda0bd95c0132fd" - ], - "markers": "python_version < '3.0.0'", - "version": "==3.1.1" - }, - "langdetect": { - "hashes": [ - "sha256:91a170d5f0ade380db809b3ba67f08e95fe6c6c8641f96d67a51ff7e98a9bf30" - ], - "version": "==1.0.7" - }, - "prettytable": { - "hashes": [ - "sha256:853c116513625c738dc3ce1aee148b5b5757a86727e67eff6502c7ca59d43c36", - "sha256:2d5460dc9db74a32bcc8f9f67de68b2c4f4d2f01fa3bd518764c69156d9cacd9", - "sha256:a53da3b43d7a5c229b5e3ca2892ef982c46b7923b51e98f0db49956531211c4f" - ], - "version": "==0.7.2" - }, - "pycrypto": { - "hashes": [ - "sha256:f2ce1e989b272cfcb677616763e0a2e7ec659effa67a88aa92b3a65528f60a3c" - ], - "version": "==2.6.1" - }, - "scrypt": { - "hashes": [ - "sha256:dc40f0e1a357a49ca62f30f2fc09e92e02c062a656f27949b436b2ba8002d9e1", - "sha256:a343c302b3e99dcb7fcbe57aa7919ed761f1568f854291ccebe1b5e6e2c9e509", - "sha256:a124719c686f2b5957e392465147fb3fd6077e7c643e9538cab1ee631eb01dde", - "sha256:bc131f74a688fa09993c518ca666a2ebd4268b207e039cbab03a034228140d3e", - "sha256:232acdbc3434d2de55def8d5dbf1bc4b9bfc50da7c5741df2a6eebc4e18d3720", - "sha256:85919f023148cd9fb01d75ad4e3e061928c298fa6249a0cd6cd469c4b947595e", - "sha256:971db040d3963ebe4b919a203fe10d7d6659951d3644066314330983dc175ed4", - "sha256:475ac80239b3d788ae71a09c3019ca915e149aaa339adcdd1c9eef121293dc88", - "sha256:4ad7188f2e42dbee2ff1cd72e3da40b170ba41847effbf0d726444f62ae60f3a", - "sha256:18ccbc63d87c6f89b753194194bb37aeaf1abc517e4b989461d115c1d93ce128", - "sha256:c23daecee405cb036845917295c76f8d747fc890158df40cb304b4b3c3640079", - "sha256:f8239b2d47fa1d40bc27efd231dc7083695d10c1c2ac51a99380360741e0362d" - ], - "version": "==0.8.6" - }, - "six": { - "hashes": [ - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9" - ], - "version": "==1.11.0" - }, - "toolz": { - "hashes": [ - "sha256:929f0a7ea7f61c178bd951bdae93920515d3fbdbafc8e6caf82d752b9b3b31c9" - ], - "version": "==0.9.0" - }, - "urllib3": { - "hashes": [ - "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", - "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" - ], - "version": "==1.22" - }, - "voluptuous": { - "hashes": [ - "sha256:6bbbe83a509d948230e9f921ba2e75173590be988eeb446dbe7a54268bef4da8", - "sha256:af7315c9fa99e0bfd195a21106c82c81619b42f0bd9b6e287b797c6b6b6a9918" - ], - "version": "==0.11.1" - }, - "w3lib": { - "hashes": [ - "sha256:aaf7362464532b1036ab0092e2eee78e8fd7b56787baa9ed4967457b083d011b", - "sha256:55994787e93b411c2d659068b51b9998d9d0c05e0df188e6daf8f45836e1ea38" - ], - "version": "==1.19.0" - } - }, - "develop": { - "apipkg": { - "hashes": [ - "sha256:65d2aa68b28e7d31233bb2ba8eb31cda40e4671f8ac2d6b241e358c9652a74b9", - "sha256:2e38399dbe842891fe85392601aab8f40a8f4cc5a9053c326de35a1cc0297ac6" - ], - "version": "==1.4" - }, - "appnope": { - "hashes": [ - "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", - "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" - ], - "version": "==0.1.0" - }, - "astroid": { - "hashes": [ - "sha256:db5cfc9af6e0b60cd07c19478fb54021fc20d2d189882fbcbc94fc69a8aecc58", - "sha256:f0a0e386dbca9f93ea9f3ea6f32b37a24720502b7baa9cb17c3976a680d43a06" - ], - "version": "==1.6.1" - }, - "attrs": { - "hashes": [ - "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450", - "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9" - ], - "version": "==17.4.0" - }, - "autopep8": { - "hashes": [ - "sha256:c7be71ab0cb2f50c9c22c82f0c9acaafc6f57492c3fbfee9790c415005c2b9a5" - ], - "version": "==1.3.4" - }, - "backports.functools-lru-cache": { - "hashes": [ - "sha256:f0b0e4eba956de51238e17573b7087e852dfe9854afd2e9c873f73fc0ca0a6dd", - "sha256:9d98697f088eb1b0fa451391f91afb5e3ebde16bbdb272819fd091151fda4f1a" - ], - "markers": "python_version == '2.7'", - "version": "==1.5" - }, - "commonmark": { - "hashes": [ - "sha256:34d73ec8085923c023930dfc0bcd1c4286e28a2a82de094bb72fabcc0281cbe5" - ], - "version": "==0.5.4" - }, - "configparser": { - "hashes": [ - "sha256:5308b47021bc2340965c371f0f058cc6971a04502638d4244225c49d80db273a" - ], - "markers": "python_version == '2.7'", - "version": "==3.5.0" - }, - "coverage": { - "hashes": [ - "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", - "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", - "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80", - "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", - "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", - "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", - "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", - "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", - "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", - "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", - "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", - "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", - "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", - "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", - "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", - "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", - "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", - "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", - "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", - "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", - "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", - "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", - "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", - "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", - "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", - "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", - "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", - "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", - "sha256:ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91", - "sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2", - "sha256:b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d", - "sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a", - "sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4", - "sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd", - "sha256:e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77", - "sha256:f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e" - ], - "version": "==4.5.1" - }, - "decorator": { - "hashes": [ - "sha256:94d1d8905f5010d74bbbd86c30471255661a14187c45f8d7f3e5aa8540fdb2e5", - "sha256:7d46dd9f3ea1cf5f06ee0e4e1277ae618cf48dfb10ada7c8427cd46c42702a0e" - ], - "version": "==4.2.1" - }, - "docutils": { - "hashes": [ - "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6", - "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", - "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274" - ], - "version": "==0.14" - }, - "enum34": { - "hashes": [ - "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", - "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", - "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1", - "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850" - ], - "markers": "python_version == '2.7'", - "version": "==1.1.6" - }, - "execnet": { - "hashes": [ - "sha256:fc155a6b553c66c838d1a22dba1dc9f5f505c43285a878c6f74a79c024750b83", - "sha256:a7a84d5fa07a089186a329528f127c9d73b9de57f1a1131b82bb5320ee651f6a" - ], - "version": "==1.5.0" - }, - "funcsigs": { - "hashes": [ - "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", - "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" - ], - "markers": "python_version < '3.3'", - "version": "==1.0.2" - }, - "futures": { - "hashes": [ - "sha256:c4884a65654a7c45435063e14ae85280eb1f111d94e542396717ba9828c4337f", - "sha256:51ecb45f0add83c806c68e4b06106f90db260585b25ef2abfcda0bd95c0132fd" - ], - "version": "==3.1.1" - }, - "ipython": { - "hashes": [ - "sha256:fcc6d46f08c3c4de7b15ae1c426e15be1b7932bcda9d83ce1a4304e8c1129df3", - "sha256:51c158a6c8b899898d1c91c6b51a34110196815cc905f9be0fa5878e19355608" - ], - "version": "==6.2.1" - }, - "ipython-genutils": { - "hashes": [ - "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", - "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" - ], - "version": "==0.2.0" - }, - "isort": { - "hashes": [ - "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497", - "sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af", - "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8" - ], - "version": "==4.3.4" - }, - "jedi": { - "hashes": [ - "sha256:d795f2c2e659f5ea39a839e5230d70a0b045d0daee7ca2403568d8f348d0ad89", - "sha256:d6e799d04d1ade9459ed0f20de47c32f2285438956a677d083d3c98def59fa97" - ], - "version": "==0.11.1" - }, - "lazy-object-proxy": { - "hashes": [ - "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019", - "sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39", - "sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c", - "sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e", - "sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b", - "sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6", - "sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b", - "sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d", - "sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff", - "sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd", - "sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f", - "sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514", - "sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92", - "sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35", - "sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff", - "sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252", - "sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7", - "sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b", - "sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f", - "sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4", - "sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577", - "sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d", - "sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109", - "sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2", - "sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33", - "sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d", - "sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5", - "sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088", - "sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a" - ], - "version": "==1.3.1" - }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "version": "==0.6.1" - }, - "mock": { - "hashes": [ - "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", - "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba" - ], - "version": "==2.0.0" - }, - "parso": { - "hashes": [ - "sha256:a7bb86fe0844304869d1c08e8bd0e52be931228483025c422917411ab82d628a", - "sha256:5815f3fe254e5665f3c5d6f54f086c2502035cb631a91341591b5a564203cffb" - ], - "version": "==0.1.1" - }, - "pathlib2": { - "hashes": [ - "sha256:db3e43032d23787d3e9aec8c7ef1e0d2c3c589d5f303477661ebda2ca6d4bfba", - "sha256:d32550b75a818b289bd4c1f96b60c89957811da205afcceab75bc8b4857ea5b3" - ], - "markers": "python_version in '2.6 2.7 3.2 3.3'", - "version": "==2.3.0" - }, - "pbr": { - "hashes": [ - "sha256:60c25b7dfd054ef9bb0ae327af949dd4676aa09ac3a9471cdc871d8a9213f9ac", - "sha256:05f61c71aaefc02d8e37c0a3eeb9815ff526ea28b3b76324769e6158d7f95be1" - ], - "version": "==3.1.1" - }, - "pep8": { - "hashes": [ - "sha256:b22cfae5db09833bb9bd7c8463b53e1a9c9b39f12e304a8d0bba729c501827ee", - "sha256:fe249b52e20498e59e0b5c5256aa52ee99fc295b26ec9eaa85776ffdb9fe6374" - ], - "version": "==1.7.1" - }, - "pexpect": { - "hashes": [ - "sha256:6ff881b07aff0cb8ec02055670443f784434395f90c3285d2ae470f921ade52a", - "sha256:67b85a1565968e3d5b5e7c9283caddc90c3947a2625bed1905be27bd5a03e47d" - ], - "version": "==4.4.0" - }, - "pickleshare": { - "hashes": [ - "sha256:c9a2541f25aeabc070f12f452e1f2a8eae2abd51e1cd19e8430402bdf4c1d8b5", - "sha256:84a9257227dfdd6fe1b4be1319096c20eb85ff1e82c7932f36efccfe1b09737b" - ], - "version": "==0.7.4" - }, - "pluggy": { - "hashes": [ - "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff" - ], - "version": "==0.6.0" - }, - "prompt-toolkit": { - "hashes": [ - "sha256:3f473ae040ddaa52b52f97f6b4a493cfa9f5920c255a12dc56a7d34397a398a4", - "sha256:1df952620eccb399c53ebb359cc7d9a8d3a9538cb34c5a1344bdbeb29fbcc381", - "sha256:858588f1983ca497f1cf4ffde01d978a3ea02b01c8a26a8bbc5cd2e66d816917" - ], - "version": "==1.0.15" - }, - "ptyprocess": { - "hashes": [ - "sha256:e8c43b5eee76b2083a9badde89fd1bbce6c8942d1045146e100b7b5e014f4f1a", - "sha256:e64193f0047ad603b71f202332ab5527c5e52aa7c8b609704fc28c0dc20c4365" - ], - "version": "==0.5.2" - }, - "py": { - "hashes": [ - "sha256:8cca5c229d225f8c1e3085be4fcf306090b00850fefad892f9d96c7b6e2f310f", - "sha256:ca18943e28235417756316bfada6cd96b23ce60dd532642690dcfdaba988a76d" - ], - "version": "==1.5.2" - }, - "pycodestyle": { - "hashes": [ - "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9", - "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766" - ], - "version": "==2.3.1" - }, - "pygments": { - "hashes": [ - "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", - "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" - ], - "version": "==2.2.0" - }, - "pylint": { - "hashes": [ - "sha256:156839bedaa798febee72893beef00c650c2e7abafb5586fc7a6a56be7f80412", - "sha256:4fe3b99da7e789545327b75548cee6b511e4faa98afe268130fea1af4b5ec022" - ], - "version": "==1.8.2" - }, - "pytest": { - "hashes": [ - "sha256:8970e25181e15ab14ae895599a0a0e0ade7d1f1c4c8ca1072ce16f25526a184d", - "sha256:9ddcb879c8cc859d2540204b5399011f842e5e8823674bf429f70ada281b3cc6" - ], - "version": "==3.4.1" - }, - "pytest-cache": { - "hashes": [ - "sha256:be7468edd4d3d83f1e844959fd6e3fd28e77a481440a7118d430130ea31b07a9" - ], - "version": "==1.0" - }, - "pytest-console-scripts": { - "hashes": [ - "sha256:0a658015805bb4746f86fe829cca0e1958c5a8f4944c695c3c998309e58a5cbc" - ], - "version": "==0.1.4" - }, - "pytest-cov": { - "hashes": [ - "sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec", - "sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d" - ], - "version": "==2.5.1" - }, - "pytest-pep8": { - "hashes": [ - "sha256:032ef7e5fa3ac30f4458c73e05bb67b0f036a8a5cb418a534b3170f89f120318" - ], - "version": "==1.0.6" - }, - "pytest-pylint": { - "hashes": [ - "sha256:aa41ed2e8671e72bc40ab33f43232fdac712a316e177b4bfc23c6e685bd6b5cc", - "sha256:644f50217834099886c951bc5d034938ae305c530aa45d8866ecacd3b50d268b", - "sha256:9c9a3c13e1f15e674a7503a921fe1332239016718e3496e4c83990f266a79bc6", - "sha256:de36a9edfc0c3cf46412634d308b97ef78e953edaf45a150feaf372ceac18c4a", - "sha256:a9975080aff6030edb344ec9ea7297ce9e0c623ae06b406f4e9ebf8773975b6d", - "sha256:879aa6445bcb11840f3bc96265aa1a89168ab22e6f4a1b4a94200d99d86ca4be", - "sha256:41d6f223ae5e0a0fbb0056e826ecdb8046be2a828eba55c0d4f66cbfd7d27168" - ], - "version": "==0.8.0" - }, - "recommonmark": { - "hashes": [ - "sha256:cd8bf902e469dae94d00367a8197fb7b81fcabc9cfb79d520e0d22d0fbeaa8b7", - "sha256:6e29c723abcf5533842376d87c4589e62923ecb6002a8e059eb608345ddaff9d" - ], - "version": "==0.4.0" - }, - "scandir": { - "hashes": [ - "sha256:f39dd5affde2860fb28176d2233f318ccca97c55019407ee8172b3fba0b211db", - "sha256:7729b8444c5f5187649ff58501e7c2ad22b84d7dc28f738f64c5b615913fec22", - "sha256:b6cb611a18a828146a178362a36a2c6557c51c596ded4314cb516dd8c947b4ce", - "sha256:d985e36eb3effebb20434e6cd7495440b4ba676a22f3ec61e9fff9f3e2995238", - "sha256:24f32112c483ac6c4a40b62f1282e61ecca7977153b66a0d26a9583a716dcb64", - "sha256:b55a091b91f9e6c9c7129889b2f58df329530172a99172de9e784545342a45e6", - "sha256:f91418e82edb5a43b020fa15e30a41d730b71c5957536749366bf63cc05427b1", - "sha256:6c80092f8fe3e62c3da3110067589c6661c722b0889906d2974e5150f1314523", - "sha256:96dfc553f50946deb6d1cd762bac5cf122832c4aa253c885ca357ef53dd8d072", - "sha256:8e3ca5925cc13787aeafbf08f055a8066c091fc20bfa8783235b916cf047afbe", - "sha256:b2d55be869c4f716084a19b1e16932f0769711316ba62de941320bf2be84763d" - ], - "markers": "python_version < '3.5'", - "version": "==1.7" - }, - "simplegeneric": { - "hashes": [ - "sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173" - ], - "version": "==0.8.1" - }, - "singledispatch": { - "hashes": [ - "sha256:833b46966687b3de7f438c761ac475213e53b306740f1abfaa86e1d1aae56aa8", - "sha256:5b06af87df13818d14f08a028e42f566640aef80805c3b50c5056b086e3c2b9c" - ], - "markers": "python_version < '3.4'", - "version": "==3.4.0.3" - }, - "six": { - "hashes": [ - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9" - ], - "version": "==1.11.0" - }, - "traitlets": { - "hashes": [ - "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9", - "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835" - ], - "version": "==4.3.2" - }, - "typing": { - "hashes": [ - "sha256:b2c689d54e1144bbcfd191b0832980a21c2dbcf7b5ff7a66248a60c90e951eb8", - "sha256:3a887b021a77b292e151afb75323dea88a7bc1b3dfa92176cff8e44c8b68bddf", - "sha256:d400a9344254803a2368533e4533a4200d21eb7b6b729c173bc38201a74db3f2" - ], - "version": "==3.6.4" - }, - "wcwidth": { - "hashes": [ - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c", - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e" - ], - "version": "==0.1.7" - }, - "wrapt": { - "hashes": [ - "sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6" - ], - "version": "==1.10.11" - }, - "yapf": { - "hashes": [ - "sha256:0ba4765012218b67a8c67da5c045d1fb9171d786ffe8dce6c8d1e11cbbaba8eb", - "sha256:7f5efdb7edf0318b91e53721d934580a77153e24a222f52f6e1c3b7629aead43" - ], - "version": "==0.20.2" - } - } -} diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 9129a4a..0000000 --- a/circle.yml +++ /dev/null @@ -1,11 +0,0 @@ -machine: - services: - - docker - -dependencies: - override: - - echo "Ignore CircleCI detected dependencies" - -test: - override: - - docker build -t steemit/steem-python . diff --git a/setup.cfg b/setup.cfg index 5b0ba6b..0634abb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,10 +1,8 @@ [metadata] description-file=README.md -[aliases] -test=pytest [tool:pytest] -norecursedirs=dist docs build .tox deploy +norecursedirs=dist docs build deploy addopts = --pep8 testpaths = tests diff --git a/setup.py b/setup.py index 8fe2837..33d49d8 100644 --- a/setup.py +++ b/setup.py @@ -1,45 +1,169 @@ -#!/usr/bin/env python3 -from pipenv.project import Project -from pipenv.utils import convert_deps_to_pip -from setuptools import setup, find_packages +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Note: To use the 'upload' functionality of this file, you must: +# $ pip install twine + +import io +import os import sys +from shutil import rmtree + +from setuptools import find_packages, setup, Command +from setuptools.command.test import test as TestCommand + +# Package meta-data. +NAME = 'steem' +DESCRIPTION = 'Official python steem library.' +URL = 'https://github.com/steemit/steem-python' +EMAIL = 'john@steemit.com' +AUTHOR = 'Steemit' + +# What packages are required for this module to be executed? +REQUIRED = [ + 'appdirs', + 'certifi', + 'ecdsa>=0.13', + 'funcy', + 'prettytable', + 'pycrypto>=1.9.1', + 'pylibscrypt>=1.6.1', + 'scrypt>=0.8.0', + 'toolz', + 'ujson', + 'urllib3', + 'voluptuous' +] +TEST_REQUIRED = [ + 'pep8', + 'pytest', + 'pytest-pylint', + 'pytest-xdist', + 'pytest-runner', + 'yapf', + 'autopep8' +] + +BUILD_REQUIRED = [ + 'twine', + 'pypandoc', + 'recommonmark' + 'wheel', + 'setuptools', + 'sphinx', + 'sphinx_rtd_theme' +] +# The rest you shouldn't have to touch too much :) +# ------------------------------------------------ +# Except, perhaps the License and Trove Classifiers! +# If you do change the License, remember to change the Trove Classifier for that! + +here = os.path.abspath(os.path.dirname(__file__)) + +# Import the README and use it as the long-description. +# Note: this will only work if 'README.rst' is present in your MANIFEST.in file! +# with io.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f: +# long_description = '\n' + f.read() + + +class PyTest(TestCommand): + user_options = [('pytest-args=', 'a', "Arguments to pass into py.test")] + + def initialize_options(self): + TestCommand.initialize_options(self) + try: + from multiprocessing import cpu_count + self.pytest_args = ['-n', str(cpu_count()), '--boxed'] + except (ImportError, NotImplementedError): + self.pytest_args = ['-n', '1', '--boxed'] + + def finalize_options(self): + TestCommand.finalize_options(self) + self.test_args = [] + self.test_suite = True + + def run_tests(self): + import pytest + + errno = pytest.main(self.pytest_args) + sys.exit(errno) + + +class UploadCommand(Command): + """Support setup.py upload.""" -pfile = Project(chdir=False).parsed_pipfile -requirements = convert_deps_to_pip(pfile['packages'], r=False) -test_requirements = convert_deps_to_pip(pfile['dev-packages'], r=False) + description = 'Build and publish the package.' + user_options = [] + @staticmethod + def status(s): + """Prints things in bold.""" + print('\033[1m{0}\033[0m'.format(s)) + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + try: + self.status('Removing previous builds…') + rmtree(os.path.join(here, 'dist')) + except OSError: + pass + + self.status('Building Source and Wheel (universal) distribution…') + os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable)) + + self.status('Uploading the package to PyPi via Twine…') + os.system('twine upload dist/*') + + sys.exit() + + +# Where the magic happens: setup( - name='steem', - version='0.18.103', - description='Official Python Steem Library', - # long_description = file: README.rst + name=NAME, + version='0.18.3', + description=DESCRIPTION, keywords=['steem', 'steemit', 'cryptocurrency', 'blockchain'], - license='MIT', - url='https://github.com/steemit/steem-python', - maintainer='steemit_inc', - maintainer_email='john@steemit.com', - packages=find_packages(), - setup_requires=[ - 'pytest-runner', - 'pipenv', - ], - tests_require=test_requirements, - install_requires=requirements, + # long_description=long_description, + author=AUTHOR, + author_email=EMAIL, + url=URL, + packages=find_packages(exclude=('tests','scripts')), entry_points={ - 'console_scripts': [ - 'piston=steem.cli:legacyentry', - 'steempy=steem.cli:legacyentry', - 'steemtail=steem.cli:steemtailentry', - ], + 'console_scripts': [ + 'piston=steem.cli:legacyentry', + 'steempy=steem.cli:legacyentry', + 'steemtail=steem.cli:steemtailentry', + ], }, + install_requires=REQUIRED, + extras_require={ + 'dev': TEST_REQUIRED + BUILD_REQUIRED, + 'build': BUILD_REQUIRED, + 'test': TEST_REQUIRED + }, + tests_require=TEST_REQUIRED, + include_package_data=True, + license='MIT', + classifiers=[ + # Trove classifiers + # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', - 'Programming Language :: Python :: 2.7' - 'Programming Language :: Python :: 3', + 'Natural Language :: English', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Python Modules', 'Development Status :: 4 - Beta' - ]) + ], + # $ setup.py publish support. + cmdclass={ + 'upload': UploadCommand, + 'test': PyTest + }, +) \ No newline at end of file From 5d72dd28b877afb4e6d17559f5de94b013000a66 Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 12 Mar 2018 12:35:12 -0500 Subject: [PATCH 070/166] Updated setup.py. --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 33d49d8..3fe3435 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,8 @@ 'pytest-pylint', 'pytest-xdist', 'pytest-runner', + 'pytest-pep8', + 'pytest-cov', 'yapf', 'autopep8' ] From c406f8d55106e59b541efcc7a94e65274e28b43a Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 12 Mar 2018 12:42:06 -0500 Subject: [PATCH 071/166] More setup.py updates. --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 3fe3435..8e28621 100644 --- a/setup.py +++ b/setup.py @@ -25,6 +25,8 @@ 'certifi', 'ecdsa>=0.13', 'funcy', + 'futures ; markers="python_version < "3.0.0"', + 'future', 'prettytable', 'pycrypto>=1.9.1', 'pylibscrypt>=1.6.1', From 6172a8b5328c5015a035e1460a4fdd44af4c8191 Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 12 Mar 2018 12:45:02 -0500 Subject: [PATCH 072/166] Small fixes. --- .circleci/config.yml | 8 ++++---- setup.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b88413e..91eee71 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,7 +42,7 @@ workflows: test_all_envs: jobs: - test27 - - test33 - - test34 - - test35 - - test36 \ No newline at end of file +# - test33 +# - test34 +# - test35 +# - test36 \ No newline at end of file diff --git a/setup.py b/setup.py index 8e28621..51b2b27 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ 'certifi', 'ecdsa>=0.13', 'funcy', - 'futures ; markers="python_version < "3.0.0"', + 'futures ; python_version < "3.0.0"', 'future', 'prettytable', 'pycrypto>=1.9.1', From ede405f154ec7fae3cd28172959a67dda5a9be44 Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 12 Mar 2018 12:47:58 -0500 Subject: [PATCH 073/166] Small fixes. --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 51b2b27..490b00a 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,8 @@ 'toolz', 'ujson', 'urllib3', - 'voluptuous' + 'voluptuous', + 'w3lib' ] TEST_REQUIRED = [ 'pep8', From d7cbe6708a52c648ebc72d7be80380ed0ef33f93 Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 12 Mar 2018 12:49:47 -0500 Subject: [PATCH 074/166] Small fixes to imports. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 490b00a..e3e4e5f 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,7 @@ 'funcy', 'futures ; python_version < "3.0.0"', 'future', + 'langdetect', 'prettytable', 'pycrypto>=1.9.1', 'pylibscrypt>=1.6.1', From 889cbd8938ab53a49c7a19bef04750d5e209b8f1 Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 12 Mar 2018 13:03:28 -0500 Subject: [PATCH 075/166] Fixed build env. --- .circleci/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 91eee71..b88413e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,7 +42,7 @@ workflows: test_all_envs: jobs: - test27 -# - test33 -# - test34 -# - test35 -# - test36 \ No newline at end of file + - test33 + - test34 + - test35 + - test36 \ No newline at end of file From 9532e9e0362fc307e7b3c68d4fa13d9257b5d61a Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 12 Mar 2018 13:11:06 -0500 Subject: [PATCH 076/166] Fixed version check. --- steem/instance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/steem/instance.py b/steem/instance.py index 7935aed..42a11a2 100644 --- a/steem/instance.py +++ b/steem/instance.py @@ -18,7 +18,7 @@ def shared_steemd_instance(): global _shared_steemd_instance if not _shared_steemd_instance: - if sys.version >= '3.0': + if sys.version >= '3.5': _shared_steemd_instance = stm.steemd.Steemd( nodes=get_config_node_list()) else: From 1f72ef563c7a6a7986a183e4c22ebaf985c4e6b3 Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 12 Mar 2018 14:20:00 -0500 Subject: [PATCH 077/166] Updated version checking. --- steem/instance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/steem/instance.py b/steem/instance.py index 42a11a2..0be78f5 100644 --- a/steem/instance.py +++ b/steem/instance.py @@ -18,7 +18,7 @@ def shared_steemd_instance(): global _shared_steemd_instance if not _shared_steemd_instance: - if sys.version >= '3.5': + if sys.version >= '3.4': _shared_steemd_instance = stm.steemd.Steemd( nodes=get_config_node_list()) else: From 1b0cbd1264c06538cfd339c1249559e54e5867a8 Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 12 Mar 2018 14:35:07 -0500 Subject: [PATCH 078/166] Initial implementation of circleci 2.0. --- .circleci/config.yml | 4 ++-- steem/instance.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b88413e..6a8b323 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,7 +42,7 @@ workflows: test_all_envs: jobs: - test27 - - test33 - - test34 +# - test33 - TODO: Fix issues with deps for 3.3. +# - test34 - TODO: Fix steemd import in instance.py. - test35 - test36 \ No newline at end of file diff --git a/steem/instance.py b/steem/instance.py index 0be78f5..42a11a2 100644 --- a/steem/instance.py +++ b/steem/instance.py @@ -18,7 +18,7 @@ def shared_steemd_instance(): global _shared_steemd_instance if not _shared_steemd_instance: - if sys.version >= '3.4': + if sys.version >= '3.5': _shared_steemd_instance = stm.steemd.Steemd( nodes=get_config_node_list()) else: From c777c2dee0607feeec13a15480e44ee9b27d448e Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 12 Mar 2018 15:06:43 -0500 Subject: [PATCH 079/166] Updated readme. --- README.md | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 7eb8b8f..5d15678 100644 --- a/README.md +++ b/README.md @@ -3,35 +3,16 @@ `steem-python` is the official Steem library for Python. It comes with a BIP38 encrypted wallet and a practical CLI utility called `steempy`. -This library works on both Python 2.7 and 3.6. +This library currently works on Python 2.7, 3.5 and 3.6. Python 3.3 and 3.4 support forthcoming. # Installation -## Installation with pipenv (recommended) - -Install pipenv first if you don't have it: - -`pip3 install --upgrade --user pipenv` - -Then, install steem-python using it: - ``` git clone https://github.com/steemit/steem-python.git cd steem-python -pipenv install --three --dev # use --two instead of --three for Python 2.7 -pipenv install . +python install setup.py ``` -## Installation with pip3 - -``` -git clone https://github.com/steemit/steem-python.git -cd steem-python -pip3 install --user . -``` - -Note that installation using `pip3` requires that the `pipenv` module be installed to parse the requirements out of the `Pipfile` so that pip3 can do the install. If you get an error about `pipenv` not being found, you can resolve it with a `pip3 install --upgrade --user pipenv`, then the install with `pip3` will work as usual. - ## Homebrew Build Prereqs If you're on a mac, you may need to do the following first: @@ -57,14 +38,9 @@ Documentation is available at **http://steem.readthedocs.io** # Tests -Some tests are included. They can be run via either docker or vagrant, -for reproducibility, e.g.: - -* `docker build .` - -or +Some tests are included. They can be run via: -* `vagrant up` +* `python setup.py test` # TODO From 2b3ddb771327f45da62869e9ef0bb7c99051f179 Mon Sep 17 00:00:00 2001 From: roadscape Date: Mon, 12 Mar 2018 15:09:26 -0500 Subject: [PATCH 080/166] update Makefile --- Makefile | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 5fda0df..2379204 100644 --- a/Makefile +++ b/Makefile @@ -1,31 +1,18 @@ PROJECT := $(shell basename $(shell pwd)) PYTHON_FILES := steem steembase tests setup.py -default: docker_test - -.PHONY: fmt init test docker_test - -docker_test: - docker build . +.PHONY: clean test fmt install clean: rm -rf build/ dist/ *.egg-info .eggs/ .tox/ \ - .cache/ .coverage htmlcov src + __pycache__/ .cache/ .coverage htmlcov src test: clean - pip3 install --upgrade pip - pip3 install --upgrade pipenv - pipenv install --three --dev - pipenv install . - pipenv run py.test + python setup.py test fmt: - pipenv run yapf --recursive --in-place --style pep8 $(PYTHON_FILES) - pipenv run pycodestyle $(PYTHON_FILES) + yapf --recursive --in-place --style pep8 $(PYTHON_FILES) + pycodestyle $(PYTHON_FILES) -init: - pip3 install --upgrade pip - pip3 install --upgrade pipenv - pipenv lock - pipenv install --three --dev - pipenv install . +install: + python setup.py install From 7cd03b4362b91a37da2838d20b08a806dc9ee145 Mon Sep 17 00:00:00 2001 From: crokkon <33018033+crokkon@users.noreply.github.com> Date: Sun, 7 Jan 2018 13:57:52 +0100 Subject: [PATCH 081/166] update default node in help text and comments --- docs/cli.rst | 2 +- docs/steem.rst | 10 ++++------ steem/cli.py | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index a4c32b3..c5d3e13 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -154,7 +154,7 @@ You can see all available commands with ``steempy -h`` optional arguments: -h, --help show this help message and exit --node NODE URL for public Steem API (default: - "https://steemd.steemit.com") + "https://api.steemit.com") --no-broadcast, -d Do not broadcast anything --no-wallet, -p Do not load the wallet --unsigned, -x Do not try to sign the transaction diff --git a/docs/steem.rst b/docs/steem.rst index bd0a70f..47c91e0 100644 --- a/docs/steem.rst +++ b/docs/steem.rst @@ -147,12 +147,12 @@ and shall contain no whitespaces. | default_vote_weight | 100 | | nodes | https://gtg.steem.house:8090/ | +---------------------+-------------------------------+ - ~ % steempy set nodes https://gtg.steem.house:8090/,https://steemd.steemit.com + ~ % steempy set nodes https://gtg.steem.house:8090/,https://api.steemit.com ~ % steempy config +---------------------+----------------------------------------------------------+ | Key | Value | +---------------------+----------------------------------------------------------+ - | nodes | https://gtg.steem.house:8090/,https://steemd.steemit.com | + | nodes | https://gtg.steem.house:8090/,https://api.steemit.com | | default_vote_weight | 100 | | default_account | furion | +---------------------+----------------------------------------------------------+ @@ -173,7 +173,7 @@ Post, etc) will use this as their default instance. steemd_nodes = [ 'https://gtg.steem.house:8090', - 'https://steemd.steemit.com', + 'https://api.steemit.com', ] set_shared_steemd_instance(Steemd(nodes=steemd_nodes)) @@ -192,11 +192,9 @@ This is useful when you want to contain a modified ``steemd`` instance to an exp steemd_nodes = [ 'https://gtg.steem.house:8090', - 'https://steemd.steemit.com', + 'https://api.steemit.com', ] custom_instance = Steemd(nodes=steemd_nodes) account = Account('furion', steemd_instance=custom_instance) blockchain = Blockchain('head', steemd_instance=custom_instance) - - diff --git a/steem/cli.py b/steem/cli.py index b79b7b1..30b4b90 100644 --- a/steem/cli.py +++ b/steem/cli.py @@ -46,7 +46,7 @@ def legacyentry(): '--node', type=str, default=configStorage["node"], - help='URL for public Steem API (default: "https://steemd.steemit.com")' + help='URL for public Steem API (default: "https://api.steemit.com")' ) parser.add_argument( From a18e60a7294107c9bc23fd77e9322d1cfa6cf112 Mon Sep 17 00:00:00 2001 From: Vladimir Kamarzin Date: Mon, 12 Mar 2018 13:22:29 +0500 Subject: [PATCH 082/166] transactionbuilder.py: set operations.default_prefix explicitly This fixes transaction signing with non-steem networks. In steem we're always have STM prefix for pubkeys, but in other networks prefix is different, so transactionbuilder just cannon find the proper privkey-pubkey pair whether prefixes does not match. --- steem/transactionbuilder.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/steem/transactionbuilder.py b/steem/transactionbuilder.py index d9796f4..1cf60e2 100644 --- a/steem/transactionbuilder.py +++ b/steem/transactionbuilder.py @@ -4,6 +4,7 @@ from steembase.account import PrivateKey from steembase.exceptions import (InsufficientAuthorityError, MissingKeyError, InvalidKeyFormat) +from steembase import operations from steembase.operations import Operation from steembase.transactions import SignedTransaction, fmt_time_from_now, \ get_block_params @@ -103,6 +104,14 @@ def sign(self): from the wallet as defined in "missing_signatures" key of the transactions. """ + + # We need to set the default prefix, otherwise pubkeys are + # presented wrongly! + if self.steemd: + operations.default_prefix = self.steemd.chain_params["prefix"] + elif "blockchain" in self: + operations.default_prefix = self["blockchain"]["prefix"] + try: signedtx = SignedTransaction(**self.json()) except: # noqa FIXME(sneak) From 6a873546168fd7f564f3dc688487f61f739202e2 Mon Sep 17 00:00:00 2001 From: Vladimir Kamarzin Date: Mon, 12 Mar 2018 13:26:08 +0500 Subject: [PATCH 083/166] wallet.py: obtain key prefix from chain_params This enables support for multiple steem-like networks. --- steem/wallet.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/steem/wallet.py b/steem/wallet.py index 4980ea9..6b1e7db 100644 --- a/steem/wallet.py +++ b/steem/wallet.py @@ -55,7 +55,13 @@ def __init__(self, steemd_instance=None, **kwargs): # RPC self.steemd = steemd_instance or shared_steemd_instance() - self.prefix = "STM" + + # Prefix + if self.steemd: + self.prefix = self.steemd.chain_params["prefix"] + else: + # If not connected, load prefix from config + self.prefix = self.configStorage["prefix"] if "keys" in kwargs: self.setKeys(kwargs["keys"]) From e7586fa1961d09e454f1b9ef0ca0920a097bf95e Mon Sep 17 00:00:00 2001 From: Vladimir Kamarzin Date: Mon, 12 Mar 2018 13:28:53 +0500 Subject: [PATCH 084/166] base58.py: add GLS to known_prefixes This eliminates the following warning: Format GLS unkown. You've been warned! --- steembase/base58.py | 1 + 1 file changed, 1 insertion(+) diff --git a/steembase/base58.py b/steembase/base58.py index 193ec11..f898a4d 100644 --- a/steembase/base58.py +++ b/steembase/base58.py @@ -12,6 +12,7 @@ known_prefixes = [ PREFIX, + "GLS", "TST", ] From 3dfa38ca192c5400e5cb454e49a6f1a74e652cf9 Mon Sep 17 00:00:00 2001 From: Vladimir Kamarzin Date: Mon, 12 Mar 2018 13:42:11 +0500 Subject: [PATCH 085/166] chains.py: add GOLOS chain definition --- steembase/chains.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/steembase/chains.py b/steembase/chains.py index a15e266..8f0524f 100644 --- a/steembase/chains.py +++ b/steembase/chains.py @@ -8,6 +8,13 @@ "sbd_symbol": "SBD", "vests_symbol": "VESTS", }, + "GOLOS": { + "chain_id": "782a3039b478c839e4cb0c941ff4eaeb7df40bdd68bd441afd444b9da763de12", + "prefix": "GLS", + "steem_symbol": "GOLOS", + "sbd_symbol": "GBG", + "vests_symbol": "GESTS", + }, "TEST": { "chain_id": "9afbce9f2416520733bacb370315d32b6b2c43d6097576df1c1222859d91eecc", From 4e6cf306c68f9f4c755a5497eba073b1a92777a8 Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 19 Mar 2018 14:17:13 -0500 Subject: [PATCH 086/166] Fixed typo in install command. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d15678..7e92e8d 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This library currently works on Python 2.7, 3.5 and 3.6. Python 3.3 and 3.4 supp ``` git clone https://github.com/steemit/steem-python.git cd steem-python -python install setup.py +python setup.py install ``` ## Homebrew Build Prereqs From 635277c04e0d9ed9c58a92b8c2ec2909308d3a68 Mon Sep 17 00:00:00 2001 From: John White Date: Tue, 20 Mar 2018 14:52:40 -0500 Subject: [PATCH 087/166] Refactored old references of future_bytes to compat_bytes. --- steem/utils.py | 2 +- steembase/storage.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/steem/utils.py b/steem/utils.py index dceb4e5..a8140ed 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -410,7 +410,7 @@ def __bytes__(self): This is the expected and necessary behavior across both platforms. - w/ future_bytes method, we will ensure that the correct bytes method is always invoked, avoiding the `str` alias in + w/ compat_bytes method, we will ensure that the correct bytes method is always invoked, avoiding the `str` alias in 2.7. :param item: this is the object who's bytes method needs to be invoked diff --git a/steembase/storage.py b/steembase/storage.py index da6f771..d4a77c4 100644 --- a/steembase/storage.py +++ b/steembase/storage.py @@ -10,6 +10,7 @@ from appdirs import user_data_dir from steem.aes import AESCipher +from steem.utils import compat_bytes log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) @@ -399,7 +400,7 @@ def newMaster(self): def deriveChecksum(self, s): """ Derive the checksum """ - checksum = hashlib.sha256(future_bytes(s, "ascii")).hexdigest() + checksum = hashlib.sha256(compat_bytes(s, "ascii")).hexdigest() return checksum[:4] def getEncryptedMaster(self): From dd72c0aba3dd34f2386bbbe85e0406eb750c7870 Mon Sep 17 00:00:00 2001 From: John White Date: Wed, 21 Mar 2018 11:36:19 -0500 Subject: [PATCH 088/166] Updated construct_identifier. --- steem/cli.py | 4 ++-- steem/post.py | 8 ++++---- steem/utils.py | 14 +++++--------- tests/steem/test_utils.py | 2 +- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/steem/cli.py b/steem/cli.py index 30b4b90..a6496bf 100644 --- a/steem/cli.py +++ b/steem/cli.py @@ -1366,11 +1366,11 @@ def format_operation_details(op, memos=False): if op[0] == "vote": return "%s: %s" % ( op[1]["voter"], - construct_identifier('@', op[1]["author"], op[1]["permlink"])) + construct_identifier(op[1]["author"], op[1]["permlink"])) elif op[0] == "comment": return "%s: %s" % ( op[1]["author"], - construct_identifier('@', op[1]["author"], op[1]["permlink"])) + construct_identifier(op[1]["author"], op[1]["permlink"])) elif op[0] == "transfer": str_ = "%s -> %s %s" % ( op[1]["from"], diff --git a/steem/post.py b/steem/post.py index 14cd2dc..c939255 100644 --- a/steem/post.py +++ b/steem/post.py @@ -48,7 +48,7 @@ def __init__(self, post, steemd_instance=None): elif isinstance(post, dict) and "author" in post and "permlink" in post: post["author"] = post["author"].replace('@', '') - self.identifier = construct_identifier('@', post["author"], + self.identifier = construct_identifier(post["author"], post["permlink"]) else: raise ValueError("Post expects an identifier or a dict " @@ -59,7 +59,7 @@ def __init__(self, post, steemd_instance=None): @staticmethod def parse_identifier(uri): """ Extract post identifier from post URL. """ - return '@%s' % uri.split('@')[-1] + return '%s' % uri.split('@')[-1] def refresh(self): post_author, post_permlink = resolve_identifier(self.identifier) @@ -144,7 +144,7 @@ def _get_root_identifier(self, post=None): category = m.group(1) author = m.group(2) permlink = m.group(3) - return construct_identifier('@', author, permlink), category + return construct_identifier(author, permlink), category def get_replies(self): """ Return **first-level** comments of the post. @@ -276,7 +276,7 @@ def edit(self, body, meta=None, replace=False): log.info("No changes made! Skipping ...") return - reply_identifier = construct_identifier('@', + reply_identifier = construct_identifier( original_post["parent_author"], original_post["parent_permlink"]) new_meta = {} diff --git a/steem/utils.py b/steem/utils.py index a8140ed..e792622 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -231,20 +231,16 @@ def remove_from_dict(obj, remove_keys=list()): return {k: v for k, v in items if k not in remove_keys} -def construct_identifier(username_prefix='@', *args): +def construct_identifier(*args): """ Create a post identifier from comment/post object or arguments. Examples: :: - construct_identifier('@', 'username', 'permlink') - construct_identifier('@', {'author': 'username', + construct_identifier('username', 'permlink') + construct_identifier({'author': 'username', 'permlink': 'permlink'}) """ - # https://github.com/steemit/steem-python/issues/165 - # this is assert here will be removed with the above issue - # addressed. - assert(username_prefix == '@') if len(args) == 1: op = args[0] @@ -255,8 +251,8 @@ def construct_identifier(username_prefix='@', *args): raise ValueError( 'construct_identifier() received unparsable arguments') - fields = dict(prefix=username_prefix, author=author, permlink=permlink) - return "{prefix}{author}/{permlink}".format(**fields) + fields = dict(author=author, permlink=permlink) + return "{author}/{permlink}".format(**fields) def json_expand(json_op, key_name='json'): diff --git a/tests/steem/test_utils.py b/tests/steem/test_utils.py index 097da8a..effb972 100644 --- a/tests/steem/test_utils.py +++ b/tests/steem/test_utils.py @@ -11,7 +11,7 @@ class Testcases(unittest.TestCase): def test_constructIdentifier(self): - self.assertEqual(construct_identifier('@', "A", "B"), "@A/B") + self.assertEqual(construct_identifier("A", "B"), "A/B") def test_sanitizePermlink(self): self.assertEqual(sanitize_permlink("aAf_0.12"), "aaf-0-12") From 1ca0991ef6f102e07f39f81be983ae3552750d07 Mon Sep 17 00:00:00 2001 From: John White Date: Wed, 21 Mar 2018 15:30:36 -0500 Subject: [PATCH 089/166] Removed the @ as a part of a post identifier in code and documentation. --- steem/cli.py | 13 +++++++------ steem/commit.py | 6 +++--- steem/post.py | 6 +++--- steem/steemd.py | 2 +- steem/utils.py | 8 +++++++- tests/steem/test_utils.py | 2 +- 6 files changed, 22 insertions(+), 15 deletions(-) diff --git a/steem/cli.py b/steem/cli.py index a6496bf..b6fd802 100644 --- a/steem/cli.py +++ b/steem/cli.py @@ -175,8 +175,8 @@ def legacyentry(): parser_upvote.add_argument( 'post', type=str, - help='@author/permlink-identifier of the post to upvote ' + - 'to (e.g. @xeroc/python-steem-0-1)') + help='author/permlink-identifier of the post to upvote ' + + 'to (e.g. xeroc/python-steem-0-1)') parser_upvote.add_argument( '--account', type=str, @@ -202,8 +202,8 @@ def legacyentry(): parser_downvote.add_argument( 'post', type=str, - help='@author/permlink-identifier of the post to downvote ' + - 'to (e.g. @xeroc/python-steem-0-1)') + help='author/permlink-identifier of the post to downvote ' + + 'to (e.g. xeroc/python-steem-0-1)') parser_downvote.add_argument( '--weight', type=float, @@ -584,7 +584,7 @@ def legacyentry(): parser_resteem.add_argument( 'identifier', type=str, - help='@author/permlink-identifier of the post to resteem') + help='author/permlink-identifier of the post to resteem') parser_resteem.add_argument( '--account', type=str, @@ -776,6 +776,7 @@ def legacyentry(): steem = stm.Steem(no_broadcast=args.no_broadcast, **options) if args.command == "set": + # TODO: Evaluate this line with cli refactor. if (args.key in ["default_account"] and args.value[0] == "@"): args.value = args.value[1:] configStorage[args.key] = args.value @@ -1011,7 +1012,7 @@ def legacyentry(): for account in args.account: a = Account(account) - print("\n@%s" % a.name) + print("\n%s" % a.name) t = PrettyTable(["Account", "STEEM", "SBD", "VESTS"]) t.align = "r" t.add_row([ diff --git a/steem/commit.py b/steem/commit.py index 6dc17c2..d1ac606 100644 --- a/steem/commit.py +++ b/steem/commit.py @@ -181,7 +181,7 @@ def post(self, If this post is intended as a reply/comment, `reply_identifier` needs to be set with the identifier of the parent post/comment (eg. - `@author/permlink`). + `author/permlink`). Optionally you can also set json_metadata, comment_options and upvote the newly created post as an author. @@ -356,7 +356,7 @@ def vote(self, identifier, weight, account=None): """ Vote for a post :param str identifier: Identifier for the post to upvote Takes - the form ``@author/permlink`` + the form ``author/permlink`` :param float weight: Voting weight. Range: -100.0 - +100.0. May not be 0.0 :param str account: Voter to use for voting. (Optional) @@ -1308,7 +1308,7 @@ def custom_json(self, def resteem(self, identifier, account=None): """ Resteem a post - :param str identifier: post identifier (@/) + :param str identifier: post identifier (/) :param str account: (optional) the account to allow access to (defaults to ``default_account``) """ diff --git a/steem/post.py b/steem/post.py index c939255..4f58ae8 100644 --- a/steem/post.py +++ b/steem/post.py @@ -27,7 +27,7 @@ class Post(dict): Args: - post (str or dict): ``@author/permlink`` or raw ``comment`` as + post (str or dict): ``author/permlink`` or raw ``comment`` as dictionary. steemd_instance (Steemd): Steemd node to connect to @@ -47,7 +47,7 @@ def __init__(self, post, steemd_instance=None): self.identifier = self.parse_identifier(post) elif isinstance(post, dict) and "author" in post and "permlink" in post: - post["author"] = post["author"].replace('@', '') + self.identifier = construct_identifier(post["author"], post["permlink"]) else: @@ -157,7 +157,7 @@ def get_replies(self): def get_all_replies(root_post=None, comments=list(), all_comments=list()): """ Recursively fetch all the child comments, and return them as a list. - Usage: all_comments = Post.get_all_replies(Post('@foo/bar')) + Usage: all_comments = Post.get_all_replies(Post('foo/bar')) """ # see if our root post has any comments if root_post: diff --git a/steem/steemd.py b/steem/steemd.py index 31746b5..7cb3f87 100644 --- a/steem/steemd.py +++ b/steem/steemd.py @@ -108,7 +108,7 @@ def get_posts(self, limit=10, sort="hot", category=None, start=None): :param str sort: Sort the list by "recent" or "payout" :param str category: Only show posts in this category :param str start: Show posts after this post. Takes an - identifier of the form ``@author/permlink`` + identifier of the form ``author/permlink`` """ discussion_query = { diff --git a/steem/utils.py b/steem/utils.py index e792622..f758d08 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -251,6 +251,8 @@ def construct_identifier(*args): raise ValueError( 'construct_identifier() received unparsable arguments') + # remove the @ sign in case it was passed in by the user. + author = author.replace('@', '') fields = dict(author=author, permlink=permlink) return "{author}/{permlink}".format(**fields) @@ -288,7 +290,11 @@ def derive_permlink(title, parent_permlink=None): def resolve_identifier(identifier): - match = re.match("@?([\w\-\.]*)/([\w\-]*)", identifier) + + # in case the user supplied the @ sign. + identifier = identifier.replace('@', '') + + match = re.match("([\w\-\.]*)/([\w\-]*)", identifier) if not hasattr(match, "group"): raise ValueError("Invalid identifier") return match.group(1), match.group(2) diff --git a/tests/steem/test_utils.py b/tests/steem/test_utils.py index effb972..b6b3016 100644 --- a/tests/steem/test_utils.py +++ b/tests/steem/test_utils.py @@ -23,7 +23,7 @@ def test_derivePermlink(self): self.assertEqual(derive_permlink("[](){}"), "") def test_resolveIdentifier(self): - self.assertEqual(resolve_identifier("@A/B"), ("A", "B")) + self.assertEqual(resolve_identifier("A/B"), ("A", "B")) def test_formatTime(self): self.assertEqual(fmt_time(1463480746), "20160517t102546") From 87da2e201782867fe0e422e2dc46959f7171d0e9 Mon Sep 17 00:00:00 2001 From: roadscape Date: Thu, 22 Mar 2018 13:49:49 -0500 Subject: [PATCH 090/166] remove sprintf and clarify comment --- steem/post.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/steem/post.py b/steem/post.py index 4f58ae8..f85d534 100644 --- a/steem/post.py +++ b/steem/post.py @@ -58,8 +58,8 @@ def __init__(self, post, steemd_instance=None): @staticmethod def parse_identifier(uri): - """ Extract post identifier from post URL. """ - return '%s' % uri.split('@')[-1] + """ Extract canonical post id/url (i.e. strip any leading `@`). """ + return uri.split('@')[-1] def refresh(self): post_author, post_permlink = resolve_identifier(self.identifier) From 41809d3d4d9a81ca78696f2ad39206a24249e4f5 Mon Sep 17 00:00:00 2001 From: Harry Schmidt Date: Thu, 22 Mar 2018 12:25:33 -0700 Subject: [PATCH 091/166] bump versions ahead of publish --- docs/conf.py | 8 ++++---- setup.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index ff20770..ea168bd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -58,17 +58,17 @@ # General information about the project. project = 'steem-python' -copyright = '2017, Steemit Inc' -author = 'furion@steemit.com' +copyright = '2018, Steemit Inc' +author = 'cyonic@steemit.com' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '0.18' +version = '1.0' # The full version, including alpha/beta/rc tags. -release = '0.18.103' +release = '1.0.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index e3e4e5f..0c1c5bd 100644 --- a/setup.py +++ b/setup.py @@ -131,7 +131,7 @@ def run(self): # Where the magic happens: setup( name=NAME, - version='0.18.3', + version='1.0.0', description=DESCRIPTION, keywords=['steem', 'steemit', 'cryptocurrency', 'blockchain'], # long_description=long_description, @@ -172,4 +172,4 @@ def run(self): 'upload': UploadCommand, 'test': PyTest }, -) \ No newline at end of file +) From 253489af289649c701f60284fd56cd1aa8408e0b Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 22 Mar 2018 14:38:49 -0500 Subject: [PATCH 092/166] Updated install instructions in README.md for pip. --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e92e8d..4002ad2 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,18 @@ This library currently works on Python 2.7, 3.5 and 3.6. Python 3.3 and 3.4 supp # Installation +With pip: + +``` +pip3 install steem # pip install steem for 2.7 +``` + +From Source: + ``` git clone https://github.com/steemit/steem-python.git cd steem-python -python setup.py install +python3 setup.py install # python setup.py install for 2.7 ``` ## Homebrew Build Prereqs From 44e6e43980c9ac60c2b809ac9f47b9625ab7f6e4 Mon Sep 17 00:00:00 2001 From: roadscape Date: Tue, 27 Mar 2018 14:53:16 -0500 Subject: [PATCH 093/166] add test, fix rpc error, remove dupe method --- steem/steemd.py | 6 +----- steembase/http_client.py | 3 +-- tests/steem/test_steemd.py | 8 ++++++++ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/steem/steemd.py b/steem/steemd.py index 7cb3f87..6ae89bc 100644 --- a/steem/steemd.py +++ b/steem/steemd.py @@ -13,15 +13,11 @@ from .post import Post from .utils import resolve_identifier from .utils import compat_compose_dictionary +from .instance import get_config_node_list logger = logging.getLogger(__name__) -def get_config_node_list(): - nodes = configStorage.get('nodes', None) - if nodes: - return nodes.split(',') - class Steemd(HttpClient): """ Connect to the Steem network. diff --git a/steembase/http_client.py b/steembase/http_client.py index 18bca89..be1011c 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -258,8 +258,7 @@ def _return(self, response=None, args=None, return_with_args=None): error = response_json['error'] if self.re_raise: - error_message = error.get( - 'detail', response_json['error']['message']) + error_message = "Error Response: " + str(error) raise RPCError(error_message) result = response_json['error'] diff --git a/tests/steem/test_steemd.py b/tests/steem/test_steemd.py index fb07b66..b733920 100644 --- a/tests/steem/test_steemd.py +++ b/tests/steem/test_steemd.py @@ -2,6 +2,14 @@ from steem.steemd import Steemd +def test_get_version(): + """ We should be able to call get_version on steemd """ + s = Steemd() + response = s.call('get_version', api='login_api') + version = response['blockchain_version'] + assert version[0:4] == '0.19' + + def test_ensured_block_ranges(): """ Post should load correctly if passed a dict or string identifier. """ s = Steemd() From bb22d02a0e57db7d71ce72a74ccdb24f8fa74737 Mon Sep 17 00:00:00 2001 From: roadscape Date: Tue, 27 Mar 2018 15:25:40 -0500 Subject: [PATCH 094/166] disable account history test --- tests/steem/test_account.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/steem/test_account.py b/tests/steem/test_account.py index f583a7e..ee1f64b 100644 --- a/tests/steem/test_account.py +++ b/tests/steem/test_account.py @@ -2,6 +2,14 @@ def test_history(): + # TODO 1: test is disabled because api.steemit.com account history + # pruning is temporarily in place, breaking assumptions. + # TODO 2: in addition, the current pruning implementation fails + # to remove the very first operation, revealing a bug in + # history_reverse() which causes it to be included once + # on every page, causing an item count mismatch. + return + a = Account('barbara2') h1 = [x['index'] for x in list(a.history())] h2 = [x['index'] for x in list(a.history_reverse())] From 4d6da9eda00fa4ae92ca3ec330e7ddbc4108f26b Mon Sep 17 00:00:00 2001 From: roadscape Date: Tue, 27 Mar 2018 15:26:19 -0500 Subject: [PATCH 095/166] add failing test case for broadcast tx #188 #193 --- tests/steem/test_steemd.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/steem/test_steemd.py b/tests/steem/test_steemd.py index b733920..a4b8f7b 100644 --- a/tests/steem/test_steemd.py +++ b/tests/steem/test_steemd.py @@ -1,6 +1,15 @@ from funcy.colls import pluck from steem.steemd import Steemd +from steem import Steem +from steem.commit import Commit + + +def test_broadcast(): + wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' + c = Commit(steem=Steem(keys=[wif])) + c.claim_reward_balance(account='test') + def test_get_version(): """ We should be able to call get_version on steemd """ From e015c2c2aae0d6af20bd21cf92438a59704c1124 Mon Sep 17 00:00:00 2001 From: John White Date: Wed, 28 Mar 2018 11:26:51 -0500 Subject: [PATCH 096/166] Added proper tracking of errors in transactionbuilder.py and typos in commit.py documentation. --- steem/commit.py | 4 ++-- steem/transactionbuilder.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/steem/commit.py b/steem/commit.py index d1ac606..c9a4c01 100644 --- a/steem/commit.py +++ b/steem/commit.py @@ -134,13 +134,13 @@ def finalizeOp(self, ops, account, permission): return tx.broadcast() def sign(self, unsigned_trx, wifs=[]): - """ Sign a provided transaction witht he provided key(s) + """ Sign a provided transaction with the provided key(s) :param dict unsigned_trx: The transaction to be signed and returned :param string wifs: One or many wif keys to use for signing a transaction. If not present, the keys will be loaded from the wallet as defined in "missing_signatures" key - of the transactizions. + of the transactions. """ tx = TransactionBuilder( unsigned_trx, diff --git a/steem/transactionbuilder.py b/steem/transactionbuilder.py index 1cf60e2..65e8f05 100644 --- a/steem/transactionbuilder.py +++ b/steem/transactionbuilder.py @@ -114,9 +114,9 @@ def sign(self): try: signedtx = SignedTransaction(**self.json()) - except: # noqa FIXME(sneak) - raise ValueError("Invalid TransactionBuilder Format") - + except Exception as e: # noqa FIXME(sneak) + # raise ValueError("Invalid TransactionBuilder Format") + print("TransactionBuilder Error: {}".format(e.message)) if not any(self.wifs): raise MissingKeyError From 24f2cd985e7e16ebfeb4b700793d239df06b0d44 Mon Sep 17 00:00:00 2001 From: roadscape Date: Wed, 28 Mar 2018 12:05:41 -0500 Subject: [PATCH 097/166] add witness_update test, add passing conditions --- tests/steem/test_broadcast.py | 32 ++++++++++++++++++++++++++++++++ tests/steem/test_steemd.py | 7 ------- 2 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 tests/steem/test_broadcast.py diff --git a/tests/steem/test_broadcast.py b/tests/steem/test_broadcast.py new file mode 100644 index 0000000..3b0bde6 --- /dev/null +++ b/tests/steem/test_broadcast.py @@ -0,0 +1,32 @@ +from steem import Steem +from steem.commit import Commit +from steembase.exceptions import RPCError + + +def test_claim_reward(): + wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' + c = Commit(steem=Steem(keys=[wif])) + + try: + c.claim_reward_balance(account='test') + except RPCError as e: + assert 'missing required posting authority' in str(e) + else: + raise Exception('expected RPCError') + + +def test_witness_update(): + wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' + c = Commit(steem=Steem(keys=[wif])) + key = 'STM1111111111111111111111111111111114T1Anm' + props = { + 'account_creation_fee': '0.500 STEEM', + 'maximum_block_size': 65536, + 'sbd_interest_rate': 0} + + try: + c.witness_update(signing_key=key, account='test', props=props, url='') + except RPCError as e: + assert 'missing required active authority' in str(e) + else: + raise Exception('expected RPCError') diff --git a/tests/steem/test_steemd.py b/tests/steem/test_steemd.py index a4b8f7b..65a01c6 100644 --- a/tests/steem/test_steemd.py +++ b/tests/steem/test_steemd.py @@ -2,13 +2,6 @@ from steem.steemd import Steemd from steem import Steem -from steem.commit import Commit - - -def test_broadcast(): - wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' - c = Commit(steem=Steem(keys=[wif])) - c.claim_reward_balance(account='test') def test_get_version(): From 4e58555ab3730ee38e13bb24805a743014d891cc Mon Sep 17 00:00:00 2001 From: roadscape Date: Wed, 28 Mar 2018 12:06:49 -0500 Subject: [PATCH 098/166] extraneous import --- tests/steem/test_steemd.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/steem/test_steemd.py b/tests/steem/test_steemd.py index 65a01c6..b733920 100644 --- a/tests/steem/test_steemd.py +++ b/tests/steem/test_steemd.py @@ -1,8 +1,6 @@ from funcy.colls import pluck from steem.steemd import Steemd -from steem import Steem - def test_get_version(): """ We should be able to call get_version on steemd """ From 09b440efef730edf539dac98a2a81a042c541d0d Mon Sep 17 00:00:00 2001 From: roadscape Date: Wed, 28 Mar 2018 12:16:14 -0500 Subject: [PATCH 099/166] pull assert out of catch --- tests/steem/test_broadcast.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/steem/test_broadcast.py b/tests/steem/test_broadcast.py index 3b0bde6..304757e 100644 --- a/tests/steem/test_broadcast.py +++ b/tests/steem/test_broadcast.py @@ -7,13 +7,16 @@ def test_claim_reward(): wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' c = Commit(steem=Steem(keys=[wif])) + rpc_error = None try: c.claim_reward_balance(account='test') except RPCError as e: - assert 'missing required posting authority' in str(e) + rpc_error = str(e) else: raise Exception('expected RPCError') + assert 'missing required posting authority' in rpc_error + def test_witness_update(): wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' @@ -24,9 +27,12 @@ def test_witness_update(): 'maximum_block_size': 65536, 'sbd_interest_rate': 0} + rpc_error = None try: c.witness_update(signing_key=key, account='test', props=props, url='') except RPCError as e: - assert 'missing required active authority' in str(e) + rpc_error = str(e) else: raise Exception('expected RPCError') + + assert 'missing required active authority' in rpc_error From 681c3f4a64df28d42eef027e662b269847a21c23 Mon Sep 17 00:00:00 2001 From: roadscape Date: Wed, 28 Mar 2018 14:46:21 -0500 Subject: [PATCH 100/166] add transfer test, fix claim_reward bug #191 --- steem/commit.py | 2 +- tests/steem/test_broadcast.py | 29 ++++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/steem/commit.py b/steem/commit.py index d1ac606..ff8523d 100644 --- a/steem/commit.py +++ b/steem/commit.py @@ -897,7 +897,7 @@ def claim_reward_balance(self, # if no values were set by user, claim all outstanding balances on # account if none( - int(first(x.split(' '))) + float(first(x.split(' '))) for x in [reward_sbd, reward_steem, reward_vests]): a = Account(account) reward_steem = a['reward_steem_balance'] diff --git a/tests/steem/test_broadcast.py b/tests/steem/test_broadcast.py index 304757e..83d6663 100644 --- a/tests/steem/test_broadcast.py +++ b/tests/steem/test_broadcast.py @@ -3,13 +3,32 @@ from steembase.exceptions import RPCError +def test_transfer(): + wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' + c = Commit(steem=Steem(keys=[wif])) + + rpc_error = None + try: + c.transfer('test2', '1.000', 'STEEM', 'foo', 'test') + except RPCError as e: + rpc_error = str(e) + else: + raise Exception('expected RPCError') + + assert 'missing required active authority' in rpc_error + + def test_claim_reward(): wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' c = Commit(steem=Steem(keys=[wif])) rpc_error = None try: - c.claim_reward_balance(account='test') + c.claim_reward_balance( + account='test', + reward_steem='1.000 STEEM', + reward_vests='0.000000 VESTS', + reward_sbd='0.000 SBD') except RPCError as e: rpc_error = str(e) else: @@ -21,7 +40,7 @@ def test_claim_reward(): def test_witness_update(): wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' c = Commit(steem=Steem(keys=[wif])) - key = 'STM1111111111111111111111111111111114T1Anm' + signing_key = 'STM1111111111111111111111111111111114T1Anm' props = { 'account_creation_fee': '0.500 STEEM', 'maximum_block_size': 65536, @@ -29,7 +48,11 @@ def test_witness_update(): rpc_error = None try: - c.witness_update(signing_key=key, account='test', props=props, url='') + c.witness_update( + signing_key=signing_key, + account='test', + props=props, + url='foo') except RPCError as e: rpc_error = str(e) else: From fe52b25c88f3728c5d863bde005858cf0e85ca3f Mon Sep 17 00:00:00 2001 From: Nar Kumar Chhantyal Date: Sun, 1 Apr 2018 12:07:01 +0200 Subject: [PATCH 101/166] fix config name change: STEEMIT_BLOCK_INTERVAL -> STEEM_BLOCK_INTERVAL --- steem/blockchain.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/steem/blockchain.py b/steem/blockchain.py index 2a6d71c..1b04fad 100644 --- a/steem/blockchain.py +++ b/steem/blockchain.py @@ -78,7 +78,7 @@ def stream_from(self, _ = kwargs # we need this # Let's find out how often blocks are generated! - block_interval = self.config().get("STEEMIT_BLOCK_INTERVAL") + block_interval = self.config().get("STEEM_BLOCK_INTERVAL") if not start_block: start_block = self.get_current_block_num() @@ -176,7 +176,7 @@ def reliable_query(_client, _method, _api, *_args): def get_reliable_block_interval(_client): return reliable_query(_client, 'get_config', - 'database_api').get('STEEMIT_BLOCK_INTERVAL') + 'database_api').get('STEEM_BLOCK_INTERVAL') def get_reliable_current_block(_client): return reliable_query(_client, 'get_dynamic_global_properties', From 55cf95564913a4d774e458246f956c5d1925fb83 Mon Sep 17 00:00:00 2001 From: roadscape Date: Mon, 2 Apr 2018 16:25:04 -0500 Subject: [PATCH 102/166] assume appbase, downgrade on error. wip #164 --- steembase/http_client.py | 110 ++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 58 deletions(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index be1011c..f98bf23 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -53,6 +53,9 @@ class HttpClient(object): """ + # set of endpoints which were found to not support appbase/condenser_api + downgraded = set() + def __init__(self, nodes, **kwargs): self.return_with_args = kwargs.get('return_with_args', False) self.re_raise = kwargs.get('re_raise', True) @@ -65,6 +68,9 @@ def __init__(self, nodes, **kwargs): pool_block = kwargs.get('pool_block', False) tcp_keepalive = kwargs.get('tcp_keepalive', True) + # When everyone upgrades to appbase, remove this flag + self._use_appbase = True + if tcp_keepalive: socket_options = HTTPConnection.default_socket_options + \ [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), ] @@ -111,6 +117,7 @@ def set_node(self, node_url): """ Change current node to provided node URL. """ self.url = node_url self.request = partial(self.http.urlopen, 'POST', self.url) + self._use_appbase = node_url not in HttpClient.downgraded @property def hostname(self): @@ -148,21 +155,15 @@ def json_rpc_body(name, *args, **kwargs): as_json = kwargs.pop('as_json', True) _id = kwargs.pop('_id', 0) - headers = {"jsonrpc": "2.0", "id": _id} + body_dict = {"jsonrpc": "2.0", "id": _id} if kwargs is not None and len(kwargs) > 0: - - body_dict = dict(headers) body_dict.update({"method": "call", "params": [api, name, kwargs]}) elif api: - - body_dict = dict(headers) body_dict.update({"method": "call", "params": [api, name, args]}) else: - - body_dict = dict(headers) body_dict.update({"method": name, "params": args}) if as_json: @@ -182,25 +183,59 @@ def call(self, as handle node fail-over, unless we are broadcasting a transaction. In latter case, the exception is **re-raised**. + TODO: Documentation for args and kwargs. """ api = kwargs.get('api', None) return_with_args = kwargs.get('return_with_args', None) _ret_cnt = kwargs.get('_ret_cnt', 0) + if self._use_appbase: + kwargs['api'] = 'condenser_api' + else: + raise Exception("not using appbase... {}".format(self.url)) + body = HttpClient.json_rpc_body(name, *args, **kwargs) response = None + retryExceptions = (MaxRetryError, ReadTimeoutError, ProtocolError, RPCError,) if sys.version > '3.0': - errorList = (MaxRetryError, ReadTimeoutError, ProtocolError, - RemoteDisconnected, ConnectionResetError,) + retryExceptions += (RemoteDisconnected, ConnectionResetError,) else: - errorList = (MaxRetryError, ReadTimeoutError, ProtocolError, - HTTPException,) + retryExceptions += (HTTPException, RPCError,) try: response = self.request(body=body) - except (errorList) as e: + + # check for valid response http status code + successCodes = tuple(list(response.REDIRECT_STATUSES) + [200]) + if response.status not in successCodes: + raise RPCError("non-200 response:%s" % response.status) + + # check response format/success + result = json.loads(response.data.decode('utf-8')) + assert result, 'result entirely blank' + + # check for steemd error + if 'error' in result: + error = result['error'] + if error['code'] == 1 and 'no method with' in error['message']: + assert self.url not in HttpClient.downgraded + HttpClient.downgraded.add(self.url) + logging.info("Downgrading {} to pre-appbase.".format(self.url)) + return self.call( + name, + return_with_args=return_with_args, + _ret_cnt=_ret_cnt + 1, + *args) + + raise RPCError("RPC {}: {}".format(self.url, str(error))) + + if return_with_args or self.return_with_args: + return result['result'], args + return result['result'] + + except retryExceptions as e: # if we broadcasted a transaction, always raise # this is to prevent potential for double spend scenario if api == 'network_broadcast_api': @@ -221,53 +256,12 @@ def call(self, return_with_args=return_with_args, _ret_cnt=_ret_cnt + 1, *args) + except Exception as e: - if self.re_raise: - raise e - else: - extra = dict(err=e, request=self.request) - logger.info('Request error', extra=extra) - return self._return( - response=response, - args=args, - return_with_args=return_with_args) - else: - redirectStatuses = list(response.REDIRECT_STATUSES) - redirectStatuses.append(200) - if response.status not in tuple(redirectStatuses): - logger.info('non 200 response:%s', response.status) - - return self._return( - response=response, - args=args, - return_with_args=return_with_args) - - def _return(self, response=None, args=None, return_with_args=None): - return_with_args = return_with_args or self.return_with_args - result = None - - if response: - try: - response_json = json.loads(response.data.decode('utf-8')) - except Exception as e: - extra = dict(response=response, request_args=args, err=e) - logger.info('failed to load response', extra=extra) - result = None - else: - if 'error' in response_json: - error = response_json['error'] - - if self.re_raise: - error_message = "Error Response: " + str(error) - raise RPCError(error_message) - - result = response_json['error'] - else: - result = response_json.get('result', None) - if return_with_args: - return result, args - else: - return result + extra = dict(err=e, request=self.request) + logger.error('Request error: {}'.format(e), extra=extra) + raise e + def call_multi_with_futures(self, name, params, api=None, max_workers=None): From 2f16a69e76008fd0fa78dba1424aa77b1f651a3d Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 2 Apr 2018 17:04:50 -0500 Subject: [PATCH 103/166] Fixed arguments passed to Commit object in tests. --- tests/steem/test_broadcast.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/steem/test_broadcast.py b/tests/steem/test_broadcast.py index 83d6663..2048c13 100644 --- a/tests/steem/test_broadcast.py +++ b/tests/steem/test_broadcast.py @@ -1,11 +1,11 @@ -from steem import Steem +from steem.steemd import Steemd from steem.commit import Commit from steembase.exceptions import RPCError def test_transfer(): wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' - c = Commit(steem=Steem(keys=[wif])) + c = Commit(steemd_instance=Steemd(nodes=[], keys=[wif])) rpc_error = None try: @@ -20,7 +20,7 @@ def test_transfer(): def test_claim_reward(): wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' - c = Commit(steem=Steem(keys=[wif])) + c = Commit(steemd_instance=Steemd(nodes=[], keys=[wif])) rpc_error = None try: @@ -39,7 +39,7 @@ def test_claim_reward(): def test_witness_update(): wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' - c = Commit(steem=Steem(keys=[wif])) + c = Commit(steemd_instance=Steemd(nodes=[], keys=[wif])) signing_key = 'STM1111111111111111111111111111111114T1Anm' props = { 'account_creation_fee': '0.500 STEEM', From d9fe59fc8163e33d7864ebca89111181f012e3a2 Mon Sep 17 00:00:00 2001 From: John White Date: Tue, 3 Apr 2018 16:19:00 -0500 Subject: [PATCH 104/166] Small change. --- tests/steem/test_broadcast.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/steem/test_broadcast.py b/tests/steem/test_broadcast.py index 2048c13..498bfae 100644 --- a/tests/steem/test_broadcast.py +++ b/tests/steem/test_broadcast.py @@ -5,7 +5,8 @@ def test_transfer(): wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' - c = Commit(steemd_instance=Steemd(nodes=[], keys=[wif])) + c = Commit(steemd_instance=Steemd(nodes=[]), + keys=[wif]) rpc_error = None try: @@ -20,7 +21,8 @@ def test_transfer(): def test_claim_reward(): wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' - c = Commit(steemd_instance=Steemd(nodes=[], keys=[wif])) + c = Commit(steemd_instance=Steemd(nodes=[]), + keys=[wif]) rpc_error = None try: @@ -39,7 +41,9 @@ def test_claim_reward(): def test_witness_update(): wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' - c = Commit(steemd_instance=Steemd(nodes=[], keys=[wif])) + c = Commit(steemd_instance=Steemd(nodes=[]), + keys=[wif]) + signing_key = 'STM1111111111111111111111111111111114T1Anm' props = { 'account_creation_fee': '0.500 STEEM', From 47bb35485c8a86bf74dc19b6880ac0840f2e433a Mon Sep 17 00:00:00 2001 From: roadscape Date: Tue, 3 Apr 2018 16:36:12 -0500 Subject: [PATCH 105/166] add test, finish downgrade mode --- steembase/http_client.py | 158 ++++++++++++++++--------------------- tests/steem/test_steemd.py | 7 ++ 2 files changed, 77 insertions(+), 88 deletions(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index f98bf23..609eab3 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -53,7 +53,7 @@ class HttpClient(object): """ - # set of endpoints which were found to not support appbase/condenser_api + # set of endpoints which were found to not support condenser_api downgraded = set() def __init__(self, nodes, **kwargs): @@ -68,9 +68,6 @@ def __init__(self, nodes, **kwargs): pool_block = kwargs.get('pool_block', False) tcp_keepalive = kwargs.get('tcp_keepalive', True) - # When everyone upgrades to appbase, remove this flag - self._use_appbase = True - if tcp_keepalive: socket_options = HTTPConnection.default_socket_options + \ [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), ] @@ -117,7 +114,6 @@ def set_node(self, node_url): """ Change current node to provided node URL. """ self.url = node_url self.request = partial(self.http.urlopen, 'POST', self.url) - self._use_appbase = node_url not in HttpClient.downgraded @property def hostname(self): @@ -155,21 +151,26 @@ def json_rpc_body(name, *args, **kwargs): as_json = kwargs.pop('as_json', True) _id = kwargs.pop('_id', 0) - body_dict = {"jsonrpc": "2.0", "id": _id} - if kwargs is not None and len(kwargs) > 0: - body_dict.update({"method": "call", - "params": [api, name, kwargs]}) - elif api: - body_dict.update({"method": "call", - "params": [api, name, args]}) + # kv args take precedence over array args + if kwargs and len(kwargs) > 0: + args = kwargs + # if api specified, resort to 'call' style + if api: + method = 'call' + params = [api, name, args] else: - body_dict.update({"method": name, "params": args}) + method = name + params = args + + body = {"jsonrpc": "2.0", + "id": _id, + "method": method, + "params": params} if as_json: - return json.dumps(body_dict, ensure_ascii=False).encode('utf8') - else: - return body_dict + return json.dumps(body, ensure_ascii=False).encode('utf8') + return body def call(self, name, @@ -183,84 +184,65 @@ def call(self, as handle node fail-over, unless we are broadcasting a transaction. In latter case, the exception is **re-raised**. - TODO: Documentation for args and kwargs. """ - api = kwargs.get('api', None) - return_with_args = kwargs.get('return_with_args', None) - _ret_cnt = kwargs.get('_ret_cnt', 0) - - if self._use_appbase: - kwargs['api'] = 'condenser_api' - else: - raise Exception("not using appbase... {}".format(self.url)) - - body = HttpClient.json_rpc_body(name, *args, **kwargs) - response = None + return_with_args = kwargs.get('return_with_args', self.return_with_args) - retryExceptions = (MaxRetryError, ReadTimeoutError, ProtocolError, RPCError,) + # tuple of Exceptions which are eligible for retry + retry_exceptions = (MaxRetryError, ReadTimeoutError, ProtocolError, RPCError,) if sys.version > '3.0': - retryExceptions += (RemoteDisconnected, ConnectionResetError,) + retry_exceptions += (RemoteDisconnected, ConnectionResetError,) else: - retryExceptions += (HTTPException, RPCError,) - - try: - response = self.request(body=body) - - # check for valid response http status code - successCodes = tuple(list(response.REDIRECT_STATUSES) + [200]) - if response.status not in successCodes: - raise RPCError("non-200 response:%s" % response.status) - - # check response format/success - result = json.loads(response.data.decode('utf-8')) - assert result, 'result entirely blank' - - # check for steemd error - if 'error' in result: - error = result['error'] - if error['code'] == 1 and 'no method with' in error['message']: - assert self.url not in HttpClient.downgraded - HttpClient.downgraded.add(self.url) - logging.info("Downgrading {} to pre-appbase.".format(self.url)) - return self.call( - name, - return_with_args=return_with_args, - _ret_cnt=_ret_cnt + 1, - *args) - - raise RPCError("RPC {}: {}".format(self.url, str(error))) - - if return_with_args or self.return_with_args: - return result['result'], args - return result['result'] - - except retryExceptions as e: - # if we broadcasted a transaction, always raise - # this is to prevent potential for double spend scenario - if api == 'network_broadcast_api': - raise e - - # try switching nodes before giving up - if _ret_cnt > 2: - # we should wait only a short period before trying - # the next node, but still slowly increase backoff - time.sleep(_ret_cnt) - if _ret_cnt > 10: + retry_exceptions += (HTTPException,) + + tries = 0 + while True: + try: + + new_kwargs = kwargs.copy() + if self.url not in HttpClient.downgraded: + new_kwargs['api'] = 'condenser_api' + + body = HttpClient.json_rpc_body(name, *args, **new_kwargs) + response = self.request(body=body) + + success_codes = tuple(list(response.REDIRECT_STATUSES) + [200]) + if response.status not in success_codes: + raise RPCError("non-200 response:%s" % response.status) + + result = json.loads(response.data.decode('utf-8')) + assert result, 'result entirely blank' + + if 'error' in result: + message = result['error']['message'] + + # -- legacy (pre-appbase) nodes always return err code 1 -- + if result['error']['code'] == 1: + message = "; ".join(message.split("\n")[0:2]) + if self.url not in HttpClient.downgraded: + logging.error('Downgrade and retry: %s', message) + HttpClient.downgraded.add(self.url) + continue + + raise RPCError("RPC {}: {}".format(self.url, message)) + + if return_with_args: + return result['result'], args + return result['result'] + + except retry_exceptions as e: + if tries > 10: + raise e + time.sleep(tries) + self.next_node() + logging.info('Rotated to %s due to %s' % (self.url, str(e))) + tries += 1 + continue + + except Exception as e: + extra = dict(err=e, request=self.request) + logger.error('Request error: {}'.format(e), extra=extra) raise e - self.next_node() - logging.debug('Switched node to %s due to exception: %s' % - (self.hostname, e.__class__.__name__)) - return self.call( - name, - return_with_args=return_with_args, - _ret_cnt=_ret_cnt + 1, - *args) - - except Exception as e: - extra = dict(err=e, request=self.request) - logger.error('Request error: {}'.format(e), extra=extra) - raise e def call_multi_with_futures(self, name, params, api=None, diff --git a/tests/steem/test_steemd.py b/tests/steem/test_steemd.py index b733920..48f59ef 100644 --- a/tests/steem/test_steemd.py +++ b/tests/steem/test_steemd.py @@ -10,6 +10,13 @@ def test_get_version(): assert version[0:4] == '0.19' +def test_get_dgp(): + """ We should be able to call get_dynamic_global_properties on steemd """ + s = Steemd() + response = s.call('get_dynamic_global_properties', api='database_api') + assert response['head_block_number'] > 20e6 + + def test_ensured_block_ranges(): """ Post should load correctly if passed a dict or string identifier. """ s = Steemd() From dbe08fb7f63f1da7d63d80e856f55a9fb9d81050 Mon Sep 17 00:00:00 2001 From: John White Date: Tue, 3 Apr 2018 17:09:53 -0500 Subject: [PATCH 106/166] Add reequest body to rpc error report in http_client. --- steembase/http_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index 609eab3..92b91b1 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -224,7 +224,7 @@ def call(self, HttpClient.downgraded.add(self.url) continue - raise RPCError("RPC {}: {}".format(self.url, message)) + raise RPCError("RPC Error - Node: {} Body: {} Error: {}".format(self.url, str(body), message)) if return_with_args: return result['result'], args From d1e3630830b8820c1f425adeec9cb5817c4d73c6 Mon Sep 17 00:00:00 2001 From: roadscape Date: Tue, 3 Apr 2018 17:33:12 -0500 Subject: [PATCH 107/166] clarifications --- steembase/http_client.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index 92b91b1..e30d1e1 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -147,29 +147,31 @@ def json_rpc_body(name, *args, **kwargs): Otherwise, a Python dictionary is returned. """ - api = kwargs.pop('api', None) + + # if kwargs is non-empty after this, we pass it to steemd as_json = kwargs.pop('as_json', True) + api = kwargs.pop('api', None) _id = kwargs.pop('_id', 0) - # kv args take precedence over array args - if kwargs and len(kwargs) > 0: + # we pass `args` to steemd. kwargs overrides, if present. + assert not (kwargs and args), 'fail - passed array AND object args' + if kwargs: args = kwargs - # if api specified, resort to 'call' style if api: - method = 'call' - params = [api, name, args] + body = {'jsonrpc': '2.0', + 'id': _id, + 'method': 'call', + 'params': [api, name, args]} else: - method = name - params = args - - body = {"jsonrpc": "2.0", - "id": _id, - "method": method, - "params": params} + body = {'jsonrpc': '2.0', + 'id': _id, + 'method': name, + 'params': args} if as_json: return json.dumps(body, ensure_ascii=False).encode('utf8') + return body def call(self, @@ -199,11 +201,11 @@ def call(self, while True: try: - new_kwargs = kwargs.copy() + body_kwargs = kwargs.copy() if self.url not in HttpClient.downgraded: - new_kwargs['api'] = 'condenser_api' + body_kwargs['api'] = 'condenser_api' - body = HttpClient.json_rpc_body(name, *args, **new_kwargs) + body = HttpClient.json_rpc_body(name, *args, **body_kwargs) response = self.request(body=body) success_codes = tuple(list(response.REDIRECT_STATUSES) + [200]) @@ -239,6 +241,8 @@ def call(self, tries += 1 continue + # TODO: unclear why this case is here; need to explicitly + # define exceptions for which we refuse to retry. except Exception as e: extra = dict(err=e, request=self.request) logger.error('Request error: {}'.format(e), extra=extra) From 41c5c93e82293f73243d0ae9f9b361c70969b289 Mon Sep 17 00:00:00 2001 From: roadscape Date: Tue, 3 Apr 2018 17:40:02 -0500 Subject: [PATCH 108/166] use new var instead of override --- steembase/http_client.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index e30d1e1..4c5e4a8 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -148,26 +148,25 @@ def json_rpc_body(name, *args, **kwargs): """ - # if kwargs is non-empty after this, we pass it to steemd + # if kwargs is non-empty after this, it becomes the call params as_json = kwargs.pop('as_json', True) api = kwargs.pop('api', None) _id = kwargs.pop('_id', 0) - # we pass `args` to steemd. kwargs overrides, if present. + # `kwargs` for object-style param, `args` for list-style. pick one. assert not (kwargs and args), 'fail - passed array AND object args' - if kwargs: - args = kwargs + params = kwargs if kwargs else args if api: body = {'jsonrpc': '2.0', 'id': _id, 'method': 'call', - 'params': [api, name, args]} + 'params': [api, name, params]} else: body = {'jsonrpc': '2.0', 'id': _id, 'method': name, - 'params': args} + 'params': params} if as_json: return json.dumps(body, ensure_ascii=False).encode('utf8') From 39eb0009d2e853af419f814261143649ed47db8e Mon Sep 17 00:00:00 2001 From: roadscape Date: Wed, 4 Apr 2018 10:18:46 -0500 Subject: [PATCH 109/166] better errors, tune retry --- steembase/http_client.py | 55 ++++++++++++++++++++--------------- tests/steem/test_broadcast.py | 6 ++-- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index 4c5e4a8..9e9ef9b 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -53,11 +53,10 @@ class HttpClient(object): """ - # set of endpoints which were found to not support condenser_api - downgraded = set() + # set of endpoints which were detected to not support condenser_api + non_appbase_nodes = set() def __init__(self, nodes, **kwargs): - self.return_with_args = kwargs.get('return_with_args', False) self.re_raise = kwargs.get('re_raise', True) self.max_workers = kwargs.get('max_workers', None) @@ -99,6 +98,14 @@ def __init__(self, nodes, **kwargs): log_level = kwargs.get('log_level', logging.INFO) logger.setLevel(log_level) + def _curr_node_downgraded(self): + return self.url in HttpClient.non_appbase_nodes + + def _downgrade_curr_node(self): + if self._curr_node_downgraded(): return False + HttpClient.non_appbase_nodes.add(self.url) + return True + def next_node(self): """ Switch to the next available node. @@ -182,13 +189,10 @@ def call(self, Warnings: This command will auto-retry in case of node failure, as well - as handle node fail-over, unless we are broadcasting a - transaction. In latter case, the exception is **re-raised**. + as handle node fail-over. """ - return_with_args = kwargs.get('return_with_args', self.return_with_args) - # tuple of Exceptions which are eligible for retry retry_exceptions = (MaxRetryError, ReadTimeoutError, ProtocolError, RPCError,) if sys.version > '3.0': @@ -200,8 +204,9 @@ def call(self, while True: try: + retriable = True body_kwargs = kwargs.copy() - if self.url not in HttpClient.downgraded: + if not self._curr_node_downgraded(): body_kwargs['api'] = 'condenser_api' body = HttpClient.json_rpc_body(name, *args, **body_kwargs) @@ -209,42 +214,44 @@ def call(self, success_codes = tuple(list(response.REDIRECT_STATUSES) + [200]) if response.status not in success_codes: - raise RPCError("non-200 response:%s" % response.status) + raise RPCError("non-200 response: %s from %s" + % (response.status, self.hostname)) result = json.loads(response.data.decode('utf-8')) assert result, 'result entirely blank' if 'error' in result: - message = result['error']['message'] - - # -- legacy (pre-appbase) nodes always return err code 1 -- - if result['error']['code'] == 1: - message = "; ".join(message.split("\n")[0:2]) - if self.url not in HttpClient.downgraded: - logging.error('Downgrade and retry: %s', message) - HttpClient.downgraded.add(self.url) + # legacy (pre-appbase) nodes always return err code 1 + legacy = result['error']['code'] == 1 + detail = result['error']['message'] + error = result['error']['data']['name'] + + if legacy: + detail = ":".join(detail.split("\n")[0:2]) + if self._downgrade_curr_node(): + logging.error('Downgrade-retry %s', self.hostname) continue - raise RPCError("RPC Error - Node: {} Body: {} Error: {}".format(self.url, str(body), message)) + retriable = error in ['lock_exception'] # TODO: other retriable error types? + raise RPCError('%s from %s (%s) in %s' % ( + error, self.hostname, detail, name)) - if return_with_args: - return result['result'], args return result['result'] except retry_exceptions as e: - if tries > 10: + if tries >= 10 or not retriable: raise e + tries += 1 + logging.error('Retry in %ds -- %s', tries, e) time.sleep(tries) self.next_node() - logging.info('Rotated to %s due to %s' % (self.url, str(e))) - tries += 1 continue # TODO: unclear why this case is here; need to explicitly # define exceptions for which we refuse to retry. except Exception as e: extra = dict(err=e, request=self.request) - logger.error('Request error: {}'.format(e), extra=extra) + logger.error('Unexpected error: %s', e, extra=extra) raise e diff --git a/tests/steem/test_broadcast.py b/tests/steem/test_broadcast.py index 83d6663..04208d0 100644 --- a/tests/steem/test_broadcast.py +++ b/tests/steem/test_broadcast.py @@ -15,7 +15,7 @@ def test_transfer(): else: raise Exception('expected RPCError') - assert 'missing required active authority' in rpc_error + assert 'tx_missing_active_auth' in rpc_error def test_claim_reward(): @@ -34,7 +34,7 @@ def test_claim_reward(): else: raise Exception('expected RPCError') - assert 'missing required posting authority' in rpc_error + assert 'tx_missing_posting_auth' in rpc_error def test_witness_update(): @@ -58,4 +58,4 @@ def test_witness_update(): else: raise Exception('expected RPCError') - assert 'missing required active authority' in rpc_error + assert 'tx_missing_active_auth' in rpc_error From 032933fbc38fc91be735d13138b4711a86e9e397 Mon Sep 17 00:00:00 2001 From: John White Date: Wed, 4 Apr 2018 18:25:04 -0500 Subject: [PATCH 110/166] Fixed json unicode issues. --- steem/transactionbuilder.py | 4 ++-- steem/utils.py | 23 +++++++++++++++++++++++ steembase/types.py | 11 ++++++++--- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/steem/transactionbuilder.py b/steem/transactionbuilder.py index 65e8f05..9aa289e 100644 --- a/steem/transactionbuilder.py +++ b/steem/transactionbuilder.py @@ -115,8 +115,8 @@ def sign(self): try: signedtx = SignedTransaction(**self.json()) except Exception as e: # noqa FIXME(sneak) - # raise ValueError("Invalid TransactionBuilder Format") - print("TransactionBuilder Error: {}".format(e.message)) + raise e + if not any(self.wifs): raise MissingKeyError diff --git a/steem/utils.py b/steem/utils.py index f758d08..856ebb8 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -385,6 +385,29 @@ def compat_compose_dictionary(dictionary, **kwargs): return composed_dict +def compat_json(data, ignore_dicts=False): + """ + + :param data: Json Data we want to ensure compatibility on. + :param ignore_dicts: should only be set to true when first called. + :return: Python compatible 2.7 byte-strings when encountering unicode. + """ + # if this is a unicode string, return its string representation + if isinstance(data, unicode): + return data.encode('utf-8') + # if this is a list of values, return list of byte-string values + if isinstance(data, list): + return [compat_json(item, ignore_dicts=True) for item in data] + # if this is a dictionary, return dictionary of byte-string keys and values + # but only if we haven't already byte-string it + if isinstance(data, dict) and not ignore_dicts: + return { + compat_json(key, ignore_dicts=True): compat_json(value, ignore_dicts=True) + for key, value in data.iteritems() + } + # if it's anything else, return it in its original form + return data + def compat_bytes(item, encoding=None): """ This method is required because Python 2.7 `bytes` is simply an alias for `str`. Without this method, diff --git a/steembase/types.py b/steembase/types.py index 1d6757e..8ac3c0e 100644 --- a/steembase/types.py +++ b/steembase/types.py @@ -1,10 +1,11 @@ import json +import sys import struct import time import array from binascii import hexlify, unhexlify from calendar import timegm -from steem.utils import compat_bytes +from steem.utils import compat_bytes, compat_json object_type = { "dynamic_global_property": 0, @@ -66,8 +67,12 @@ def JsonObj(data): """ Returns json object from data """ try: - return json.loads(str(data)) - except: # noqa FIXME(sneak) + if sys.version >= '3.0': + return json.loads(str(data)) + else: + return compat_json(json.loads(str(data), object_hook=compat_json), + ignore_dicts=True) + except Exception as e: # noqa FIXME(sneak) try: return data.__str__() except: # noqa FIXME(sneak) From bf5c716921c6302b59869a34c8b891d8ae94a9b1 Mon Sep 17 00:00:00 2001 From: roadscape Date: Fri, 6 Apr 2018 11:44:41 -0500 Subject: [PATCH 111/166] exception cleanup, fix retriable rpc errs --- steem/blockchain.py | 12 ++++-------- steem/steem.py | 4 +--- steembase/exceptions.py | 4 ++++ steembase/http_client.py | 40 ++++++++++++++++++++++++++++------------ 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/steem/blockchain.py b/steem/blockchain.py index 1b04fad..0db10f9 100644 --- a/steem/blockchain.py +++ b/steem/blockchain.py @@ -156,23 +156,19 @@ def get_reliable_client(_timeout): def reliable_query(_client, _method, _api, *_args): # this will ALWAYS eventually return, at all costs - retval = None - while retval is None: + while True: try: - retval = _client.call(_method, *_args, api=_api) + return _client.call(_method, *_args, api=_api) except Exception as e: - logger.info( - 'Failed to get response', + logger.error( + 'Error: %s' % str(s), extra=dict( exc=e, response=retval, api_name=_api, api_method=_method, api_args=_args)) - retval = None - if retval is None: time.sleep(1) - return retval def get_reliable_block_interval(_client): return reliable_query(_client, 'get_config', diff --git a/steem/steem.py b/steem/steem.py index 58047fe..8a65d34 100644 --- a/steem/steem.py +++ b/steem/steem.py @@ -91,10 +91,8 @@ def __init__(self, api_name="", method_name="", exec_method=None): return def __call__(self, *args, **kwargs): + assert not (args and kwargs), "specified both args and kwargs" if len(kwargs) > 0: - if len(args) > 0: - raise RPCError( - "Cannot specify both args and kwargs in RPC") return self.exec_method( self.method_name, kwargs=kwargs, api=self.api_name) return self.exec_method(self.method_name, *args, api=self.api_name) diff --git a/steembase/exceptions.py b/steembase/exceptions.py index 15af068..686af94 100644 --- a/steembase/exceptions.py +++ b/steembase/exceptions.py @@ -21,6 +21,10 @@ class RPCError(Exception): pass +class RPCErrorRecoverable(RPCError): + pass + + class NumRetriesReached(Exception): pass diff --git a/steembase/http_client.py b/steembase/http_client.py index 9e9ef9b..1e3442a 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -9,7 +9,7 @@ import concurrent.futures import certifi import urllib3 -from steembase.exceptions import RPCError +from steembase.exceptions import RPCError, RPCErrorRecoverable from urllib3.connection import HTTPConnection from urllib3.exceptions import MaxRetryError, ReadTimeoutError, ProtocolError @@ -102,9 +102,20 @@ def _curr_node_downgraded(self): return self.url in HttpClient.non_appbase_nodes def _downgrade_curr_node(self): - if self._curr_node_downgraded(): return False HttpClient.non_appbase_nodes.add(self.url) - return True + + def _is_error_recoverable(self, error): + message = error['message'] + + # {"code"=>-32003, "message"=>"Unable to acquire database lock"} + if message == 'Unable to acquire database lock': + return True + + # {"code"=>-32000, "message"=>"Unknown exception", "data"=>"0 exception: unspecified\nUnknown Exception\n[...]"} + if message == 'Unknown exception': + return True + + return False def next_node(self): """ Switch to the next available node. @@ -194,7 +205,8 @@ def call(self, """ # tuple of Exceptions which are eligible for retry - retry_exceptions = (MaxRetryError, ReadTimeoutError, ProtocolError, RPCError,) + retry_exceptions = (MaxRetryError, ReadTimeoutError, + ProtocolError, RPCErrorRecoverable,) if sys.version > '3.0': retry_exceptions += (RemoteDisconnected, ConnectionResetError,) else: @@ -204,7 +216,6 @@ def call(self, while True: try: - retriable = True body_kwargs = kwargs.copy() if not self._curr_node_downgraded(): body_kwargs['api'] = 'condenser_api' @@ -214,8 +225,8 @@ def call(self, success_codes = tuple(list(response.REDIRECT_STATUSES) + [200]) if response.status not in success_codes: - raise RPCError("non-200 response: %s from %s" - % (response.status, self.hostname)) + raise RPCErrorRecoverable("non-200 response: %s from %s" + % (response.status, self.hostname)) result = json.loads(response.data.decode('utf-8')) assert result, 'result entirely blank' @@ -228,18 +239,23 @@ def call(self, if legacy: detail = ":".join(detail.split("\n")[0:2]) - if self._downgrade_curr_node(): + if not self._curr_node_downgraded(): + self._downgrade_curr_node() logging.error('Downgrade-retry %s', self.hostname) continue - retriable = error in ['lock_exception'] # TODO: other retriable error types? - raise RPCError('%s from %s (%s) in %s' % ( - error, self.hostname, detail, name)) + detail = ('%s from %s (%s) in %s' % ( + error, self.hostname, detail, name)) + + if self._is_error_recoverable(result['error']): + raise RPCErrorRecoverable(detail) + else: + raise RPCError(detail) return result['result'] except retry_exceptions as e: - if tries >= 10 or not retriable: + if tries >= 10: raise e tries += 1 logging.error('Retry in %ds -- %s', tries, e) From 9b9b4f058f89c9642b64776dad18051580005ba1 Mon Sep 17 00:00:00 2001 From: roadscape Date: Mon, 16 Apr 2018 12:32:11 -0500 Subject: [PATCH 112/166] fix error info-access, add retry case and get_block test --- steem/steem.py | 1 - steembase/http_client.py | 24 ++++++++++++++++++++++-- tests/steem/test_steemd.py | 20 ++++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/steem/steem.py b/steem/steem.py index 8a65d34..ddea7ad 100644 --- a/steem/steem.py +++ b/steem/steem.py @@ -1,6 +1,5 @@ from .commit import Commit from .steemd import Steemd -from steembase.exceptions import RPCError class Steem: diff --git a/steembase/http_client.py b/steembase/http_client.py index 1e3442a..06bdee4 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -105,16 +105,27 @@ def _downgrade_curr_node(self): HttpClient.non_appbase_nodes.add(self.url) def _is_error_recoverable(self, error): + assert 'message' in error, "missing error msg key: {}".format(error) + assert 'code' in error, "missing error code key: {}".format(error) message = error['message'] + code = error['code'] + # common steemd error # {"code"=>-32003, "message"=>"Unable to acquire database lock"} if message == 'Unable to acquire database lock': return True + # rare steemd error # {"code"=>-32000, "message"=>"Unknown exception", "data"=>"0 exception: unspecified\nUnknown Exception\n[...]"} if message == 'Unknown exception': return True + # generic jussi error + # {'code': -32603, 'message': 'Internal Error', 'data': {'error_id': 'c7a15140-f306-4727-acbd-b5e8f3717e9b', + # 'request': {'amzn_trace_id': 'Root=1-5ad4cb9f-9bc86fbca98d9a180850fb80', 'jussi_request_id': None}}} + if message == 'Internal Error' and code == -32603: + return True + return False def next_node(self): @@ -235,7 +246,15 @@ def call(self, # legacy (pre-appbase) nodes always return err code 1 legacy = result['error']['code'] == 1 detail = result['error']['message'] - error = result['error']['data']['name'] + + # some errors have no data key (db lock error) + if 'data' not in result['error']: + error = 'unspecified error' + # some errors have no name key (jussi errors) + elif 'name' not in result['error']['data']: + error = 'unspecified error(2)' + else: + error = result['error']['data']['name'] if legacy: detail = ":".join(detail.split("\n")[0:2]) @@ -256,9 +275,10 @@ def call(self, except retry_exceptions as e: if tries >= 10: + logging.error('Failed after %d attempts -- %s', tries, e) raise e tries += 1 - logging.error('Retry in %ds -- %s', tries, e) + logging.info('Retry in %ds -- %s', tries, e) time.sleep(tries) self.next_node() continue diff --git a/tests/steem/test_steemd.py b/tests/steem/test_steemd.py index 48f59ef..74a8689 100644 --- a/tests/steem/test_steemd.py +++ b/tests/steem/test_steemd.py @@ -17,6 +17,26 @@ def test_get_dgp(): assert response['head_block_number'] > 20e6 +def test_get_block(): + """ We should be able to fetch some blocks. """ + s = Steemd() + + for num in [1000, 1000000, 10000000, 20000000, 21000000]: + b = s.get_block(num) + assert b, 'block %d was blank' % num + assert num == int(b['block_id'][:8], base=16) + + start = 21000000 + for num in range(start, start + 50): + b = s.get_block(num) + assert b, 'block %d was blank' % num + assert num == int(b['block_id'][:8], base=16) + + non_existent_block = 99999999 + b = s.get_block(non_existent_block) + assert not b, 'block %d expected to be blank' % non_existent_block + + def test_ensured_block_ranges(): """ Post should load correctly if passed a dict or string identifier. """ s = Steemd() From 451b1be5bb240407776541d69c5b4f76347e209f Mon Sep 17 00:00:00 2001 From: roadscape Date: Mon, 16 Apr 2018 12:43:17 -0500 Subject: [PATCH 113/166] adjust names --- steembase/http_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index 06bdee4..c8034a9 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -249,10 +249,10 @@ def call(self, # some errors have no data key (db lock error) if 'data' not in result['error']: - error = 'unspecified error' + error = 'error' # some errors have no name key (jussi errors) elif 'name' not in result['error']['data']: - error = 'unspecified error(2)' + error = 'unspecified error' else: error = result['error']['data']['name'] From ddaf04634a4e6c7d7faad69b462d02944a44ec0b Mon Sep 17 00:00:00 2001 From: roadscape Date: Mon, 16 Apr 2018 12:54:10 -0500 Subject: [PATCH 114/166] ignore verify_authority result when encountering steem #2284. close #212 --- steem/transactionbuilder.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/steem/transactionbuilder.py b/steem/transactionbuilder.py index 9aa289e..fe6493f 100644 --- a/steem/transactionbuilder.py +++ b/steem/transactionbuilder.py @@ -136,7 +136,13 @@ def broadcast(self): if not self.steemd.verify_authority(self.json()): raise InsufficientAuthorityError except Exception as e: - raise e + # There is an issue with some appbase builds which makes + # `verify_authority` unusable. TODO: remove this case #212 + if 'Bad Cast:Invalid cast from string_type to Array' in str(e): + log.error("Ignoring verify_authority failure. See #212.") + else: + print("failing on {}".format(e)) + raise e try: self.steemd.broadcast_transaction(self.json()) From b9b77a6ab57703cf3f83a4baefaef07bf594087a Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 16 Apr 2018 19:42:17 -0500 Subject: [PATCH 115/166] Removed test. (#214) --- tests/steem/test_broadcast.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/steem/test_broadcast.py b/tests/steem/test_broadcast.py index 9fc622c..cb0444d 100644 --- a/tests/steem/test_broadcast.py +++ b/tests/steem/test_broadcast.py @@ -40,6 +40,8 @@ def test_claim_reward(): def test_witness_update(): + # TODO: Remove when witness_update is fixed. + return wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' c = Commit(steemd_instance=Steemd(nodes=[]), keys=[wif]) From 5bc5cc37621ebfe1a9cb0c61080c4f40d8bf9a8c Mon Sep 17 00:00:00 2001 From: John White Date: Tue, 17 Apr 2018 15:16:54 -0500 Subject: [PATCH 116/166] Added issue template. (#217) --- docs/issue_template.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 docs/issue_template.md diff --git a/docs/issue_template.md b/docs/issue_template.md new file mode 100644 index 0000000..f87aa51 --- /dev/null +++ b/docs/issue_template.md @@ -0,0 +1,22 @@ +### Version of Python you are running ### + + + +### Version of steem-python you are running ### + + + +### Expected Behavior ### + + + +### Actual Behavior ### + + + +### Steps to reproduce ### + + + +### Stack Trace ### + From a5389336ff0cd03893c627a71878213cd463364e Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 26 Apr 2018 13:59:24 -0500 Subject: [PATCH 117/166] Added JSONDecodeError to list of retriable errors to allow for node cycling. --- steem/__init__.py | 2 +- steembase/http_client.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/steem/__init__.py b/steem/__init__.py index a1dae32..c533530 100644 --- a/steem/__init__.py +++ b/steem/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- from .steem import Steem -__version__ = '0.18.103' +__version__ = '1.0.0' diff --git a/steembase/http_client.py b/steembase/http_client.py index c8034a9..7c0c177 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -217,7 +217,7 @@ def call(self, # tuple of Exceptions which are eligible for retry retry_exceptions = (MaxRetryError, ReadTimeoutError, - ProtocolError, RPCErrorRecoverable,) + ProtocolError, RPCErrorRecoverable, json.decoder.JSONDecodeError,) if sys.version > '3.0': retry_exceptions += (RemoteDisconnected, ConnectionResetError,) else: From 34262e87b20e609b0f321db4706ff04b13216b1b Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 26 Apr 2018 14:36:14 -0500 Subject: [PATCH 118/166] Removed erroneous logging and added ValueError for below 3.5. --- steembase/http_client.py | 10 +++++++++- tests/steem/test_transactions.py | 8 -------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index 7c0c177..966f35b 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -217,7 +217,13 @@ def call(self, # tuple of Exceptions which are eligible for retry retry_exceptions = (MaxRetryError, ReadTimeoutError, - ProtocolError, RPCErrorRecoverable, json.decoder.JSONDecodeError,) + ProtocolError, RPCErrorRecoverable,) + + if sys.version > '3.5': + retry_exceptions += (json.decoder.JSONDecodeError,) + else: + retry_exceptions += (ValueError, ) + if sys.version > '3.0': retry_exceptions += (RemoteDisconnected, ConnectionResetError,) else: @@ -274,6 +280,8 @@ def call(self, return result['result'] except retry_exceptions as e: + if e == ValueError and 'JSON' not in e.args[0]: + raise e if tries >= 10: logging.error('Failed after %d attempts -- %s', tries, e) raise e diff --git a/tests/steem/test_transactions.py b/tests/steem/test_transactions.py index 44f84f8..5c32b0c 100644 --- a/tests/steem/test_transactions.py +++ b/tests/steem/test_transactions.py @@ -812,14 +812,6 @@ def compareConstructedTX(self): rpc = self.steem.commit.wallet compare = rpc.serialize_transaction(tx.json()) - pprint(tx.json()) - - print("\n") - print(compare[:-130]) - print(tx_wire[:-130]) - print("\n") - - print(tx_wire[:-130] == compare[:-130]) self.assertEqual(compare[:-130], tx_wire[:-130]) From ec1921acd1918416c18e4adcfb1a915a4ec88b4e Mon Sep 17 00:00:00 2001 From: roadscape Date: Mon, 30 Apr 2018 12:31:31 -0500 Subject: [PATCH 119/166] add comment about JSONDecodeError workaround --- steembase/http_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index 966f35b..1ad6116 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -281,7 +281,7 @@ def call(self, except retry_exceptions as e: if e == ValueError and 'JSON' not in e.args[0]: - raise e + raise e # (python<3.5 lacks json.decoder.JSONDecodeError) if tries >= 10: logging.error('Failed after %d attempts -- %s', tries, e) raise e From f5d27f417eb89f136131069bd5aaefa57ab2e346 Mon Sep 17 00:00:00 2001 From: roadscape Date: Mon, 30 Apr 2018 14:02:22 -0500 Subject: [PATCH 120/166] linting and more informative logging, close #196 --- steembase/http_client.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index 1ad6116..462af91 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -270,7 +270,7 @@ def call(self, continue detail = ('%s from %s (%s) in %s' % ( - error, self.hostname, detail, name)) + error, self.hostname, detail, name)) if self._is_error_recoverable(result['error']): raise RPCErrorRecoverable(detail) @@ -283,10 +283,12 @@ def call(self, if e == ValueError and 'JSON' not in e.args[0]: raise e # (python<3.5 lacks json.decoder.JSONDecodeError) if tries >= 10: - logging.error('Failed after %d attempts -- %s', tries, e) + logging.error('Failed after %d attempts -- %s: %s', + tries, e.__class__.__name__, e) raise e tries += 1 - logging.info('Retry in %ds -- %s', tries, e) + logging.warning('Retry in %ds -- %s: %s', tries, + e.__class__.__name__, e) time.sleep(tries) self.next_node() continue @@ -295,21 +297,22 @@ def call(self, # define exceptions for which we refuse to retry. except Exception as e: extra = dict(err=e, request=self.request) - logger.error('Unexpected error: %s', e, extra=extra) + logger.error('Unexpected exception! Please report at ' + + 'https://github.com/steemit/steem-python/issues' + + ' -- %s: %s', e.__class__.__name__, e, extra=extra) raise e def call_multi_with_futures(self, name, params, api=None, max_workers=None): with concurrent.futures.ThreadPoolExecutor( - max_workers=max_workers) as executor: + max_workers=max_workers) as executor: # Start the load operations and mark each future with its URL - def ensure_list(parameter): - return parameter if type(parameter) in (list, tuple, - set) else [parameter] + def ensure_list(val): + return val if isinstance(val, (list, tuple, set)) else [val] futures = (executor.submit( self.call, name, *ensure_list(param), api=api) - for param in params) + for param in params) for future in concurrent.futures.as_completed(futures): yield future.result() From 04e1fcbf941d49ffaca6f12f45f1241d4dc5b8cd Mon Sep 17 00:00:00 2001 From: crokkon <33018033+crokkon@users.noreply.github.com> Date: Tue, 1 May 2018 13:00:19 +0200 Subject: [PATCH 121/166] utils.is_comment() comment detection fix Don't expect "re-" in permlink for comments, fixes #228 --- steem/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/steem/utils.py b/steem/utils.py index 856ebb8..8842fef 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -186,7 +186,7 @@ def is_comment(item): The item can be a Post object or just a raw comment object from the blockchain. """ - return item['permlink'][:3] == "re-" and item['parent_author'] + return item['parent_author'] != "" def time_elapsed(posting_time): From 6644808b8b181e644d15328457036cbaa08a99d2 Mon Sep 17 00:00:00 2001 From: John White Date: Wed, 2 May 2018 16:46:18 -0500 Subject: [PATCH 122/166] Refactored 'MasterPassword' to KeyEncryptionKey, and 'password' to userPassphrase. --- steem/cli.py | 2 +- steem/utils.py | 2 +- steem/wallet.py | 72 +++++++++++++++++----------------- steembase/storage.py | 92 ++++++++++++++++++++++---------------------- 4 files changed, 84 insertions(+), 84 deletions(-) diff --git a/steem/cli.py b/steem/cli.py index b6fd802..542f7d3 100644 --- a/steem/cli.py +++ b/steem/cli.py @@ -884,7 +884,7 @@ def legacyentry(): print("Couldn't identify object to read") elif args.command == "changewalletpassphrase": - steem.commit.wallet.changePassphrase() + steem.commit.wallet.changeUserPassphrase() elif args.command == "addkey": if args.unsafe_import_key: diff --git a/steem/utils.py b/steem/utils.py index 8842fef..9da991e 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -326,7 +326,7 @@ def fmt_time_from_now(secs=0): def env_unlocked(): - """ Check if wallet password is provided as ENV variable. """ + """ Check if wallet passphrase is provided as ENV variable. """ return os.getenv('UNLOCK', False) diff --git a/steem/wallet.py b/steem/wallet.py index 6b1e7db..db19fdc 100644 --- a/steem/wallet.py +++ b/steem/wallet.py @@ -38,11 +38,11 @@ class Wallet: any account. This mode is only used for *foreign* signatures! """ - masterpassword = None + decryptedKEK = None # Keys from database configStorage = None - MasterPassword = None + keyEncryptionKey = None keyStorage = None # Manually provided keys @@ -69,8 +69,8 @@ def __init__(self, steemd_instance=None, **kwargs): """ If no keys are provided manually we load the SQLite keyStorage """ - from steembase.storage import (keyStorage, MasterPassword) - self.MasterPassword = MasterPassword + from steembase.storage import (keyStorage, KeyEncryptionKey) + self.keyEncryptionKey = KeyEncryptionKey self.keyStorage = keyStorage def setKeys(self, loadkeys): @@ -92,41 +92,41 @@ def setKeys(self, loadkeys): raise InvalidWifError Wallet.keys[format(key.pubkey, self.prefix)] = str(key) - def unlock(self, pwd=None): + def unlock(self, user_passphrase=None): """ Unlock the wallet database """ if not self.created(): self.newWallet() - if (self.masterpassword is None - and self.configStorage[self.MasterPassword.config_key]): - if pwd is None: - pwd = self.getPassword() - masterpwd = self.MasterPassword(pwd) - self.masterpassword = masterpwd.decrypted_master + if (self.decryptedKEK is None + and self.configStorage[self.keyEncryptionKey.config_key]): + if user_passphrase is None: + user_passphrase = self.getUserPassphrase() + kek = self.keyEncryptionKey(user_passphrase) + self.decryptedKEK = kek.decrypted_KEK def lock(self): """ Lock the wallet database """ - self.masterpassword = None + self.decryptedKEK = None def locked(self): """ Is the wallet database locked? """ - return False if self.masterpassword else True + return False if self.decryptedKEK else True - def changePassphrase(self): - """ Change the passphrase for the wallet database + def changeUserPassphrase(self): + """ Change the user entered password for the wallet database """ # Open Existing Wallet - pwd = self.getPassword() - masterpwd = self.MasterPassword(pwd) - self.masterpassword = masterpwd.decrypted_master + pwd = self.getUserPassphrase() + kek = self.keyEncryptionKey(pwd) + self.decryptedKEK = kek.decrypted_KEK # Provide new passphrase - print("Please provide the new password") - newpwd = self.getPassword(confirm=True) + print("Please provide the new passphrase") + newpwd = self.getUserPassphrase(confirm=True) # Change passphrase - masterpwd.changePassword(newpwd) + kek.changePassphrase(newpwd) def created(self): """ Do we have a wallet database already? @@ -134,8 +134,8 @@ def created(self): if len(self.getPublicKeys()): # Already keys installed return True - elif self.MasterPassword.config_key in self.configStorage: - # no keys but a master password + elif self.keyEncryptionKey.config_key in self.configStorage: + # no keys but a KeyEncryptionKey return True else: return False @@ -145,17 +145,17 @@ def newWallet(self): """ if self.created(): raise WalletExists("You already have created a wallet!") - print("Please provide a password for the new wallet") - pwd = self.getPassword(confirm=True) - masterpwd = self.MasterPassword(pwd) - self.masterpassword = masterpwd.decrypted_master + print("Please provide a passphrase for the new wallet") + pwd = self.getUserPassphrase(confirm=True) + kek = self.keyEncryptionKey(pwd) + self.decryptedKEK = kek.decrypted_KEK def encrypt_wif(self, wif): """ Encrypt a wif key """ self.unlock() return format( - bip38.encrypt(PrivateKey(wif), self.masterpassword), "encwif") + bip38.encrypt(PrivateKey(wif), self.decryptedKEK), "encwif") def decrypt_wif(self, encwif): """ decrypt a wif key @@ -167,26 +167,26 @@ def decrypt_wif(self, encwif): except: # noqa FIXME(sneak) pass self.unlock() - return format(bip38.decrypt(encwif, self.masterpassword), "wif") + return format(bip38.decrypt(encwif, self.decryptedKEK), "wif") - def getPassword(self, confirm=False, text='Passphrase: '): - """ Obtain a password from the user + def getUserPassphrase(self, confirm=False, text='Passphrase: '): + """ Obtain a passphrase from the user """ import getpass if "UNLOCK" in os.environ: - # overwrite password from environmental variable + # overwrite passphrase from environmental variable return os.environ.get("UNLOCK") if confirm: # Loop until both match while True: - pw = self.getPassword(confirm=False) + pw = self.getUserPassphrase(confirm=False) if not pw: - print("You cannot chosen an empty password! " + - "If you want to automate the use of the libs, " + + print("You cannot choose an empty password! " + + "If you want to automate the use of the library, " + "please use the `UNLOCK` environmental variable!") continue else: - pwck = self.getPassword( + pwck = self.getUserPassphrase( confirm=False, text="Confirm Passphrase: ") if pw == pwck: return pw diff --git a/steembase/storage.py b/steembase/storage.py index d4a77c4..151a780 100644 --- a/steembase/storage.py +++ b/steembase/storage.py @@ -331,71 +331,71 @@ def __len__(self): return len(cursor.fetchall()) -class WrongMasterPasswordException(Exception): +class WrongKEKException(Exception): pass -class MasterPassword(object): - """ The keys are encrypted with a Masterpassword that is stored in +class KeyEncryptionKey(object): + """ The keys are encrypted with a KeyEncryptionKey that is stored in the configurationStore. It has a checksum to verify correctness - of the password + of the userPassphrase """ - password = "" - decrypted_master = "" + userPassphrase = "" + decrypted_KEK = "" - #: This key identifies the encrypted master password - # stored in the confiration + #: This key identifies the encrypted KeyEncryptionKey + # stored in the configuration config_key = "encrypted_master_password" - def __init__(self, password): + def __init__(self, user_passphrase): """ The encrypted private keys in `keys` are encrypted with a - random encrypted masterpassword that is stored in the + random encrypted KeyEncryptionKey that is stored in the configuration. - The password is used to encrypt this masterpassword. To + The userPassphrase is used to encrypt this KeyEncryptionKey. To decrypt the keys stored in the keys database, one must use - BIP38, decrypt the masterpassword from the configuration - store with the user password, and use the decrypted - masterpassword to decrypt the BIP38 encrypted private keys + BIP38, decrypt the KeyEncryptionKey from the configuration + store with the userPassphrase, and use the decrypted + KeyEncryptionKey to decrypt the BIP38 encrypted private keys from the keys storage! - :param str password: Password to use for en-/de-cryption + :param str user_passphrase: Password to use for en-/de-cryption """ - self.password = password + self.userPassphrase = user_passphrase if self.config_key not in configStorage: - self.newMaster() - self.saveEncrytpedMaster() + self.newKEK() + self.saveEncrytpedKEK() else: - self.decryptEncryptedMaster() + self.decryptEncryptedKEK() - def decryptEncryptedMaster(self): - """ Decrypt the encrypted masterpassword + def decryptEncryptedKEK(self): + """ Decrypt the encrypted KeyEncryptionKey """ - aes = AESCipher(self.password) - checksum, encrypted_master = configStorage[self.config_key].split("$") + aes = AESCipher(self.userPassphrase) + checksum, encrypted_kek = configStorage[self.config_key].split("$") try: - decrypted_master = aes.decrypt(encrypted_master) + decrypted_kek = aes.decrypt(encrypted_kek) except: # noqa FIXME(sneak) - raise WrongMasterPasswordException - if checksum != self.deriveChecksum(decrypted_master): - raise WrongMasterPasswordException - self.decrypted_master = decrypted_master + raise WrongKEKException + if checksum != self.deriveChecksum(decrypted_kek): + raise WrongKEKException + self.decrypted_KEK = decrypted_kek - def saveEncrytpedMaster(self): - """ Store the encrypted master password in the configuration + def saveEncrytpedKEK(self): + """ Store the encrypted KeyEncryptionKey in the configuration store """ - configStorage[self.config_key] = self.getEncryptedMaster() + configStorage[self.config_key] = self.getEncryptedKEK() - def newMaster(self): - """ Generate a new random masterpassword + def newKEK(self): + """ Generate a new random KeyEncryptionKey """ # make sure to not overwrite an existing key if (self.config_key in configStorage and configStorage[self.config_key]): return - self.decrypted_master = hexlify(os.urandom(32)).decode("ascii") + self.decrypted_KEK = hexlify(os.urandom(32)).decode("ascii") def deriveChecksum(self, s): """ Derive the checksum @@ -403,24 +403,24 @@ def deriveChecksum(self, s): checksum = hashlib.sha256(compat_bytes(s, "ascii")).hexdigest() return checksum[:4] - def getEncryptedMaster(self): - """ Obtain the encrypted masterkey + def getEncryptedKEK(self): + """ Obtain the encrypted KeyEncryptionKey """ - if not self.decrypted_master: - raise Exception("master not decrypted") - aes = AESCipher(self.password) + if not self.decrypted_KEK: + raise Exception("KeyEncryptionKey not decrypted") + aes = AESCipher(self.userPassphrase) return "{}${}".format( - self.deriveChecksum(self.decrypted_master), - aes.encrypt(self.decrypted_master)) + self.deriveChecksum(self.decrypted_KEK), + aes.encrypt(self.decrypted_KEK)) - def changePassword(self, newpassword): - """ Change the password + def changePassphrase(self, newpassphrase): + """ Change the passphrase """ - self.password = newpassword - self.saveEncrytpedMaster() + self.userPassphrase = newpassphrase + self.saveEncrytpedKEK() def purge(self): - """ Remove the masterpassword from the configuration store + """ Remove the KeyEncryptionKey from the configuration store """ configStorage[self.config_key] = "" From f7267cf09fed5c7b4b3415eb7072ba3f76ea08d0 Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 3 May 2018 18:52:05 -0500 Subject: [PATCH 123/166] Added branch in bip38 for Python 2.7 compatibility. --- steembase/bip38.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/steembase/bip38.py b/steembase/bip38.py index 939c626..6897ff2 100644 --- a/steembase/bip38.py +++ b/steembase/bip38.py @@ -56,7 +56,10 @@ def encrypt(privkey, passphrase): a = compat_bytes(addr, 'ascii') salt = hashlib.sha256(hashlib.sha256(a).digest()).digest()[0:4] if SCRYPT_MODULE == "scrypt": - key = scrypt.hash(passphrase, salt, 16384, 8, 8) + if sys.version >= '3.0.0': + key = scrypt.hash(passphrase, salt, 16384, 8, 8) + else: + key = scrypt.hash(str(passphrase), str(salt), 16384, 8, 8) elif SCRYPT_MODULE == "pylibscrypt": key = scrypt.scrypt(compat_bytes(passphrase, "utf-8"), salt, 16384, 8, 8) else: From 1d9abe6b39b45442add3d827b6754e759ff7affe Mon Sep 17 00:00:00 2001 From: John White Date: Fri, 4 May 2018 15:00:47 -0500 Subject: [PATCH 124/166] Fix input parsing in delete key function. --- steem/cli.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/steem/cli.py b/steem/cli.py index 542f7d3..e953fbe 100644 --- a/steem/cli.py +++ b/steem/cli.py @@ -1335,7 +1335,13 @@ def confirm(question, default="yes"): raise ValueError("invalid default answer: '%s'" % default) while True: sys.stdout.write(question + prompt) - choice = input().lower() + # Python 2.7 `input` attempts to evaluate the input, while in 3+ + # it returns a string. Python 2.7 `raw_input` returns a str as desired. + if sys.version >= '3.0': + choice = input().lower() + else: + choice = raw_input().lower() + if default is not None and choice == '': return valid[default] elif choice in valid: From 627154094c233b6c7142db1701a479392144e043 Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 7 May 2018 10:56:49 -0500 Subject: [PATCH 125/166] Added a Python 2.7 branch for scrypt in bip38's encrypt method. --- steembase/bip38.py | 9 ++++++--- tests/steembase/test_bip38.py | 3 +-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/steembase/bip38.py b/steembase/bip38.py index 6897ff2..4a726a9 100644 --- a/steembase/bip38.py +++ b/steembase/bip38.py @@ -73,8 +73,8 @@ def encrypt(privkey, passphrase): b'\x01' + b'\x42' + b'\xc0' + salt + encrypted_half1 + encrypted_half2) " Checksum " checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4] - privatkey = hexlify(payload + checksum).decode('ascii') - return Base58(privatkey) + privatekey = hexlify(payload + checksum).decode('ascii') + return Base58(privatekey) def decrypt(encrypted_privkey, passphrase): @@ -97,7 +97,10 @@ def decrypt(encrypted_privkey, passphrase): salt = d[0:4] d = d[4:-4] if SCRYPT_MODULE == "scrypt": - key = scrypt.hash(passphrase, salt, 16384, 8, 8) + if sys.version >= '3.0.0': + key = scrypt.hash(passphrase, salt, 16384, 8, 8) + else: + key = scrypt.hash(str(passphrase), str(salt), 16384, 8, 8) elif SCRYPT_MODULE == "pylibscrypt": key = scrypt.scrypt(compat_bytes(passphrase, "utf-8"), salt, 16384, 8, 8) else: diff --git a/tests/steembase/test_bip38.py b/tests/steembase/test_bip38.py index 2c05317..57c8816 100644 --- a/tests/steembase/test_bip38.py +++ b/tests/steembase/test_bip38.py @@ -28,7 +28,7 @@ def test_encrypt(self): "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq" ]) - def test_deencrypt(self): + def test_decrypt(self): self.assertEqual([ format( decrypt("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" @@ -45,6 +45,5 @@ def test_deencrypt(self): "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5" ]) - if __name__ == '__main__': unittest.main() From de389dd4aea76729cdc150bda6bcb9c5b8ab0be3 Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 7 May 2018 13:46:12 -0500 Subject: [PATCH 126/166] Enforce list type for nodes if not a list when passed in (ie - a single node as a string.) --- steembase/http_client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/steembase/http_client.py b/steembase/http_client.py index 462af91..23d2c5e 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -89,6 +89,8 @@ def __init__(self, nodes, **kwargs): pool_timeout=None, release_conn=None, chunked=False, body_pos=None, **response_kw) ''' + if not isinstance(nodes, list): + nodes = [nodes] self.nodes = cycle(nodes) self.url = '' From 538eed401e3a7f8320b9757342e24e658b4d61ae Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 7 May 2018 16:58:49 -0500 Subject: [PATCH 127/166] Added a method to sanitize node input and fail loudly on incorrect input. --- steembase/http_client.py | 56 ++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index 23d2c5e..8f2d12d 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -89,10 +89,8 @@ def __init__(self, nodes, **kwargs): pool_timeout=None, release_conn=None, chunked=False, body_pos=None, **response_kw) ''' - if not isinstance(nodes, list): - nodes = [nodes] - self.nodes = cycle(nodes) + self.nodes = cycle(self.sanitize_nodes(nodes)) self.url = '' self.request = None self.next_node() @@ -224,7 +222,7 @@ def call(self, if sys.version > '3.5': retry_exceptions += (json.decoder.JSONDecodeError,) else: - retry_exceptions += (ValueError, ) + retry_exceptions += (ValueError,) if sys.version > '3.0': retry_exceptions += (RemoteDisconnected, ConnectionResetError,) @@ -283,7 +281,7 @@ def call(self, except retry_exceptions as e: if e == ValueError and 'JSON' not in e.args[0]: - raise e # (python<3.5 lacks json.decoder.JSONDecodeError) + raise e # (python<3.5 lacks json.decoder.JSONDecodeError) if tries >= 10: logging.error('Failed after %d attempts -- %s: %s', tries, e.__class__.__name__, e) @@ -304,17 +302,59 @@ def call(self, ' -- %s: %s', e.__class__.__name__, e, extra=extra) raise e - def call_multi_with_futures(self, name, params, api=None, max_workers=None): with concurrent.futures.ThreadPoolExecutor( - max_workers=max_workers) as executor: + max_workers=max_workers) as executor: # Start the load operations and mark each future with its URL def ensure_list(val): return val if isinstance(val, (list, tuple, set)) else [val] futures = (executor.submit( self.call, name, *ensure_list(param), api=api) - for param in params) + for param in params) for future in concurrent.futures.as_completed(futures): yield future.result() + + def sanitize_nodes(self, nodes): + """ + + This method is designed to explicitly validate the user defined + nodes that are passed to http_client. If left unvalidated, improper + input causes a variety of explicit-to-red herring errors in the code base. + This will fail loudly in the event that incorrect input has been passed. + + There are three types of input allowed when defining nodes. + 1. a string of a single node url. ie nodes='https://api.steemit.com' + 2. a comma separated string of several node url's. + nodes='https://api.steemit.com,<>,<>' + 3. a list of string node url's. + nodes=['https://api.steemit.com','<>','<>'] + + Any other input will result in a ValueError being thrown. + + :param nodes: the nodes argument passed to http_client + :return: a list of node url's. + """ + + if isinstance(nodes, str) or \ + (sys.version < '3.0' and isinstance(nodes, unicode)): + if ',' in nodes: + nodes = nodes.split(',') # split nodes if commas in the string. + else: + nodes = [nodes] # otherwise create a list of size one. + elif isinstance(nodes, list): + if not all( + ( + isinstance(node, str) + or + (sys.version < '3.0' and isinstance(nodes, unicode)) + ) for node in nodes): + raise ValueError("All nodes in list must be a string.") + else: + raise ValueError("nodes arg must be a " + "comma separated string of node url's, " + "a single string url, " + "or a list of strings.") + + return nodes From 548df7e15e2fb0106a4f73bb2a691a69baa509ca Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 7 May 2018 20:15:26 -0500 Subject: [PATCH 128/166] Added _isString method as well as simplified logic in sanitizeNodes. --- steembase/http_client.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index 8f2d12d..31d685d 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -337,19 +337,10 @@ def sanitize_nodes(self, nodes): :return: a list of node url's. """ - if isinstance(nodes, str) or \ - (sys.version < '3.0' and isinstance(nodes, unicode)): - if ',' in nodes: - nodes = nodes.split(',') # split nodes if commas in the string. - else: - nodes = [nodes] # otherwise create a list of size one. + if self._isString(nodes): + nodes = nodes.split(',') elif isinstance(nodes, list): - if not all( - ( - isinstance(node, str) - or - (sys.version < '3.0' and isinstance(nodes, unicode)) - ) for node in nodes): + if not all(self._isString(node) for node in nodes): raise ValueError("All nodes in list must be a string.") else: raise ValueError("nodes arg must be a " @@ -358,3 +349,7 @@ def sanitize_nodes(self, nodes): "or a list of strings.") return nodes + + def _isString(self, input): + return isinstance(input, str) or \ + (sys.version < '3.0' and isinstance(input, unicode)) From a1fa51e4d18335966ab6ad244793d0c897aff589 Mon Sep 17 00:00:00 2001 From: John G G Date: Tue, 8 May 2018 09:09:05 -0400 Subject: [PATCH 129/166] Allow configurable SCRYPT_MODULE usage --- steembase/bip38.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/steembase/bip38.py b/steembase/bip38.py index 4a726a9..36ad481 100644 --- a/steembase/bip38.py +++ b/steembase/bip38.py @@ -1,5 +1,6 @@ import hashlib import logging +import os import sys from binascii import hexlify, unhexlify @@ -14,7 +15,7 @@ except ImportError: raise ImportError("Missing dependency: pycrypto") -SCRYPT_MODULE = None +SCRYPT_MODULE = os.environ.get('SCRYPT_MODULE', None) if not SCRYPT_MODULE: try: import scrypt From 5e28afdc2628801b044088c65fd7a1b620f19acf Mon Sep 17 00:00:00 2001 From: John White Date: Tue, 8 May 2018 11:00:13 -0500 Subject: [PATCH 130/166] Made it possible to explicitly specify scrypt or pylibscrypt with environment variables. --- steembase/bip38.py | 11 ++++ tests/steembase/test_bip38.py | 97 ++++++++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/steembase/bip38.py b/steembase/bip38.py index 36ad481..acbe4e3 100644 --- a/steembase/bip38.py +++ b/steembase/bip38.py @@ -28,6 +28,17 @@ SCRYPT_MODULE = "pylibscrypt" except ImportError: raise ImportError("Missing dependency: scrypt or pylibscrypt") +elif 'pylibscrypt' in SCRYPT_MODULE: + try: + import pylibscrypt as scrypt + except ImportError: + raise ImportError("Missing dependency: pylibscrypt explicitly set but missing") +elif 'scrypt' in SCRYPT_MODULE: + try: + import scrypt + except ImportError: + raise ImportError("Missing dependency: scrypt explicitly set but missing") + log.debug("Using scrypt module: %s" % SCRYPT_MODULE) diff --git a/tests/steembase/test_bip38.py b/tests/steembase/test_bip38.py index 57c8816..01bf964 100644 --- a/tests/steembase/test_bip38.py +++ b/tests/steembase/test_bip38.py @@ -1,11 +1,11 @@ import unittest - +import os from steembase.account import PrivateKey -from steembase.bip38 import encrypt, decrypt class Testcases(unittest.TestCase): def test_encrypt(self): + from steembase.bip38 import encrypt self.assertEqual([ format( encrypt( @@ -29,6 +29,98 @@ def test_encrypt(self): ]) def test_decrypt(self): + from steembase.bip38 import decrypt + self.assertEqual([ + format( + decrypt("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" + "xsUW8epQi", "TestingOneTwoThree"), "wif"), + format( + decrypt("6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" + "n2Nh2ZoGg", "TestingOneTwoThree"), "wif"), + format( + decrypt("6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" + "PPX1dWByq", "Satoshi"), "wif") + ], [ + "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", + "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", + "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5" + ]) + + def test_encrypt_scrypt(self): + os.environ['SCRYPT_MODULE'] = 'scrypt' + from steembase.bip38 import encrypt + self.assertEqual([ + format( + encrypt( + PrivateKey( + "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd"), + "TestingOneTwoThree"), "encwif"), + format( + encrypt( + PrivateKey( + "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"), + "TestingOneTwoThree"), "encwif"), + format( + encrypt( + PrivateKey( + "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"), + "Satoshi"), "encwif") + ], [ + "6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxThxsUW8epQi", + "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", + "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq" + ]) + os.environ['SCRYPT_MODULE'] = '' + + def test_decrypt_scrypt(self): + os.environ['SCRYPT_MODULE'] = 'scrypt' + from steembase.bip38 import decrypt + self.assertEqual([ + format( + decrypt("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" + "xsUW8epQi", "TestingOneTwoThree"), "wif"), + format( + decrypt("6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" + "n2Nh2ZoGg", "TestingOneTwoThree"), "wif"), + format( + decrypt("6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" + "PPX1dWByq", "Satoshi"), "wif") + ], [ + "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", + "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", + "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5" + ]) + os.environ['SCRYPT_MODULE'] = '' + + def test_encrypt_pylibscrypt(self): + os.environ['SCRYPT_MODULE'] = 'pylibscrypt' + from steembase.bip38 import encrypt + self.assertEqual([ + format( + encrypt( + PrivateKey( + "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd"), + "TestingOneTwoThree"), "encwif"), + format( + encrypt( + PrivateKey( + "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"), + "TestingOneTwoThree"), "encwif"), + format( + encrypt( + PrivateKey( + "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"), + "Satoshi"), "encwif") + ], [ + "6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxThxsUW8epQi", + "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", + "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq" + ]) + os.environ['SCRYPT_MODULE'] = '' + + def test_decrypt_pylibscrypt(self): + os.environ['SCRYPT_MODULE'] = 'pylibscrypt' + from steembase.bip38 import decrypt self.assertEqual([ format( decrypt("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" @@ -44,6 +136,7 @@ def test_decrypt(self): "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5" ]) + os.environ['SCRYPT_MODULE'] = '' if __name__ == '__main__': unittest.main() From 41c33548fb9db9dd832820bff11a5ebdf44954e0 Mon Sep 17 00:00:00 2001 From: John G G Date: Tue, 8 May 2018 12:17:18 -0400 Subject: [PATCH 131/166] tweek ENV var handling --- tests/steembase/test_bip38.py | 64 ++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/tests/steembase/test_bip38.py b/tests/steembase/test_bip38.py index 01bf964..5964ee0 100644 --- a/tests/steembase/test_bip38.py +++ b/tests/steembase/test_bip38.py @@ -5,20 +5,22 @@ class Testcases(unittest.TestCase): def test_encrypt(self): - from steembase.bip38 import encrypt + if 'SCRYPT_MODULE' in os.environ: + del os.environ['SCRYPT_MODULE'] + import steembase.bip38 self.assertEqual([ format( - encrypt( + steembase.bip38.encrypt( PrivateKey( "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd"), "TestingOneTwoThree"), "encwif"), format( - encrypt( + steembase.bip38.encrypt( PrivateKey( "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"), "TestingOneTwoThree"), "encwif"), format( - encrypt( + steembase.bip38.encrypt( PrivateKey( "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"), "Satoshi"), "encwif") @@ -29,16 +31,18 @@ def test_encrypt(self): ]) def test_decrypt(self): - from steembase.bip38 import decrypt + if 'SCRYPT_MODULE' in os.environ: + del os.environ['SCRYPT_MODULE'] + import steembase.bip38 self.assertEqual([ format( - decrypt("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" + steembase.bip38.decrypt("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" "xsUW8epQi", "TestingOneTwoThree"), "wif"), format( - decrypt("6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" + steembase.bip38.decrypt("6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" "n2Nh2ZoGg", "TestingOneTwoThree"), "wif"), format( - decrypt("6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" + steembase.bip38.decrypt("6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" "PPX1dWByq", "Satoshi"), "wif") ], [ "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", @@ -48,20 +52,21 @@ def test_decrypt(self): def test_encrypt_scrypt(self): os.environ['SCRYPT_MODULE'] = 'scrypt' - from steembase.bip38 import encrypt + import steembase.bip38 + assert steembase.bip38.SCRYPT_MODULE == 'scrypt' self.assertEqual([ format( - encrypt( + steembase.bip38.encrypt( PrivateKey( "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd"), "TestingOneTwoThree"), "encwif"), format( - encrypt( + steembase.bip38.encrypt( PrivateKey( "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"), "TestingOneTwoThree"), "encwif"), format( - encrypt( + steembase.bip38.encrypt( PrivateKey( "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"), "Satoshi"), "encwif") @@ -70,44 +75,46 @@ def test_encrypt_scrypt(self): "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq" ]) - os.environ['SCRYPT_MODULE'] = '' + del os.environ['SCRYPT_MODULE'] def test_decrypt_scrypt(self): os.environ['SCRYPT_MODULE'] = 'scrypt' - from steembase.bip38 import decrypt + import steembase.bip38 + assert steembase.bip38.SCRYPT_MODULE == 'scrypt' self.assertEqual([ format( - decrypt("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" + steembase.bip38.decrypt("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" "xsUW8epQi", "TestingOneTwoThree"), "wif"), format( - decrypt("6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" + steembase.bip38.decrypt("6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" "n2Nh2ZoGg", "TestingOneTwoThree"), "wif"), format( - decrypt("6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" + steembase.bip38.decrypt("6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" "PPX1dWByq", "Satoshi"), "wif") ], [ "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5" ]) - os.environ['SCRYPT_MODULE'] = '' + del os.environ['SCRYPT_MODULE'] def test_encrypt_pylibscrypt(self): os.environ['SCRYPT_MODULE'] = 'pylibscrypt' - from steembase.bip38 import encrypt + import steembase.bip38 + assert steembase.bip38.SCRYPT_MODULE == 'pylibscrypt' self.assertEqual([ format( - encrypt( + steembase.bip38.encrypt( PrivateKey( "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd"), "TestingOneTwoThree"), "encwif"), format( - encrypt( + steembase.bip38.encrypt( PrivateKey( "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"), "TestingOneTwoThree"), "encwif"), format( - encrypt( + steembase.bip38.encrypt( PrivateKey( "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"), "Satoshi"), "encwif") @@ -116,27 +123,28 @@ def test_encrypt_pylibscrypt(self): "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq" ]) - os.environ['SCRYPT_MODULE'] = '' + del os.environ['SCRYPT_MODULE'] def test_decrypt_pylibscrypt(self): os.environ['SCRYPT_MODULE'] = 'pylibscrypt' - from steembase.bip38 import decrypt + import steembase.bip38 + assert steembase.bip38.SCRYPT_MODULE == 'pylibscrypt' self.assertEqual([ format( - decrypt("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" + steembase.bip38.decrypt("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" "xsUW8epQi", "TestingOneTwoThree"), "wif"), format( - decrypt("6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" + steembase.bip38.decrypt("6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" "n2Nh2ZoGg", "TestingOneTwoThree"), "wif"), format( - decrypt("6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" + steembase.bip38.decrypt("6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" "PPX1dWByq", "Satoshi"), "wif") ], [ "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5" ]) - os.environ['SCRYPT_MODULE'] = '' + del os.environ['SCRYPT_MODULE'] if __name__ == '__main__': unittest.main() From df519b4f45e537b577196e91c36cf555f376acc4 Mon Sep 17 00:00:00 2001 From: John G G Date: Tue, 8 May 2018 12:36:51 -0400 Subject: [PATCH 132/166] fix pep8, hacky reloading --- tests/steembase/test_bip38.py | 70 ++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/tests/steembase/test_bip38.py b/tests/steembase/test_bip38.py index 5964ee0..25705b8 100644 --- a/tests/steembase/test_bip38.py +++ b/tests/steembase/test_bip38.py @@ -7,6 +7,10 @@ class Testcases(unittest.TestCase): def test_encrypt(self): if 'SCRYPT_MODULE' in os.environ: del os.environ['SCRYPT_MODULE'] + try: + del sys.modules['steembase.bip38'] + except KeyError: + pass import steembase.bip38 self.assertEqual([ format( @@ -33,17 +37,24 @@ def test_encrypt(self): def test_decrypt(self): if 'SCRYPT_MODULE' in os.environ: del os.environ['SCRYPT_MODULE'] + try: + del sys.modules['steembase.bip38'] + except KeyError: + pass import steembase.bip38 self.assertEqual([ format( - steembase.bip38.decrypt("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" - "xsUW8epQi", "TestingOneTwoThree"), "wif"), + steembase.bip38.decrypt( + "6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" + "xsUW8epQi", "TestingOneTwoThree"), "wif"), format( - steembase.bip38.decrypt("6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" - "n2Nh2ZoGg", "TestingOneTwoThree"), "wif"), + steembase.bip38.decrypt( + "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" + "n2Nh2ZoGg", "TestingOneTwoThree"), "wif"), format( - steembase.bip38.decrypt("6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" - "PPX1dWByq", "Satoshi"), "wif") + steembase.bip38.decrypt( + "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" + "PPX1dWByq", "Satoshi"), "wif") ], [ "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", @@ -52,6 +63,10 @@ def test_decrypt(self): def test_encrypt_scrypt(self): os.environ['SCRYPT_MODULE'] = 'scrypt' + try: + del sys.modules['steembase.bip38'] + except KeyError: + pass import steembase.bip38 assert steembase.bip38.SCRYPT_MODULE == 'scrypt' self.assertEqual([ @@ -79,18 +94,25 @@ def test_encrypt_scrypt(self): def test_decrypt_scrypt(self): os.environ['SCRYPT_MODULE'] = 'scrypt' + try: + del sys.modules['steembase.bip38'] + except KeyError: + pass import steembase.bip38 assert steembase.bip38.SCRYPT_MODULE == 'scrypt' self.assertEqual([ format( - steembase.bip38.decrypt("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" - "xsUW8epQi", "TestingOneTwoThree"), "wif"), + steembase.bip38.decrypt( + "6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" + "xsUW8epQi", "TestingOneTwoThree"), "wif"), format( - steembase.bip38.decrypt("6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" - "n2Nh2ZoGg", "TestingOneTwoThree"), "wif"), + steembase.bip38.decrypt( + "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" + "n2Nh2ZoGg", "TestingOneTwoThree"), "wif"), format( - steembase.bip38.decrypt("6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" - "PPX1dWByq", "Satoshi"), "wif") + steembase.bip38.decrypt( + "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" + "PPX1dWByq", "Satoshi"), "wif") ], [ "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", @@ -100,6 +122,10 @@ def test_decrypt_scrypt(self): def test_encrypt_pylibscrypt(self): os.environ['SCRYPT_MODULE'] = 'pylibscrypt' + try: + del sys.modules['steembase.bip38'] + except KeyError: + pass import steembase.bip38 assert steembase.bip38.SCRYPT_MODULE == 'pylibscrypt' self.assertEqual([ @@ -127,18 +153,25 @@ def test_encrypt_pylibscrypt(self): def test_decrypt_pylibscrypt(self): os.environ['SCRYPT_MODULE'] = 'pylibscrypt' + try: + del sys.modules['steembase.bip38'] + except KeyError: + pass import steembase.bip38 assert steembase.bip38.SCRYPT_MODULE == 'pylibscrypt' self.assertEqual([ format( - steembase.bip38.decrypt("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" - "xsUW8epQi", "TestingOneTwoThree"), "wif"), + steembase.bip38.decrypt( + "6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" + "xsUW8epQi", "TestingOneTwoThree"), "wif"), format( - steembase.bip38.decrypt("6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" - "n2Nh2ZoGg", "TestingOneTwoThree"), "wif"), + steembase.bip38.decrypt( + "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" + "n2Nh2ZoGg", "TestingOneTwoThree"), "wif"), format( - steembase.bip38.decrypt("6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" - "PPX1dWByq", "Satoshi"), "wif") + steembase.bip38.decrypt( + "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" + "PPX1dWByq", "Satoshi"), "wif") ], [ "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", @@ -146,5 +179,6 @@ def test_decrypt_pylibscrypt(self): ]) del os.environ['SCRYPT_MODULE'] + if __name__ == '__main__': unittest.main() From 419c5616cbb0333c9c130b1ca3574b56201a8a73 Mon Sep 17 00:00:00 2001 From: John G G Date: Tue, 8 May 2018 12:40:41 -0400 Subject: [PATCH 133/166] fix import errors --- tests/steembase/test_bip38.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/steembase/test_bip38.py b/tests/steembase/test_bip38.py index 25705b8..357f422 100644 --- a/tests/steembase/test_bip38.py +++ b/tests/steembase/test_bip38.py @@ -1,5 +1,6 @@ import unittest import os +import sys from steembase.account import PrivateKey From cbb00959dc4ea6a3f5ceaf2d367881eb42507f5c Mon Sep 17 00:00:00 2001 From: John White Date: Tue, 8 May 2018 14:37:54 -0500 Subject: [PATCH 134/166] Broke scrypt and pylibscrypt tests into separate test units. --- tests/steembase/test_bip38.py | 133 +--------------------- tests/steembase/test_bip38_pylibscrypt.py | 57 ++++++++++ tests/steembase/test_bip38_scrypt.py | 57 ++++++++++ 3 files changed, 115 insertions(+), 132 deletions(-) create mode 100644 tests/steembase/test_bip38_pylibscrypt.py create mode 100644 tests/steembase/test_bip38_scrypt.py diff --git a/tests/steembase/test_bip38.py b/tests/steembase/test_bip38.py index 357f422..1a3ae19 100644 --- a/tests/steembase/test_bip38.py +++ b/tests/steembase/test_bip38.py @@ -2,17 +2,11 @@ import os import sys from steembase.account import PrivateKey +import steembase.bip38 class Testcases(unittest.TestCase): def test_encrypt(self): - if 'SCRYPT_MODULE' in os.environ: - del os.environ['SCRYPT_MODULE'] - try: - del sys.modules['steembase.bip38'] - except KeyError: - pass - import steembase.bip38 self.assertEqual([ format( steembase.bip38.encrypt( @@ -36,13 +30,6 @@ def test_encrypt(self): ]) def test_decrypt(self): - if 'SCRYPT_MODULE' in os.environ: - del os.environ['SCRYPT_MODULE'] - try: - del sys.modules['steembase.bip38'] - except KeyError: - pass - import steembase.bip38 self.assertEqual([ format( steembase.bip38.decrypt( @@ -62,124 +49,6 @@ def test_decrypt(self): "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5" ]) - def test_encrypt_scrypt(self): - os.environ['SCRYPT_MODULE'] = 'scrypt' - try: - del sys.modules['steembase.bip38'] - except KeyError: - pass - import steembase.bip38 - assert steembase.bip38.SCRYPT_MODULE == 'scrypt' - self.assertEqual([ - format( - steembase.bip38.encrypt( - PrivateKey( - "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd"), - "TestingOneTwoThree"), "encwif"), - format( - steembase.bip38.encrypt( - PrivateKey( - "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"), - "TestingOneTwoThree"), "encwif"), - format( - steembase.bip38.encrypt( - PrivateKey( - "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"), - "Satoshi"), "encwif") - ], [ - "6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxThxsUW8epQi", - "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", - "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq" - ]) - del os.environ['SCRYPT_MODULE'] - - def test_decrypt_scrypt(self): - os.environ['SCRYPT_MODULE'] = 'scrypt' - try: - del sys.modules['steembase.bip38'] - except KeyError: - pass - import steembase.bip38 - assert steembase.bip38.SCRYPT_MODULE == 'scrypt' - self.assertEqual([ - format( - steembase.bip38.decrypt( - "6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" - "xsUW8epQi", "TestingOneTwoThree"), "wif"), - format( - steembase.bip38.decrypt( - "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" - "n2Nh2ZoGg", "TestingOneTwoThree"), "wif"), - format( - steembase.bip38.decrypt( - "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" - "PPX1dWByq", "Satoshi"), "wif") - ], [ - "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", - "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", - "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5" - ]) - del os.environ['SCRYPT_MODULE'] - - def test_encrypt_pylibscrypt(self): - os.environ['SCRYPT_MODULE'] = 'pylibscrypt' - try: - del sys.modules['steembase.bip38'] - except KeyError: - pass - import steembase.bip38 - assert steembase.bip38.SCRYPT_MODULE == 'pylibscrypt' - self.assertEqual([ - format( - steembase.bip38.encrypt( - PrivateKey( - "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd"), - "TestingOneTwoThree"), "encwif"), - format( - steembase.bip38.encrypt( - PrivateKey( - "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"), - "TestingOneTwoThree"), "encwif"), - format( - steembase.bip38.encrypt( - PrivateKey( - "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"), - "Satoshi"), "encwif") - ], [ - "6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxThxsUW8epQi", - "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", - "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq" - ]) - del os.environ['SCRYPT_MODULE'] - - def test_decrypt_pylibscrypt(self): - os.environ['SCRYPT_MODULE'] = 'pylibscrypt' - try: - del sys.modules['steembase.bip38'] - except KeyError: - pass - import steembase.bip38 - assert steembase.bip38.SCRYPT_MODULE == 'pylibscrypt' - self.assertEqual([ - format( - steembase.bip38.decrypt( - "6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" - "xsUW8epQi", "TestingOneTwoThree"), "wif"), - format( - steembase.bip38.decrypt( - "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" - "n2Nh2ZoGg", "TestingOneTwoThree"), "wif"), - format( - steembase.bip38.decrypt( - "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" - "PPX1dWByq", "Satoshi"), "wif") - ], [ - "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", - "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", - "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5" - ]) - del os.environ['SCRYPT_MODULE'] - if __name__ == '__main__': unittest.main() diff --git a/tests/steembase/test_bip38_pylibscrypt.py b/tests/steembase/test_bip38_pylibscrypt.py new file mode 100644 index 0000000..877a60c --- /dev/null +++ b/tests/steembase/test_bip38_pylibscrypt.py @@ -0,0 +1,57 @@ +import unittest +import os +from steembase.account import PrivateKey + + +class Testcases(unittest.TestCase): + + def test_encrypt_pylibscrypt(self): + os.environ['SCRYPT_MODULE'] = 'pylibscrypt' + import steembase.bip38 + self.assertEqual([ + format( + steembase.bip38.encrypt( + PrivateKey( + "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd"), + "TestingOneTwoThree"), "encwif"), + format( + steembase.bip38.encrypt( + PrivateKey( + "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"), + "TestingOneTwoThree"), "encwif"), + format( + steembase.bip38.encrypt( + PrivateKey( + "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"), + "Satoshi"), "encwif") + ], [ + "6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxThxsUW8epQi", + "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", + "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq" + ]) + + def test_decrypt_pylibscrypt(self): + os.environ['SCRYPT_MODULE'] = 'pylibscrypt' + import steembase.bip38 + self.assertEqual([ + format( + steembase.bip38.decrypt( + "6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" + "xsUW8epQi", "TestingOneTwoThree"), "wif"), + format( + steembase.bip38.decrypt( + "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" + "n2Nh2ZoGg", "TestingOneTwoThree"), "wif"), + format( + steembase.bip38.decrypt( + "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" + "PPX1dWByq", "Satoshi"), "wif") + ], [ + "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", + "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", + "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5" + ]) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/steembase/test_bip38_scrypt.py b/tests/steembase/test_bip38_scrypt.py new file mode 100644 index 0000000..c318643 --- /dev/null +++ b/tests/steembase/test_bip38_scrypt.py @@ -0,0 +1,57 @@ +import unittest +import os +from steembase.account import PrivateKey + + +class Testcases(unittest.TestCase): + + def test_encrypt_scrypt(self): + os.environ['SCRYPT_MODULE'] = 'scrypt' + import steembase.bip38 + self.assertEqual([ + format( + steembase.bip38.encrypt( + PrivateKey( + "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd"), + "TestingOneTwoThree"), "encwif"), + format( + steembase.bip38.encrypt( + PrivateKey( + "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"), + "TestingOneTwoThree"), "encwif"), + format( + steembase.bip38.encrypt( + PrivateKey( + "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"), + "Satoshi"), "encwif") + ], [ + "6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxThxsUW8epQi", + "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", + "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq" + ]) + + def test_decrypt_scrypt(self): + os.environ['SCRYPT_MODULE'] = 'scrypt' + import steembase.bip38 + self.assertEqual([ + format( + steembase.bip38.decrypt( + "6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxTh" + "xsUW8epQi", "TestingOneTwoThree"), "wif"), + format( + steembase.bip38.decrypt( + "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEb" + "n2Nh2ZoGg", "TestingOneTwoThree"), "wif"), + format( + steembase.bip38.decrypt( + "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTe" + "PPX1dWByq", "Satoshi"), "wif") + ], [ + "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", + "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", + "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5" + ]) + + +if __name__ == '__main__': + unittest.main() From 483544f755b79438f2789d8a82e0983476327f24 Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 10 May 2018 15:46:43 -0500 Subject: [PATCH 135/166] Added ChangeRecoveryAccount Operation to steembase/operations.py . --- steembase/operations.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/steembase/operations.py b/steembase/operations.py index 28a32e7..d77b287 100644 --- a/steembase/operations.py +++ b/steembase/operations.py @@ -440,6 +440,19 @@ def __init__(self, *args, **kwargs): ])) +class ChangeRecoveryAccount(GrapheneObject): + def __init__(self, *args, **kwargs): + if isArgsThisClass(self, args): + self.data = args[0].data + else: + super(ChangeRecoveryAccount, self).__init__( + OrderedDict([ + ('account_to_recover', String(kwargs["account_to_recover"])), + ('new_recovery_account', String(kwargs["new_recovery_account"])), + ('extensions', Array([])), + ])) + + class Transfer(GrapheneObject): def __init__(self, *args, **kwargs): if isArgsThisClass(self, args): From 53e3a27d69941c34640d905b2ae0df30b3a0918d Mon Sep 17 00:00:00 2001 From: John White Date: Mon, 14 May 2018 14:50:19 -0500 Subject: [PATCH 136/166] Added test w/ input to test_transactions.py. change_recovery_account needs test output that has been generated using another tool. Then the test can verify the feature has been added correctly. --- tests/steem/test_transactions.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/steem/test_transactions.py b/tests/steem/test_transactions.py index 5c32b0c..2a44f78 100644 --- a/tests/steem/test_transactions.py +++ b/tests/steem/test_transactions.py @@ -394,6 +394,26 @@ def test_account_update(self): "53fbeccb331067") self.assertEqual(compare[:-130], tx_wire[:-130]) + # def test_change_recovery_account(self): + # op = operations.ChangeRecoveryAccount( + # **{ + # "account_to_recover": "barrie", + # "extensions": [], + # "new_recovery_account": "boombastic" + # }) + # ops = [operations.Operation(op)] + # tx = SignedTransaction( + # ref_block_num=ref_block_num, + # ref_block_prefix=ref_block_prefix, + # expiration=expiration, + # operations=ops) + # tx = tx.sign([wif], chain=self.steem.chain_params) + # + # tx_wire = hexlify(compat_bytes(tx)).decode("ascii") + # TODO: generate test case output. + # compare = () + # self.assertEqual(compare[:-130], tx_wire[:-130]) + def test_order_cancel(self): op = operations.LimitOrderCancel(**{ "owner": "", From 101eed49510d353fa10576ea35c14f97059bb82a Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 17 May 2018 15:42:55 -0500 Subject: [PATCH 137/166] Added output for test case. --- tests/steem/test_transactions.py | 41 +++++++++++++++++--------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/tests/steem/test_transactions.py b/tests/steem/test_transactions.py index 2a44f78..21cc45b 100644 --- a/tests/steem/test_transactions.py +++ b/tests/steem/test_transactions.py @@ -394,25 +394,28 @@ def test_account_update(self): "53fbeccb331067") self.assertEqual(compare[:-130], tx_wire[:-130]) - # def test_change_recovery_account(self): - # op = operations.ChangeRecoveryAccount( - # **{ - # "account_to_recover": "barrie", - # "extensions": [], - # "new_recovery_account": "boombastic" - # }) - # ops = [operations.Operation(op)] - # tx = SignedTransaction( - # ref_block_num=ref_block_num, - # ref_block_prefix=ref_block_prefix, - # expiration=expiration, - # operations=ops) - # tx = tx.sign([wif], chain=self.steem.chain_params) - # - # tx_wire = hexlify(compat_bytes(tx)).decode("ascii") - # TODO: generate test case output. - # compare = () - # self.assertEqual(compare[:-130], tx_wire[:-130]) + def test_change_recovery_account(self): + op = operations.ChangeRecoveryAccount( + **{ + "account_to_recover": "barrie", + "extensions": [], + "new_recovery_account": "boombastic" + }) + ops = [operations.Operation(op)] + tx = SignedTransaction( + ref_block_num=ref_block_num, + ref_block_prefix=ref_block_prefix, + expiration=expiration, + operations=ops) + tx = tx.sign([wif], chain=self.steem.chain_params) + + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") + + compare = ('f68585abf4dce7c80457011a066261727269650a626f6f6d6261' + '737469630000011f5513c021e89be2c4d8a725dc9b89334ffcde' + '6a9535487f4b6d42f93de1722c251fd9d4ec414335dc41ea081a' + 'a7a4d27b179b1a07d3415f7e0a6190852b86ecde') + self.assertEqual(compare[:-130], tx_wire[:-130]) def test_order_cancel(self): op = operations.LimitOrderCancel(**{ From 052c74540849e79c170c9cd614b54dab80c837c9 Mon Sep 17 00:00:00 2001 From: Harry Schmidt Date: Mon, 21 May 2018 09:37:38 -0700 Subject: [PATCH 138/166] bump to 1.0.1 --- docs/conf.py | 2 +- setup.py | 2 +- steem/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index ea168bd..7318dfe 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -68,7 +68,7 @@ # The short X.Y version. version = '1.0' # The full version, including alpha/beta/rc tags. -release = '1.0.0' +release = '1.0.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 0c1c5bd..f3254cf 100644 --- a/setup.py +++ b/setup.py @@ -131,7 +131,7 @@ def run(self): # Where the magic happens: setup( name=NAME, - version='1.0.0', + version='1.0.1', description=DESCRIPTION, keywords=['steem', 'steemit', 'cryptocurrency', 'blockchain'], # long_description=long_description, diff --git a/steem/__init__.py b/steem/__init__.py index c533530..54fa57c 100644 --- a/steem/__init__.py +++ b/steem/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- from .steem import Steem -__version__ = '1.0.0' +__version__ = '1.0.1' From 3e0cdba49b61d9be1a3e31d03d54d4bd603d121c Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 24 May 2018 15:21:00 -0500 Subject: [PATCH 139/166] Uncommented tests. --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6a8b323..b88413e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,7 +42,7 @@ workflows: test_all_envs: jobs: - test27 -# - test33 - TODO: Fix issues with deps for 3.3. -# - test34 - TODO: Fix steemd import in instance.py. + - test33 + - test34 - test35 - test36 \ No newline at end of file From 7d87368cbe34f1b41ba5af0aa9f32d63f73f0efa Mon Sep 17 00:00:00 2001 From: John White Date: Thu, 24 May 2018 15:26:05 -0500 Subject: [PATCH 140/166] Moved Exception to different version block. --- .circleci/config.yml | 1 + setup.py | 2 +- steem/instance.py | 2 +- steembase/http_client.py | 8 +++++--- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b88413e..6fe414b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,6 +17,7 @@ jobs: - image: circleci/python:3.3 steps: - checkout + - run: sudo pip3 install setuptools_scm - run: python setup.py test test34: docker: diff --git a/setup.py b/setup.py index f3254cf..615cc6a 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ TEST_REQUIRED = [ 'pep8', 'pytest', - 'pytest-pylint', + 'pytest-pylint ; python_version >= "3.4.0"', 'pytest-xdist', 'pytest-runner', 'pytest-pep8', diff --git a/steem/instance.py b/steem/instance.py index 42a11a2..7935aed 100644 --- a/steem/instance.py +++ b/steem/instance.py @@ -18,7 +18,7 @@ def shared_steemd_instance(): global _shared_steemd_instance if not _shared_steemd_instance: - if sys.version >= '3.5': + if sys.version >= '3.0': _shared_steemd_instance = stm.steemd.Steemd( nodes=get_config_node_list()) else: diff --git a/steembase/http_client.py b/steembase/http_client.py index 31d685d..bb47892 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -13,8 +13,10 @@ from urllib3.connection import HTTPConnection from urllib3.exceptions import MaxRetryError, ReadTimeoutError, ProtocolError -if sys.version >= '3.0': +if sys.version >= '3.5': from http.client import RemoteDisconnected + +if sys.version >= '3.0': from urllib.parse import urlparse else: from urlparse import urlparse @@ -220,12 +222,12 @@ def call(self, ProtocolError, RPCErrorRecoverable,) if sys.version > '3.5': - retry_exceptions += (json.decoder.JSONDecodeError,) + retry_exceptions += (json.decoder.JSONDecodeError, RemoteDisconnected,) else: retry_exceptions += (ValueError,) if sys.version > '3.0': - retry_exceptions += (RemoteDisconnected, ConnectionResetError,) + retry_exceptions += (ConnectionResetError,) else: retry_exceptions += (HTTPException,) From 0eb3de91fe8df7b95d932666432e66be5a63b9a8 Mon Sep 17 00:00:00 2001 From: crokkon <33018033+crokkon@users.noreply.github.com> Date: Mon, 4 Jun 2018 07:20:17 +0200 Subject: [PATCH 141/166] Fix beneficiaries handling in CommentOptions Fixes #9 --- steembase/operations.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/steembase/operations.py b/steembase/operations.py index d77b287..b71a8c7 100644 --- a/steembase/operations.py +++ b/steembase/operations.py @@ -738,12 +738,12 @@ def __init__(self, *args, **kwargs): kwargs = args[0] # handle beneficiaries + if "beneficiaries" in kwargs and kwargs['beneficiaries']: + kwargs['extensions'] = [[0, {'beneficiaries': kwargs['beneficiaries']}]] + extensions = Array([]) - beneficiaries = kwargs.get('beneficiaries') - if beneficiaries and type(beneficiaries) == list: - ext_obj = [0, {'beneficiaries': beneficiaries}] - ext = CommentOptionExtensions(ext_obj) - extensions = Array([ext]) + if "extensions" in kwargs and kwargs["extensions"]: + extensions = Array([CommentOptionExtensions(o) for o in kwargs["extensions"]]) super(CommentOptions, self).__init__( OrderedDict([ From 9f37415b5aec672cf0edc5d3d5e0be63190a4f91 Mon Sep 17 00:00:00 2001 From: Alex Chien Date: Thu, 7 Jun 2018 02:13:47 +0800 Subject: [PATCH 142/166] fix missing positional argument: mode error when addkey --- steembase/bip38.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/steembase/bip38.py b/steembase/bip38.py index acbe4e3..d6bbbe5 100644 --- a/steembase/bip38.py +++ b/steembase/bip38.py @@ -77,7 +77,7 @@ def encrypt(privkey, passphrase): else: raise ValueError("No scrypt module loaded") (derived_half1, derived_half2) = (key[:32], key[32:]) - aes = AES.new(derived_half2) + aes = AES.new(derived_half2,AES.MODE_ECB) encrypted_half1 = _encrypt_xor(privkeyhex[:32], derived_half1[:16], aes) encrypted_half2 = _encrypt_xor(privkeyhex[32:], derived_half1[16:], aes) " flag byte is forced 0xc0 because Graphene only uses compressed keys " @@ -121,7 +121,7 @@ def decrypt(encrypted_privkey, passphrase): derivedhalf2 = key[32:64] encryptedhalf1 = d[0:16] encryptedhalf2 = d[16:32] - aes = AES.new(derivedhalf2) + aes = AES.new(derivedhalf2,AES.MODE_ECB) decryptedhalf2 = aes.decrypt(encryptedhalf2) decryptedhalf1 = aes.decrypt(encryptedhalf1) privraw = decryptedhalf1 + decryptedhalf2 From 8a90ab8d9c9ca253a5dd1fb758131083941a856b Mon Sep 17 00:00:00 2001 From: roadscape Date: Fri, 8 Jun 2018 11:15:37 -0700 Subject: [PATCH 143/166] handle new timeout error --- steembase/http_client.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/steembase/http_client.py b/steembase/http_client.py index bb47892..8732900 100644 --- a/steembase/http_client.py +++ b/steembase/http_client.py @@ -128,6 +128,12 @@ def _is_error_recoverable(self, error): if message == 'Internal Error' and code == -32603: return True + # jussi timeout error + # {'code': 1100, 'message': 'Bad or missing upstream response', + # 'data':{'error_id': 'fc3650d7-72...', 'exception': TimeoutError()}} + if message == 'Bad or missing upstream response' and code == 1100: + return True + return False def next_node(self): @@ -260,7 +266,10 @@ def call(self, error = 'error' # some errors have no name key (jussi errors) elif 'name' not in result['error']['data']: - error = 'unspecified error' + if 'exception' in error['data']: + error = error['data']['exception'] + else: + error = 'unspecified error' else: error = result['error']['data']['name'] From 6af70917c5c0bfcf696ac5f06efa05a9632aef5e Mon Sep 17 00:00:00 2001 From: roadscape Date: Wed, 19 Sep 2018 12:31:00 -0500 Subject: [PATCH 144/166] update testnet conf --- steembase/chains.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/steembase/chains.py b/steembase/chains.py index 8f0524f..93851aa 100644 --- a/steembase/chains.py +++ b/steembase/chains.py @@ -15,9 +15,9 @@ "sbd_symbol": "GBG", "vests_symbol": "GESTS", }, - "TEST": { + "TESTS": { "chain_id": - "9afbce9f2416520733bacb370315d32b6b2c43d6097576df1c1222859d91eecc", + "46d82ab7d8db682eb1959aed0ada039a6d49afa1602491f93dde9cac3e8e6c32", "prefix": "TST", "steem_symbol": From 977de6422fe937caa8a37d876971b9cd553cc53a Mon Sep 17 00:00:00 2001 From: Anthony Martin Date: Mon, 24 Sep 2018 13:57:28 -0700 Subject: [PATCH 145/166] handle operation: witness_set_properties #264 --- steem/commit.py | 34 +++++++++++++ steembase/operationids.py | 1 + steembase/operations.py | 53 ++++++++++++++++++++ tests/block_data/witness_set_properties.json | 20 ++++++++ tests/steem/test_broadcast.py | 28 ++++++++++- tests/steem/test_steemd.py | 2 +- tests/steem/test_transactions.py | 28 +++++++++++ 7 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 tests/block_data/witness_set_properties.json diff --git a/steem/commit.py b/steem/commit.py index 976c0c3..3b39a51 100644 --- a/steem/commit.py +++ b/steem/commit.py @@ -1003,6 +1003,40 @@ def witness_update(self, signing_key, url, props, account=None): }) return self.finalizeOp(op, account, "active") + def witness_set_properties(self, signing_key, props, account=None): + """ Update witness + + :param pubkey signing_key: Signing key + :param dict props: Properties + :param str account: (optional) witness account name + + Properties::: + + [ + ["account_creation_fee": x] + ["maximum_block_size": x] + ["sbd_interest_rate": x] + ] + + """ + if not account: + account = configStorage.get("default_account") + if not account: + raise ValueError("You need to provide an account") + + try: + PublicKey(signing_key) + except Exception as e: + raise e + + op = operations.WitnessSetProperties( + **{ + "owner": account, + "props": props, + "extensions": [] + }) + return self.finalizeOp(op, account, "active") + def decode_memo(self, enc_memo): """ Try to decode an encrypted memo """ diff --git a/steembase/operationids.py b/steembase/operationids.py index b2744d0..195a21b 100644 --- a/steembase/operationids.py +++ b/steembase/operationids.py @@ -41,6 +41,7 @@ 'claim_reward_balance', 'delegate_vesting_shares', 'account_create_with_delegation', + 'witness_set_properties', 'fill_convert_request', 'author_reward', 'curation_reward', diff --git a/steembase/operations.py b/steembase/operations.py index d77b287..4be0e1f 100644 --- a/steembase/operations.py +++ b/steembase/operations.py @@ -1,5 +1,6 @@ import importlib import json +from binascii import hexlify, unhexlify import re import struct from collections import OrderedDict @@ -683,6 +684,58 @@ def __init__(self, *args, **kwargs): ('fee', Amount(kwargs["fee"])), ])) +class WitnessSetProperties(GrapheneObject): + """ + Based on https://github.com/holgern/beem/blob/6cc303d1b0fdfb096da78d3ff331aaa79a18ad8f/beembase/operations.py#L278-L318 + """ + def __init__(self, *args, **kwargs): + if isArgsThisClass(self, args): + self.data = args[0].data + else: + if len(args) == 1 and len(kwargs) == 0: + kwargs = args[0] + prefix = kwargs.pop("prefix", default_prefix) + extensions = Array([]) + props = {} + for k in kwargs["props"]: + if "key" == k[0]: + block_signing_key = (PublicKey(k[1], prefix=prefix)) + props["key"] = repr(block_signing_key) + elif "new_signing_key" == k[0]: + new_signing_key = (PublicKey(k[1], prefix=prefix)) + props["new_signing_key"] = repr(new_signing_key) + for k in kwargs["props"]: + if k[0] in ["key", "new_signing_key"]: + continue + if isinstance(k[1], str): + is_hex = re.fullmatch(r'[0-9a-fA-F]+', k[1] or '') is not None + else: + is_hex = False + if isinstance(k[1], int) and k[0] in ["account_subsidy_budget", "account_subsidy_decay", "maximum_block_size", "sbd_interest_rate"]: + props[k[0]] = (hexlify(Uint32(k[1]).__bytes__())).decode() + elif not isinstance(k[1], str) and k[0] in ["account_creation_fee"]: + props[k[0]] = (hexlify(Amount(k[1]).__bytes__())).decode() + elif not is_hex and isinstance(k[1], str) and k[0] in ["account_creation_fee"]: + props[k[0]] = (hexlify(Amount(k[1]).__bytes__())).decode() + elif not isinstance(k[1], str) and k[0] in ["sbd_exchange_rate"]: + props[k[0]] = (hexlify(ExchangeRate(k[1]).__bytes__())).decode() + elif not is_hex and k[0] in ["url"]: + props[k[0]] = (hexlify(String(k[1]).__bytes__())).decode() + else: + props[k[0]] = (k[1]) + props_list = [] + for k in props: + if k == "key": + props_list.append(([String(k), String(props[k])])) + else: + props_list.append(([String(k), String(props[k])])) + map_props = Map(props_list) + + super(WitnessSetProperties, self).__init__(OrderedDict([ + ('owner', String(kwargs["owner"])), + ('props', map_props), + ('extensions', extensions), + ])) class AccountWitnessVote(GrapheneObject): def __init__(self, *args, **kwargs): diff --git a/tests/block_data/witness_set_properties.json b/tests/block_data/witness_set_properties.json new file mode 100644 index 0000000..2e351e4 --- /dev/null +++ b/tests/block_data/witness_set_properties.json @@ -0,0 +1,20 @@ +{ + "ref_block_prefix": 82172829, + "expiration": "2018-09-24T19:22:18", + "operations": [ + [ + "witness_set_properties", { + "owner": "init-1", + "props": [ + ["account_creation_fee", "d0070000000000000354455354530000"], + ["key", "032d2a4af3e23294e0a1d9dbc46e0272d8e1977ce2ae3349527cc90fe1cc9c5db9"] + ] + } + ] + ], + "signatures": [ + "a1489ddbe5046a39a95b012a06696e69742d3102146163636f756e745f6372656174696f6e5f66656510d0070000000000000354455354530000036b657921032d2a4af3e23294e0a1d9dbc46e0272d8e1977ce2ae3349527cc90fe1cc9c5db9000000" + ], + "ref_block_num": 18593, + "extensions": [] +} diff --git a/tests/steem/test_broadcast.py b/tests/steem/test_broadcast.py index cb0444d..3f65082 100644 --- a/tests/steem/test_broadcast.py +++ b/tests/steem/test_broadcast.py @@ -40,8 +40,6 @@ def test_claim_reward(): def test_witness_update(): - # TODO: Remove when witness_update is fixed. - return wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' c = Commit(steemd_instance=Steemd(nodes=[]), keys=[wif]) @@ -65,3 +63,29 @@ def test_witness_update(): raise Exception('expected RPCError') assert 'tx_missing_active_auth' in rpc_error + + +def test_witness_set_properties(): + wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' + c = Commit(steemd_instance=Steemd(nodes=[]), + keys=[wif]) + + signing_key = 'STM1111111111111111111111111111111114T1Anm' + props = [ + ['account_creation_fee', 'd0070000000000000354455354530000'], + ['key', ('032d2a4af3e23294e0a1d9dbc46e0272d' + '8e1977ce2ae3349527cc90fe1cc9c5db9')] + ] + + rpc_error = None + try: + c.witness_set_properties( + signing_key=signing_key, + props=props, + account='test') + except RPCError as e: + rpc_error = str(e) + else: + raise Exception('expected RPCError') + + assert 'tx_missing_other_auth' in rpc_error diff --git a/tests/steem/test_steemd.py b/tests/steem/test_steemd.py index 74a8689..8dfd7a6 100644 --- a/tests/steem/test_steemd.py +++ b/tests/steem/test_steemd.py @@ -7,7 +7,7 @@ def test_get_version(): s = Steemd() response = s.call('get_version', api='login_api') version = response['blockchain_version'] - assert version[0:4] == '0.19' + assert version[0:4] == '0.20' def test_get_dgp(): diff --git a/tests/steem/test_transactions.py b/tests/steem/test_transactions.py index 21cc45b..06c545f 100644 --- a/tests/steem/test_transactions.py +++ b/tests/steem/test_transactions.py @@ -713,6 +713,34 @@ def test_witness_update(self): "b9f2405478badadb4c") self.assertEqual(compare[:-130], tx_wire[:-130]) + def test_witness_set_properties(self): + op = operations.WitnessSetProperties(**{ + "owner": "init-1", + "props": [ + ["account_creation_fee", "d0070000000000000354455354530000"], + ["key", ("032d2a4af3e23294e0a1d9dbc46e0272d" + "8e1977ce2ae3349527cc90fe1cc9c5db9")] + ] + }) + ops = [operations.Operation(op)] + tx = SignedTransaction( + ref_block_num=ref_block_num, + ref_block_prefix=ref_block_prefix, + expiration=expiration, + operations=ops) + tx = tx.sign([wif], chain=self.steem.chain_params) + tx_wire = hexlify(compat_bytes(tx)).decode("ascii") + compare = ("f68585abf4dce7c80457012a06696e69742d3102036b65794230" + "3332643261346166336532333239346530613164396462633436" + "6530323732643865313937376365326165333334393532376363" + "39306665316363396335646239146163636f756e745f63726561" + "74696f6e5f666565206430303730303030303030303030303030" + "3335343435353335343533303030300000011f3ed64264c74203" + "7955e9eb71f7de8a6bd2635251ad569629f849f23d53f2874d0a" + "879f2579bee31f34bbc7c67456e5be08ee8436f0fd5e0e4cc030" + "3030000001") + self.assertEqual(compare[:-130], tx_wire[:-130]) + def test_witness_vote(self): op = operations.AccountWitnessVote(**{ "account": "xeroc", From 550fb48c8e3dcc01d92dc9688476891bf1320741 Mon Sep 17 00:00:00 2001 From: Anthony Martin Date: Mon, 24 Sep 2018 13:57:48 -0700 Subject: [PATCH 146/166] version bump #264 --- docs/conf.py | 2 +- setup.py | 2 +- steem/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 7318dfe..4ec1d80 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -68,7 +68,7 @@ # The short X.Y version. version = '1.0' # The full version, including alpha/beta/rc tags. -release = '1.0.1' +release = '1.0.2-rc1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 615cc6a..b126ea0 100644 --- a/setup.py +++ b/setup.py @@ -131,7 +131,7 @@ def run(self): # Where the magic happens: setup( name=NAME, - version='1.0.1', + version=__import__('steem').__version__, description=DESCRIPTION, keywords=['steem', 'steemit', 'cryptocurrency', 'blockchain'], # long_description=long_description, diff --git a/steem/__init__.py b/steem/__init__.py index 54fa57c..35540d9 100644 --- a/steem/__init__.py +++ b/steem/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- from .steem import Steem -__version__ = '1.0.1' +__version__ = '1.0.2-rc1' From d4c59c6ff030e246e70428c97b4f0f1d2c4df8eb Mon Sep 17 00:00:00 2001 From: Anthony Martin Date: Mon, 24 Sep 2018 14:45:48 -0700 Subject: [PATCH 147/166] to avoid voluptuous error --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b126ea0..d364b68 100644 --- a/setup.py +++ b/setup.py @@ -131,7 +131,7 @@ def run(self): # Where the magic happens: setup( name=NAME, - version=__import__('steem').__version__, + version='1.0.2-rc1', description=DESCRIPTION, keywords=['steem', 'steemit', 'cryptocurrency', 'blockchain'], # long_description=long_description, From 21cf229c54f1d304d67e1e8c8f5e118a86a3bdc2 Mon Sep 17 00:00:00 2001 From: Anthony Martin Date: Mon, 24 Sep 2018 19:18:38 -0700 Subject: [PATCH 148/166] for python 3.3 support --- steembase/operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/steembase/operations.py b/steembase/operations.py index 4be0e1f..5688567 100644 --- a/steembase/operations.py +++ b/steembase/operations.py @@ -708,7 +708,7 @@ def __init__(self, *args, **kwargs): if k[0] in ["key", "new_signing_key"]: continue if isinstance(k[1], str): - is_hex = re.fullmatch(r'[0-9a-fA-F]+', k[1] or '') is not None + is_hex = re.match(r'^[0-9a-fA-F]+$', k[1] or '') is not None else: is_hex = False if isinstance(k[1], int) and k[0] in ["account_subsidy_budget", "account_subsidy_decay", "maximum_block_size", "sbd_interest_rate"]: From 6dfa66e11b4731ec6308c0cd331271034023dc12 Mon Sep 17 00:00:00 2001 From: roadscape Date: Mon, 24 Sep 2018 21:32:59 -0500 Subject: [PATCH 149/166] bump ci From 309bd20813f3c8678795771cbc8867121080274f Mon Sep 17 00:00:00 2001 From: Anthony Martin Date: Tue, 25 Sep 2018 08:59:39 -0700 Subject: [PATCH 150/166] fix to enable broadcast #264 --- steembase/operations.py | 14 ++++++++------ steembase/types.py | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/steembase/operations.py b/steembase/operations.py index 5688567..d8a7fad 100644 --- a/steembase/operations.py +++ b/steembase/operations.py @@ -8,8 +8,8 @@ from steem.utils import compat_bytes from .account import PublicKey from .operationids import operations -from .types import (Int16, Uint16, Uint32, Uint64, String, Bytes, Array, - PointInTime, Bool, Optional, Map, Id, JsonObj, +from .types import (Int16, Uint16, Uint32, Uint64, String, HexString, Bytes, + Array, PointInTime, Bool, Optional, Map, Id, JsonObj, StaticVariant) default_prefix = "STM" @@ -725,10 +725,12 @@ def __init__(self, *args, **kwargs): props[k[0]] = (k[1]) props_list = [] for k in props: - if k == "key": - props_list.append(([String(k), String(props[k])])) - else: - props_list.append(([String(k), String(props[k])])) + props_list.append(([String(k), HexString(props[k])])) + props_list = sorted( + props_list, + key=lambda x: str(x[0]), + reverse=False, + ) map_props = Map(props_list) super(WitnessSetProperties, self).__init__(OrderedDict([ diff --git a/steembase/types.py b/steembase/types.py index 8ac3c0e..42585fa 100644 --- a/steembase/types.py +++ b/steembase/types.py @@ -193,6 +193,20 @@ def unicodify(self): return compat_bytes("".join(r), "utf-8") +class HexString(object): + def __init__(self, d): + self.data = d + + def __bytes__(self): + """Returns bytes representation.""" + d = bytes(unhexlify(bytes(self.data, 'ascii'))) + return varint(len(d)) + d + + def __str__(self): + """Returns data as string.""" + return '%s' % str(self.data) + + class Bytes: def __init__(self, d, length=None): self.data = d From 91a5f82f848794f95b2f95eacc6ee814fc080aa0 Mon Sep 17 00:00:00 2001 From: Anthony Martin Date: Tue, 25 Sep 2018 09:00:41 -0700 Subject: [PATCH 151/166] serialized transaction now matches my local curl on get_transaction_hex #254 --- tests/steem/test_transactions.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/steem/test_transactions.py b/tests/steem/test_transactions.py index 06c545f..f969b73 100644 --- a/tests/steem/test_transactions.py +++ b/tests/steem/test_transactions.py @@ -730,15 +730,13 @@ def test_witness_set_properties(self): operations=ops) tx = tx.sign([wif], chain=self.steem.chain_params) tx_wire = hexlify(compat_bytes(tx)).decode("ascii") - compare = ("f68585abf4dce7c80457012a06696e69742d3102036b65794230" - "3332643261346166336532333239346530613164396462633436" - "6530323732643865313937376365326165333334393532376363" - "39306665316363396335646239146163636f756e745f63726561" - "74696f6e5f666565206430303730303030303030303030303030" - "3335343435353335343533303030300000011f3ed64264c74203" - "7955e9eb71f7de8a6bd2635251ad569629f849f23d53f2874d0a" - "879f2579bee31f34bbc7c67456e5be08ee8436f0fd5e0e4cc030" - "3030000001") + compare = ("f68585abf4dce7c80457012a06696e69742d3102146163636f75" + "6e745f6372656174696f6e5f66656510d0070000000000000354" + "455354530000036b657921032d2a4af3e23294e0a1d9dbc46e02" + "72d8e1977ce2ae3349527cc90fe1cc9c5db90000011f7797b8f7" + "3e03c04d603512f278aeceb5f76de1d0c527052886df806badb0" + "e41f55a8321abc6431c38130cc789b992e6c79ed4d403eb5906d" + "5af6d3b83626a3e7") self.assertEqual(compare[:-130], tx_wire[:-130]) def test_witness_vote(self): From 3bf21744bce535cd10f5c7bda92e60b802da7ec4 Mon Sep 17 00:00:00 2001 From: Anthony Martin Date: Tue, 25 Sep 2018 09:47:46 -0700 Subject: [PATCH 152/166] compatibility with python 2.7 #264 --- steembase/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/steembase/types.py b/steembase/types.py index 42585fa..1797e1d 100644 --- a/steembase/types.py +++ b/steembase/types.py @@ -199,7 +199,7 @@ def __init__(self, d): def __bytes__(self): """Returns bytes representation.""" - d = bytes(unhexlify(bytes(self.data, 'ascii'))) + d = bytes(unhexlify(compat_bytes(self.data, 'ascii'))) return varint(len(d)) + d def __str__(self): From 2fab47f5b4d969244d77e33a004f41040afe364d Mon Sep 17 00:00:00 2001 From: Anthony Martin Date: Tue, 25 Sep 2018 10:13:25 -0700 Subject: [PATCH 153/166] bump ci From 9a3fdb085d66c703ade8965e16bbc47a9791e30b Mon Sep 17 00:00:00 2001 From: Anthony Martin Date: Tue, 25 Sep 2018 10:35:47 -0700 Subject: [PATCH 154/166] bump ci From eb859cfbc00ea26343ab2b9d7f665751fb9138dd Mon Sep 17 00:00:00 2001 From: Anthony Martin Date: Tue, 25 Sep 2018 10:41:13 -0700 Subject: [PATCH 155/166] bump ci From 4c81333a6e45062df0f78f4c90a1df4aac30d78a Mon Sep 17 00:00:00 2001 From: Donovan Walker Date: Wed, 26 Sep 2018 15:21:21 -0500 Subject: [PATCH 156/166] [#267] remove -rc1 from version --- docs/conf.py | 2 +- setup.py | 2 +- steem/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 4ec1d80..1ccb322 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -68,7 +68,7 @@ # The short X.Y version. version = '1.0' # The full version, including alpha/beta/rc tags. -release = '1.0.2-rc1' +release = '1.0.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index d364b68..d7877ab 100644 --- a/setup.py +++ b/setup.py @@ -131,7 +131,7 @@ def run(self): # Where the magic happens: setup( name=NAME, - version='1.0.2-rc1', + version='1.0.2', description=DESCRIPTION, keywords=['steem', 'steemit', 'cryptocurrency', 'blockchain'], # long_description=long_description, diff --git a/steem/__init__.py b/steem/__init__.py index 35540d9..09cc22e 100644 --- a/steem/__init__.py +++ b/steem/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- from .steem import Steem -__version__ = '1.0.2-rc1' +__version__ = '1.0.2' From bcdfdbb1bdb7bb2235759649e98130bc90df577b Mon Sep 17 00:00:00 2001 From: Donovan Walker Date: Wed, 26 Sep 2018 15:40:29 -0500 Subject: [PATCH 157/166] bump ci From db22033bcf84685197d3f7e87608ec7457411768 Mon Sep 17 00:00:00 2001 From: Donovan Walker Date: Wed, 26 Sep 2018 15:55:27 -0500 Subject: [PATCH 158/166] bump ci From 2225e4b1de52b688b51cfb14142d696491be5c67 Mon Sep 17 00:00:00 2001 From: Donovan Walker Date: Wed, 26 Sep 2018 16:04:57 -0500 Subject: [PATCH 159/166] bump ci From f8423c7e5a817092dd2553d4dee7033b67150a48 Mon Sep 17 00:00:00 2001 From: "shine.wong" <40054991+wjashan@users.noreply.github.com> Date: Tue, 23 Oct 2018 12:08:21 +0800 Subject: [PATCH 160/166] =?UTF-8?q?=E7=A1=AC=E5=8F=8920=E5=AE=A1=E6=9F=A5?= =?UTF-8?q?=E6=83=A9=E7=BD=9A=E6=97=B6=E9=97=B4=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit curation_reward_pct()从原来的30分钟改为15分钟 --- steem/post.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/steem/post.py b/steem/post.py index f85d534..0925968 100644 --- a/steem/post.py +++ b/steem/post.py @@ -195,9 +195,9 @@ def is_comment(self): return self['depth'] > 0 def curation_reward_pct(self): - """ If post is less than 30 minutes old, it will incur a curation + """ If post is less than 15 minutes old, it will incur a curation reward penalty. """ - reward = (self.time_elapsed().seconds / 1800) * 100 + reward = (self.time_elapsed().seconds / 900) * 100 if reward > 100: reward = 100 return reward From efdf8ce3dfc9e6f3fb925821bf23625a8436a88e Mon Sep 17 00:00:00 2001 From: Seungwon Eugene Jeong Date: Wed, 12 Dec 2018 20:22:49 +0000 Subject: [PATCH 161/166] fix sbd_to_rshares get_dynamic_global_properties() no longer returns reward_fund info. Use get_reward_fund() instead. --- steem/converter.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/steem/converter.py b/steem/converter.py index d3813d4..4675b19 100644 --- a/steem/converter.py +++ b/steem/converter.py @@ -93,16 +93,11 @@ def sbd_to_rshares(self, sbd_payout): """ steem_payout = self.sbd_to_steem(sbd_payout) - props = self.steemd.get_dynamic_global_properties() - total_reward_fund_steem = Amount(props['total_reward_fund_steem']) - total_reward_shares2 = int(props['total_reward_shares2']) - - post_rshares2 = ( - steem_payout / total_reward_fund_steem) * total_reward_shares2 + reward_fund = self.steemd.get_reward_fund() + reward_balance = Amount(reward_fund['reward_balance']).amount + recent_claims = int(reward_fund['recent_claims']) - rshares = math.sqrt( - self.CONTENT_CONSTANT ** 2 + post_rshares2) - self.CONTENT_CONSTANT - return rshares + return int(recent_claims * steem_payout / (reward_balance - steem_payout)) def rshares_2_weight(self, rshares): """ Obtain weight from rshares From ccd3a22e29c09d4facab3e0025a88270a3a62fff Mon Sep 17 00:00:00 2001 From: roadscape Date: Mon, 4 Feb 2019 12:49:56 -0600 Subject: [PATCH 162/166] fix whitespace --- steembase/bip38.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/steembase/bip38.py b/steembase/bip38.py index d6bbbe5..b5f1b22 100644 --- a/steembase/bip38.py +++ b/steembase/bip38.py @@ -77,7 +77,7 @@ def encrypt(privkey, passphrase): else: raise ValueError("No scrypt module loaded") (derived_half1, derived_half2) = (key[:32], key[32:]) - aes = AES.new(derived_half2,AES.MODE_ECB) + aes = AES.new(derived_half2, AES.MODE_ECB) encrypted_half1 = _encrypt_xor(privkeyhex[:32], derived_half1[:16], aes) encrypted_half2 = _encrypt_xor(privkeyhex[32:], derived_half1[16:], aes) " flag byte is forced 0xc0 because Graphene only uses compressed keys " @@ -121,7 +121,7 @@ def decrypt(encrypted_privkey, passphrase): derivedhalf2 = key[32:64] encryptedhalf1 = d[0:16] encryptedhalf2 = d[16:32] - aes = AES.new(derivedhalf2,AES.MODE_ECB) + aes = AES.new(derivedhalf2, AES.MODE_ECB) decryptedhalf2 = aes.decrypt(encryptedhalf2) decryptedhalf1 = aes.decrypt(encryptedhalf1) privraw = decryptedhalf1 + decryptedhalf2 From a0d938cb94857a97acaee6416c007a871d7d9d47 Mon Sep 17 00:00:00 2001 From: Cookie Monster <3112680+Dont-Copy-That-Floppy@users.noreply.github.com> Date: Fri, 14 Feb 2025 22:03:25 -0700 Subject: [PATCH 163/166] - updated codebase to be compatible with python 3.12 --- README.md | 4 +- pyproject.toml | 3 + setup.cfg | 2 +- setup.py | 244 +++++++++++++++------------------------------ steembase/bip38.py | 2 +- 5 files changed, 90 insertions(+), 165 deletions(-) create mode 100644 pyproject.toml diff --git a/README.md b/README.md index 4002ad2..80a129c 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,9 @@ From Source: ``` git clone https://github.com/steemit/steem-python.git cd steem-python -python3 setup.py install # python setup.py install for 2.7 +pip install build +python -m build +pip install . ``` ## Homebrew Build Prereqs diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8fe2f47 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=42", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/setup.cfg b/setup.cfg index 0634abb..6c48413 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -description-file=README.md +description_file=README.md [tool:pytest] norecursedirs=dist docs build deploy diff --git a/setup.py b/setup.py index d7877ab..9f7f273 100644 --- a/setup.py +++ b/setup.py @@ -1,175 +1,95 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - -# Note: To use the 'upload' functionality of this file, you must: -# $ pip install twine +""" +WARNING: + Direct invocation of setup.py is deprecated. + Please use "pip install ." or "python -m build" (with a proper pyproject.toml) + to build and install this package. +""" import io import os -import sys -from shutil import rmtree - -from setuptools import find_packages, setup, Command -from setuptools.command.test import test as TestCommand - -# Package meta-data. -NAME = 'steem' -DESCRIPTION = 'Official python steem library.' -URL = 'https://github.com/steemit/steem-python' -EMAIL = 'john@steemit.com' -AUTHOR = 'Steemit' - -# What packages are required for this module to be executed? -REQUIRED = [ - 'appdirs', - 'certifi', - 'ecdsa>=0.13', - 'funcy', - 'futures ; python_version < "3.0.0"', - 'future', - 'langdetect', - 'prettytable', - 'pycrypto>=1.9.1', - 'pylibscrypt>=1.6.1', - 'scrypt>=0.8.0', - 'toolz', - 'ujson', - 'urllib3', - 'voluptuous', - 'w3lib' -] -TEST_REQUIRED = [ - 'pep8', - 'pytest', - 'pytest-pylint ; python_version >= "3.4.0"', - 'pytest-xdist', - 'pytest-runner', - 'pytest-pep8', - 'pytest-cov', - 'yapf', - 'autopep8' -] - -BUILD_REQUIRED = [ - 'twine', - 'pypandoc', - 'recommonmark' - 'wheel', - 'setuptools', - 'sphinx', - 'sphinx_rtd_theme' -] -# The rest you shouldn't have to touch too much :) -# ------------------------------------------------ -# Except, perhaps the License and Trove Classifiers! -# If you do change the License, remember to change the Trove Classifier for that! +import setuptools here = os.path.abspath(os.path.dirname(__file__)) -# Import the README and use it as the long-description. -# Note: this will only work if 'README.rst' is present in your MANIFEST.in file! -# with io.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f: -# long_description = '\n' + f.read() - - -class PyTest(TestCommand): - user_options = [('pytest-args=', 'a', "Arguments to pass into py.test")] - - def initialize_options(self): - TestCommand.initialize_options(self) - try: - from multiprocessing import cpu_count - self.pytest_args = ['-n', str(cpu_count()), '--boxed'] - except (ImportError, NotImplementedError): - self.pytest_args = ['-n', '1', '--boxed'] - - def finalize_options(self): - TestCommand.finalize_options(self) - self.test_args = [] - self.test_suite = True - - def run_tests(self): - import pytest - - errno = pytest.main(self.pytest_args) - sys.exit(errno) - - -class UploadCommand(Command): - """Support setup.py upload.""" - - description = 'Build and publish the package.' - user_options = [] - - @staticmethod - def status(s): - """Prints things in bold.""" - print('\033[1m{0}\033[0m'.format(s)) - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - try: - self.status('Removing previous builds…') - rmtree(os.path.join(here, 'dist')) - except OSError: - pass - - self.status('Building Source and Wheel (universal) distribution…') - os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable)) - - self.status('Uploading the package to PyPi via Twine…') - os.system('twine upload dist/*') - - sys.exit() - - -# Where the magic happens: -setup( - name=NAME, - version='1.0.2', - description=DESCRIPTION, - keywords=['steem', 'steemit', 'cryptocurrency', 'blockchain'], - # long_description=long_description, - author=AUTHOR, - author_email=EMAIL, - url=URL, - packages=find_packages(exclude=('tests','scripts')), +# Read the long description from the README file (if available) +try: + with io.open(os.path.join(here, "README.rst"), encoding="utf-8") as f: + long_description = f.read() +except FileNotFoundError: + long_description = "Official python steem library." + +setuptools.setup( + name="steem", + version="1.0.2", + author="Steemit", + author_email="john@steemit.com", + description="Official python steem library.", + long_description=long_description, + long_description_content_type="text/x-rst", + url="https://github.com/steemit/steem-python", + packages=setuptools.find_packages(exclude=("tests", "scripts")), + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules", + ], + python_requires=">=3.5", + install_requires=[ + "appdirs", + "certifi", + "ecdsa>=0.13", + "funcy", + 'futures; python_version < "3.0.0"', + "future", + "langdetect", + "prettytable", + "pycryptodome>=3.20.0", + "pylibscrypt>=1.6.1", + "scrypt>=0.8.0", + "toolz", + "ujson", + "urllib3", + "voluptuous", + "w3lib", + ], entry_points={ - 'console_scripts': [ - 'piston=steem.cli:legacyentry', - 'steempy=steem.cli:legacyentry', - 'steemtail=steem.cli:steemtailentry', - ], + "console_scripts": [ + "piston=steem.cli:legacyentry", + "steempy=steem.cli:legacyentry", + "steemtail=steem.cli:steemtailentry", + ], }, - install_requires=REQUIRED, extras_require={ - 'dev': TEST_REQUIRED + BUILD_REQUIRED, - 'build': BUILD_REQUIRED, - 'test': TEST_REQUIRED - }, - tests_require=TEST_REQUIRED, - include_package_data=True, - license='MIT', - - classifiers=[ - # Trove classifiers - # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Topic :: Software Development :: Libraries', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Development Status :: 4 - Beta' - ], - # $ setup.py publish support. - cmdclass={ - 'upload': UploadCommand, - 'test': PyTest + "dev": [ + "pytest", + "pytest-cov", + "pytest-xdist", + "autopep8", + "yapf", + "twine", + "pypandoc", + "recommonmark", + "wheel", + "setuptools", + "sphinx", + "sphinx_rtd_theme", + ], + "test": [ + "pytest", + "pytest-cov", + "pytest-xdist", + "autopep8", + "yapf", + ], }, ) diff --git a/steembase/bip38.py b/steembase/bip38.py index b5f1b22..29bb9c7 100644 --- a/steembase/bip38.py +++ b/steembase/bip38.py @@ -13,7 +13,7 @@ try: from Crypto.Cipher import AES except ImportError: - raise ImportError("Missing dependency: pycrypto") + raise ImportError("Missing dependency: pycryptodome") SCRYPT_MODULE = os.environ.get('SCRYPT_MODULE', None) if not SCRYPT_MODULE: From 9af6e8ca4d3feb77b0ae5ffa2d8724a6dd1addd0 Mon Sep 17 00:00:00 2001 From: Cookie Monster <3112680+Dont-Copy-That-Floppy@users.noreply.github.com> Date: Fri, 14 Feb 2025 22:10:20 -0700 Subject: [PATCH 164/166] - added pick requirement --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 9f7f273..2dbf222 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ 'futures; python_version < "3.0.0"', "future", "langdetect", + "pick>=2.4.1", "prettytable", "pycryptodome>=3.20.0", "pylibscrypt>=1.6.1", From 3b5f50e38dc92f65ed89a330bb2520867d992349 Mon Sep 17 00:00:00 2001 From: Cookie Monster <3112680+Dont-Copy-That-Floppy@users.noreply.github.com> Date: Fri, 14 Feb 2025 22:17:06 -0700 Subject: [PATCH 165/166] - fixed pick version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2dbf222..88e4a74 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ 'futures; python_version < "3.0.0"', "future", "langdetect", - "pick>=2.4.1", + "pick>=2.4.0", "prettytable", "pycryptodome>=3.20.0", "pylibscrypt>=1.6.1", From 1574ab6943e052837742d644d73b1409154486f8 Mon Sep 17 00:00:00 2001 From: Cookie Monster <3112680+Dont-Copy-That-Floppy@users.noreply.github.com> Date: Fri, 14 Feb 2025 22:20:15 -0700 Subject: [PATCH 166/166] - regex syntax fix and var to int comparison fix --- steem/utils.py | 126 ++++++++++++++++---------------------- steembase/transactions.py | 2 +- 2 files changed, 55 insertions(+), 73 deletions(-) diff --git a/steem/utils.py b/steem/utils.py index 9da991e..4943ab3 100755 --- a/steem/utils.py +++ b/steem/utils.py @@ -15,7 +15,7 @@ from langdetect.lang_detect_exception import LangDetectException from toolz import update_in, assoc -if sys.version >= '3.0': +if sys.version >= "3.0": from urllib.parse import urlparse else: from urlparse import urlparse @@ -24,9 +24,7 @@ # https://github.com/matiasb/python-unidiff/blob/master/unidiff/constants.py#L37 # @@ (source offset, length) (target offset, length) @@ (section header) -RE_HUNK_HEADER = re.compile( - r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))?\ @@[ ]?(.*)$", - flags=re.MULTILINE) +RE_HUNK_HEADER = re.compile(r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))?\ @@[ ]?(.*)$", flags=re.MULTILINE) # ensure deterministec language detection DetectorFactory.seed = 0 @@ -82,34 +80,29 @@ def chunkify(iterable, chunksize=10000): def ensure_decoded(thing): if not thing: - logger.debug('ensure_decoded thing is logically False') + logger.debug("ensure_decoded thing is logically False") return None if isinstance(thing, (list, dict)): - logger.debug('ensure_decoded thing is already decoded') + logger.debug("ensure_decoded thing is already decoded") return thing single_encoded_dict = double_encoded_dict = None try: single_encoded_dict = json.loads(thing) if isinstance(single_encoded_dict, dict): - logger.debug('ensure_decoded thing is single encoded dict') + logger.debug("ensure_decoded thing is single encoded dict") return single_encoded_dict elif isinstance(single_encoded_dict, str): - logger.debug('ensure_decoded thing is single encoded str') + logger.debug("ensure_decoded thing is single encoded str") if single_encoded_dict == "": - logger.debug( - 'ensure_decoded thing is single encoded str == ""') + logger.debug('ensure_decoded thing is single encoded str == ""') return None else: double_encoded_dict = json.loads(single_encoded_dict) - logger.debug('ensure_decoded thing is double encoded') + logger.debug("ensure_decoded thing is double encoded") return double_encoded_dict except Exception as e: - extra = dict( - thing=thing, - single_encoded_dict=single_encoded_dict, - double_encoded_dict=double_encoded_dict, - error=e) - logger.error('ensure_decoded error', extra=extra) + extra = dict(thing=thing, single_encoded_dict=single_encoded_dict, double_encoded_dict=double_encoded_dict, error=e) + logger.error("ensure_decoded error", extra=extra) return None @@ -137,31 +130,30 @@ def extract_keys_from_meta(meta, keys): elif isinstance(item, (list, tuple)): extracted.extend(item) else: - logger.warning('unusual item in meta: %s', item) + logger.warning("unusual item in meta: %s", item) return extracted def build_comment_url(parent_permlink=None, author=None, permlink=None): - return '/'.join([parent_permlink, author, permlink]) + return "/".join([parent_permlink, author, permlink]) def canonicalize_url(url, **kwargs): try: canonical_url = w3lib.url.canonicalize_url(url, **kwargs) except Exception as e: - logger.warning('url preparation error', extra=dict(url=url, error=e)) + logger.warning("url preparation error", extra=dict(url=url, error=e)) return None if canonical_url != url: - logger.debug('canonical_url changed %s to %s', url, canonical_url) + logger.debug("canonical_url changed %s to %s", url, canonical_url) try: parsed_url = urlparse(canonical_url) if not parsed_url.scheme and not parsed_url.netloc: - _log = dict( - url=url, canonical_url=canonical_url, parsed_url=parsed_url) - logger.warning('bad url encountered', extra=_log) + _log = dict(url=url, canonical_url=canonical_url, parsed_url=parsed_url) + logger.warning("bad url encountered", extra=_log) return None except Exception as e: - logger.warning('url parse error', extra=dict(url=url, error=e)) + logger.warning("url parse error", extra=dict(url=url, error=e)) return None return canonical_url @@ -172,7 +164,7 @@ def findall_patch_hunks(body=None): def detect_language(text): if not text or len(text) < MIN_TEXT_LENGTH_FOR_DETECTION: - logger.debug('not enough text to perform langdetect') + logger.debug("not enough text to perform langdetect") return None try: return detect(text) @@ -186,7 +178,7 @@ def is_comment(item): The item can be a Post object or just a raw comment object from the blockchain. """ - return item['parent_author'] != "" + return item["parent_author"] != "" def time_elapsed(posting_time): @@ -202,7 +194,7 @@ def parse_time(block_time): """Take a string representation of time from the blockchain, and parse it into datetime object. """ - return datetime.strptime(block_time, '%Y-%m-%dT%H:%M:%S') + return datetime.strptime(block_time, "%Y-%m-%dT%H:%M:%S") def time_diff(time1, time2): @@ -210,8 +202,7 @@ def time_diff(time1, time2): def keep_in_dict(obj, allowed_keys=list()): - """ Prune a class or dictionary of all but allowed keys. - """ + """Prune a class or dictionary of all but allowed keys.""" if type(obj) == dict: items = obj.items() else: @@ -221,8 +212,7 @@ def keep_in_dict(obj, allowed_keys=list()): def remove_from_dict(obj, remove_keys=list()): - """ Prune a class or dictionary of specified keys. - """ + """Prune a class or dictionary of specified keys.""" if type(obj) == dict: items = obj.items() else: @@ -232,7 +222,7 @@ def remove_from_dict(obj, remove_keys=list()): def construct_identifier(*args): - """ Create a post identifier from comment/post object or arguments. + """Create a post identifier from comment/post object or arguments. Examples: @@ -244,21 +234,20 @@ def construct_identifier(*args): if len(args) == 1: op = args[0] - author, permlink = op['author'], op['permlink'] + author, permlink = op["author"], op["permlink"] elif len(args) == 2: author, permlink = args else: - raise ValueError( - 'construct_identifier() received unparsable arguments') + raise ValueError("construct_identifier() received unparsable arguments") # remove the @ sign in case it was passed in by the user. - author = author.replace('@', '') + author = author.replace("@", "") fields = dict(author=author, permlink=permlink) return "{author}/{permlink}".format(**fields) -def json_expand(json_op, key_name='json'): - """ Convert a string json object to Python dict in an op. """ +def json_expand(json_op, key_name="json"): + """Convert a string json object to Python dict in an op.""" if type(json_op) == dict and key_name in json_op and json_op[key_name]: try: return update_in(json_op, [key_name], json.loads) @@ -270,9 +259,9 @@ def json_expand(json_op, key_name='json'): def sanitize_permlink(permlink): permlink = permlink.strip() - permlink = re.sub("_|\s|\.", "-", permlink) - permlink = re.sub("[^\w-]", "", permlink) - permlink = re.sub("[^a-zA-Z0-9-]", "", permlink) + permlink = re.sub(r"_|\s|\.", "-", permlink) + permlink = re.sub(r"[^\w-]", "", permlink) + permlink = re.sub(r"[^a-zA-Z0-9-]", "", permlink) permlink = permlink.lower() return permlink @@ -292,53 +281,49 @@ def derive_permlink(title, parent_permlink=None): def resolve_identifier(identifier): # in case the user supplied the @ sign. - identifier = identifier.replace('@', '') + identifier = identifier.replace("@", "") - match = re.match("([\w\-\.]*)/([\w\-]*)", identifier) + match = re.match(r"([\w\-\.]*)/([\w\-]*)", identifier) if not hasattr(match, "group"): raise ValueError("Invalid identifier") return match.group(1), match.group(2) def fmt_time(t): - """ Properly Format Time for permlinks - """ + """Properly Format Time for permlinks""" return datetime.utcfromtimestamp(t).strftime("%Y%m%dt%H%M%S%Z") def fmt_time_string(t): - """ Properly Format Time for permlinks - """ - return datetime.strptime(t, '%Y-%m-%dT%H:%M:%S') + """Properly Format Time for permlinks""" + return datetime.strptime(t, "%Y-%m-%dT%H:%M:%S") def fmt_time_from_now(secs=0): - """ Properly Format Time that is `x` seconds in the future + """Properly Format Time that is `x` seconds in the future - :param int secs: Seconds to go in the future (`x>0`) or the - past (`x<0`) - :return: Properly formated time for Graphene (`%Y-%m-%dT%H:%M:%S`) - :rtype: str + :param int secs: Seconds to go in the future (`x>0`) or the + past (`x<0`) + :return: Properly formated time for Graphene (`%Y-%m-%dT%H:%M:%S`) + :rtype: str """ - return datetime.utcfromtimestamp(time.time() + int(secs)).strftime( - '%Y-%m-%dT%H:%M:%S') + return datetime.utcfromtimestamp(time.time() + int(secs)).strftime("%Y-%m-%dT%H:%M:%S") def env_unlocked(): - """ Check if wallet passphrase is provided as ENV variable. """ - return os.getenv('UNLOCK', False) + """Check if wallet passphrase is provided as ENV variable.""" + return os.getenv("UNLOCK", False) # todo remove these def strfage(time, fmt=None): - """ Format time/age - """ + """Format time/age""" if not hasattr(time, "days"): # dirty hack now = datetime.utcnow() if isinstance(time, str): - time = datetime.strptime(time, '%Y-%m-%dT%H:%M:%S') - time = (now - time) + time = datetime.strptime(time, "%Y-%m-%dT%H:%M:%S") + time = now - time d = {"days": time.days} d["hours"], rem = divmod(time.seconds, 3600) @@ -355,8 +340,7 @@ def strfage(time, fmt=None): def strfdelta(tdelta, fmt): - """ Format time/age - """ + """Format time/age""" if not tdelta or not hasattr(tdelta, "days"): # dirty hack return None @@ -367,7 +351,7 @@ def strfdelta(tdelta, fmt): def is_valid_account_name(name): - return re.match('^[a-z][a-z0-9\-.]{2,15}$', name) + return re.match(r"^[a-z][a-z0-9\-.]{2,15}$", name) def compat_compose_dictionary(dictionary, **kwargs): @@ -394,20 +378,18 @@ def compat_json(data, ignore_dicts=False): """ # if this is a unicode string, return its string representation if isinstance(data, unicode): - return data.encode('utf-8') + return data.encode("utf-8") # if this is a list of values, return list of byte-string values if isinstance(data, list): return [compat_json(item, ignore_dicts=True) for item in data] # if this is a dictionary, return dictionary of byte-string keys and values # but only if we haven't already byte-string it if isinstance(data, dict) and not ignore_dicts: - return { - compat_json(key, ignore_dicts=True): compat_json(value, ignore_dicts=True) - for key, value in data.iteritems() - } + return {compat_json(key, ignore_dicts=True): compat_json(value, ignore_dicts=True) for key, value in data.iteritems()} # if it's anything else, return it in its original form return data + def compat_bytes(item, encoding=None): """ This method is required because Python 2.7 `bytes` is simply an alias for `str`. Without this method, @@ -442,7 +424,7 @@ def __bytes__(self): :param encoding: optional encoding parameter to handle the Python 3.6 two argument 'bytes' method. :return: a bytes object that functions the same across 3.6 and 2.7 """ - if hasattr(item, '__bytes__'): + if hasattr(item, "__bytes__"): return item.__bytes__() else: if encoding: @@ -461,7 +443,7 @@ def compat_chr(item): :param item: a length 1 string who's `chr` method needs to be invoked :return: the unichr code point of the single character string, item """ - if sys.version >= '3.0': + if sys.version >= "3.0": return chr(item) else: return unichr(item) diff --git a/steembase/transactions.py b/steembase/transactions.py index df79641..5aef114 100644 --- a/steembase/transactions.py +++ b/steembase/transactions.py @@ -326,7 +326,7 @@ def sign(self, wifkeys, chain=None): # lenR = sigder[3] lenS = sigder[5 + lenR] - if lenR is 32 and lenS is 32: + if int(lenR) == 32 and int(lenS) == 32: # Derive the recovery parameter # i = self.recoverPubkeyParameter(