feat: Add new cache
All checks were successful
Build Docker / BuildImage (push) Successful in 36s

This commit is contained in:
Nathan Woodburn 2024-12-05 12:53:05 +11:00
parent 0ead851279
commit e8d5dc8f9f
Signed by: nathanwoodburn
GPG Key ID: 203B000478AD0EF1
14 changed files with 130 additions and 43 deletions

49
cache.py Normal file
View File

@ -0,0 +1,49 @@
import os
import json
import hashlib
from functools import wraps
from time import time
def file_cache(folder="cache", ttl=300):
"""
Decorator to cache function results in the specified folder with a TTL.
Args:
folder (str): Directory where cached files will be stored.
ttl (int): Time-to-live for the cache in seconds.
"""
if not os.path.exists(folder):
os.makedirs(folder)
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# Create a unique cache key based on the function name and arguments
cache_key = hashlib.md5(
f"{func.__name__}-{args}-{kwargs}".encode("utf-8")
).hexdigest()
cache_file = os.path.join(folder, f"{cache_key}.json")
# Check if cache exists and is valid
if os.path.exists(cache_file):
try:
with open(cache_file, "r") as f:
cached_data = json.load(f)
# Check if the cache has expired
if time() - cached_data["timestamp"] < ttl:
return cached_data["result"]
except (IOError, ValueError, KeyError):
pass # In case of error, re-compute the result
# Call the function and cache the result
result = func(*args, **kwargs)
try:
with open(cache_file, "w") as f:
json.dump({"timestamp": time(), "result": result}, f)
except (IOError, TypeError) as e:
print(f"Warning: Could not cache result: {e}")
return result
return wrapper
return decorator

View File

@ -0,0 +1 @@
{"timestamp": 1733363360.6520803, "result": "120"}

View File

@ -0,0 +1 @@
{"timestamp": 1733363478.77673, "result": 1.14}

View File

@ -0,0 +1 @@
{"timestamp": 1733363478.8822353, "result": 4.16}

View File

@ -0,0 +1 @@
{"timestamp": 1733363358.2618918, "result": 240.01}

View File

@ -0,0 +1 @@
{"timestamp": 1733363355.9661899, "result": 226.21}

View File

@ -0,0 +1 @@
{"timestamp": 1733363361.094433, "result": 120.0}

View File

@ -0,0 +1 @@
{"timestamp": 1733363355.9663823, "result": 1.47422459502}

View File

@ -0,0 +1 @@
{"timestamp": 1733363357.525003, "result": 0.999445}

View File

@ -0,0 +1 @@
{"timestamp": 1733363478.8832045, "result": [{"mint": "jupSoLaHXQiZZTSfEWMTRRgpnyFm8f6sZdosWBjx93v", "balance": 0.039815492, "price": 240.01, "value": 9.55611623492, "name": "Jupiter Staked SOL", "symbol": "jupsol"}, {"mint": "27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4", "balance": 2.402337, "price": 4.16, "value": 9.99372192, "name": "Jupiter Perpetuals Liquidity Provider Token", "symbol": "jlp"}]}

View File

@ -0,0 +1 @@
{"timestamp": 1733363478.7767982, "result": 115.43342152993999}

View File

@ -0,0 +1 @@
{"timestamp": 1733363419.3752136, "result": 82.815227}

View File

@ -4,6 +4,7 @@ import server
from gunicorn.app.base import BaseApplication
import os
import dotenv
import threading
class GunicornApp(BaseApplication):
@ -23,6 +24,7 @@ class GunicornApp(BaseApplication):
if __name__ == '__main__':
dotenv.load_dotenv()
@ -37,6 +39,8 @@ if __name__ == '__main__':
'threads': threads,
}
threading.Thread(target=server.update_data).start()
gunicorn_app = GunicornApp(server.app, options)
print(f'Starting server with {workers} workers and {threads} threads', flush=True)
gunicorn_app.run()

109
server.py
View File

@ -24,6 +24,9 @@ from solana.rpc.types import TokenAccountOpts
from pycoingecko import CoinGeckoAPI
from cachetools import TTLCache
from cachetools import cached
import threading
import time
import cache
dotenv.load_dotenv()
@ -31,8 +34,10 @@ app = Flask(__name__)
solana_client = Client(os.getenv("SOLANA_URL"))
blockFrost_API = os.getenv("BLOCKFROST")
stWDBRN_token_mint = Pubkey.from_string("mNT61ixgiLnggJ4qf5hDCNj7vTiCqnqysosjncrBydf")
vault_sol_address = Pubkey.from_string("NWywvhcqdkJsm1s9VVviPm9UfyDtyCW9t8kDb24PDPN")
stWDBRN_token_mint = Pubkey.from_string(
"mNT61ixgiLnggJ4qf5hDCNj7vTiCqnqysosjncrBydf")
vault_sol_address = Pubkey.from_string(
"NWywvhcqdkJsm1s9VVviPm9UfyDtyCW9t8kDb24PDPN")
vault_cardano_address = "stake1uy4qd785pcds7ph2jue2lrhhxa698c5959375lqdv3yphcgwc8qna"
fiat = "USD"
@ -48,6 +53,8 @@ def find(name, path):
return os.path.join(root, name)
# Assets routes
@app.route("/assets/<path:path>")
def send_assets(path):
if path.endswith(".json"):
@ -102,27 +109,24 @@ def index():
vaultBalance = getVaultBalance()
vaultBalance = "{:.2f}".format(vaultBalance)
# For testing
# tokenSupply = 20
# tokenValue = 1.01
# tokens = [{"symbol":"stWDBRN","name":"Stake With Us","value":1.01}]
# solValue = 10
# vaultBalance = "20.00"
pie_chart_data = [(token['symbol'].upper(), token['value'], f"{token['name']}: ${'{:.2f}'.format(token['value'])}") for token in tokens]
pie_chart_data.append(("SOL", solValue, f"Solana: ${'{:.2f}'.format(solValue)}"))
pie_chart_data = [(token['symbol'].upper(), token['value'], f"{token['name']}: ${
'{:.2f}'.format(token['value'])}") for token in tokens]
pie_chart_data.append(
("SOL", solValue, f"Solana: ${'{:.2f}'.format(solValue)}"))
cardanoBalance = getCardanoValue(vault_cardano_address)
cardanoBalance = "{:.2f}".format(cardanoBalance)
pie_chart_data.append(("ADA", cardanoBalance, f"Cardano: ${cardanoBalance}"))
pie_chart_data.append(
("ADA", cardanoBalance, f"Cardano: ${cardanoBalance}"))
return render_template("index.html", value=tokenValue, supply=tokenSupply, pie_chart=pie_chart_data,vault=vaultBalance)
return render_template("index.html", value=tokenValue, supply=tokenSupply, pie_chart=pie_chart_data, vault=vaultBalance)
@app.route("/<path:path>")
@ -150,52 +154,46 @@ def catch_all(path: str):
# region Solana
coin_price_cache = TTLCache(maxsize=100, ttl=3600)
@cached(coin_price_cache)
@cache.file_cache()
def get_coin_price(coin_id):
try:
if coin_id in stablecoins:
return 1 * usd_to_fiat
price = coingecko_client.get_price(ids=coin_id, vs_currencies=fiat)
return price[coin_id][fiat.lower()]
except:
return 0
token_price_cache = TTLCache(maxsize=100, ttl=3600)
@cached(token_price_cache)
def get_token_price(token_address:str):
@cache.file_cache()
def get_token_price(token_address: str):
try:
price = coingecko_client.get_token_price(id='solana',contract_addresses=token_address,vs_currencies=fiat)
price = coingecko_client.get_token_price(
id='solana', contract_addresses=token_address, vs_currencies=fiat)
return price[token_address][fiat.lower()]
except:
return 0
token_supply_str_cache = TTLCache(maxsize=1, ttl=3600)
@cached(token_supply_str_cache)
@cache.file_cache()
def getTokenSupplyString() -> str:
supply = solana_client.get_token_supply(stWDBRN_token_mint)
return supply.value.ui_amount_string
token_supply_cache = TTLCache(maxsize=1, ttl=3600)
@cached(token_supply_cache)
@cache.file_cache()
def getTokenSupply() -> int:
supply = solana_client.get_token_supply(stWDBRN_token_mint)
return supply.value.ui_amount
sol_value_cache = TTLCache(maxsize=1, ttl=3600)
@cached(sol_value_cache)
@cache.file_cache()
def getSolValue() -> int:
SOLbalance = solana_client.get_balance(vault_sol_address).value / 1000000000
SOLbalance = solana_client.get_balance(
vault_sol_address).value / 1000000000
SOLPrice = get_coin_price("solana")
return SOLbalance * SOLPrice
vault_balance_cache = TTLCache(maxsize=1, ttl=3600)
@cached(vault_balance_cache)
@cache.file_cache()
def getVaultBalance() -> int:
# Get balance of vault
vaultBalance = 0
@ -210,16 +208,15 @@ def getVaultBalance() -> int:
return vaultBalance
get_tokens_cache = TTLCache(maxsize=1, ttl=3600)
@cached(get_tokens_cache)
@cache.file_cache()
def getTokens():
programID = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
programID = Pubkey.from_string(
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
tokenAccounts = solana_client.get_token_accounts_by_owner(
vault_sol_address,
TokenAccountOpts(program_id=programID)
)
vault_sol_address,
TokenAccountOpts(program_id=programID)
)
tokenAccounts = tokenAccounts.value
tokens = []
@ -237,7 +234,7 @@ def getTokens():
token["price"] = get_token_price(token["mint"])
token["value"] = token["price"] * token["balance"]
if token["value"] < 0.01:
continue
@ -257,7 +254,8 @@ def getTokenData(tokenMint):
data = json.load(f)
return data
else:
data = coingecko_client.get_coin_info_from_contract_address_by_id('solana', tokenMint)
data = coingecko_client.get_coin_info_from_contract_address_by_id(
'solana', tokenMint)
with open(f"tokens/{tokenMint}.json", "w") as f:
json.dump(data, f)
return data
@ -270,7 +268,7 @@ def getTokenPrice():
value = vaultBalance/supply
# Round value to 2 decimal places and ensure it shows 2 decimal places
value = "{:.2f}".format(value)
return value
@ -278,11 +276,14 @@ def getTokenPrice():
# region Cardano
get_cardano_balance_cache = TTLCache(maxsize=1, ttl=3600)
@cached(get_cardano_balance_cache)
@cache.file_cache()
def getCardanoBalance(address: str):
# Get balance of cardano address
try:
response = requests.get(f"https://cardano-mainnet.blockfrost.io/api/v0/accounts/{address}",headers={"project_id": blockFrost_API})
response = requests.get(f"https://cardano-mainnet.blockfrost.io/api/v0/accounts/{
address}", headers={"project_id": blockFrost_API})
if response.status_code != 200:
print("Error getting cardano balance")
return 0
@ -295,6 +296,7 @@ def getCardanoBalance(address: str):
print("Error getting cardano balance")
return 0
def getCardanoValue(address: str):
balance = getCardanoBalance(address)
price = get_coin_price("cardano")
@ -304,11 +306,32 @@ def getCardanoValue(address: str):
# region Error Catching
# 404 catch all
@app.errorhandler(404)
def not_found(e):
return render_template("404.html"), 404
# endregion
# region Background Threads
def update_data():
try:
print("Updating Solana data...")
getSolValue()
getTokens()
getVaultBalance()
print("Updating Cardano data...")
getCardanoBalance(vault_cardano_address)
getCardanoValue(vault_cardano_address)
print("Updating data complete.")
except Exception as e:
print(f"Error updating data: {e}")
# endregion
if __name__ == "__main__":
threading.Thread(target=update_data).start()
app.run(debug=True, port=5000, host="0.0.0.0")