Skip to content

Commit

Permalink
Implement async Asset
Browse files Browse the repository at this point in the history
  • Loading branch information
bitphage committed May 22, 2019
1 parent caaa814 commit 8e97399
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 2 deletions.
Empty file added graphenecommon/aio/__init__.py
Empty file.
37 changes: 37 additions & 0 deletions graphenecommon/aio/asset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from ..exceptions import AssetDoesNotExistsException
from ..asset import Asset as SyncAsset
from .blockchainobject import BlockchainObject


class Asset(BlockchainObject, SyncAsset):
async def __init__(self, *args, **kwargs):
self.define_classes()
assert self.type_id

self.full = kwargs.pop("full", False)
await BlockchainObject.__init__(self, *args, **kwargs)

async def refresh(self):
""" Refresh the data from the API server
"""
asset = await self.blockchain.rpc.get_asset(self.identifier)
if not asset:
raise AssetDoesNotExistsException(self.identifier)
await super(Asset, self).__init__(asset, blockchain_instance=self.blockchain)
if self.full:
if "bitasset_data_id" in asset:
self["bitasset_data"] = await self.blockchain.rpc.get_object(
asset["bitasset_data_id"]
)
self["dynamic_asset_data"] = await self.blockchain.rpc.get_object(
asset["dynamic_asset_data_id"]
)

async def update_cer(self, cer, account=None, **kwargs):
""" Update the Core Exchange Rate (CER) of an asset
"""
assert callable(self.blockchain.update_cer)
return await self.blockchain.update_cer(
self["symbol"], cer, account=account, **kwargs
)
96 changes: 96 additions & 0 deletions graphenecommon/aio/blockchainobject.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
from asyncinit import asyncinit

from ..blockchainobject import (
Caching as SyncCaching,
BlockchainObjects as SyncBlockchainObjects,
BlockchainObject as SyncBlockchainObject,
)


class Caching(SyncCaching):
def __getitem__(self, key):
return dict.__getitem__(self, key)

async def items(self):
""" This overwrites items() so that refresh() is called it the
object is not already fetched
"""
if not self._fetched:
await self.refresh()
return dict.items(self)

def __contains__(self, key):
if not self._fetched:
self.refresh()
return dict.__contains__(self, key)


class BlockchainObjects(Caching, list):
def __init__(self, *args, **kwargs):
Caching.__init__(self, *args, **kwargs)
# Some lists are specific to some key value that is then provided as
# first argument
if len(args) > 0 and isinstance(args[0], str):
key = self._cache_key(args[0])
else:
key = self._cache_key()
if self.incached(key):
list.__init__(self, self.getfromcache(key))
else:
if kwargs.get("refresh", True):
self.refresh(*args, **kwargs)


@asyncinit
class BlockchainObject(Caching, SyncBlockchainObject):
async def __init__(
self, data, klass=None, lazy=False, use_cache=True, *args, **kwargs
):
Caching.__init__(self, *args, **kwargs)
self._use_cache = use_cache
if self.perform_id_tests:
assert self.type_id or self.type_ids, "Need type_id or type_ids"
self._fetched = False
self._lazy = lazy

if "_cache_expiration" in kwargs:
self.set_expiration(kwargs["_cache_expiration"])

# We don't read lists, sets, or tuples
if isinstance(data, (list, set, tuple)):
raise ValueError(
"Cannot interpret lists! Please load elements individually!"
)

if klass and isinstance(data, klass):
self.identifier = data.get("id")
dict.__init__(self, data)
elif isinstance(data, dict):
self.identifier = data.get("id")
dict.__init__(self, data)
elif isinstance(data, int):
# This is only for block number bascially
self.identifier = data
if self.incached(str(data)):
dict.__init__(self, self.getfromcache(str(data)))
self._fetched = True
if not self._lazy and not self._fetched:
await self.refresh()
# make sure to store the blocknumber for caching
self["id"] = str(data)
# Set identifier again as it is overwritten in super() in refresh()
self.identifier = data
else:
self.identifier = data
if self.perform_id_tests and self.test_valid_objectid(self.identifier):
# Here we assume we deal with an id
self.testid(self.identifier)

if self.incached(data):
dict.__init__(self, dict(self.getfromcache(data)))
elif not self._lazy and not self._fetched:
await self.refresh()

if self._use_cache and not self._lazy:
self._store_item()
111 changes: 111 additions & 0 deletions graphenecommon/aio/chain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
import logging

from ..chain import AbstractGrapheneChain

log = logging.getLogger(__name__)


class AbstractGrapheneChain(AbstractGrapheneChain):
async def info(self):
""" Returns the global properties
"""
return await self.rpc.get_dynamic_global_properties()

async def finalizeOp(self, ops, account, permission, **kwargs):
""" This method obtains the required private keys if present in
the wallet, finalizes the transaction, signs it and
broadacasts it
: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
signing (active, owner, posting)
:param object append_to: This allows to provide an instance of
ProposalsBuilder (see :func:`new_proposal`) or
TransactionBuilder (see :func:`new_tx()`) to specify
where to put a specific operation.
... note:: ``append_to`` is exposed to every method used in the
this class
... note::
If ``ops`` is a list of operation, they all need to be
signable by the same key! Thus, you cannot combine ops
that require active permission with ops that require
posting permission. Neither can you use different
accounts for different operations!
... note:: This uses ``txbuffer`` as instance of
:class:`transactionbuilder.TransactionBuilder`.
You may want to use your own txbuffer
"""
if "append_to" in kwargs and kwargs["append_to"]:
if self.proposer:
log.warning(
"You may not use append_to and self.proposer at "
"the same time. Append new_proposal(..) instead"
)
# Append to the append_to and return
append_to = kwargs["append_to"]
parent = append_to.get_parent()
assert isinstance(
append_to, (self.transactionbuilder_class, self.proposalbuilder_class)
)
append_to.appendOps(ops)
# Add the signer to the buffer so we sign the tx properly
if isinstance(append_to, self.proposalbuilder_class):
parent.appendSigner(append_to.proposer, permission)
else:
parent.appendSigner(account, permission)
# This returns as we used append_to, it does NOT broadcast, or sign
return append_to.get_parent()
elif self.proposer:
# Legacy proposer mode!
proposal = self.proposal()
proposal.set_proposer(self.proposer)
proposal.set_expiration(self.proposal_expiration)
proposal.set_review(self.proposal_review)
proposal.appendOps(ops)
# Go forward to see what the other options do ...
else:
# Append tot he default buffer
self.txbuffer.appendOps(ops)

# The API that obtains the fee only allows to specify one particular
# fee asset for all operations in that transaction even though the
# blockchain itself could allow to pay multiple operations with
# different fee assets.
if "fee_asset" in kwargs and kwargs["fee_asset"]:
self.txbuffer.set_fee_asset(kwargs["fee_asset"])

# Add signing information, signer, sign and optionally broadcast
if self.unsigned:
# In case we don't want to sign anything
self.txbuffer.addSigningInformation(account, permission)
return self.txbuffer
elif self.bundle:
# In case we want to add more ops to the tx (bundle)
self.txbuffer.appendSigner(account, permission)
return self.txbuffer.json()
else:
# default behavior: sign + broadcast
self.txbuffer.appendSigner(account, permission)
self.txbuffer.sign()
return await self.txbuffer.broadcast()

async def broadcast(self, tx=None):
""" Broadcast a transaction to the Blockchain
:param tx tx: Signed transaction to broadcast
"""
if tx:
# If tx is provided, we broadcast the tx
return await self.transactionbuilder_class(
tx, blockchain_instance=self
).broadcast()
else:
return await self.txbuffer.broadcast()
21 changes: 21 additions & 0 deletions graphenecommon/aio/instance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from ..instance import (
AbstractBlockchainInstanceProvider as SyncAbstractBlockchainInstanceProvider,
)


class AbstractBlockchainInstanceProvider(SyncAbstractBlockchainInstanceProvider):
@classmethod
def inject(slf, cls):
class NewClass(slf, cls):
blockchain_instance_class = slf

async def __init__(self, *args, **kwargs):
slf.__init__(self, *args, **kwargs)
await cls.__init__(self, *args, **kwargs)

NewClass.__name__ = cls.__name__
NewClass.__qualname__ = cls.__qualname__
NewClass.__doc__ = cls.__doc__
NewClass.__module__ = cls.__module__
return NewClass
5 changes: 3 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ appdirs==1.4.3
scrypt==0.8.13
secp256k1==0.13.2
websockets==7.0
jsonrpcclient[websockets]==3.3.1
jsonrpcclient[aiohttp]==3.3.1
aiohttp==3.5.4
jsonrpcclient==3.3.1
asyncinit==0.2.4

0 comments on commit 8e97399

Please sign in to comment.