dao-scripts 1.2.2__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.
Files changed (27) hide show
  1. dao_analyzer/cache_scripts/_version.py +2 -2
  2. dao_analyzer/cache_scripts/aragon/runner.py +23 -26
  3. dao_analyzer/cache_scripts/argparser.py +14 -19
  4. dao_analyzer/cache_scripts/common/__init__.py +3 -1
  5. dao_analyzer/cache_scripts/common/api_requester.py +14 -13
  6. dao_analyzer/cache_scripts/common/blockscout.py +11 -13
  7. dao_analyzer/cache_scripts/common/common.py +55 -28
  8. dao_analyzer/cache_scripts/common/cryptocompare.py +4 -4
  9. dao_analyzer/cache_scripts/common/thegraph.py +203 -0
  10. dao_analyzer/cache_scripts/config.py +57 -15
  11. dao_analyzer/cache_scripts/daohaus/runner.py +20 -20
  12. dao_analyzer/cache_scripts/daostack/runner.py +25 -28
  13. dao_analyzer/cache_scripts/endpoints.json +14 -18
  14. dao_analyzer/cache_scripts/logging.py +98 -0
  15. dao_analyzer/cache_scripts/main.py +83 -77
  16. dao_analyzer/cache_scripts/metadata.py +6 -6
  17. dao_scripts-1.3.0-py3.11-nspkg.pth +1 -0
  18. dao_scripts-1.3.0.dist-info/LICENSE +674 -0
  19. {dao_scripts-1.2.2.dist-info → dao_scripts-1.3.0.dist-info}/METADATA +41 -7
  20. dao_scripts-1.3.0.dist-info/RECORD +32 -0
  21. {dao_scripts-1.2.2.dist-info → dao_scripts-1.3.0.dist-info}/WHEEL +1 -1
  22. dao_analyzer/cache_scripts/common/graphql.py +0 -143
  23. dao_scripts-1.2.2-py3.11-nspkg.pth +0 -1
  24. dao_scripts-1.2.2.dist-info/RECORD +0 -30
  25. {dao_scripts-1.2.2.dist-info → dao_scripts-1.3.0.dist-info}/entry_points.txt +0 -0
  26. {dao_scripts-1.2.2.dist-info → dao_scripts-1.3.0.dist-info}/namespace_packages.txt +0 -0
  27. {dao_scripts-1.2.2.dist-info → dao_scripts-1.3.0.dist-info}/top_level.txt +0 -0
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.2.2'
16
- __version_tuple__ = version_tuple = (1, 2, 2)
15
+ __version__ = version = '1.3.0'
16
+ __version_tuple__ = version_tuple = (1, 3, 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.graphql import GraphQLCollector, GraphQLRunner
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(GraphQLCollector):
23
+ class AppsCollector(TheGraphCollector):
24
24
  def __init__(self, runner, network: str):
25
- super().__init__('apps', runner, endpoint=ENDPOINTS[network]['aragon'], network=network)
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(GraphQLCollector):
42
+ class CastsCollector(TheGraphCollector):
43
43
  def __init__(self, runner, network: str):
44
- super().__init__('casts', runner, endpoint=ENDPOINTS[network]['aragon_voting'], network=network, pbar_enabled=False)
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(GraphQLCollector):
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', runner, endpoint=ENDPOINTS[network]['aragon'], network=network)
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(GraphQLCollector):
116
+ class MiniMeTokensCollector(TheGraphCollector):
117
117
  def __init__(self, runner, network: str):
118
- super().__init__('miniMeTokens', runner, endpoint=ENDPOINTS[network]['aragon_tokens'], network=network, pbar_enabled=False)
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(GraphQLCollector):
135
- def __init__(self, runner: GraphQLRunner, network: str):
136
- super().__init__('tokenHolders', runner, endpoint=ENDPOINTS[network]['aragon_tokens'], network=network)
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(GraphQLCollector):
159
+ class ReposCollector(TheGraphCollector):
160
160
  def __init__(self, runner, network: str):
161
- super().__init__('repos', runner, network=network, endpoint=ENDPOINTS[network]['aragon'])
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(GraphQLCollector):
173
+ class TransactionsCollector(TheGraphCollector):
174
174
  def __init__(self, runner, network: str):
175
- super().__init__('transactions', runner, network=network, endpoint=ENDPOINTS[network]['aragon_finance'])
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(GraphQLCollector):
191
+ class VotesCollector(TheGraphCollector):
192
192
  def __init__(self, runner, network: str):
193
- super().__init__('votes', runner, network=network, endpoint=ENDPOINTS[network]['aragon_voting'])
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(GraphQLRunner):
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
- "--ignore-errors",
30
- default=True,
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='store_true',
90
+ action='store_false',
103
91
  required=False,
104
- default=False,
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 Dict, List, Union, Iterable
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
- logging.debug(f"Invoked ApiRequester with endpoint: {endpoint}")
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]) -> Dict:
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
- logging.debug(f"Requesting: {query}")
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]) -> Dict:
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:DSLType, index='id', last_index: str = "", block_hash: str = None) -> List[Dict]:
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: List[Dict] = list()
132
- result = Dict
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
- logging.warning(f'Invalid api key: {api_key}')
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) -> Dict[str, str]:
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
- logging.warning("Warning in query", r.url, ":", j["Message"])
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: GraphQLCollector, name: str='tokenBalances', network: str='mainnet', addr_key: str='id'):
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
- logging.warning('Skipping token balances because --skip-token-balances flag was set')
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
- logging.warning(f"Status {j['status']}, message: {j['message']}")
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
- logging.warning(f"Too many requests, sleep and retry {retry}/{maxretries} time")
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
- logging.warning(f"Service unavailable, sleep and retry {retry}/{maxretries} time")
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
- logging.warning(f"Requests returned Gateway Time-out, ignoring response for addr {addr}")
95
+ self.logger.warning(f"Requests returned Gateway Time-out, ignoring response for addr {addr}")
98
96
  return pd.DataFrame()
99
97
  else:
100
- logging.error(f'Requests failed for address "{addr}" with status code {r.status_code}: {r.reason}')
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
- logging.warning("No addresses returned, not running blockscout collector")
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 List, Dict, Iterable
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: Dict = json.loads(pkgutil.get_data(cache_scripts.__name__, 'endpoints.json'))
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
@@ -40,6 +57,10 @@ class Collector(ABC):
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
- logging.warning("Empty dataframe, not updating file")
91
+ self.logger.warning("Empty dataframe, not updating file")
71
92
  return
72
93
 
73
94
  if not self.data_path.is_file():
@@ -106,11 +127,12 @@ class UpdatableCollector(Collector): # Flag class
106
127
  pass
107
128
 
108
129
  class Runner(ABC):
109
- def __init__(self, dw: Path = Path()):
130
+ def __init__(self, dw: Path):
110
131
  self.__dw: Path = dw
111
132
 
112
- def set_dw(self, dw) -> Path:
113
- self.__dw = dw
133
+ @property
134
+ def logger(self):
135
+ return logging.getLogger(f'dao_analyzer.runner.{self.name}')
114
136
 
115
137
  @property
116
138
  def cache(self) -> Path:
@@ -122,14 +144,14 @@ class Runner(ABC):
122
144
  return self.__dw / self.name
123
145
 
124
146
  @property
125
- def collectors(self) -> List[Collector]:
147
+ def collectors(self) -> list[Collector]:
126
148
  return []
127
149
 
128
150
  def run(self, **kwargs):
129
151
  raise NotImplementedError
130
152
 
131
153
  class NetworkRunner(Runner, ABC):
132
- def __init__(self, dw = None):
154
+ def __init__(self, dw):
133
155
  super().__init__(dw)
134
156
  self.networks = {n for n,v in ENDPOINTS.items() if self.name in v and not n.startswith('_')}
135
157
 
@@ -138,9 +160,9 @@ class NetworkRunner(Runner, ABC):
138
160
  names: Iterable[str] = [],
139
161
  long_names: Iterable[str] = []
140
162
  ) -> Iterable[Collector]:
141
- result = self.collectors
163
+ result: Iterable[Collector] = self.collectors
142
164
 
143
- if config.only_updatable:
165
+ if config.run_only_updatable:
144
166
  result = filter(lambda c: isinstance(c, UpdatableCollector), result)
145
167
 
146
168
  # networks ^ (names v long_names)
@@ -155,11 +177,11 @@ class NetworkRunner(Runner, ABC):
155
177
  return result
156
178
 
157
179
  def filterCollector(self,
158
- collector_id: str = None,
159
- network: str = None,
160
- name: str = None,
161
- long_name: str = None,
162
- ) -> 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]:
163
185
  if collector_id:
164
186
  return next((c for c in self.collectors if c.collectorid == collector_id), None)
165
187
 
@@ -169,10 +191,9 @@ class NetworkRunner(Runner, ABC):
169
191
  long_names=[long_name] if long_name else []
170
192
  ), None)
171
193
 
172
- @staticmethod
173
194
  @retry(retry=retry_if_exception_type(TransportQueryError), wait=wait_exponential(max=10), stop=stop_after_attempt(3))
174
- def validated_block(network: str, prev_block: Block = None) -> Block:
175
- requester = GQLRequester(ENDPOINTS[network]["_blocks"])
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']))
176
197
  ds = requester.get_schema()
177
198
 
178
199
  number_gte = prev_block.number if prev_block else 0
@@ -187,10 +208,11 @@ class NetworkRunner(Runner, ABC):
187
208
  }
188
209
  }
189
210
 
190
- if config.block_datetime:
211
+ # TODO: SET THE UNTIL_DATE
212
+ if until_date:
191
213
  del args["skip"]
192
214
  del args["where"]["number_gte"]
193
- args["where"]["timestamp_lte"] = int(config.block_datetime.timestamp())
215
+ args["where"]["timestamp_lte"] = int(until_date.timestamp())
194
216
 
195
217
  response = requester.request(ds.Query.blocks(**args).select(
196
218
  ds.Block.id,
@@ -199,13 +221,13 @@ class NetworkRunner(Runner, ABC):
199
221
  ))["blocks"]
200
222
 
201
223
  if len(response) == 0:
202
- logging.warning(f"Blocks query returned no response with args {args}")
224
+ self.logger.warning(f"Blocks query returned no response with args {args}")
203
225
  return prev_block
204
226
 
205
227
  return Block(response[0])
206
228
 
207
229
  @staticmethod
208
- def _verifyCollectors(tocheck: Iterable[Collector]):
230
+ def _verifyCollectors(tocheck: Iterable[Collector]) -> Iterable[Collector]:
209
231
  verified = []
210
232
  for c in tqdm(list(tocheck), desc="Verifying"):
211
233
  try:
@@ -218,7 +240,7 @@ class NetworkRunner(Runner, ABC):
218
240
  traceback.print_exc()
219
241
  return verified
220
242
 
221
- def run(self, networks: List[str] = [], force=False, collectors=None):
243
+ def run(self, networks: list[str] = [], force=False, collectors=None, until_date: Optional[datetime]=None):
222
244
  self.basedir.mkdir(parents=True, exist_ok=True)
223
245
 
224
246
  print("Verifying collectors")
@@ -239,14 +261,18 @@ class NetworkRunner(Runner, ABC):
239
261
  if c.network not in blocks:
240
262
  # Getting a block more recent than the one in the metadata (just to narrow down the search)
241
263
  print("Requesting a block number...", end='\r')
242
- blocks[c.network] = self.validated_block(c.network, None if force else metadata[c.collectorid].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
+ )
243
269
  print(f"Using block {blocks[c.network].id} for {c.network} (ts: {blocks[c.network].timestamp.isoformat()})")
244
270
 
245
271
  print(f"Running collector {c.long_name} ({c.network})")
246
272
  olderBlock = blocks[c.network] < metadata[c.collectorid].block
247
273
  if not force and olderBlock:
248
274
  print("Warning: Forcing because requesting an older block")
249
- logging.warning("Forcing because using an older block")
275
+ self.logger.warning("Forcing because using an older block")
250
276
 
251
277
  # Running the collector
252
278
  c.run(
@@ -266,8 +292,9 @@ class NetworkRunner(Runner, ABC):
266
292
  metadata[c.collectorid].last_update = datetime.now(timezone.utc)
267
293
  except Exception as e:
268
294
  metadata.errors[c.collectorid] = e.__str__()
269
- if config.ignore_errors:
270
- print(traceback.format_exc())
271
- else:
295
+ if config.raise_runner_errors:
272
296
  raise e
297
+ else:
298
+ # TODO: Use a logger instead
299
+ print(traceback.format_exc(), file=sys.stderr)
273
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 --cc-api-key argument or the DAOA_CC_API_KEY env variable.
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.cc_api_key)
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.cc_api_key)
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
- logging.warning(EMPTY_KEY_MSG)
48
+ self.logger.warning(EMPTY_KEY_MSG)
49
49
  return False
50
50
 
51
51
  return super().verify()