Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions rotkehlchen/assets/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@ class AssetWithNameAndType(Asset, abc.ABC):
asset_type: AssetType = field(init=False)
name: str = field(init=False)

def get_asset_data(self) -> dict[str, Any]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. why not make it a TypedDict?
  2. why not move this method the Asset class?

"""Returns asset data as a dictionary"""
raise NotImplementedError('Subclasses must implement get_asset_data')

def to_dict(self) -> dict[str, Any]:
return super().to_dict() | {
'name': self.name,
Expand Down Expand Up @@ -323,6 +327,25 @@ def initialize(
object.__setattr__(asset, 'coingecko', coingecko)
return asset

def get_asset_data(self) -> dict[str, Any]:
"""Returns asset data as a dictionary for this FiatAsset"""
return {
'identifier': self.identifier,
'name': self.name,
'symbol': self.symbol,
'asset_type': self.asset_type,
'started': None,
'forked': None,
'swapped_for': None,
'address': None,
'chain_id': None,
'token_kind': None,
'decimals': None,
'cryptocompare': self.cryptocompare,
'coingecko': self.coingecko,
'protocol': None,
}


@dataclass(init=True, repr=False, eq=False, order=False, unsafe_hash=False, frozen=True)
class CryptoAsset(AssetWithOracles):
Expand Down Expand Up @@ -391,6 +414,25 @@ def to_dict(self) -> dict[str, Any]:
'coingecko': self.coingecko,
}

def get_asset_data(self) -> dict[str, Any]:
"""Returns asset data as a dictionary for this CryptoAsset"""
return {
'identifier': self.identifier,
'name': self.name,
'symbol': self.symbol,
'asset_type': self.asset_type,
'started': self.started,
'forked': self.forked.identifier if self.forked is not None else None,
'swapped_for': self.swapped_for.identifier if self.swapped_for is not None else None,
'address': None,
'chain_id': None,
'token_kind': None,
'decimals': None,
'cryptocompare': self.cryptocompare,
'coingecko': self.coingecko,
'protocol': None,
}


class CustomAsset(AssetWithNameAndType):
notes: str | None = field(init=False)
Expand Down Expand Up @@ -435,6 +477,25 @@ def serialize_for_db(self) -> tuple[str, str, str | None]:
self.notes,
)

def get_asset_data(self) -> dict[str, Any]:
"""Returns asset data as a dictionary for this CustomAsset"""
return {
'identifier': self.identifier,
'name': self.name,
'symbol': '', # Custom assets don't have symbols
'asset_type': self.asset_type,
'started': None,
'forked': None,
'swapped_for': None,
'address': None,
'chain_id': None,
'token_kind': None,
'decimals': None,
'cryptocompare': None,
'coingecko': None,
'protocol': None,
}

def to_dict(self, export_with_type: bool = True) -> dict[str, Any]:
result = super().to_dict() | {
'identifier': self.identifier,
Expand Down Expand Up @@ -576,6 +637,25 @@ def is_liability(self) -> bool:
DEBT_TOKEN_SYMBOL_REGEX.match(self.symbol) is not None
)

def get_asset_data(self) -> dict[str, Any]:
"""Returns asset data as a dictionary for this EvmToken"""
return {
'identifier': self.identifier,
'name': self.name,
'symbol': self.symbol,
'asset_type': self.asset_type,
'started': self.started,
'forked': self.forked.identifier if self.forked is not None else None,
'swapped_for': self.swapped_for.identifier if self.swapped_for is not None else None,
'address': self.evm_address,
'chain_id': self.chain_id,
'token_kind': self.token_kind,
'decimals': self.decimals,
'cryptocompare': self.cryptocompare,
'coingecko': self.coingecko,
'protocol': self.protocol,
}


class Nft(EvmToken):

Expand Down
43 changes: 29 additions & 14 deletions rotkehlchen/globaldb/asset_updates/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

from rotkehlchen.assets.asset import Asset
from rotkehlchen.assets.resolver import AssetResolver
from rotkehlchen.assets.types import AssetData
from rotkehlchen.constants.misc import GLOBALDB_NAME, GLOBALDIR_NAME
from rotkehlchen.db.drivers.gevent import DBCursor
from rotkehlchen.db.settings import CachedSettings
Expand Down Expand Up @@ -176,7 +175,7 @@ def __init__(self, msg_aggregator: 'MessagesAggregator', globaldb: 'GlobalDBHand
self.msg_aggregator = msg_aggregator
self.local_assets_version = globaldb.get_setting_value(ASSETS_VERSION_KEY, 0)
self.last_remote_checked_version = -1 # integer value that represents no update
self.conflicts: dict[str, tuple[AssetData, AssetData]] = {}
self.conflicts: dict[str, tuple[dict[str, Any], dict[str, Any]]] = {}
self.asset_parser = AssetParser()
self.asset_collection_parser = AssetCollectionParser()
self.multiasset_mappings_parser = MultiAssetMappingsParser()
Expand Down Expand Up @@ -290,7 +289,7 @@ def _process_multiasset_mapping(
def _handle_asset_update(
self,
connection: 'DBConnection',
remote_asset_data: AssetData,
remote_asset_data: dict[str, Any],
assets_conflicts: dict[Asset, Literal['remote', 'local']] | None,
action: str,
full_insert: str,
Expand All @@ -303,14 +302,14 @@ def _handle_asset_update(
local_asset: Asset | None = None
with suppress(UnknownAsset):
# we avoid querying the packaged db to prevent the copy of constant assets
local_asset = Asset(remote_asset_data.identifier).check_existence(query_packaged_db=False) # noqa: E501
local_asset = Asset(remote_asset_data['identifier']).check_existence(query_packaged_db=False) # noqa: E501

try:
with connection.savepoint_ctx() as cursor:
# if the action is to update an asset, but it doesn't exist in the DB
if action.strip().startswith('UPDATE') and cursor.execute(
'SELECT COUNT(*) FROM assets WHERE identifier=?',
(remote_asset_data.identifier,),
(remote_asset_data['identifier'],),
).fetchone()[0] == 0:
executeall(cursor, full_insert) # we apply the full insert query
else:
Expand All @@ -325,7 +324,7 @@ def _handle_asset_update(
executeall(cursor, full_insert)
except sqlite3.Error as e:
self.msg_aggregator.add_warning(
f'Failed to add asset {remote_asset_data.identifier} in the '
f'Failed to add asset {remote_asset_data["identifier"]} in the '
f'DB during the v{version} assets update. Skipping entry. '
f'Error: {e!s}',
)
Expand All @@ -349,19 +348,18 @@ def _handle_asset_update(
_force_remote_asset(cursor, local_asset, full_insert)
except sqlite3.Error as e:
self.msg_aggregator.add_warning(
f'Failed to resolve conflict for {remote_asset_data.identifier} in '
f'Failed to resolve conflict for {remote_asset_data["identifier"]} in '
f'the DB during the v{version} assets update. Skipping entry. '
f'Error: {e!s}',
)
return # fail or succeed continue to next entry

# else can't resolve. Mark it for the user to resolve.
# TODO: When assets refactor is finished, remove the usage of AssetData here
local_data = self.globaldb.get_all_asset_data(
mapping=False,
serialized=False,
specific_ids=[local_asset.identifier],
)[0]
try:
local_data = local_asset.resolve().get_asset_data()
except UnknownAsset:
# If we can't resolve the asset, skip this conflict
return
# always take the last one, if there is multiple conflicts for a single asset
self.conflicts[local_asset.identifier] = (local_data, remote_asset_data)

Expand Down Expand Up @@ -540,8 +538,25 @@ def perform_update(

temp_db_connection.close()
if len(self.conflicts) != 0:
def serialize_asset_dict(asset_dict: dict[str, Any]) -> dict[str, Any]:
"""Serialize asset dict similar to AssetData.serialize()"""
result = asset_dict.copy()
result.pop('identifier', None)
result.pop('chain_id', None)
if 'asset_type' in result and hasattr(result['asset_type'], 'serialize_for_db'): # noqa: E501
result['asset_type'] = str(result['asset_type'])
if asset_dict.get('chain_id') is not None:
result['evm_chain'] = asset_dict['chain_id'].to_name()
if 'token_kind' in result and result['token_kind'] is not None and hasattr(result['token_kind'], 'serialize'): # noqa: E501
result['token_kind'] = result['token_kind'].serialize()
return result

return [
{'identifier': x[0].identifier, 'local': x[0].serialize(), 'remote': x[1].serialize()} # noqa: E501
{
'identifier': x[0]['identifier'],
'local': serialize_asset_dict(x[0]),
'remote': serialize_asset_dict(x[1]),
}
for x in self.conflicts.values()
]

Expand Down
78 changes: 39 additions & 39 deletions rotkehlchen/globaldb/asset_updates/parsers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import abc
import logging
import re
from typing import TYPE_CHECKING, Generic, TypeVar
from typing import TYPE_CHECKING, Any, Generic, TypeVar

from rotkehlchen.assets.types import AssetData, AssetType
from rotkehlchen.assets.types import AssetType
from rotkehlchen.errors.asset import UnknownAsset
from rotkehlchen.errors.serialization import DeserializationError
from rotkehlchen.logging import RotkehlchenLogsAdapter
Expand Down Expand Up @@ -94,7 +94,7 @@ def replace_double_quotes(match_result: re.Match[str]) -> str:
return self._double_quotes_re.sub(replace_double_quotes, text)


class AssetParser(BaseAssetParser[AssetData]):
class AssetParser(BaseAssetParser[dict[str, Any]]):
"""Parser for assets introduced in global db v3 (assets v15+)."""

def __init__(self) -> None:
Expand All @@ -106,30 +106,30 @@ def __init__(self) -> None:
(VersionRange(15, None), self._parse),
]

def _parse(self, connection: 'DBConnection', insert_text: str) -> AssetData:
def _parse(self, connection: 'DBConnection', insert_text: str) -> dict[str, Any]:
asset_data = self._parse_asset_data(insert_text)
address = decimals = protocol = chain_id = token_kind = None
if asset_data.asset_type == AssetType.EVM_TOKEN:
if asset_data['asset_type'] == AssetType.EVM_TOKEN:
address, decimals, protocol, chain_id, token_kind = self._parse_evm_token_data(insert_text) # noqa: E501

return AssetData(
identifier=asset_data.identifier,
name=asset_data.name,
symbol=asset_data.symbol,
asset_type=asset_data.asset_type,
started=asset_data.started,
forked=asset_data.forked,
swapped_for=asset_data.swapped_for,
address=address,
chain_id=chain_id,
token_kind=token_kind,
decimals=decimals,
cryptocompare=asset_data.cryptocompare,
coingecko=asset_data.coingecko,
protocol=protocol,
)

def _parse_asset_data(self, insert_text: str) -> AssetData:
return {
'identifier': asset_data['identifier'],
'name': asset_data['name'],
'symbol': asset_data['symbol'],
'asset_type': asset_data['asset_type'],
'started': asset_data['started'],
'forked': asset_data['forked'],
'swapped_for': asset_data['swapped_for'],
'address': address,
'chain_id': chain_id,
'token_kind': token_kind,
'decimals': decimals,
'cryptocompare': asset_data['cryptocompare'],
'coingecko': asset_data['coingecko'],
'protocol': protocol,
}

def _parse_asset_data(self, insert_text: str) -> dict[str, Any]:
"""Parse basic asset data for format"""
assets_match = self._assets_re.match(insert_text)
if assets_match is None:
Expand All @@ -152,38 +152,38 @@ def _parse_asset_data(self, insert_text: str) -> AssetData:

raw_started = self._parse_optional_int(common_details_match.group(6), 'started', insert_text) # noqa: E501
started = Timestamp(raw_started) if raw_started else None
return AssetData(
identifier=self._parse_str(common_details_match.group(1), 'identifier', insert_text),
asset_type=asset_type,
name=self._parse_str(assets_match.group(2), 'name', insert_text),
symbol=self._parse_str(common_details_match.group(2), 'symbol', insert_text),
started=started,
swapped_for=self._parse_optional_str(
return {
'identifier': self._parse_str(common_details_match.group(1), 'identifier', insert_text), # noqa: E501
'asset_type': asset_type,
'name': self._parse_str(assets_match.group(2), 'name', insert_text),
'symbol': self._parse_str(common_details_match.group(2), 'symbol', insert_text),
'started': started,
'swapped_for': self._parse_optional_str(
common_details_match.group(7),
'swapped_for',
insert_text,
),
coingecko=self._parse_optional_str(
'coingecko': self._parse_optional_str(
common_details_match.group(3),
'coingecko',
insert_text,
),
cryptocompare=self._parse_optional_str(
'cryptocompare': self._parse_optional_str(
common_details_match.group(4),
'cryptocompare',
insert_text,
),
forked=self._parse_optional_str(
'forked': self._parse_optional_str(
common_details_match.group(5),
'forked',
insert_text,
),
chain_id=None,
address=None,
token_kind=None,
decimals=None,
protocol=None,
)
'chain_id': None,
'address': None,
'token_kind': None,
'decimals': None,
'protocol': None,
}

def _parse_evm_token_data(
self,
Expand Down
Loading
Loading