-
-
Notifications
You must be signed in to change notification settings - Fork 527
/
resolver.py
179 lines (146 loc) · 7.99 KB
/
resolver.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import logging
from typing import TYPE_CHECKING, Optional, TypeVar
from rotkehlchen.assets.types import AssetType
from rotkehlchen.errors.asset import UnknownAsset, WrongAssetType
from rotkehlchen.logging import RotkehlchenLogsAdapter
from rotkehlchen.utils.data_structures import LRUCacheWithRemove
if TYPE_CHECKING:
from rotkehlchen.assets.asset import (
AssetWithNameAndType,
AssetWithOracles,
AssetWithSymbol,
CryptoAsset,
EvmToken,
FiatAsset,
Nft,
ResolvedAsset,
)
logger = logging.getLogger(__name__)
log = RotkehlchenLogsAdapter(logger)
T = TypeVar('T', 'FiatAsset', 'CryptoAsset', 'EvmToken', 'Nft', 'AssetWithNameAndType', 'AssetWithSymbol', 'AssetWithOracles') # noqa: E501
class AssetResolver:
__instance: Optional['AssetResolver'] = None
# A cache so that the DB is not hit every time
# the cache maps identifier -> final representation of the asset
assets_cache: LRUCacheWithRemove['ResolvedAsset'] = LRUCacheWithRemove(maxsize=512)
types_cache: LRUCacheWithRemove[AssetType] = LRUCacheWithRemove(maxsize=512)
def __new__(cls) -> 'AssetResolver':
"""Lazily initializes AssetResolver
It always uses the GlobalDB to resolve assets
"""
if AssetResolver.__instance is not None:
return AssetResolver.__instance
AssetResolver.__instance = object.__new__(cls)
return AssetResolver.__instance
@staticmethod
def clean_memory_cache(identifier: Optional[str] = None) -> None:
"""Clean the memory cache of either a single or all assets"""
assert AssetResolver.__instance is not None, 'when cleaning the cache instance should be set' # noqa: E501
if identifier is not None:
AssetResolver.__instance.assets_cache.remove(identifier)
AssetResolver.__instance.types_cache.remove(identifier)
else:
AssetResolver.__instance.assets_cache.clear()
AssetResolver.__instance.types_cache.clear()
@staticmethod
def resolve_asset(identifier: str) -> 'ResolvedAsset':
"""
Get all asset data for a valid asset identifier. May return any valid subclass of the
Asset class.
Thanks to querying the DB the resolved asset will have the normalized
asset identifier. So say if you pass 'eTh' the returned asset id will be 'ETH'
May raise:
- UnknownAsset
- WrongAssetType
"""
# TODO: This is ugly here but is here to avoid a cyclic import in the Assets file
# Couldn't find a reorg that solves this cyclic import
from rotkehlchen.constants.assets import CONSTANT_ASSETS # pylint: disable=import-outside-toplevel # isort:skip # noqa: E501
from rotkehlchen.globaldb.handler import GlobalDBHandler # pylint: disable=import-outside-toplevel # isort:skip # noqa: E501
instance = AssetResolver()
cached_data = instance.assets_cache.get(identifier)
if cached_data is not None:
return cached_data
# If was not found in the cache try querying it in the globaldb
try:
asset = GlobalDBHandler().resolve_asset(identifier=identifier)
# `WrongAssetType` exception is handled by `resolve_asset_to_class`
except UnknownAsset:
if identifier not in CONSTANT_ASSETS:
raise
log.debug(f'Attempt to resolve asset {identifier} using the packaged database')
asset = GlobalDBHandler().resolve_asset_from_packaged_and_store(identifier=identifier)
# Save it in the cache
instance.assets_cache.add(identifier, asset)
return asset
@staticmethod
def get_asset_type(identifier: str, query_packaged_db: bool = True) -> AssetType:
# TODO: This is ugly here but is here to avoid a cyclic import in the Assets file
# Couldn't find a reorg that solves this cyclic import
from rotkehlchen.constants.assets import CONSTANT_ASSETS # pylint: disable=import-outside-toplevel # isort:skip # noqa: E501
from rotkehlchen.globaldb.handler import GlobalDBHandler # pylint: disable=import-outside-toplevel # isort:skip # noqa: E501
instance = AssetResolver()
cached_data = instance.types_cache.get(identifier)
if cached_data is not None:
return cached_data
try:
asset_type = GlobalDBHandler().get_asset_type(identifier)
except UnknownAsset:
if identifier not in CONSTANT_ASSETS or query_packaged_db is False:
raise
log.debug(f'Attempt to get asset_type for {identifier} using the packaged database')
asset = GlobalDBHandler().resolve_asset_from_packaged_and_store(identifier=identifier)
asset_type = asset.asset_type
instance.types_cache.add(identifier, asset_type)
return asset_type
@staticmethod
def check_existence(identifier: str, query_packaged_db: bool = True) -> str:
"""Check that an asset with the given identifier exists and return normalized identifier
For example if 'eTh' is given here then 'ETH' should be returned.
May raise:
- UnknownAsset: If asset identifier does not exist.
"""
# TODO: This is ugly here but is here to avoid a cyclic import in the Assets file
# Couldn't find a reorg that solves this cyclic import
from rotkehlchen.constants.assets import CONSTANT_ASSETS # pylint: disable=import-outside-toplevel # isort:skip # noqa: E501
from rotkehlchen.globaldb.handler import GlobalDBHandler # pylint: disable=import-outside-toplevel # isort:skip # noqa: E501
instance = AssetResolver()
cached_data = instance.assets_cache.get(identifier)
if cached_data is not None:
return cached_data.identifier
try:
normalized_id = GlobalDBHandler().asset_id_exists(identifier)
except UnknownAsset:
if identifier not in CONSTANT_ASSETS or query_packaged_db is False:
raise
log.debug(f'Attempt to find normalized asset ID for {identifier} using the packaged database') # noqa: E501
normalized_id = GlobalDBHandler().asset_id_exists(identifier=identifier, use_packaged_db=True) # noqa: E501
return normalized_id
@staticmethod
def resolve_asset_to_class(identifier: str, expected_type: type[T]) -> T:
"""
Try to resolve an identifier to the Asset subclass defined in expected_type.
Whenever `WrongAssetType` is encountered for an asset present in `CONSTANT_ASSETS`
we use the packaged global db to resolve the asset.
May raise:
- WrongAssetType: if the asset is resolved but the class is not the expected one.
- UnknownAsset: if the asset was not found in the database.
"""
from rotkehlchen.constants.assets import CONSTANT_ASSETS # pylint: disable=import-outside-toplevel # isort:skip # noqa: E501
from rotkehlchen.globaldb.handler import GlobalDBHandler # pylint: disable=import-outside-toplevel # isort:skip # noqa: E501
resolved_asset = AssetResolver().resolve_asset(identifier=identifier)
if isinstance(resolved_asset, expected_type) is True:
# resolve_asset returns Asset, but we already narrow type with the if check above
return resolved_asset # type: ignore
if identifier in CONSTANT_ASSETS:
# Check if the version in the packaged globaldb is correct
resolved_asset = GlobalDBHandler().resolve_asset_from_packaged_and_store(identifier=identifier) # noqa: E501
AssetResolver().assets_cache.add(identifier, resolved_asset)
if isinstance(resolved_asset, expected_type) is True:
# resolve_asset returns Asset, but we already narrow type with the if check above
return resolved_asset # type: ignore
raise WrongAssetType(
identifier=identifier,
expected_type=expected_type,
real_type=type(resolved_asset),
)