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.
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 +61 -29
  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 +21 -21
  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.1.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.1.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.1-py3.11-nspkg.pth +0 -1
  24. dao_scripts-1.2.1.dist-info/RECORD +0 -30
  25. {dao_scripts-1.2.1.dist-info → dao_scripts-1.3.0.dist-info}/entry_points.txt +0 -0
  26. {dao_scripts-1.2.1.dist-info → dao_scripts-1.3.0.dist-info}/namespace_packages.txt +0 -0
  27. {dao_scripts-1.2.1.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.1'
16
- __version_tuple__ = version_tuple = (1, 2, 1)
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
@@ -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
- 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,25 +127,31 @@ 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}')
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) -> List[Collector]:
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 = None):
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.only_updatable:
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]["_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']))
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
- if config.block_datetime:
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(config.block_datetime.timestamp())
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
- logging.warning(f"Blocks query returned no response with args {args}")
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: List[str] = [], force=False, collectors=None):
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(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
+ )
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
- logging.warning("Forcing because using an older block")
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.ignore_errors:
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 --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()