from __future__ import annotations
import copy
from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Union
from terra_proto.cosmos.base.v1beta1 import Coin as Coin_pb
from terra_sdk.util.json import JSONSerializable
from .coin import Coin
from .numeric import Numeric
[docs]class Coins(JSONSerializable, List[Coin_pb]):
"""Represents an unordered collection of :class:`Coin` objects
-- analagous to ``sdk.Coins`` and ``sdk.DecCoins`` in Cosmos SDK. If one of the
input coins would be ``Dec``-amount type coin, the resultant Coins is converted to
``Dec``-amount coins.
Args:
arg (Optional[Coins.Input], optional): argument to convert. Defaults to ``{}``.
Raises:
TypeError: if ``arg`` is not an Iterable
"""
Input = Union[Iterable[Coin], str, Dict[str, Numeric.Input], Dict[str, Coin]]
"""Types which can be converted into a :class:`Coins` object."""
_coins: Dict[str, Coin]
def __repr__(self) -> str:
if len(self) == 0:
return "Coins()"
else:
return f"Coins('{self!s}')"
def __str__(self) -> str:
return ",".join(str(coin) for coin in self)
[docs] @classmethod
def from_str(cls, s: str) -> Coins:
"""Converts a comma-separated list of Coin-format strings to :class:`Coins`.
>>> Coins.from_str('1000uluna,1234ukrw')
Coins("1000uluna,1234ukrw")
Args:
s (str): string to convert
"""
coin_strings = s.split(r",")
return Coins(Coin.from_str(cs) for cs in coin_strings)
def __init__(self, arg: Optional[Coins.Input] = {}, **denoms):
"""Converts the argument into a :class:`Coins` object."""
if arg is None:
self._coins = {}
return
# arg should be an iterable
try:
iter(arg)
except TypeError:
raise TypeError(f"could not create Coins object with argument: {arg!s}")
if isinstance(arg, Coins):
self._coins = copy.deepcopy(arg._coins)
return
if isinstance(arg, str):
self._coins = Coins.from_str(arg)._coins
return
self._coins = Coins(denoms)._coins if denoms else {}
coins: Iterable[Coin]
if isinstance(arg, dict):
coins = [Coin(denom, arg[denom]) for denom in arg]
else:
coins = arg
for coin in coins:
x = self._coins.get(coin.denom)
if x is not None:
self._coins[coin.denom] = x + coin
else:
self._coins[coin.denom] = coin
# make all coins DecCoin if one is DecCoin
if not all([c.is_int_coin() for c in self]):
for denom in self._coins:
self._coins[denom] = self._coins[denom].to_dec_coin()
def __getitem__(self, denom: str) -> Coin:
return self._coins[denom]
[docs] def get(self, denom: str) -> Optional[Coin]:
"""Get the Coin with the denom contained in the Coins set.
Args:
denom (str): denom
Returns:
Optional[Coin]: result (can be ``None``)
"""
return self._coins.get(denom)
[docs] @classmethod
def from_data(cls, data: list) -> Coins:
"""Converts list of Coin-data objects to :class:`Coins`.
Args:
data (list): list of Coin-data objects
"""
coins = map(Coin.from_data, data)
return cls(coins)
[docs] @classmethod
def from_amino(cls, amino: list) -> Coins:
"""Converts list of Coin-amino objects to :class:`Coins`.
Args:
amino (list): list of Coin-data objects
"""
coins = map(Coin.from_amino, amino)
return cls(coins)
def to_amino(self) -> List[dict]:
return [coin.to_amino() for coin in self]
[docs] def to_data(self) -> List[dict]:
return [coin.to_data() for coin in self]
[docs] @classmethod
def from_proto(cls, proto: List[Coin_pb]) -> Coins:
"""Converts list of Coin-data objects to :class:`Coins`.
Args:
data (list): list of Coin-data objects
"""
coins = map(Coin.from_proto, proto)
return cls(coins)
def to_proto(self) -> List[Coin_pb]:
return [coin.to_proto() for coin in self]
def to_dict(self) -> List[dict]:
return [coin.to_dict for coin in self]
[docs] def denoms(self) -> List[str]:
"""Get the list of denoms for all Coin objects contained."""
return [c.denom for c in self]
[docs] def to_dec_coins(self) -> Coins:
"""Creates new set of :class:`Coins` that have :class`Dec` amounts."""
return Coins(c.to_dec_coin() for c in self)
[docs] def to_int_coins(self) -> Coins:
"""Creates new set of :class:`Coins` that have ``int`` amounts."""
return Coins(c.to_int_coin() for c in self)
[docs] def to_int_ceil_coins(self) -> Coins:
"""Creates a new :class:`Coins` object with all ``int`` coins with ceiling the amount"""
return Coins(c.to_int_ceil_coin() for c in self)
[docs] def add(self, addend: Union[Coin, Coins]) -> Coins:
"""Performs addition, which combines the sets of Coin objects. Coins of similar denoms
will be merged into one Coin representing the denom.
Args:
addend (Union[Coin, Coins]): addend
"""
if isinstance(addend, Coin):
return Coins([addend, *self.to_list()])
else:
return Coins([*addend.to_list(), *self.to_list()])
def __add__(self, addend: Union[Coin, Coins]) -> Coins:
return self.add(addend)
[docs] def sub(self, subtrahend: Union[Coin, Coins]) -> Coins:
"""Performs subtraction, which combines the sets of Coin objects. Coins of similar denoms
will be merged into one Coin representing the denom.
Args:
subtrahend (Union[Coin, Coins]): subtrahend
"""
return self.add(subtrahend.mul(-1))
def __sub__(self, subtrahend: Union[Coin, Coins]) -> Coins:
return self.sub(subtrahend)
[docs] def mul(self, multiplier: Numeric.Input) -> Coins:
"""Performs multiplicaiton, which multiplies all the Coin objects in the set by a
multiplier.
Args:
multiplier (Numeric.Input): multiplier
"""
return Coins(coin.mul(multiplier) for coin in self)
def __mul__(self, multiplier: Numeric.Input) -> Coins:
return self.mul(multiplier)
[docs] def div(self, divisor: Numeric.Input) -> Coins:
"""Performs division, which divides all the Coin objects in the set by a divisor.
Args:
divisor (Numeric.Input): divisor
"""
return Coins(coin.div(divisor) for coin in self)
def __truediv__(self, divisor: Numeric.Input) -> Coins:
return Coins(coin / divisor for coin in self)
def __floordiv__(self, divisor: Numeric.Input) -> Coins:
return Coins(coin // divisor for coin in self)
[docs] def to_list(self) -> List[Coin]:
"""Converts the set of :class:`Coin` objects contained into a sorted list by denom.
Returns:
List[Coin]: list, sorted by denom
"""
return sorted(self._coins.values(), key=lambda c: c.denom)
[docs] def filter(self, predicate: Callable[[Coin], bool]) -> Coins:
"""Creates a new :class:`Coins` collection which filters out all Coin objects that
do not meet the predicate.
Args:
predicate (Callable[[Coin], bool]): predicate for filtering
"""
return Coins(c for c in self if predicate(c))
[docs] def map(self, fn: Callable[[Coin], Any]) -> Iterator[Any]:
"""Creates an iterable which applies the function to all coins in the set,
ordered by denomination.
Args:
fn (Callable[[Coin], Any]): function to apply
Returns:
Iterator[Any]: coin map
Yields:
Iterator[Any]: coin map
"""
return map(fn, self)
def __eq__(self, other) -> bool:
try:
return self.to_list() == other.to_list()
except AttributeError:
return False
def __iter__(self):
return iter(self.to_list())
def __len__(self):
return len(self.to_list())
def __contains__(self, denom: str) -> bool:
return denom in self._coins