dao-scripts 1.2.1__py3-none-any.whl → 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dao_analyzer/cache_scripts/_version.py +2 -2
- dao_analyzer/cache_scripts/aragon/runner.py +23 -26
- dao_analyzer/cache_scripts/argparser.py +14 -19
- dao_analyzer/cache_scripts/common/__init__.py +3 -1
- dao_analyzer/cache_scripts/common/api_requester.py +14 -13
- dao_analyzer/cache_scripts/common/blockscout.py +11 -13
- dao_analyzer/cache_scripts/common/common.py +61 -29
- dao_analyzer/cache_scripts/common/cryptocompare.py +4 -4
- dao_analyzer/cache_scripts/common/thegraph.py +203 -0
- dao_analyzer/cache_scripts/config.py +57 -15
- dao_analyzer/cache_scripts/daohaus/runner.py +21 -21
- dao_analyzer/cache_scripts/daostack/runner.py +25 -28
- dao_analyzer/cache_scripts/endpoints.json +14 -18
- dao_analyzer/cache_scripts/logging.py +98 -0
- dao_analyzer/cache_scripts/main.py +83 -77
- dao_analyzer/cache_scripts/metadata.py +6 -6
- dao_scripts-1.3.0-py3.11-nspkg.pth +1 -0
- dao_scripts-1.3.0.dist-info/LICENSE +674 -0
- {dao_scripts-1.2.1.dist-info → dao_scripts-1.3.0.dist-info}/METADATA +41 -7
- dao_scripts-1.3.0.dist-info/RECORD +32 -0
- {dao_scripts-1.2.1.dist-info → dao_scripts-1.3.0.dist-info}/WHEEL +1 -1
- dao_analyzer/cache_scripts/common/graphql.py +0 -143
- dao_scripts-1.2.1-py3.11-nspkg.pth +0 -1
- dao_scripts-1.2.1.dist-info/RECORD +0 -30
- {dao_scripts-1.2.1.dist-info → dao_scripts-1.3.0.dist-info}/entry_points.txt +0 -0
- {dao_scripts-1.2.1.dist-info → dao_scripts-1.3.0.dist-info}/namespace_packages.txt +0 -0
- {dao_scripts-1.2.1.dist-info → dao_scripts-1.3.0.dist-info}/top_level.txt +0 -0
@@ -16,13 +16,13 @@ import json
|
|
16
16
|
|
17
17
|
from ..aragon import __name__ as aragon_module_name
|
18
18
|
from ..common.cryptocompare import CCPricesCollector
|
19
|
-
from ..common import ENDPOINTS, Collector
|
20
|
-
from ..common.
|
19
|
+
from ..common import ENDPOINTS, Collector, NetworkRunner
|
20
|
+
from ..common.thegraph import TheGraphCollector
|
21
21
|
from ..common.blockscout import BlockscoutBallancesCollector
|
22
22
|
|
23
|
-
class AppsCollector(
|
23
|
+
class AppsCollector(TheGraphCollector):
|
24
24
|
def __init__(self, runner, network: str):
|
25
|
-
super().__init__('apps',
|
25
|
+
super().__init__('apps', network, ENDPOINTS[network]['aragon'], runner)
|
26
26
|
|
27
27
|
def query(self, **kwargs) -> DSLField:
|
28
28
|
ds = self.schema
|
@@ -39,9 +39,9 @@ class BalancesCollector(BlockscoutBallancesCollector):
|
|
39
39
|
def __init__(self, runner, base, network: str):
|
40
40
|
super().__init__(runner, addr_key='recoveryVault', base=base, network=network)
|
41
41
|
|
42
|
-
class CastsCollector(
|
42
|
+
class CastsCollector(TheGraphCollector):
|
43
43
|
def __init__(self, runner, network: str):
|
44
|
-
super().__init__('casts',
|
44
|
+
super().__init__('casts', network, ENDPOINTS[network]['aragon_voting'], runner, pbar_enabled=False)
|
45
45
|
|
46
46
|
@self.postprocessor
|
47
47
|
def changeColumnNames(df: pd.DataFrame) -> pd.DataFrame:
|
@@ -66,11 +66,11 @@ class CastsCollector(GraphQLCollector):
|
|
66
66
|
)
|
67
67
|
)
|
68
68
|
|
69
|
-
class OrganizationsCollector(
|
69
|
+
class OrganizationsCollector(TheGraphCollector):
|
70
70
|
DAO_NAMES=pkgutil.get_data(aragon_module_name, 'dao_names.json')
|
71
71
|
|
72
72
|
def __init__(self, runner, network: str):
|
73
|
-
super().__init__('organizations',
|
73
|
+
super().__init__('organizations', network, ENDPOINTS[network]['aragon'], runner)
|
74
74
|
|
75
75
|
@self.postprocessor
|
76
76
|
def set_dead_recoveryVault(df: pd.DataFrame) -> pd.DataFrame:
|
@@ -113,9 +113,9 @@ class OrganizationsCollector(GraphQLCollector):
|
|
113
113
|
ds.Organization.recoveryVault
|
114
114
|
)
|
115
115
|
|
116
|
-
class MiniMeTokensCollector(
|
116
|
+
class MiniMeTokensCollector(TheGraphCollector):
|
117
117
|
def __init__(self, runner, network: str):
|
118
|
-
super().__init__('miniMeTokens',
|
118
|
+
super().__init__('miniMeTokens', network, ENDPOINTS[network]['aragon_tokens'], runner, pbar_enabled=False)
|
119
119
|
|
120
120
|
def query(self, **kwargs) -> DSLField:
|
121
121
|
ds = self.schema
|
@@ -131,9 +131,9 @@ class MiniMeTokensCollector(GraphQLCollector):
|
|
131
131
|
ds.MiniMeToken.lastUpdateAt
|
132
132
|
)
|
133
133
|
|
134
|
-
class TokenHoldersCollector(
|
135
|
-
def __init__(self, runner:
|
136
|
-
super().__init__('tokenHolders',
|
134
|
+
class TokenHoldersCollector(TheGraphCollector):
|
135
|
+
def __init__(self, runner: NetworkRunner, network: str):
|
136
|
+
super().__init__('tokenHolders', network, ENDPOINTS[network]['aragon_tokens'], runner)
|
137
137
|
|
138
138
|
@self.postprocessor
|
139
139
|
def add_minitokens(df: pd.DataFrame) -> pd.DataFrame:
|
@@ -156,9 +156,9 @@ class TokenHoldersCollector(GraphQLCollector):
|
|
156
156
|
class TokenPricesCollector(CCPricesCollector):
|
157
157
|
pass
|
158
158
|
|
159
|
-
class ReposCollector(
|
159
|
+
class ReposCollector(TheGraphCollector):
|
160
160
|
def __init__(self, runner, network: str):
|
161
|
-
super().__init__('repos',
|
161
|
+
super().__init__('repos', network, ENDPOINTS[network]['aragon'], runner)
|
162
162
|
|
163
163
|
def query(self, **kwargs) -> DSLField:
|
164
164
|
ds = self.schema
|
@@ -170,9 +170,9 @@ class ReposCollector(GraphQLCollector):
|
|
170
170
|
ds.Repo.appCount
|
171
171
|
)
|
172
172
|
|
173
|
-
class TransactionsCollector(
|
173
|
+
class TransactionsCollector(TheGraphCollector):
|
174
174
|
def __init__(self, runner, network: str):
|
175
|
-
super().__init__('transactions',
|
175
|
+
super().__init__('transactions', network, ENDPOINTS[network]['aragon_finance'], runner)
|
176
176
|
|
177
177
|
def query(self, **kwargs) -> DSLField:
|
178
178
|
ds = self.schema
|
@@ -188,9 +188,9 @@ class TransactionsCollector(GraphQLCollector):
|
|
188
188
|
ds.Transaction.reference
|
189
189
|
)
|
190
190
|
|
191
|
-
class VotesCollector(
|
191
|
+
class VotesCollector(TheGraphCollector):
|
192
192
|
def __init__(self, runner, network: str):
|
193
|
-
super().__init__('votes',
|
193
|
+
super().__init__('votes', network, ENDPOINTS[network]['aragon_voting'], runner)
|
194
194
|
|
195
195
|
def query(self, **kwargs) -> DSLField:
|
196
196
|
ds = self.schema
|
@@ -214,14 +214,12 @@ class VotesCollector(GraphQLCollector):
|
|
214
214
|
ds.Vote.metadata,
|
215
215
|
)
|
216
216
|
|
217
|
-
class AragonRunner(
|
217
|
+
class AragonRunner(NetworkRunner):
|
218
218
|
name: str = 'aragon'
|
219
219
|
|
220
220
|
def __init__(self, dw=None):
|
221
221
|
super().__init__(dw)
|
222
222
|
self._collectors: List[Collector] = []
|
223
|
-
## TODO: Fix aragon-tokens xdai subgraph and redeploy
|
224
|
-
self.networks = ['mainnet']
|
225
223
|
|
226
224
|
for n in self.networks:
|
227
225
|
self._collectors.extend([
|
@@ -231,11 +229,10 @@ class AragonRunner(GraphQLRunner):
|
|
231
229
|
ReposCollector(self, n),
|
232
230
|
TransactionsCollector(self, n),
|
233
231
|
TokenHoldersCollector(self, n),
|
234
|
-
VotesCollector(self, n)
|
232
|
+
VotesCollector(self, n),
|
233
|
+
oc := OrganizationsCollector(self, n),
|
234
|
+
BalancesCollector(self, oc, n),
|
235
235
|
])
|
236
|
-
oc = OrganizationsCollector(self, n)
|
237
|
-
bc = BalancesCollector(self, oc, n)
|
238
|
-
self._collectors += [oc, bc]
|
239
236
|
|
240
237
|
self._collectors.append(CCPricesCollector(self))
|
241
238
|
|
@@ -3,7 +3,6 @@ from typing import List
|
|
3
3
|
|
4
4
|
from datetime import datetime
|
5
5
|
import pathlib
|
6
|
-
import os
|
7
6
|
|
8
7
|
from . import config
|
9
8
|
|
@@ -26,13 +25,13 @@ class CacheScriptsArgParser(ArgumentParser):
|
|
26
25
|
help="The platforms to update. Every platform is updated by default."
|
27
26
|
)
|
28
27
|
self.add_argument(
|
29
|
-
"--
|
30
|
-
default=
|
28
|
+
"--raise-runner-errors",
|
29
|
+
default=False,
|
31
30
|
action=BooleanOptionalAction,
|
32
31
|
help="Whether to ignore errors and continue")
|
33
32
|
self.add_argument(
|
34
33
|
"-d", "--debug",
|
35
|
-
action='store_true', default=False,
|
34
|
+
dest="DEBUG", action='store_true', default=False,
|
36
35
|
help="Shows debug info"
|
37
36
|
)
|
38
37
|
self.add_argument(
|
@@ -45,11 +44,6 @@ class CacheScriptsArgParser(ArgumentParser):
|
|
45
44
|
action="store_true", default=False,
|
46
45
|
help="Removes the datawarehouse folder before doing anything"
|
47
46
|
)
|
48
|
-
self.add_argument(
|
49
|
-
"--skip-daohaus-names",
|
50
|
-
action="store_true", default=False,
|
51
|
-
help="Skips the step of getting Daohaus Moloch's names, which takes some time"
|
52
|
-
)
|
53
47
|
self.add_argument(
|
54
48
|
"--skip-token-balances",
|
55
49
|
action="store_true", default=False,
|
@@ -70,7 +64,7 @@ class CacheScriptsArgParser(ArgumentParser):
|
|
70
64
|
help="Collectors to run. For example: aragon/casts"
|
71
65
|
)
|
72
66
|
self.add_argument(
|
73
|
-
"--block-datetime",
|
67
|
+
"-B", "--block-datetime",
|
74
68
|
required=False,
|
75
69
|
type=datetime.fromisoformat,
|
76
70
|
help="Get data up to a block datetime (input in ISO format)"
|
@@ -82,15 +76,9 @@ class CacheScriptsArgParser(ArgumentParser):
|
|
82
76
|
type=pathlib.Path,
|
83
77
|
default=config.DEFAULT_DATAWAREHOUSE
|
84
78
|
)
|
85
|
-
self.add_argument(
|
86
|
-
"--cc-api-key",
|
87
|
-
help="Set the CryptoCompare API key (overrides environment variable)",
|
88
|
-
required=False,
|
89
|
-
type=str,
|
90
|
-
default=os.getenv('DAOA_CC_API_KEY')
|
91
|
-
)
|
92
79
|
self.add_argument(
|
93
80
|
"--only-updatable",
|
81
|
+
dest='run_only_updatable',
|
94
82
|
help=SUPPRESS, # "Run only updatable collectors (only for testing)",
|
95
83
|
action='store_true',
|
96
84
|
required=False,
|
@@ -99,7 +87,14 @@ class CacheScriptsArgParser(ArgumentParser):
|
|
99
87
|
self.add_argument(
|
100
88
|
"--daostack-all",
|
101
89
|
help="Obtain all DAOs in DAOstack, not only registered ones",
|
102
|
-
action='
|
90
|
+
action='store_false',
|
103
91
|
required=False,
|
104
|
-
|
92
|
+
# For the user is "daostack_all", for us is "registered only"
|
93
|
+
dest='daostack__registered_only',
|
94
|
+
default=True,
|
95
|
+
)
|
96
|
+
self.add_argument(
|
97
|
+
"--daohaus-skip-names",
|
98
|
+
action="store_true", default=False, dest='daohaus__skip_names',
|
99
|
+
help="Skips the step of getting Daohaus Moloch's names, which takes some time"
|
105
100
|
)
|
@@ -7,10 +7,12 @@
|
|
7
7
|
<david@ddavo.me>
|
8
8
|
"""
|
9
9
|
|
10
|
-
from .common import Collector, Runner, ENDPOINTS
|
10
|
+
from .common import Collector, Runner, ENDPOINTS, NetworkRunner, NetworkCollector
|
11
11
|
|
12
12
|
__all__ = [
|
13
13
|
'Collector',
|
14
14
|
'Runner',
|
15
15
|
'ENDPOINTS',
|
16
|
+
'NetworkRunner',
|
17
|
+
'NetworkCollector',
|
16
18
|
]
|
@@ -17,7 +17,7 @@ from functools import partial
|
|
17
17
|
import logging
|
18
18
|
import sys
|
19
19
|
from tqdm import tqdm
|
20
|
-
from typing import
|
20
|
+
from typing import Optional, Union, Iterable, Callable
|
21
21
|
|
22
22
|
class GQLQueryException(Exception):
|
23
23
|
def __init__(self, errors, msg="Errors in GraphQL Query"):
|
@@ -84,22 +84,23 @@ class GQLRequester:
|
|
84
84
|
self.__transport = RequestsHTTPTransport(endpoint)
|
85
85
|
self.__client: Client = Client(transport=self.__transport, fetch_schema_from_transport=introspection)
|
86
86
|
self.pbar = IndexProgressBar if pbar_enabled else RequestProgressSpinner
|
87
|
+
self.logger = logging.getLogger('dao-scripts.gql-requester')
|
87
88
|
|
88
|
-
|
89
|
+
self.logger.debug(f"Invoked ApiRequester with endpoint: {endpoint}")
|
89
90
|
|
90
91
|
def get_schema(self) -> DSLSchema:
|
91
92
|
with self.__client:
|
92
93
|
assert(self.__client.schema is not None)
|
93
94
|
return DSLSchema(self.__client.schema)
|
94
95
|
|
95
|
-
def request(self, query: Union[DSLQuery, DSLField, str]) ->
|
96
|
+
def request(self, query: Union[DSLQuery, DSLField, str]) -> dict:
|
96
97
|
"""
|
97
98
|
Requests data from endpoint.
|
98
99
|
"""
|
99
100
|
if isinstance(query, DSLField):
|
100
101
|
query = DSLQuery(query)
|
101
102
|
|
102
|
-
|
103
|
+
self.logger.debug(f"Requesting: {query}")
|
103
104
|
|
104
105
|
if isinstance(query, DSLQuery):
|
105
106
|
result = self.__client.execute(dsl_gql(query))
|
@@ -111,14 +112,14 @@ class GQLRequester:
|
|
111
112
|
|
112
113
|
return result
|
113
114
|
|
114
|
-
def request_single(self, q: Union[DSLQuery, DSLField, str]) ->
|
115
|
+
def request_single(self, q: Union[DSLQuery, DSLField, str]) -> dict:
|
115
116
|
result = self.request(q)
|
116
117
|
if result and len(result.values()) == 1:
|
117
118
|
return next(iter(result.values()))
|
118
119
|
else:
|
119
120
|
raise
|
120
121
|
|
121
|
-
def n_requests(self, query:
|
122
|
+
def n_requests(self, query: Callable[..., DSLField], index='id', last_index: str = "", block_hash: Optional[str] = None) -> list[dict]:
|
122
123
|
"""
|
123
124
|
Requests all chunks from endpoint.
|
124
125
|
|
@@ -128,8 +129,8 @@ class GQLRequester:
|
|
128
129
|
* last_index: used to continue the request
|
129
130
|
* block_hash: make the request to that block hash
|
130
131
|
"""
|
131
|
-
elements:
|
132
|
-
result =
|
132
|
+
elements: list[dict] = list()
|
133
|
+
result = dict()
|
133
134
|
|
134
135
|
# do-while structure
|
135
136
|
exit: bool = False
|
@@ -171,18 +172,18 @@ class CryptoCompareQueryException(Exception):
|
|
171
172
|
class CryptoCompareRequester:
|
172
173
|
BASEURL = 'https://min-api.cryptocompare.com/data/'
|
173
174
|
|
174
|
-
def __init__(self, api_key: str = None, pbar_enabled: bool = True):
|
175
|
-
self.logger = logging.getLogger('ccrequester')
|
175
|
+
def __init__(self, api_key: Optional[str] = None, pbar_enabled: bool = True):
|
176
|
+
self.logger = logging.getLogger('dao-scripts.ccrequester')
|
176
177
|
self.pbar = partial(tqdm, delay=1, file=sys.stdout, desc="Requesting",
|
177
178
|
dynamic_ncols=True)
|
178
179
|
|
179
180
|
if not api_key:
|
180
|
-
|
181
|
+
self.logger.warning('CryptoCompare API key is not set')
|
181
182
|
api_key = ""
|
182
183
|
|
183
184
|
self.api_key = api_key
|
184
185
|
|
185
|
-
def _build_headers(self) ->
|
186
|
+
def _build_headers(self) -> dict[str, str]:
|
186
187
|
return {
|
187
188
|
'Authorization': 'Apikey ' + self.api_key
|
188
189
|
}
|
@@ -204,7 +205,7 @@ class CryptoCompareRequester:
|
|
204
205
|
if 'Data' not in j:
|
205
206
|
return j
|
206
207
|
if "HasWarning" in j and j["HasWarning"]:
|
207
|
-
|
208
|
+
self.logger.warning("Warning in query", r.url, ":", j["Message"])
|
208
209
|
if j["Type"] == 100:
|
209
210
|
return j['Data']
|
210
211
|
|
@@ -1,10 +1,9 @@
|
|
1
1
|
import pandas as pd
|
2
2
|
import requests
|
3
|
-
import logging
|
4
3
|
from functools import partial
|
5
4
|
from tqdm import tqdm
|
6
5
|
from time import sleep
|
7
|
-
from typing import Union
|
6
|
+
from typing import Union, Optional
|
8
7
|
|
9
8
|
import numpy as np
|
10
9
|
|
@@ -14,14 +13,13 @@ from .. import config
|
|
14
13
|
from . import ENDPOINTS
|
15
14
|
from .common import NetworkCollector, solve_decimals
|
16
15
|
from .cryptocompare import cc_postprocessor
|
17
|
-
from .graphql import GraphQLCollector
|
18
16
|
|
19
17
|
MSG_NO_TOKENS_FOUND = "No tokens found"
|
20
18
|
|
21
19
|
class BlockscoutBallancesCollector(NetworkCollector):
|
22
20
|
ERR_SLEEP = 60
|
23
21
|
|
24
|
-
def __init__(self, runner, base:
|
22
|
+
def __init__(self, runner, base: NetworkCollector, name: str='tokenBalances', network: str='mainnet', addr_key: str='id'):
|
25
23
|
""" Initializes a ballance collector that uses blockscout
|
26
24
|
|
27
25
|
Parameters:
|
@@ -37,7 +35,7 @@ class BlockscoutBallancesCollector(NetworkCollector):
|
|
37
35
|
|
38
36
|
def verify(self) -> bool:
|
39
37
|
if config.skip_token_balances:
|
40
|
-
|
38
|
+
self.logger.warning('Skipping token balances because --skip-token-balances flag was set')
|
41
39
|
return False
|
42
40
|
else:
|
43
41
|
return super().verify()
|
@@ -46,7 +44,7 @@ class BlockscoutBallancesCollector(NetworkCollector):
|
|
46
44
|
def endpoint(self) -> str:
|
47
45
|
return ENDPOINTS[self.network]['blockscout']
|
48
46
|
|
49
|
-
def _get_from_address(self, addr: str, retry: int = 0, maxretries: int = 3, block: Union[int, Block] = None, ignore_errors=False) -> pd.DataFrame: # noqa: C901
|
47
|
+
def _get_from_address(self, addr: str, retry: int = 0, maxretries: int = 3, block: Union[int, Block, None] = None, ignore_errors=False) -> pd.DataFrame: # noqa: C901
|
50
48
|
if retry >= maxretries:
|
51
49
|
raise ValueError(f"Too many retries {retry}/{maxretries}")
|
52
50
|
|
@@ -83,32 +81,32 @@ class BlockscoutBallancesCollector(NetworkCollector):
|
|
83
81
|
elif j['message'] == MSG_NO_TOKENS_FOUND:
|
84
82
|
return pd.DataFrame()
|
85
83
|
else:
|
86
|
-
|
84
|
+
self.logger.warning(f"Status {j['status']}, message: {j['message']}")
|
87
85
|
return pd.DataFrame()
|
88
86
|
elif r.status_code == 429: # Too many requests
|
89
|
-
|
87
|
+
self.logger.warning(f"Too many requests, sleep and retry {retry}/{maxretries} time")
|
90
88
|
sleep(self.ERR_SLEEP)
|
91
89
|
return self._get_from_address(addr, retry=retry+1, maxretries=maxretries)
|
92
90
|
elif r.status_code == 503:
|
93
|
-
|
91
|
+
self.logger.warning(f"Service unavailable, sleep and retry {retry}/{maxretries} time")
|
94
92
|
sleep(self.ERR_SLEEP)
|
95
93
|
return self._get_from_address(addr, retry=retry+1, maxretries=maxretries)
|
96
94
|
elif r.status_code == 504: # Gateway Time-out (Response too large)
|
97
|
-
|
95
|
+
self.logger.warning(f"Requests returned Gateway Time-out, ignoring response for addr {addr}")
|
98
96
|
return pd.DataFrame()
|
99
97
|
else:
|
100
|
-
|
98
|
+
self.logger.error(f'Requests failed for address "{addr}" with status code {r.status_code}: {r.reason}')
|
101
99
|
if ignore_errors:
|
102
100
|
return pd.DataFrame()
|
103
101
|
else:
|
104
102
|
raise ValueError(f"Requests failed for address {addr[:12]}... with status code {r.status_code}: {r.reason}")
|
105
103
|
|
106
|
-
def run(self, force=False, block: Block = None, prev_block: Block = None, **kwargs):
|
104
|
+
def run(self, force=False, block: Optional[Block] = None, prev_block: Optional[Block] = None, **kwargs):
|
107
105
|
# For each of the DAOs in the df, get the token balance
|
108
106
|
addresses = self.base.df[self.addr_key].drop_duplicates()
|
109
107
|
|
110
108
|
if addresses.empty:
|
111
|
-
|
109
|
+
self.logger.warning("No addresses returned, not running blockscout collector")
|
112
110
|
return
|
113
111
|
|
114
112
|
ptqdm = partial(tqdm, delay=1, desc="Requesting token balances",
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
2
|
from pathlib import Path
|
3
|
-
from typing import
|
3
|
+
from typing import Optional, Iterable
|
4
4
|
import logging
|
5
5
|
import sys
|
6
6
|
import json
|
@@ -19,7 +19,24 @@ from .. import config
|
|
19
19
|
from dao_analyzer import cache_scripts
|
20
20
|
|
21
21
|
# To be able to obtain endpoints.json
|
22
|
-
ENDPOINTS:
|
22
|
+
ENDPOINTS: dict = json.loads(pkgutil.get_data(cache_scripts.__name__, 'endpoints.json'))
|
23
|
+
THE_GRAPH_URL_TEMPLATE = 'https://gateway-arbitrum.network.thegraph.com/api/{api_key}/subgraphs/id/{subgraph_id}'
|
24
|
+
THE_GRAPH_DEPLOYMENT_TEMPLATE = 'https://gateway-arbitrum.network.thegraph.com/api/{api_key}/deployments/id/{deployment_id}'
|
25
|
+
|
26
|
+
def get_graph_url(subgraph_id: str) -> str:
|
27
|
+
if subgraph_id.startswith("http"):
|
28
|
+
return subgraph_id
|
29
|
+
|
30
|
+
if subgraph_id.startswith("Qm"):
|
31
|
+
return THE_GRAPH_DEPLOYMENT_TEMPLATE.format(
|
32
|
+
api_key=config.THE_GRAPH_API_KEY,
|
33
|
+
deployment_id=subgraph_id,
|
34
|
+
)
|
35
|
+
|
36
|
+
return THE_GRAPH_URL_TEMPLATE.format(
|
37
|
+
api_key=config.THE_GRAPH_API_KEY,
|
38
|
+
subgraph_id=subgraph_id,
|
39
|
+
)
|
23
40
|
|
24
41
|
def solve_decimals(df: pd.DataFrame) -> pd.DataFrame:
|
25
42
|
""" Adds the balanceFloat column to the dataframe
|
@@ -36,10 +53,14 @@ def solve_decimals(df: pd.DataFrame) -> pd.DataFrame:
|
|
36
53
|
class Collector(ABC):
|
37
54
|
INDEX = ['network', 'id']
|
38
55
|
|
39
|
-
def __init__(self, name:str, runner):
|
56
|
+
def __init__(self, name:str, runner: 'Runner'):
|
40
57
|
self.name: str = name
|
41
58
|
self.runner = runner
|
42
59
|
|
60
|
+
@property
|
61
|
+
def logger(self):
|
62
|
+
return logging.getLogger(f'dao_analyzer.collectors.{self.collectorid}')
|
63
|
+
|
43
64
|
@property
|
44
65
|
def data_path(self) -> Path:
|
45
66
|
return self.runner.basedir / (self.name + '.arr')
|
@@ -67,7 +88,7 @@ class Collector(ABC):
|
|
67
88
|
""" Updates the dataframe in `self.data_path` with the new data.
|
68
89
|
"""
|
69
90
|
if df.empty:
|
70
|
-
|
91
|
+
self.logger.warning("Empty dataframe, not updating file")
|
71
92
|
return
|
72
93
|
|
73
94
|
if not self.data_path.is_file():
|
@@ -106,25 +127,31 @@ class UpdatableCollector(Collector): # Flag class
|
|
106
127
|
pass
|
107
128
|
|
108
129
|
class Runner(ABC):
|
109
|
-
def __init__(self, dw: Path
|
130
|
+
def __init__(self, dw: Path):
|
110
131
|
self.__dw: Path = dw
|
111
132
|
|
112
|
-
|
113
|
-
|
133
|
+
@property
|
134
|
+
def logger(self):
|
135
|
+
return logging.getLogger(f'dao_analyzer.runner.{self.name}')
|
136
|
+
|
137
|
+
@property
|
138
|
+
def cache(self) -> Path:
|
139
|
+
# Common cache folder for everyone
|
140
|
+
return self.__dw / '.cache'
|
114
141
|
|
115
142
|
@property
|
116
143
|
def basedir(self) -> Path:
|
117
144
|
return self.__dw / self.name
|
118
145
|
|
119
146
|
@property
|
120
|
-
def collectors(self) ->
|
147
|
+
def collectors(self) -> list[Collector]:
|
121
148
|
return []
|
122
149
|
|
123
150
|
def run(self, **kwargs):
|
124
151
|
raise NotImplementedError
|
125
152
|
|
126
153
|
class NetworkRunner(Runner, ABC):
|
127
|
-
def __init__(self, dw
|
154
|
+
def __init__(self, dw):
|
128
155
|
super().__init__(dw)
|
129
156
|
self.networks = {n for n,v in ENDPOINTS.items() if self.name in v and not n.startswith('_')}
|
130
157
|
|
@@ -133,9 +160,9 @@ class NetworkRunner(Runner, ABC):
|
|
133
160
|
names: Iterable[str] = [],
|
134
161
|
long_names: Iterable[str] = []
|
135
162
|
) -> Iterable[Collector]:
|
136
|
-
result = self.collectors
|
163
|
+
result: Iterable[Collector] = self.collectors
|
137
164
|
|
138
|
-
if config.
|
165
|
+
if config.run_only_updatable:
|
139
166
|
result = filter(lambda c: isinstance(c, UpdatableCollector), result)
|
140
167
|
|
141
168
|
# networks ^ (names v long_names)
|
@@ -150,11 +177,11 @@ class NetworkRunner(Runner, ABC):
|
|
150
177
|
return result
|
151
178
|
|
152
179
|
def filterCollector(self,
|
153
|
-
collector_id: str = None,
|
154
|
-
network: str = None,
|
155
|
-
name: str = None,
|
156
|
-
long_name: str = None,
|
157
|
-
) -> Collector:
|
180
|
+
collector_id: Optional[str] = None,
|
181
|
+
network: Optional[str] = None,
|
182
|
+
name: Optional[str] = None,
|
183
|
+
long_name: Optional[str] = None,
|
184
|
+
) -> Optional[Collector]:
|
158
185
|
if collector_id:
|
159
186
|
return next((c for c in self.collectors if c.collectorid == collector_id), None)
|
160
187
|
|
@@ -164,10 +191,9 @@ class NetworkRunner(Runner, ABC):
|
|
164
191
|
long_names=[long_name] if long_name else []
|
165
192
|
), None)
|
166
193
|
|
167
|
-
@staticmethod
|
168
194
|
@retry(retry=retry_if_exception_type(TransportQueryError), wait=wait_exponential(max=10), stop=stop_after_attempt(3))
|
169
|
-
def validated_block(network: str, prev_block: Block = None) -> Block:
|
170
|
-
requester = GQLRequester(ENDPOINTS[network][
|
195
|
+
def validated_block(self, network: str, prev_block: Optional[Block] = None, until_date: Optional[datetime] = None) -> Optional[Block]:
|
196
|
+
requester = GQLRequester(get_graph_url(ENDPOINTS[network]['_blocks']))
|
171
197
|
ds = requester.get_schema()
|
172
198
|
|
173
199
|
number_gte = prev_block.number if prev_block else 0
|
@@ -182,10 +208,11 @@ class NetworkRunner(Runner, ABC):
|
|
182
208
|
}
|
183
209
|
}
|
184
210
|
|
185
|
-
|
211
|
+
# TODO: SET THE UNTIL_DATE
|
212
|
+
if until_date:
|
186
213
|
del args["skip"]
|
187
214
|
del args["where"]["number_gte"]
|
188
|
-
args["where"]["timestamp_lte"] = int(
|
215
|
+
args["where"]["timestamp_lte"] = int(until_date.timestamp())
|
189
216
|
|
190
217
|
response = requester.request(ds.Query.blocks(**args).select(
|
191
218
|
ds.Block.id,
|
@@ -194,13 +221,13 @@ class NetworkRunner(Runner, ABC):
|
|
194
221
|
))["blocks"]
|
195
222
|
|
196
223
|
if len(response) == 0:
|
197
|
-
|
224
|
+
self.logger.warning(f"Blocks query returned no response with args {args}")
|
198
225
|
return prev_block
|
199
226
|
|
200
227
|
return Block(response[0])
|
201
228
|
|
202
229
|
@staticmethod
|
203
|
-
def _verifyCollectors(tocheck: Iterable[Collector]):
|
230
|
+
def _verifyCollectors(tocheck: Iterable[Collector]) -> Iterable[Collector]:
|
204
231
|
verified = []
|
205
232
|
for c in tqdm(list(tocheck), desc="Verifying"):
|
206
233
|
try:
|
@@ -213,7 +240,7 @@ class NetworkRunner(Runner, ABC):
|
|
213
240
|
traceback.print_exc()
|
214
241
|
return verified
|
215
242
|
|
216
|
-
def run(self, networks:
|
243
|
+
def run(self, networks: list[str] = [], force=False, collectors=None, until_date: Optional[datetime]=None):
|
217
244
|
self.basedir.mkdir(parents=True, exist_ok=True)
|
218
245
|
|
219
246
|
print("Verifying collectors")
|
@@ -234,14 +261,18 @@ class NetworkRunner(Runner, ABC):
|
|
234
261
|
if c.network not in blocks:
|
235
262
|
# Getting a block more recent than the one in the metadata (just to narrow down the search)
|
236
263
|
print("Requesting a block number...", end='\r')
|
237
|
-
blocks[c.network] = self.validated_block(
|
264
|
+
blocks[c.network] = self.validated_block(
|
265
|
+
network=c.network,
|
266
|
+
prev_block=None if force else metadata[c.collectorid].block,
|
267
|
+
until_date=until_date,
|
268
|
+
)
|
238
269
|
print(f"Using block {blocks[c.network].id} for {c.network} (ts: {blocks[c.network].timestamp.isoformat()})")
|
239
270
|
|
240
271
|
print(f"Running collector {c.long_name} ({c.network})")
|
241
272
|
olderBlock = blocks[c.network] < metadata[c.collectorid].block
|
242
273
|
if not force and olderBlock:
|
243
274
|
print("Warning: Forcing because requesting an older block")
|
244
|
-
|
275
|
+
self.logger.warning("Forcing because using an older block")
|
245
276
|
|
246
277
|
# Running the collector
|
247
278
|
c.run(
|
@@ -261,8 +292,9 @@ class NetworkRunner(Runner, ABC):
|
|
261
292
|
metadata[c.collectorid].last_update = datetime.now(timezone.utc)
|
262
293
|
except Exception as e:
|
263
294
|
metadata.errors[c.collectorid] = e.__str__()
|
264
|
-
if config.
|
265
|
-
print(traceback.format_exc())
|
266
|
-
else:
|
295
|
+
if config.raise_runner_errors:
|
267
296
|
raise e
|
297
|
+
else:
|
298
|
+
# TODO: Use a logger instead
|
299
|
+
print(traceback.format_exc(), file=sys.stderr)
|
268
300
|
print(f'--- {self.name}\'s datawarehouse updated ---')
|
@@ -11,11 +11,11 @@ import logging
|
|
11
11
|
EMPTY_KEY_MSG = \
|
12
12
|
"""\
|
13
13
|
Empty CryptoCompare API key. You can obtain one from https://www.cryptocompare.com/cryptopian/api-keys
|
14
|
-
You can set the API key using
|
14
|
+
You can set the API key using the DAOA_CC_API_KEY env variable.
|
15
15
|
"""
|
16
16
|
|
17
17
|
def cc_postprocessor(df: pd.DataFrame) -> pd.DataFrame:
|
18
|
-
ccrequester = CryptoCompareRequester(api_key=config.
|
18
|
+
ccrequester = CryptoCompareRequester(api_key=config.CC_API_KEY)
|
19
19
|
|
20
20
|
tokenSymbols = df['symbol'].drop_duplicates()
|
21
21
|
|
@@ -41,11 +41,11 @@ def cc_postprocessor(df: pd.DataFrame) -> pd.DataFrame:
|
|
41
41
|
class CCPricesCollector(Collector):
|
42
42
|
def __init__(self, runner: NetworkRunner, name: str='tokenPrices'):
|
43
43
|
super().__init__(name, runner)
|
44
|
-
self.requester = CryptoCompareRequester(api_key=config.
|
44
|
+
self.requester = CryptoCompareRequester(api_key=config.CC_API_KEY)
|
45
45
|
|
46
46
|
def verify(self) -> bool:
|
47
47
|
if not self.requester.api_key:
|
48
|
-
|
48
|
+
self.logger.warning(EMPTY_KEY_MSG)
|
49
49
|
return False
|
50
50
|
|
51
51
|
return super().verify()
|