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.
- {strideutils-0.3.14 → strideutils-0.4.0}/PKG-INFO +1 -1
- {strideutils-0.3.14 → strideutils-0.4.0}/pyproject.toml +1 -1
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/run_safely.py +25 -6
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/stride_alerts.py +48 -23
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/stride_config.py +23 -11
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/stride_requests.py +30 -1
- {strideutils-0.3.14 → strideutils-0.4.0}/README.md +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/Makefile +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/__init__.py +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/coingecko.py +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/config_examples/strideutils_config.yaml.example +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/config_examples/strideutils_secrets.yaml.example +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/cryptography.py +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/exec_tx.py +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/invariant_helpers.py +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/sheets_connector.py +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/slack_connector.py +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/stride_upstash.py +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/twilio_connector.py +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils/types.py +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/__init__.pyi +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/coingecko.pyi +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/cryptography.pyi +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/exec_tx.pyi +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/invariant_helpers.pyi +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/run_safely.pyi +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/sheets_connector.pyi +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/slack_connector.pyi +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/stride_alerts.pyi +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/stride_config.pyi +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/stride_requests.pyi +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/stride_upstash.pyi +0 -0
- {strideutils-0.3.14 → strideutils-0.4.0}/strideutils-stubs/twilio_connector.pyi +0 -0
|
@@ -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,
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
top_level_message = str(
|
|
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
|
-
|
|
78
|
+
messages_list[0] += "\nβ _This alert is in beta and might be unstable_"
|
|
77
79
|
|
|
78
80
|
if tag:
|
|
79
|
-
|
|
81
|
+
messages_list[0] += f"\n{get_tags(tag)}"
|
|
80
82
|
|
|
81
|
-
slack_connector.post_msg(
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
163
|
-
if dt.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.
|
|
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
|
|
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.
|
|
193
|
-
if
|
|
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
|
-
|
|
349
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{strideutils-0.3.14 → strideutils-0.4.0}/strideutils/config_examples/strideutils_config.yaml.example
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|