strideutils 0.3.14__tar.gz → 0.4.0__tar.gz

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 (33) hide show
  1. {strideutils-0.3.14 → strideutils-0.4.0}/PKG-INFO +1 -1
  2. {strideutils-0.3.14 → strideutils-0.4.0}/pyproject.toml +1 -1
  3. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/run_safely.py +25 -6
  4. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/stride_alerts.py +48 -23
  5. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/stride_config.py +23 -11
  6. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/stride_requests.py +30 -1
  7. {strideutils-0.3.14 → strideutils-0.4.0}/README.md +0 -0
  8. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/Makefile +0 -0
  9. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/__init__.py +0 -0
  10. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/coingecko.py +0 -0
  11. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/config_examples/strideutils_config.yaml.example +0 -0
  12. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/config_examples/strideutils_secrets.yaml.example +0 -0
  13. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/cryptography.py +0 -0
  14. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/exec_tx.py +0 -0
  15. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/invariant_helpers.py +0 -0
  16. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/sheets_connector.py +0 -0
  17. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/slack_connector.py +0 -0
  18. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/stride_upstash.py +0 -0
  19. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/twilio_connector.py +0 -0
  20. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/types.py +0 -0
  21. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/__init__.pyi +0 -0
  22. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/coingecko.pyi +0 -0
  23. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/cryptography.pyi +0 -0
  24. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/exec_tx.pyi +0 -0
  25. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/invariant_helpers.pyi +0 -0
  26. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/run_safely.pyi +0 -0
  27. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/sheets_connector.pyi +0 -0
  28. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/slack_connector.pyi +0 -0
  29. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/stride_alerts.pyi +0 -0
  30. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/stride_config.pyi +0 -0
  31. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/stride_requests.pyi +0 -0
  32. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/stride_upstash.pyi +0 -0
  33. {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/twilio_connector.pyi +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: strideutils
3
- Version: 0.3.14
3
+ Version: 0.4.0
4
4
  Summary:
5
5
  Author: Your Name
6
6
  Author-email: you@example.com
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "strideutils"
3
- version = "0.3.14"
3
+ version = "0.4.0"
4
4
  description = ""
5
5
  authors = ["Your Name <you@example.com>"]
6
6
  readme = "README.md"
@@ -6,7 +6,11 @@ from strideutils.stride_config import config
6
6
 
7
7
 
8
8
  def try_and_log_with_status(
9
- *functions: Callable, slack_channel: str = 'alerts-status', job_name: str = "alerts", botname: Optional[str] = None
9
+ *functions: Callable,
10
+ slack_channel: str = 'alerts-status',
11
+ job_name: str = "alerts",
12
+ botname: Optional[str] = None,
13
+ print_status: bool = False,
10
14
  ):
11
15
  """
12
16
  Wrap functions in a try/catch and log exceptions.
@@ -21,6 +25,7 @@ def try_and_log_with_status(
21
25
  """
22
26
  if config.ENV == 'DEV':
23
27
  slack_channel = 'alerts-debug'
28
+ print_status = True
24
29
 
25
30
  tracebacks = []
26
31
  msgs = []
@@ -29,16 +34,30 @@ def try_and_log_with_status(
29
34
  # (to cover cases where click was used)
30
35
  func_name = getattr(func, 'name', None) or func.__name__
31
36
  try:
32
- print(f"Running {func_name}...", end="")
33
- func()
34
- print("✅")
35
- msgs += [f':white_check_mark: {func_name}']
37
+ if print_status:
38
+ print(f"Running {func_name}...", end="")
39
+
40
+ # If the function was wrapped in a run_daily/run_epochly decorator,
41
+ # the function will return a bool indicating if the function was executed
42
+ # If it was skipped, use a different emoji
43
+ output = func()
44
+ func_skipped = len(output) == 2 and not output[0]
45
+
46
+ emoji_print = "⚪" if func_skipped else "✅"
47
+ emoji_slack = ":white_circle:" if func_skipped else ":white_check_mark:"
48
+ msgs += [f'{emoji_slack} {func_name}']
49
+
50
+ if print_status:
51
+ print(emoji_print)
52
+
36
53
  except Exception:
37
- print("❌")
38
54
  print(traceback.format_exc())
39
55
  msgs += [f':x: {func_name} ']
40
56
  tracebacks.append(f"{func_name} \n\n" + traceback.format_exc())
41
57
 
58
+ if print_status:
59
+ print("❌")
60
+
42
61
  count_succeeded_msg = f"{len(functions) - len(tracebacks)}/{len(functions)} {job_name} succeeded"
43
62
  if len(tracebacks) > 0:
44
63
  status_msg = f':x: {count_succeeded_msg}'
@@ -2,7 +2,7 @@ import datetime
2
2
  import os
3
3
  from enum import Enum
4
4
  from functools import wraps
5
- from typing import Callable, Iterable, List, Optional, Union, cast
5
+ from typing import Any, Callable, Iterable, List, Optional, Tuple, Union, cast
6
6
 
7
7
  import pandas as pd
8
8
  import tabulate
@@ -10,6 +10,8 @@ import tabulate
10
10
  from strideutils import slack_connector, twilio_connector
11
11
  from strideutils.stride_config import config
12
12
 
13
+ DEFAULT_DAILY_UTC_HOUR = 17
14
+
13
15
 
14
16
  class AlertType(Enum):
15
17
  DEBUG = 'debug'
@@ -38,7 +40,7 @@ def wrap_in_code_block(message: str) -> str:
38
40
 
39
41
 
40
42
  def notify(
41
- msgs: Union[str, List[str]],
43
+ messages: Union[str, List[str]],
42
44
  urgency,
43
45
  tag: Optional[Union[str, Iterable[str]]] = None,
44
46
  call: Optional[Union[str, Iterable[str]]] = None,
@@ -57,8 +59,8 @@ def notify(
57
59
  """
58
60
  urgency = AlertType(urgency)
59
61
 
60
- msgs: List = cast(List, [msgs] if type(msgs) is str else msgs)
61
- top_level_message = str(msgs[0])
62
+ messages_list: List = cast(List, [messages] if type(messages) is str else messages)
63
+ top_level_message = str(messages_list[0])
62
64
 
63
65
  debug_msg = f"Read more: {debug_readme}{debug_readme_section}"
64
66
 
@@ -73,12 +75,12 @@ def notify(
73
75
  channel = "#alerts-critical"
74
76
 
75
77
  if beta_alert:
76
- msgs[0] += "\nβ _This alert is in beta and might be unstable_"
78
+ messages_list[0] += "\nβ _This alert is in beta and might be unstable_"
77
79
 
78
80
  if tag:
79
- msgs[0] += f"\n{get_tags(tag)}"
81
+ messages_list[0] += f"\n{get_tags(tag)}"
80
82
 
81
- slack_connector.post_msg(msgs + [debug_msg], channel)
83
+ slack_connector.post_msg(messages_list + [debug_msg], channel)
82
84
 
83
85
  if call:
84
86
  twilio_connector.send_calls(top_level_message, call)
@@ -110,7 +112,8 @@ def raise_alert(
110
112
  def _decorator(f: Callable):
111
113
  @wraps(f)
112
114
  def _func(*args, **kwargs):
113
- msgs = f(*args, **kwargs)
115
+ output = f(*args, **kwargs)
116
+ msgs = output[-1] if type(output) is Tuple else output
114
117
  if urgency == 'debug':
115
118
  debug_msg = (
116
119
  'In debug mode. Tags and calls have been disabled' 'and the message is routed to alerts-debug.'
@@ -119,7 +122,7 @@ def raise_alert(
119
122
  msgs += [debug_msg]
120
123
  if msgs:
121
124
  notify(
122
- msgs=msgs,
125
+ messages=msgs,
123
126
  urgency=urgency,
124
127
  tag=tag,
125
128
  call=call,
@@ -150,18 +153,19 @@ def run_daily(utc_hour_or_alert: Union[int, Callable]) -> Callable:
150
153
  def bar():
151
154
  ...
152
155
  ```
153
- """
154
- DEFAULT_UTC_HOUR = 17
155
156
 
157
+ The returned function also returns a bool to indicate if the function was invoked
158
+ """
156
159
  # If used without an argument, act as an unparameterized decorator
157
160
  if callable(utc_hour_or_alert):
158
161
  alert = utc_hour_or_alert
159
162
 
160
163
  @wraps(alert)
161
- def _func(*args, **kwargs):
162
- dt = datetime.datetime.utcnow()
163
- if dt.hour == DEFAULT_UTC_HOUR:
164
- return alert(*args, **kwargs)
164
+ def _func(*args, **kwargs) -> Tuple[bool, Any]:
165
+ dt = datetime.datetime.now(tz=datetime.timezone.utc)
166
+ if config.ENV == "DEV" or dt.hour == DEFAULT_DAILY_UTC_HOUR:
167
+ return True, alert(*args, **kwargs)
168
+ return False, None
165
169
 
166
170
  return _func
167
171
 
@@ -173,24 +177,45 @@ def run_daily(utc_hour_or_alert: Union[int, Callable]) -> Callable:
173
177
  def _decorator(f: Callable):
174
178
  @wraps(f)
175
179
  def _func(*args, **kwargs):
176
- dt = datetime.datetime.utcnow()
177
- if dt.hour == utc_hour:
178
- return f(*args, **kwargs)
180
+ dt = datetime.datetime.now(tz=datetime.timezone.utc)
181
+ if config.ENV == "DEV" or dt.hour == utc_hour:
182
+ return True, f(*args, **kwargs)
183
+ return False, None
179
184
 
180
185
  return _func
181
186
 
182
187
  return _decorator
183
188
 
184
189
 
185
- def run_epochly(f: Callable) -> Callable:
190
+ def run_stride_epochly(f: Callable) -> Callable:
186
191
  """
187
192
  Modifies an function to run once per stride epoch
193
+ Returns the new function returns a bool indicating if the function was executed
194
+ When running in DEV mode, the function always executes
195
+ """
196
+
197
+ @wraps(f)
198
+ def _func(*args, **kwargs) -> Tuple[bool, Any]:
199
+ dt = datetime.datetime.now(tz=datetime.timezone.utc)
200
+ if config.ENV == "DEV" or dt.hour in {1, 7, 13, 19}:
201
+ return True, f(*args, **kwargs)
202
+ return False, None
203
+
204
+ return _func
205
+
206
+
207
+ def run_day_epochly(f: Callable) -> Callable:
208
+ """
209
+ Modifies an function to run once per day epoch
210
+ Returns the new function returns a bool indicating if the function was executed
211
+ When running in DEV mode, the function always executes
188
212
  """
189
213
 
190
214
  @wraps(f)
191
- def _func(*args, **kwargs):
192
- dt = datetime.datetime.utcnow()
193
- if dt.hour in {1, 7, 13, 19}:
194
- return f(*args, **kwargs)
215
+ def _func(*args, **kwargs) -> Tuple[bool, Any]:
216
+ dt = datetime.datetime.now(tz=datetime.timezone.utc)
217
+ if config.ENV == "DEV" or dt.hour == 19:
218
+ return True, f(*args, **kwargs)
219
+ return False, None
195
220
 
196
221
  return _func
@@ -78,6 +78,7 @@ class ChainConfig(ConfigObj):
78
78
  ticker: str = ""
79
79
  api_endpoint: str = ""
80
80
  rpc_endpoint: str = ""
81
+ evm_chain: bool = False
81
82
 
82
83
  def __repr__(self) -> str:
83
84
  return f"ChainConfig(name={self.name}, id={self.id})"
@@ -344,25 +345,36 @@ def _load_raw_secrets() -> dict[str, Optional[str]]:
344
345
  def _parse_raw_configs(raw_config: dict[str, Any], raw_secrets: dict[str, Optional[str]]) -> Config:
345
346
  """
346
347
  Builds the relevant config data classes from the raw jsons
348
+
349
+ When parsing into the dataclasses, only consider fields that are defined in the dataclass
350
+ so that the package maintains backwards compatibility when new fields are added
347
351
  """
348
- host_chain_fields = [field.name for field in fields(HostChainsConfig)]
349
- app_chain_fields = [field.name for field in fields(AppChainsConfig)]
352
+ host_chain_names = [field.name for field in fields(HostChainsConfig)]
353
+ app_chain_names = [field.name for field in fields(AppChainsConfig)]
350
354
 
351
- host_chain_config_dict = {
352
- name: HostChainConfig(**info) for name, info in raw_config["host_zones"].items() if name in host_chain_fields
353
- }
354
- app_chain_config_dict = {
355
- name: ChainConfig(**info) for name, info in raw_config["app_chains"].items() if name in app_chain_fields
356
- }
355
+ host_chain_fields = [field.name for field in fields(HostChainConfig)]
356
+ app_chain_fields = [field.name for field in fields(ChainConfig)]
357
+
358
+ host_chain_config_dict = {}
359
+ for name, info in raw_config["host_zones"].items():
360
+ if name in host_chain_names:
361
+ filtered_info = {key: value for key, value in info.items() if key in host_chain_fields}
362
+ host_chain_config_dict[name] = HostChainConfig(**filtered_info)
363
+
364
+ app_chain_config_dict = {}
365
+ for name, info in raw_config["app_chains"].items():
366
+ if name in app_chain_names:
367
+ filtered_info = {key: value for key, value in info.items() if key in app_chain_fields}
368
+ app_chain_config_dict[name] = ChainConfig(**filtered_info)
357
369
 
358
370
  stride_chain_config = StrideChainConfig(**raw_config["stride"])
359
371
  host_zone_config = HostChainsConfig(**host_chain_config_dict)
360
372
  app_chain_config = AppChainsConfig(**app_chain_config_dict)
361
373
 
362
374
  chain_configs = {
363
- 'stride': stride_chain_config,
364
- 'host_zones': host_zone_config,
365
- 'app_chains': app_chain_config,
375
+ "stride": stride_chain_config,
376
+ "host_zones": host_zone_config,
377
+ "app_chains": app_chain_config,
366
378
  }
367
379
 
368
380
  # Load non-nested configs, then overwrite with the chain configs
@@ -68,6 +68,7 @@ def request(
68
68
  url: str,
69
69
  headers: dict = {},
70
70
  params: Union[dict, List[Tuple[str, str]]] = {},
71
+ data: Optional[str] = None,
71
72
  block_height: int = 0,
72
73
  cache_response: bool = False,
73
74
  _cache: Dict[str, Any] = {},
@@ -96,7 +97,7 @@ def request(
96
97
  if type(params) is dict:
97
98
  params = list(params.items())
98
99
 
99
- resp = requests_session.get(url, headers=headers, params=params)
100
+ resp = requests_session.get(url, headers=headers, params=params, data=data)
100
101
  if not resp.ok: # resp.ok is true if state_code is less than 400
101
102
  print(f"Error fetching {url}: {resp.status_code} {resp.json()}")
102
103
  raise Exception(f"Error fetching {url}: {resp.status_code} {resp.json()}")
@@ -1218,3 +1219,31 @@ def get_empirical_annual_fees(ticker: str) -> float:
1218
1219
  stride_upstash.get("sheet_STRD_STAKING_ST{}_DOLLARS".format(ticker.upper()), db_name="frontend") or "0"
1219
1220
  )
1220
1221
  return float(cast(str, empirical_dollar_fees))
1222
+
1223
+
1224
+ def query_evm_method(method: str, params: list[str], rpc_endpoint: str) -> str:
1225
+ """
1226
+ Generic querier for evm contract calls
1227
+ Returns a hex endcoded response specific to the contract call
1228
+ """
1229
+ headers = {"Content-Type": "application/json"}
1230
+ payload = {
1231
+ "jsonrpc": "2.0",
1232
+ "method": method,
1233
+ "params": params,
1234
+ "id": 1,
1235
+ }
1236
+
1237
+ response = request(rpc_endpoint, headers=headers, data=json.dumps(payload))
1238
+ return response["result"]
1239
+
1240
+
1241
+ def get_evm_balance(address: str, rpc_endpoint: str) -> int:
1242
+ """
1243
+ Queries the native token balance of an evm address
1244
+ Address should be a hex address with an 0x prefix
1245
+ """
1246
+ params = [address, "latest"]
1247
+ balance_raw = query_evm_method("eth_getBalance", params=params, rpc_endpoint=rpc_endpoint)
1248
+ balance = int(balance_raw, 16)
1249
+ return balance
File without changes