From af113d242b79f652000df85f275a34ae5a2bc331 Mon Sep 17 00:00:00 2001 From: Holger <holger@nahrstaedt.de> Date: Fri, 15 Jun 2018 13:47:45 +0200 Subject: [PATCH] All dates, ints, and amounts are parsed in account, block, comment, vote and witness Account * _parse_json_data improved and all missing ints and dates added * json() adapted to _parse_json_data() Block * _parse_json_data added, expiration and timestamp are now datetime objects * property json_transactions and json_operations added, which return trx and ops with date strings * BlockHeader improved and datetime parsing added Blockchain * adapted to new datetime objects CLI * adapted to code changes Comment * int and dates inside active_votes are parsed * Missing dates and int are parsed Market * date parsing to datetime added to market_history Vote * Some improvements Witness * _parse_json_data added * all date and ints are parsed * Demo added to Witnesses, WitnessesVotedByAccount, WitnessesRankedByVote and ListWitnesses beemgraphenebase/types * datetime handling added to PointInTime Unit tests * test_account adapted * test_export added to test_block * test in test_comment fixed * test_export added to test_witness --- README.rst | 8 +- beem/account.py | 65 +++++++--- beem/block.py | 182 +++++++++++++++++++++++++++- beem/blockchain.py | 6 +- beem/cli.py | 11 +- beem/comment.py | 100 +++++++++------ beem/market.py | 13 +- beem/vote.py | 37 +++--- beem/witness.py | 89 ++++++++++++-- beemgraphenebase/types.py | 4 +- tests/beem/test_account.py | 2 +- tests/beem/test_block.py | 46 +++++++ tests/beem/test_blockchain_batch.py | 1 + tests/beem/test_comment.py | 5 +- tests/beem/test_witness.py | 27 +++++ 15 files changed, 500 insertions(+), 96 deletions(-) diff --git a/README.rst b/README.rst index f7646693..dfed84c6 100644 --- a/README.rst +++ b/README.rst @@ -168,7 +168,13 @@ Changelog * poloniex removed and huobi and ubpit added to steem_btc_ticker() * Add timeout to websocket connections * Documentation improved by crokkon -* time, reputation and rshares are parsed from string in all vote objects +* "time", "reputation" and "rshares" are parsed from string in all vote objects and inside all active_votes from a comment object +* lazy and full properly passed +* "votes", "virtual_last_update", "virtual_position", "virtual_scheduled_time", + "created", "last_sbd_exchange_update", "hardfork_time_vote" are properly casted in all witness objects +* "time" and "expiration" are parsed to a datetime object inside all block objects +* The json() function returns the original not parsed json dict. It is available for Account, Block, BlockHeader, Comment, Vote and Witness +* json_transactions and json_operations added to Block, for returning all dates as string 0.19.37 ------- diff --git a/beem/account.py b/beem/account.py index 820af4c7..81ff3077 100644 --- a/beem/account.py +++ b/beem/account.py @@ -46,9 +46,9 @@ class Account(BlockchainObject): .. code-block:: python >>> from beem.account import Account - >>> account = Account("test") + >>> account = Account("gtg") >>> print(account) - <Account test> + <Account gtg> >>> print(account.balances) # doctest: +SKIP .. note:: This class comes with its own caching function to reduce the @@ -78,6 +78,9 @@ class Account(BlockchainObject): """ self.full = full self.lazy = lazy + self.steem = steem_instance or shared_steem_instance() + if isinstance(account, dict): + account = self._parse_json_data(account) super(Account, self).__init__( account, lazy=lazy, @@ -85,7 +88,6 @@ class Account(BlockchainObject): id_item="name", steem_instance=steem_instance ) - self._parse_json_data() def refresh(self): """ Refresh/Obtain an account's data from the API server @@ -108,13 +110,27 @@ class Account(BlockchainObject): account = account[0] if not account: raise AccountDoesNotExistsException(self.identifier) + account = self._parse_json_data(account) self.identifier = account["name"] # self.steem.refresh_data() super(Account, self).__init__(account, id_item="name", lazy=self.lazy, full=self.full, steem_instance=self.steem) - self._parse_json_data() - def _parse_json_data(self): + def _parse_json_data(self, account): + parse_int = [ + "sbd_seconds", "savings_sbd_seconds", "average_bandwidth", "lifetime_bandwidth", "lifetime_market_bandwidth", "reputation", + ] + for p in parse_int: + if p in account and isinstance(account.get(p), string_types): + account[p] = int(account.get(p, "0")) + if "proxied_vsf_votes" in account: + proxied_vsf_votes = [] + for p_int in account["proxied_vsf_votes"]: + if isinstance(p_int, string_types): + proxied_vsf_votes.append(int(p_int)) + else: + proxied_vsf_votes.append(p_int) + account["proxied_vsf_votes"] = proxied_vsf_votes parse_times = [ "last_owner_update", "last_account_update", "created", "last_owner_proved", "last_active_proved", "last_account_recovery", "last_vote_time", "sbd_seconds_last_update", "sbd_last_interest_payment", @@ -122,8 +138,8 @@ class Account(BlockchainObject): "last_market_bandwidth_update", "last_post", "last_root_post", "last_bandwidth_update" ] for p in parse_times: - if p in self and isinstance(self.get(p), string_types): - self[p] = formatTimeString(self.get(p, "1970-01-01T00:00:00")) + if p in account and isinstance(account.get(p), string_types): + account[p] = formatTimeString(account.get(p, "1970-01-01T00:00:00")) # Parse Amounts amounts = [ "balance", @@ -136,14 +152,31 @@ class Account(BlockchainObject): "reward_vesting_steem", "vesting_shares", "delegated_vesting_shares", - "received_vesting_shares" + "received_vesting_shares", + "vesting_withdraw_rate", + "vesting_balance", ] for p in amounts: - if p in self and isinstance(self.get(p), (string_types, list, dict)): - self[p] = Amount(self[p], steem_instance=self.steem) + if p in account and isinstance(account.get(p), (string_types, list, dict)): + account[p] = Amount(account[p], steem_instance=self.steem) + return account def json(self): output = self.copy() + parse_int = [ + "sbd_seconds", "savings_sbd_seconds", "average_bandwidth", "lifetime_bandwidth", "lifetime_market_bandwidth", "reputation", + ] + for p in parse_int: + if p in output and isinstance(output[p], int) and output[p] != 0: + output[p] = str(output[p]) + if "proxied_vsf_votes" in output: + proxied_vsf_votes = [] + for p_int in output["proxied_vsf_votes"]: + if isinstance(p_int, int) and p_int != 0: + proxied_vsf_votes.append(str(p_int)) + else: + proxied_vsf_votes.append(p_int) + output["proxied_vsf_votes"] = proxied_vsf_votes parse_times = [ "last_owner_update", "last_account_update", "created", "last_owner_proved", "last_active_proved", "last_account_recovery", "last_vote_time", "sbd_seconds_last_update", "sbd_last_interest_payment", @@ -152,11 +185,11 @@ class Account(BlockchainObject): ] for p in parse_times: if p in output: - date = output.get(p, datetime(1970, 1, 1, 0, 0)) - if isinstance(date, (datetime, date, time)): - output[p] = formatTimeString(date) + p_date = output.get(p, datetime(1970, 1, 1, 0, 0)) + if isinstance(p_date, (datetime, date, time)): + output[p] = formatTimeString(p_date) else: - output[p] = date + output[p] = p_date amounts = [ "balance", "savings_balance", @@ -168,7 +201,9 @@ class Account(BlockchainObject): "reward_vesting_steem", "vesting_shares", "delegated_vesting_shares", - "received_vesting_shares" + "received_vesting_shares", + "vesting_withdraw_rate", + "vesting_balance" ] for p in amounts: if p in output: diff --git a/beem/block.py b/beem/block.py index c369d472..e8657c4b 100644 --- a/beem/block.py +++ b/beem/block.py @@ -3,10 +3,13 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals +from datetime import datetime, timedelta, date +import json from .exceptions import BlockDoesNotExistsException -from .utils import parse_time +from .utils import parse_time, formatTimeString from .blockchainobject import BlockchainObject from beemapi.exceptions import ApiNotSupported +from beemgraphenebase.py23 import bytes_types, integer_types, string_types, text_type class Block(BlockchainObject): @@ -62,6 +65,10 @@ class Block(BlockchainObject): self.lazy = lazy self.only_ops = only_ops self.only_virtual_ops = only_virtual_ops + if isinstance(block, float): + block = int(block) + elif isinstance(block, dict): + block = self._parse_json_data(block) super(Block, self).__init__( block, lazy=lazy, @@ -69,6 +76,65 @@ class Block(BlockchainObject): steem_instance=steem_instance ) + def _parse_json_data(self, block): + parse_times = [ + "timestamp", + ] + for p in parse_times: + if p in block and isinstance(block.get(p), string_types): + block[p] = formatTimeString(block.get(p, "1970-01-01T00:00:00")) + if "transactions" in block: + new_transactions = [] + for trx in block["transactions"]: + if 'expiration' in trx and isinstance(trx.get('expiration'), string_types): + trx['expiration'] = formatTimeString(trx.get('expiration', "1970-01-01T00:00:00")) + new_transactions.append(trx) + block["transactions"] = new_transactions + elif "operations" in block: + new_operations = [] + for trx in block["operations"]: + if 'timestamp' in trx and isinstance(trx.get('timestamp'), string_types): + trx['timestamp'] = formatTimeString(trx.get('timestamp', "1970-01-01T00:00:00")) + new_operations.append(trx) + block["operations"] = new_operations + return block + + def json(self): + output = self.copy() + parse_times = [ + "timestamp", + ] + for p in parse_times: + if p in output: + p_date = output.get(p, datetime(1970, 1, 1, 0, 0)) + if isinstance(p_date, (datetime, date)): + output[p] = formatTimeString(p_date) + else: + output[p] = p_date + if "transactions" in output: + new_transactions = [] + for trx in output["transactions"]: + if 'expiration' in trx: + p_date = trx.get('expiration', datetime(1970, 1, 1, 0, 0)) + if isinstance(p_date, (datetime, date)): + trx['expiration'] = formatTimeString(p_date) + else: + trx['expiration'] = p_date + new_transactions.append(trx) + output["transactions"] = new_transactions + elif "operations" in output: + new_operations = [] + for trx in output["operations"]: + if 'timestamp' in trx: + p_date = trx.get('timestamp', datetime(1970, 1, 1, 0, 0)) + if isinstance(p_date, (datetime, date)): + trx['timestamp'] = formatTimeString(p_date) + else: + trx['timestamp'] = p_date + new_operations.append(trx) + output["operations"] = new_operations + return json.loads(str(json.dumps(output))) + def refresh(self): """ Even though blocks never change, you freshly obtain its contents from an API with this method @@ -103,6 +169,7 @@ class Block(BlockchainObject): block = self.steem.rpc.get_block(self.identifier) if not block: raise BlockDoesNotExistsException(str(self.identifier)) + block = self._parse_json_data(block) super(Block, self).__init__(block, lazy=self.lazy, full=self.full, steem_instance=self.steem) @property @@ -112,7 +179,7 @@ class Block(BlockchainObject): def time(self): """Return a datatime instance for the timestamp of this block""" - return parse_time(self['timestamp']) + return self['timestamp'] @property def transactions(self): @@ -142,6 +209,46 @@ class Block(BlockchainObject): ops.append(op) return ops + @property + def json_transactions(self): + """ Returns all transactions as list, all dates are strings.""" + if self.only_ops or self.only_virtual_ops: + return list() + trxs = self["transactions"] + for i in range(len(trxs)): + trx = trxs[i] + trx['transaction_id'] = self['transaction_ids'][i] + trx['block_num'] = self.block_num + trx['transaction_num'] = i + if 'expiration' in trx: + p_date = trx.get('expiration', datetime(1970, 1, 1, 0, 0)) + if isinstance(p_date, (datetime, date)): + trx['expiration'] = formatTimeString(p_date) + else: + trx['expiration'] = p_date + trxs[i] = trx + return trxs + + @property + def json_operations(self): + """Returns all block operations as list, all dates are strings.""" + if self.only_ops or self.only_virtual_ops: + return self["operations"] + ops = [] + trxs = self["transactions"] + for tx in trxs: + for op in tx["operations"]: + # Replace opid by op name + # op[0] = getOperationNameForId(op[0]) + if 'timestamp' in op: + p_date = op.get('timestamp', datetime(1970, 1, 1, 0, 0)) + if isinstance(p_date, (datetime, date)): + op['timestamp'] = formatTimeString(p_date) + else: + op['timestamp'] = p_date + ops.append(op) + return ops + def ops_statistics(self, add_to_ops_stat=None): """Returns a statistic with the occurrence of the different operation types""" if add_to_ops_stat is None: @@ -166,6 +273,49 @@ class Block(BlockchainObject): class BlockHeader(BlockchainObject): + """ Read a single block header from the chain + + :param int block: block number + :param beem.steem.Steem steem_instance: Steem + instance + :param bool lazy: Use lazy loading + + In addition to the block data, the block number is stored as self["id"] or self.identifier. + + .. code-block:: python + + >>> from beem.block import BlockHeader + >>> block = BlockHeader(1) + >>> print(block) + <BlockHeader 1> + + """ + def __init__( + self, + block, + full=True, + lazy=False, + steem_instance=None + ): + """ Initilize a block + + :param int block: block number + :param beem.steem.Steem steem_instance: Steem + instance + :param bool lazy: Use lazy loading + + """ + self.full = full + self.lazy = lazy + if isinstance(block, float): + block = int(block) + super(BlockHeader, self).__init__( + block, + lazy=lazy, + full=full, + steem_instance=steem_instance + ) + def refresh(self): """ Even though blocks never change, you freshly obtain its contents from an API with this method @@ -181,17 +331,41 @@ class BlockHeader(BlockchainObject): block = self.steem.rpc.get_block_header(self.identifier) if not block: raise BlockDoesNotExistsException(str(self.identifier)) + block = self._parse_json_data(block) super(BlockHeader, self).__init__( - block, + block, lazy=self.lazy, full=self.full, steem_instance=self.steem ) def time(self): """ Return a datatime instance for the timestamp of this block """ - return parse_time(self['timestamp']) + return self['timestamp'] @property def block_num(self): """Returns the block number""" return self.identifier + + def _parse_json_data(self, block): + parse_times = [ + "timestamp", + ] + for p in parse_times: + if p in block and isinstance(block.get(p), string_types): + block[p] = formatTimeString(block.get(p, "1970-01-01T00:00:00")) + return block + + def json(self): + output = self.copy() + parse_times = [ + "timestamp", + ] + for p in parse_times: + if p in output: + p_date = output.get(p, datetime(1970, 1, 1, 0, 0)) + if isinstance(p_date, (datetime, date)): + output[p] = formatTimeString(p_date) + else: + output[p] = p_date + return json.loads(str(json.dumps(output))) diff --git a/beem/blockchain.py b/beem/blockchain.py index 94f02be2..a57c667f 100644 --- a/beem/blockchain.py +++ b/beem/blockchain.py @@ -481,7 +481,7 @@ class Blockchain(object): trx_id = block["transaction_ids"][trx_nr] block_num = block.get("id") _id = self.hash_op(event) - timestamp = formatTimeString(block.get("timestamp")) + timestamp = block.get("timestamp") elif isinstance(event, dict) and "type" in event and "value" in event: op_type = event["type"] if len(op_type) > 10 and op_type[len(op_type) - 10:] == "_operation": @@ -490,13 +490,13 @@ class Blockchain(object): trx_id = block["transaction_ids"][trx_nr] block_num = block.get("id") _id = self.hash_op(event) - timestamp = formatTimeString(block.get("timestamp")) + timestamp = block.get("timestamp") else: op_type, op = event["op"] trx_id = event.get("trx_id") block_num = event.get("block") _id = self.hash_op(event["op"]) - timestamp = formatTimeString(event.get("timestamp")) + timestamp = event.get("timestamp") if not opNames or op_type in opNames: if raw_ops: yield {"block_num": block_num, diff --git a/beem/cli.py b/beem/cli.py index 707729b7..0d2e2738 100644 --- a/beem/cli.py +++ b/beem/cli.py @@ -2221,7 +2221,7 @@ def curation(authorperm, account, limit, min_vote, max_vote, min_performance, ma else: performance = 0 penalty = 0 - vote_befor_min = ((formatTimeString(vote["time"]) - comment["created"]).total_seconds() / 60) + vote_befor_min = (((vote["time"]) - comment["created"]).total_seconds() / 60) sum_curation[0] += vote_SBD sum_curation[1] += penalty sum_curation[2] += float(curation_SP) @@ -2413,7 +2413,7 @@ def rewards(accounts, only_sum, post, comment, curation, length, author, permlin permlink_row = c.permlink rows.append([c["author"], permlink_row, - ((now - formatTimeString(v["timestamp"])).total_seconds() / 60 / 60 / 24), + ((now - (v["timestamp"])).total_seconds() / 60 / 60 / 24), (payout_SBD), (payout_STEEM), (payout_SP), @@ -2433,7 +2433,7 @@ def rewards(accounts, only_sum, post, comment, curation, length, author, permlin permlink_row = v["comment_permlink"] rows.append([v["comment_author"], permlink_row, - ((now - formatTimeString(v["timestamp"])).total_seconds() / 60 / 60 / 24), + ((now - (v["timestamp"])).total_seconds() / 60 / 60 / 24), 0.000, 0.000, payout_SP, @@ -2865,8 +2865,9 @@ def info(objects): if block: t = PrettyTable(["Key", "Value"]) t.align = "l" - for key in sorted(block): - value = block[key] + block_json = block.json() + for key in sorted(block_json): + value = block_json[key] if key == "transactions" and not bool(tran_nr): t.add_row(["Nr. of transactions", len(value)]) elif key == "transactions" and bool(tran_nr): diff --git a/beem/comment.py b/beem/comment.py index c8ab3448..b6fdfbd7 100644 --- a/beem/comment.py +++ b/beem/comment.py @@ -53,13 +53,16 @@ class Comment(BlockchainObject): ): self.full = full self.lazy = lazy + self.steem = steem_instance or shared_steem_instance() if isinstance(authorperm, string_types) and authorperm != "": [author, permlink] = resolve_authorperm(authorperm) self["id"] = 0 self["author"] = author self["permlink"] = permlink + self["authorperm"] = authorperm elif isinstance(authorperm, dict) and "author" in authorperm and "permlink" in authorperm: authorperm["authorperm"] = construct_authorperm(authorperm["author"], authorperm["permlink"]) + authorperm = self._parse_json_data(authorperm) super(Comment, self).__init__( authorperm, id_item="authorperm", @@ -67,18 +70,15 @@ class Comment(BlockchainObject): full=full, steem_instance=steem_instance ) - if "author" in self and "permlink" in self: - self.identifier = construct_authorperm(self["author"], self["permlink"]) - self._parse_json_data() - def _parse_json_data(self): + def _parse_json_data(self, comment): parse_times = [ "active", "cashout_time", "created", "last_payout", "last_update", "max_cashout_time" ] for p in parse_times: - if p in self and isinstance(self.get(p), string_types): - self[p] = formatTimeString(self.get(p, "1970-01-01T00:00:00")) + if p in comment and isinstance(comment.get(p), string_types): + comment[p] = formatTimeString(comment.get(p, "1970-01-01T00:00:00")) # Parse Amounts sbd_amounts = [ "total_payout_value", @@ -89,36 +89,48 @@ class Comment(BlockchainObject): "promoted", ] for p in sbd_amounts: - if p in self and isinstance(self.get(p), (string_types, list, dict)): - self[p] = Amount(self.get(p, "0.000 SBD"), steem_instance=self.steem) + if p in comment and isinstance(comment.get(p), (string_types, list, dict)): + comment[p] = Amount(comment.get(p, "0.000 SBD"), steem_instance=self.steem) # turn json_metadata into python dict - self._metadata_to_dict() - self["tags"] = [] - self['community'] = '' - if isinstance(self['json_metadata'], dict): - if "tags" in self['json_metadata']: - self["tags"] = self['json_metadata']["tags"] - if 'community' in self['json_metadata']: - self['community'] = self['json_metadata']['community'] + meta_str = comment.get("json_metadata", "{}") + if meta_str == "{}": + comment['json_metadata'] = meta_str + if isinstance(meta_str, (string_types, bytes_types, bytearray)): + try: + comment['json_metadata'] = json.loads(meta_str) + except: + comment['json_metadata'] = {} + + comment["tags"] = [] + comment['community'] = '' + if isinstance(comment['json_metadata'], dict): + if "tags" in comment['json_metadata']: + comment["tags"] = comment['json_metadata']["tags"] + if 'community' in comment['json_metadata']: + comment['community'] = comment['json_metadata']['community'] parse_int = [ "author_reputation", ] for p in parse_int: - if p in self and isinstance(self.get(p), string_types): - self[p] = int(self.get(p, "0")) - - def _metadata_to_dict(self): - """turn json_metadata into python dict""" - meta_str = self.get("json_metadata", "{}") - if meta_str == "{}": - self['json_metadata'] = meta_str - if isinstance(meta_str, (string_types, bytes_types, bytearray)): - try: - self['json_metadata'] = json.loads(meta_str) - except: - self['json_metadata'] = {} + if p in comment and isinstance(comment.get(p), string_types): + comment[p] = int(comment.get(p, "0")) + + if "active_votes" in comment: + new_active_votes = [] + for vote in comment["active_votes"]: + if 'time' in vote and isinstance(vote.get('time'), string_types): + vote['time'] = formatTimeString(vote.get('time', "1970-01-01T00:00:00")) + parse_int = [ + "rshares", "reputation", + ] + for p in parse_int: + if p in vote and isinstance(vote.get(p), string_types): + vote[p] = int(vote.get(p, "0")) + new_active_votes.append(vote) + comment["active_votes"] = new_active_votes + return comment def refresh(self): if self.identifier == "": @@ -133,10 +145,9 @@ class Comment(BlockchainObject): content = self.steem.rpc.get_content(author, permlink) if not content or not content['author'] or not content['permlink']: raise ContentDoesNotExistsException(self.identifier) + content = self._parse_json_data(content) + content["authorperm"] = construct_authorperm(content['author'], content['permlink']) super(Comment, self).__init__(content, id_item="authorperm", lazy=self.lazy, full=self.full, steem_instance=self.steem) - self["authorperm"] = construct_authorperm(self["author"], self["permlink"]) - self.identifier = self["authorperm"] - self._parse_json_data() def json(self): output = self.copy() @@ -154,11 +165,11 @@ class Comment(BlockchainObject): ] for p in parse_times: if p in output: - date = output.get(p, datetime(1970, 1, 1, 0, 0)) - if isinstance(date, (datetime, date)): - output[p] = formatTimeString(date) + p_date = output.get(p, datetime(1970, 1, 1, 0, 0)) + if isinstance(p_date, (datetime, date)): + output[p] = formatTimeString(p_date) else: - output[p] = date + output[p] = p_date sbd_amounts = [ "total_payout_value", "max_accepted_payout", @@ -176,6 +187,23 @@ class Comment(BlockchainObject): for p in parse_int: if p in output and isinstance(output[p], int): output[p] = str(output[p]) + if "active_votes" in output: + new_active_votes = [] + for vote in output["active_votes"]: + if 'time' in vote: + p_date = vote.get('time', datetime(1970, 1, 1, 0, 0)) + if isinstance(p_date, (datetime, date)): + vote['time'] = formatTimeString(p_date) + else: + vote['time'] = p_date + parse_int = [ + "rshares", "reputation", + ] + for p in parse_int: + if p in vote and isinstance(vote[p], int): + vote[p] = str(vote[p]) + new_active_votes.append(vote) + output["active_votes"] = new_active_votes return json.loads(str(json.dumps(output))) @property diff --git a/beem/market.py b/beem/market.py index 7afde7f3..03773804 100644 --- a/beem/market.py +++ b/beem/market.py @@ -16,6 +16,7 @@ from .amount import Amount from .price import Price, Order, FilledOrder from .account import Account from beembase import operations +from beemgraphenebase.py23 import bytes_types, integer_types, string_types, text_type REQUEST_MODULE = None if not REQUEST_MODULE: try: @@ -404,7 +405,7 @@ class Market(dict): else: return ret - def market_history(self, bucket_seconds=300, start_age=3600, end_age=0): + def market_history(self, bucket_seconds=300, start_age=3600, end_age=0, raw_data=False): """ Return the market history (filled orders). :param int bucket_seconds: Bucket size in seconds (see @@ -413,6 +414,7 @@ class Market(dict): window (default: 1h/3600) :param int end_age: Age (in seconds) of the end of the window (default: now/0) + :param bool raw_data: (optional) returns raw data if set True Example: .. code-block:: js @@ -451,7 +453,14 @@ class Market(dict): formatTimeFromNow(-start_age - end_age), formatTimeFromNow(-end_age), api="market_history") - return history + if raw_data: + return history + new_history = [] + for h in history: + if 'open' in h and isinstance(h.get('open'), string_types): + h['open'] = formatTimeString(h.get('open', "1970-01-01T00:00:00")) + new_history.append(h) + return new_history def accountopenorders(self, account=None, raw_data=False): """ Returns open Orders diff --git a/beem/vote.py b/beem/vote.py index b6f91816..ee1d3c3d 100644 --- a/beem/vote.py +++ b/beem/vote.py @@ -9,7 +9,7 @@ import math import pytz import logging from prettytable import PrettyTable -from datetime import datetime +from datetime import datetime, date from beemgraphenebase.py23 import integer_types, string_types, text_type from .instance import shared_steem_instance from .account import Account @@ -46,6 +46,7 @@ class Vote(BlockchainObject): ): self.full = full self.lazy = lazy + self.steem = steem_instance or shared_steem_instance() if isinstance(voter, string_types) and authorperm is not None: [author, permlink] = resolve_authorperm(authorperm) self["voter"] = voter @@ -56,6 +57,7 @@ class Vote(BlockchainObject): elif isinstance(voter, dict) and "author" in voter and "permlink" in voter and "voter" in voter: authorpermvoter = voter authorpermvoter["authorpermvoter"] = construct_authorpermvoter(voter["author"], voter["permlink"], voter["voter"]) + authorpermvoter = self._parse_json_data(authorpermvoter) elif isinstance(voter, dict) and "authorperm" in voter and authorperm is not None: [author, permlink] = resolve_authorperm(voter["authorperm"]) authorpermvoter = voter @@ -63,12 +65,14 @@ class Vote(BlockchainObject): authorpermvoter["author"] = author authorpermvoter["permlink"] = permlink authorpermvoter["authorpermvoter"] = construct_authorpermvoter(author, permlink, authorperm) + authorpermvoter = self._parse_json_data(authorpermvoter) elif isinstance(voter, dict) and "voter" in voter and authorperm is not None: [author, permlink] = resolve_authorperm(authorperm) authorpermvoter = voter authorpermvoter["author"] = author authorpermvoter["permlink"] = permlink authorpermvoter["authorpermvoter"] = construct_authorpermvoter(author, permlink, voter["voter"]) + authorpermvoter = self._parse_json_data(authorpermvoter) else: authorpermvoter = voter [author, permlink, voter] = resolve_authorpermvoter(authorpermvoter) @@ -82,7 +86,6 @@ class Vote(BlockchainObject): full=full, steem_instance=steem_instance ) - self._parse_json_data() def refresh(self): if self.identifier is None: @@ -105,25 +108,25 @@ class Vote(BlockchainObject): vote = x if not vote: raise VoteDoesNotExistsException(self.identifier) + vote = self._parse_json_data(vote) + vote["authorpermvoter"] = construct_authorpermvoter(author, permlink, voter) super(Vote, self).__init__(vote, id_item="authorpermvoter", lazy=self.lazy, full=self.full, steem_instance=self.steem) - self.identifier = construct_authorpermvoter(author, permlink, voter) - self._parse_json_data() - - def _parse_json_data(self): + def _parse_json_data(self, vote): parse_int = [ "rshares", "reputation", ] for p in parse_int: - if p in self and isinstance(self.get(p), string_types): - self[p] = int(self.get(p, "0")) + if p in vote and isinstance(vote.get(p), string_types): + vote[p] = int(vote.get(p, "0")) - if "time" in self and isinstance(self.get("time"), string_types) and self.get("time") != '': - self["time"] = formatTimeString(self.get("time", "1970-01-01T00:00:00")) - elif "timestamp" in self and isinstance(self.get("timestamp"), string_types) and self.get("timestamp") != '': - self["time"] = formatTimeString(self.get("timestamp", "1970-01-01T00:00:00")) + if "time" in vote and isinstance(vote.get("time"), string_types) and vote.get("time") != '': + vote["time"] = formatTimeString(vote.get("time", "1970-01-01T00:00:00")) + elif "timestamp" in vote and isinstance(vote.get("timestamp"), string_types) and vote.get("timestamp") != '': + vote["time"] = formatTimeString(vote.get("timestamp", "1970-01-01T00:00:00")) else: - self["time"] = formatTimeString("1970-01-01T00:00:00") + vote["time"] = formatTimeString("1970-01-01T00:00:00") + return vote def json(self): output = self.copy() @@ -136,11 +139,11 @@ class Vote(BlockchainObject): ] for p in parse_times: if p in output: - date = output.get(p, datetime(1970, 1, 1, 0, 0)) - if isinstance(date, (datetime, date)): - output[p] = formatTimeString(date) + p_date = output.get(p, datetime(1970, 1, 1, 0, 0)) + if isinstance(p_date, (datetime, date)): + output[p] = formatTimeString(p_date) else: - output[p] = date + output[p] = p_date parse_int = [ "rshares", "reputation", ] diff --git a/beem/witness.py b/beem/witness.py index 24e7c188..820afd93 100644 --- a/beem/witness.py +++ b/beem/witness.py @@ -4,6 +4,7 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals from builtins import str +import json from beem.instance import shared_steem_instance from beemgraphenebase.py23 import bytes_types, integer_types, string_types, text_type from .account import Account @@ -11,7 +12,7 @@ from .amount import Amount from .exceptions import WitnessDoesNotExistsException from .blockchainobject import BlockchainObject from .utils import formatTimeString -from datetime import datetime, timedelta +from datetime import datetime, timedelta, date from beembase import transactions, operations from beemgraphenebase.account import PrivateKey, PublicKey import pytz @@ -43,6 +44,9 @@ class Witness(BlockchainObject): ): self.full = full self.lazy = lazy + self.steem = steem_instance or shared_steem_instance() + if isinstance(owner, dict): + owner = self._parse_json_data(owner) super(Witness, self).__init__( owner, lazy=lazy, @@ -65,8 +69,43 @@ class Witness(BlockchainObject): witness = self.steem.rpc.get_witness_by_account(self.identifier) if not witness: raise WitnessDoesNotExistsException(self.identifier) + witness = self._parse_json_data(witness) super(Witness, self).__init__(witness, id_item="owner", lazy=self.lazy, full=self.full, steem_instance=self.steem) - self.identifier = self["owner"] + + def _parse_json_data(self, witness): + parse_times = [ + "created", "last_sbd_exchange_update", "hardfork_time_vote", + ] + for p in parse_times: + if p in witness and isinstance(witness.get(p), string_types): + witness[p] = formatTimeString(witness.get(p, "1970-01-01T00:00:00")) + parse_int = [ + "votes", "virtual_last_update", "virtual_position", "virtual_scheduled_time", + ] + for p in parse_int: + if p in witness and isinstance(witness.get(p), string_types): + witness[p] = int(witness.get(p, "0")) + return witness + + def json(self): + output = self.copy() + parse_times = [ + "created", "last_sbd_exchange_update", "hardfork_time_vote", + ] + for p in parse_times: + if p in output: + p_date = output.get(p, datetime(1970, 1, 1, 0, 0)) + if isinstance(p_date, (datetime, date)): + output[p] = formatTimeString(p_date) + else: + output[p] = p_date + parse_int = [ + "votes", "virtual_last_update", "virtual_position", "virtual_scheduled_time", + ] + for p in parse_int: + if p in output and isinstance(output[p], int): + output[p] = str(output[p]) + return json.loads(str(json.dumps(output))) @property def account(self): @@ -156,7 +195,7 @@ class WitnessesObject(list): elif sort_key == 'quote': sortedList = sorted(self, key=lambda self: self['sbd_exchange_rate']['quote'], reverse=reverse) elif sort_key == 'last_sbd_exchange_update': - sortedList = sorted(self, key=lambda self: (utc.localize(datetime.utcnow()) - formatTimeString(self['last_sbd_exchange_update'])).total_seconds(), reverse=reverse) + sortedList = sorted(self, key=lambda self: (utc.localize(datetime.utcnow()) - self['last_sbd_exchange_update']).total_seconds(), reverse=reverse) elif sort_key == 'account_creation_fee': sortedList = sorted(self, key=lambda self: self['props']['account_creation_fee'], reverse=reverse) elif sort_key == 'sbd_interest_rate': @@ -168,7 +207,7 @@ class WitnessesObject(list): else: sortedList = sorted(self, key=lambda self: self[sort_key], reverse=reverse) for witness in sortedList: - td = utc.localize(datetime.utcnow()) - formatTimeString(witness['last_sbd_exchange_update']) + td = utc.localize(datetime.utcnow()) - witness['last_sbd_exchange_update'] disabled = "" if not witness.is_active: disabled = "yes" @@ -219,8 +258,15 @@ class Witnesses(WitnessesObject): :param steem steem_instance: Steem() instance to use when accesing a RPC + + .. code-block:: python + + >>> from beem.witness import Witnesses + >>> Witnesses() + <Witnesses > + """ - def __init__(self, lazy=False, full=False, steem_instance=None): + def __init__(self, lazy=False, full=True, steem_instance=None): self.steem = steem_instance or shared_steem_instance() self.steem.rpc.set_next_node_on_empty_reply(False) if self.steem.rpc.get_use_appbase(): @@ -245,8 +291,15 @@ class WitnessesVotedByAccount(WitnessesObject): :param str account: Account name :param steem steem_instance: Steem() instance to use when accesing a RPC + + .. code-block:: python + + >>> from beem.witness import WitnessesVotedByAccount + >>> WitnessesVotedByAccount("gtg") + <WitnessesVotedByAccount gtg> + """ - def __init__(self, account, lazy=False, full=False, steem_instance=None): + def __init__(self, account, lazy=False, full=True, steem_instance=None): self.steem = steem_instance or shared_steem_instance() self.account = Account(account, full=True, steem_instance=self.steem) account_name = self.account["name"] @@ -276,9 +329,17 @@ class WitnessesVotedByAccount(WitnessesObject): class WitnessesRankedByVote(WitnessesObject): """ Obtain a list of witnesses ranked by Vote - :param str from_account: Witness name + :param str from_account: Witness name from which the lists starts (default = "") + :param int limit: Limits the number of shown witnesses (default = 100) :param steem steem_instance: Steem() instance to use when accesing a RPC + + .. code-block:: python + + >>> from beem.witness import WitnessesRankedByVote + >>> WitnessesRankedByVote(limit=100) + <WitnessesRankedByVote > + """ def __init__(self, from_account="", limit=100, lazy=False, full=False, steem_instance=None): self.steem = steem_instance or shared_steem_instance() @@ -321,13 +382,21 @@ class WitnessesRankedByVote(WitnessesObject): class ListWitnesses(WitnessesObject): - """ Obtain a list of witnesses which have been voted by an account + """ List witnesses ranked by name - :param str from_account: Account name + :param str from_account: Witness name from which the lists starts (default = "") + :param int limit: Limits the number of shown witnesses (default = 100) :param steem steem_instance: Steem() instance to use when accesing a RPC + + .. code-block:: python + + >>> from beem.witness import ListWitnesses + >>> ListWitnesses(from_account="gtg", limit=100) + <ListWitnesses gtg> + """ - def __init__(self, from_account, limit, lazy=False, full=False, steem_instance=None): + def __init__(self, from_account="", limit=100, lazy=False, full=False, steem_instance=None): self.steem = steem_instance or shared_steem_instance() self.identifier = from_account self.steem.rpc.set_next_node_on_empty_reply(False) diff --git a/beemgraphenebase/types.py b/beemgraphenebase/types.py index b95af895..8fc72b75 100644 --- a/beemgraphenebase/types.py +++ b/beemgraphenebase/types.py @@ -264,7 +264,9 @@ class PointInTime(object): def __bytes__(self): """Returns bytes representation.""" - if sys.version > '3': + if isinstance(self.data, datetime): + unixtime = timegm(self.data.timetuple()) + elif sys.version > '3': unixtime = timegm(time.strptime((self.data + "UTC"), timeformat)) else: unixtime = timegm(time.strptime((self.data + "UTC"), timeformat.encode("utf-8"))) diff --git a/tests/beem/test_account.py b/tests/beem/test_account.py index 7ffc289b..d8f58f0e 100644 --- a/tests/beem/test_account.py +++ b/tests/beem/test_account.py @@ -476,7 +476,7 @@ class Testcases(unittest.TestCase): json_content = account.json() for k in keys: - if k not in "json_metadata" and k != 'reputation' and k != 'active_votes': + if k not in "json_metadata" and k != 'reputation' and k != 'active_votes' and k != 'savings_sbd_seconds': if isinstance(content[k], dict) and isinstance(json_content[k], list): self.assertEqual(list(content[k].values()), json_content[k]) else: diff --git a/tests/beem/test_block.py b/tests/beem/test_block.py index 6b354c29..4a2c8c1d 100644 --- a/tests/beem/test_block.py +++ b/tests/beem/test_block.py @@ -107,3 +107,49 @@ class Testcases(unittest.TestCase): exceptions.BlockDoesNotExistsException ): BlockHeader(0, steem_instance=bts) + + @parameterized.expand([ + ("non_appbase"), + ("appbase"), + ]) + def test_export(self, node_param): + if node_param == "non_appbase": + bts = self.bts + else: + bts = self.appbase + block_num = 2000000 + if bts.rpc.get_use_appbase(): + block = bts.rpc.get_block({"block_num": block_num}, api="block") + if block and "block" in block: + block = block["block"] + else: + block = bts.rpc.get_block(block_num) + + b = Block(block_num, steem_instance=bts) + keys = list(block.keys()) + json_content = b.json() + + for k in keys: + if k not in "json_metadata": + if isinstance(block[k], dict) and isinstance(json_content[k], list): + self.assertEqual(list(block[k].values()), json_content[k]) + else: + self.assertEqual(block[k], json_content[k]) + + if bts.rpc.get_use_appbase(): + block = bts.rpc.get_block_header({"block_num": block_num}, api="block") + if "header" in block: + block = block["header"] + else: + block = bts.rpc.get_block_header(block_num) + + b = BlockHeader(block_num, steem_instance=bts) + keys = list(block.keys()) + json_content = b.json() + + for k in keys: + if k not in "json_metadata": + if isinstance(block[k], dict) and isinstance(json_content[k], list): + self.assertEqual(list(block[k].values()), json_content[k]) + else: + self.assertEqual(block[k], json_content[k]) diff --git a/tests/beem/test_blockchain_batch.py b/tests/beem/test_blockchain_batch.py index 579afdd7..2171e29a 100644 --- a/tests/beem/test_blockchain_batch.py +++ b/tests/beem/test_blockchain_batch.py @@ -28,6 +28,7 @@ class Testcases(unittest.TestCase): node=nodelist.get_nodes(normal=False, appbase=True, dev=True), nobroadcast=True, num_retries=10, + timeout=30, use_condenser=False, keys={"active": wif}, ) diff --git a/tests/beem/test_comment.py b/tests/beem/test_comment.py index ebb94dff..c9236204 100644 --- a/tests/beem/test_comment.py +++ b/tests/beem/test_comment.py @@ -165,7 +165,10 @@ class Testcases(unittest.TestCase): for k in keys: if k not in "json_metadata" and k != 'reputation' and k != 'active_votes': - self.assertEqual(content[k], json_content[k]) + if isinstance(content[k], dict) and isinstance(json_content[k], list): + self.assertEqual(list(content[k].values()), json_content[k]) + else: + self.assertEqual(content[k], json_content[k]) def test_resteem(self): bts = self.bts diff --git a/tests/beem/test_witness.py b/tests/beem/test_witness.py index 5eb482a7..e4ffd1d2 100644 --- a/tests/beem/test_witness.py +++ b/tests/beem/test_witness.py @@ -121,3 +121,30 @@ class Testcases(unittest.TestCase): w.printAsTable() self.assertTrue(len(w) > 0) self.assertTrue(isinstance(w[0], Witness)) + + @parameterized.expand([ + ("non_appbase"), + ("appbase"), + ]) + def test_export(self, node_param): + if node_param == "non_appbase": + bts = self.bts + else: + bts = self.appbase + owner = "gtg" + if bts.rpc.get_use_appbase(): + witness = bts.rpc.find_witnesses({'owners': [owner]}, api="database")['witnesses'] + if len(witness) > 0: + witness = witness[0] + else: + witness = bts.rpc.get_witness_by_account(owner) + + w = Witness(owner, steem_instance=bts) + keys = list(witness.keys()) + json_witness = w.json() + + for k in keys: + if isinstance(witness[k], dict) and isinstance(json_witness[k], list): + self.assertEqual(list(witness[k].values()), json_witness[k]) + else: + self.assertEqual(witness[k], json_witness[k]) -- GitLab