Add experimental run interface

This commit is contained in:
vrde 2017-01-25 19:05:48 +01:00
parent cd7d65b63e
commit baeae19951
No known key found for this signature in database
GPG Key ID: 6581C7C39B3D397D
4 changed files with 180 additions and 99 deletions

View File

@ -5,6 +5,7 @@ from pymongo import MongoClient
from pymongo import errors from pymongo import errors
import bigchaindb import bigchaindb
from bigchaindb.utils import Lazy
from bigchaindb.common import exceptions from bigchaindb.common import exceptions
from bigchaindb.backend.connection import Connection from bigchaindb.backend.connection import Connection
@ -43,6 +44,9 @@ class MongoDBConnection(Connection):
def db(self): def db(self):
return self.conn[self.dbname] return self.conn[self.dbname]
def run(self, query):
return query.run(self.db)
def _connect(self): def _connect(self):
# we should only return a connection if the replica set is # we should only return a connection if the replica set is
# initialized. initialize_replica_set will check if the # initialized. initialize_replica_set will check if the
@ -60,6 +64,10 @@ class MongoDBConnection(Connection):
time.sleep(2**i) time.sleep(2**i)
def table(name):
return Lazy()[name]
def initialize_replica_set(): def initialize_replica_set():
"""Initialize a replica set. If already initialized skip.""" """Initialize a replica set. If already initialized skip."""

View File

@ -5,10 +5,11 @@ from time import time
from pymongo import ReturnDocument from pymongo import ReturnDocument
from pymongo import errors from pymongo import errors
from bigchaindb import backend from bigchaindb import backend
from bigchaindb.common.exceptions import CyclicBlockchainError from bigchaindb.common.exceptions import CyclicBlockchainError
from bigchaindb.backend.utils import module_dispatch_registrar from bigchaindb.backend.utils import module_dispatch_registrar
from bigchaindb.backend.mongodb.connection import MongoDBConnection from bigchaindb.backend.mongodb.connection import MongoDBConnection, table
register_query = module_dispatch_registrar(backend.query) register_query = module_dispatch_registrar(backend.query)
@ -17,7 +18,9 @@ register_query = module_dispatch_registrar(backend.query)
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def write_transaction(conn, signed_transaction): def write_transaction(conn, signed_transaction):
try: try:
return conn.db['backlog'].insert_one(signed_transaction) return conn.run(
table('backlog')
.insert_one(signed_transaction))
except errors.DuplicateKeyError: except errors.DuplicateKeyError:
return return
@ -26,40 +29,49 @@ def write_transaction(conn, signed_transaction):
def update_transaction(conn, transaction_id, doc): def update_transaction(conn, transaction_id, doc):
# with mongodb we need to add update operators to the doc # with mongodb we need to add update operators to the doc
doc = {'$set': doc} doc = {'$set': doc}
return conn.db['backlog']\ return conn.run(
.find_one_and_update({'id': transaction_id}, table('backlog')
doc, .find_one_and_update(
return_document=ReturnDocument.AFTER) {'id': transaction_id},
doc,
return_document=ReturnDocument.AFTER))
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def delete_transaction(conn, *transaction_id): def delete_transaction(conn, *transaction_id):
return conn.db['backlog'].delete_many({'id': {'$in': transaction_id}}) return conn.run(
table('backlog')
.delete_many({'id': {'$in': transaction_id}}))
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def get_stale_transactions(conn, reassign_delay): def get_stale_transactions(conn, reassign_delay):
return conn.db['backlog']\ return conn.run(
.find({'assignment_timestamp': {'$lt': time() - reassign_delay}}, table('backlog')
projection={'_id': False}) .find({'assignment_timestamp': {'$lt': time() - reassign_delay}},
projection={'_id': False}))
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def get_transaction_from_block(conn, transaction_id, block_id): def get_transaction_from_block(conn, transaction_id, block_id):
try: try:
return conn.db['bigchain'].aggregate([ return conn.run(
{'$match': {'id': block_id}}, table('bigchain')
{'$project': { .aggregate([
'block.transactions': { {'$match': {'id': block_id}},
'$filter': { {'$project': {
'input': '$block.transactions', 'block.transactions': {
'as': 'transaction', '$filter': {
'cond': { 'input': '$block.transactions',
'$eq': ['$$transaction.id', transaction_id] 'as': 'transaction',
'cond': {
'$eq': ['$$transaction.id', transaction_id]
}
}
} }
} }}])
} .next()['block']['transactions']
}}]).next()['block']['transactions'].pop() .pop())
except (StopIteration, IndexError): except (StopIteration, IndexError):
# StopIteration is raised if the block was not found # StopIteration is raised if the block was not found
# IndexError is returned if the block is found but no transactions # IndexError is returned if the block is found but no transactions
@ -69,33 +81,38 @@ def get_transaction_from_block(conn, transaction_id, block_id):
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def get_transaction_from_backlog(conn, transaction_id): def get_transaction_from_backlog(conn, transaction_id):
return conn.db['backlog']\ return conn.run(
.find_one({'id': transaction_id}, table('backlog')
projection={'_id': False, 'assignee': False, .find_one({'id': transaction_id},
'assignment_timestamp': False}) projection={'_id': False,
'assignee': False,
'assignment_timestamp': False}))
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def get_blocks_status_from_transaction(conn, transaction_id): def get_blocks_status_from_transaction(conn, transaction_id):
return conn.db['bigchain']\ return conn.run(
.find({'block.transactions.id': transaction_id}, table('bigchain')
projection=['id', 'block.voters']) .find({'block.transactions.id': transaction_id},
projection=['id', 'block.voters']))
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def get_asset_by_id(conn, asset_id): def get_asset_by_id(conn, asset_id):
cursor = conn.db['bigchain'].aggregate([ cursor = conn.run(
{'$match': { table('bigchain')
'block.transactions.id': asset_id, .aggregate([
'block.transactions.operation': 'CREATE' {'$match': {
}}, 'block.transactions.id': asset_id,
{'$unwind': '$block.transactions'}, 'block.transactions.operation': 'CREATE'
{'$match': { }},
'block.transactions.id': asset_id, {'$unwind': '$block.transactions'},
'block.transactions.operation': 'CREATE' {'$match': {
}}, 'block.transactions.id': asset_id,
{'$project': {'block.transactions.asset': True}} 'block.transactions.operation': 'CREATE'
]) }},
{'$project': {'block.transactions.asset': True}}
]))
# we need to access some nested fields before returning so lets use a # we need to access some nested fields before returning so lets use a
# generator to avoid having to read all records on the cursor at this point # generator to avoid having to read all records on the cursor at this point
return (elem['block']['transactions'] for elem in cursor) return (elem['block']['transactions'] for elem in cursor)
@ -103,13 +120,14 @@ def get_asset_by_id(conn, asset_id):
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def get_spent(conn, transaction_id, output): def get_spent(conn, transaction_id, output):
cursor = conn.db['bigchain'].aggregate([ cursor = conn.run(
{'$unwind': '$block.transactions'}, table('bigchain').aggregate([
{'$match': { {'$unwind': '$block.transactions'},
'block.transactions.inputs.fulfills.txid': transaction_id, {'$match': {
'block.transactions.inputs.fulfills.output': output 'block.transactions.inputs.fulfills.txid': transaction_id,
}} 'block.transactions.inputs.fulfills.output': output
]) }}
]))
# we need to access some nested fields before returning so lets use a # we need to access some nested fields before returning so lets use a
# generator to avoid having to read all records on the cursor at this point # generator to avoid having to read all records on the cursor at this point
return (elem['block']['transactions'] for elem in cursor) return (elem['block']['transactions'] for elem in cursor)
@ -117,14 +135,16 @@ def get_spent(conn, transaction_id, output):
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def get_owned_ids(conn, owner): def get_owned_ids(conn, owner):
cursor = conn.db['bigchain'].aggregate([ cursor = conn.run(
{'$unwind': '$block.transactions'}, table('bigchain')
{'$match': { .aggregate([
'block.transactions.outputs.public_keys': { {'$unwind': '$block.transactions'},
'$elemMatch': {'$eq': owner} {'$match': {
} 'block.transactions.outputs.public_keys': {
}} '$elemMatch': {'$eq': owner}
]) }
}}
]))
# we need to access some nested fields before returning so lets use a # we need to access some nested fields before returning so lets use a
# generator to avoid having to read all records on the cursor at this point # generator to avoid having to read all records on the cursor at this point
return (elem['block']['transactions'] for elem in cursor) return (elem['block']['transactions'] for elem in cursor)
@ -132,66 +152,80 @@ def get_owned_ids(conn, owner):
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def get_votes_by_block_id(conn, block_id): def get_votes_by_block_id(conn, block_id):
return conn.db['votes']\ return conn.run(
.find({'vote.voting_for_block': block_id}, table('votes')
projection={'_id': False}) .find({'vote.voting_for_block': block_id},
projection={'_id': False}))
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def get_votes_by_block_id_and_voter(conn, block_id, node_pubkey): def get_votes_by_block_id_and_voter(conn, block_id, node_pubkey):
return conn.db['votes']\ return conn.run(
.find({'vote.voting_for_block': block_id, table('votes')
'node_pubkey': node_pubkey}, .find({'vote.voting_for_block': block_id,
projection={'_id': False}) 'node_pubkey': node_pubkey},
projection={'_id': False}))
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def write_block(conn, block): def write_block(conn, block):
return conn.db['bigchain'].insert_one(block.to_dict()) return conn.run(
table('bigchain')
.insert_one(block.to_dict()))
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def get_block(conn, block_id): def get_block(conn, block_id):
return conn.db['bigchain'].find_one({'id': block_id}, return conn.run(
projection={'_id': False}) table('bigchain')
.find_one({'id': block_id},
projection={'_id': False}))
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def has_transaction(conn, transaction_id): def has_transaction(conn, transaction_id):
return bool(conn.db['bigchain'] return bool(conn.run(
.find_one({'block.transactions.id': transaction_id})) table('bigchain')
.find_one({'block.transactions.id': transaction_id})))
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def count_blocks(conn): def count_blocks(conn):
return conn.db['bigchain'].count() return conn.run(
table('bigchain')
.count())
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def count_backlog(conn): def count_backlog(conn):
return conn.db['backlog'].count() return conn.run(
table('backlog')
.count())
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def write_vote(conn, vote): def write_vote(conn, vote):
conn.db['votes'].insert_one(vote) conn.run(table('votes').insert_one(vote))
vote.pop('_id') vote.pop('_id')
return vote return vote
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def get_genesis_block(conn): def get_genesis_block(conn):
return conn.db['bigchain'].find_one( return conn.run(
{'block.transactions.0.operation': 'GENESIS'}, table('bigchain')
{'_id': False} .find_one(
) {'block.transactions.0.operation': 'GENESIS'},
{'_id': False}
))
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def get_last_voted_block(conn, node_pubkey): def get_last_voted_block(conn, node_pubkey):
last_voted = conn.db['votes']\ last_voted = conn.run(
.find({'node_pubkey': node_pubkey}, table('votes')
sort=[('vote.timestamp', -1)]) .find({'node_pubkey': node_pubkey},
sort=[('vote.timestamp', -1)]))
# pymongo seems to return a cursor even if there are no results # pymongo seems to return a cursor even if there are no results
# so we actually need to check the count # so we actually need to check the count
@ -219,21 +253,22 @@ def get_last_voted_block(conn, node_pubkey):
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
def get_unvoted_blocks(conn, node_pubkey): def get_unvoted_blocks(conn, node_pubkey):
return conn.db['bigchain'].aggregate([ return conn.run(
{'$lookup': { table('bigchain').aggregate([
'from': 'votes', {'$lookup': {
'localField': 'id', 'from': 'votes',
'foreignField': 'vote.voting_for_block', 'localField': 'id',
'as': 'votes' 'foreignField': 'vote.voting_for_block',
}}, 'as': 'votes'
{'$match': { }},
'votes.node_pubkey': {'$ne': node_pubkey}, {'$match': {
'block.transactions.operation': {'$ne': 'GENESIS'} 'votes.node_pubkey': {'$ne': node_pubkey},
}}, 'block.transactions.operation': {'$ne': 'GENESIS'}
{'$project': { }},
'votes': False, '_id': False {'$project': {
}} 'votes': False, '_id': False
]) }}
]))
@register_query(MongoDBConnection) @register_query(MongoDBConnection)
@ -243,10 +278,12 @@ def get_txids_filtered(conn, asset_id, operation=None):
if operation: if operation:
match['block.transactions.operation'] = operation match['block.transactions.operation'] = operation
cursor = conn.db['bigchain'].aggregate([ cursor = conn.run(
{'$match': match}, table('bigchain')
{'$unwind': '$block.transactions'}, .aggregate([
{'$match': match}, {'$match': match},
{'$project': {'block.transactions.id': True}} {'$unwind': '$block.transactions'},
]) {'$match': match},
{'$project': {'block.transactions.id': True}}
]))
return (r['block']['transactions']['id'] for r in cursor) return (r['block']['transactions']['id'] for r in cursor)

View File

@ -157,3 +157,31 @@ def is_genesis_block(block):
return block.transactions[0].operation == 'GENESIS' return block.transactions[0].operation == 'GENESIS'
except AttributeError: except AttributeError:
return block['block']['transactions'][0]['operation'] == 'GENESIS' return block['block']['transactions'][0]['operation'] == 'GENESIS'
class Lazy:
def __init__(self):
self.stack = []
def __getattr__(self, name):
self.stack.append(name)
return self
def __call__(self, *args, **kwargs):
self.stack.append((args, kwargs))
return self
def __getitem__(self, key):
self.stack.append('__getitem__')
self.stack.append(([key], {}))
return self
def run(self, instance):
last = instance
for method, (args, kwargs) in zip(self.stack[::2], self.stack[1::2]):
last = getattr(last, method)(*args, **kwargs)
self.stack = []
return last

View File

@ -137,3 +137,11 @@ def test_is_genesis_block_returns_true_if_genesis(b):
from bigchaindb.utils import is_genesis_block from bigchaindb.utils import is_genesis_block
genesis_block = b.prepare_genesis_block() genesis_block = b.prepare_genesis_block()
assert is_genesis_block(genesis_block) assert is_genesis_block(genesis_block)
def test_lazy_execution():
from bigchaindb.utils import Lazy
l = Lazy()
l.split(',')[1].split(' ').pop(1).strip()
result = l.run('Like humans, cats tend to favor one paw over another')
assert result == 'cats'