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

101
server.py
View File

@ -24,6 +24,9 @@ from solana.rpc.types import TokenAccountOpts
from pycoingecko import CoinGeckoAPI from pycoingecko import CoinGeckoAPI
from cachetools import TTLCache from cachetools import TTLCache
from cachetools import cached from cachetools import cached
import threading
import time
import cache
dotenv.load_dotenv() dotenv.load_dotenv()
@ -31,8 +34,10 @@ app = Flask(__name__)
solana_client = Client(os.getenv("SOLANA_URL")) solana_client = Client(os.getenv("SOLANA_URL"))
blockFrost_API = os.getenv("BLOCKFROST") blockFrost_API = os.getenv("BLOCKFROST")
stWDBRN_token_mint = Pubkey.from_string("mNT61ixgiLnggJ4qf5hDCNj7vTiCqnqysosjncrBydf") stWDBRN_token_mint = Pubkey.from_string(
vault_sol_address = Pubkey.from_string("NWywvhcqdkJsm1s9VVviPm9UfyDtyCW9t8kDb24PDPN") "mNT61ixgiLnggJ4qf5hDCNj7vTiCqnqysosjncrBydf")
vault_sol_address = Pubkey.from_string(
"NWywvhcqdkJsm1s9VVviPm9UfyDtyCW9t8kDb24PDPN")
vault_cardano_address = "stake1uy4qd785pcds7ph2jue2lrhhxa698c5959375lqdv3yphcgwc8qna" vault_cardano_address = "stake1uy4qd785pcds7ph2jue2lrhhxa698c5959375lqdv3yphcgwc8qna"
fiat = "USD" fiat = "USD"
@ -48,6 +53,8 @@ def find(name, path):
return os.path.join(root, name) return os.path.join(root, name)
# Assets routes # Assets routes
@app.route("/assets/<path:path>") @app.route("/assets/<path:path>")
def send_assets(path): def send_assets(path):
if path.endswith(".json"): if path.endswith(".json"):
@ -102,7 +109,6 @@ def index():
vaultBalance = getVaultBalance() vaultBalance = getVaultBalance()
vaultBalance = "{:.2f}".format(vaultBalance) vaultBalance = "{:.2f}".format(vaultBalance)
# For testing # For testing
# tokenSupply = 20 # tokenSupply = 20
# tokenValue = 1.01 # tokenValue = 1.01
@ -110,19 +116,17 @@ def index():
# solValue = 10 # solValue = 10
# vaultBalance = "20.00" # vaultBalance = "20.00"
pie_chart_data = [(token['symbol'].upper(), token['value'], f"{token['name']}: ${
pie_chart_data = [(token['symbol'].upper(), token['value'], f"{token['name']}: ${'{:.2f}'.format(token['value'])}") for token in tokens] '{:.2f}'.format(token['value'])}") for token in tokens]
pie_chart_data.append(("SOL", solValue, f"Solana: ${'{:.2f}'.format(solValue)}")) pie_chart_data.append(
("SOL", solValue, f"Solana: ${'{:.2f}'.format(solValue)}"))
cardanoBalance = getCardanoValue(vault_cardano_address) cardanoBalance = getCardanoValue(vault_cardano_address)
cardanoBalance = "{:.2f}".format(cardanoBalance) 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>") @app.route("/<path:path>")
@ -150,9 +154,7 @@ def catch_all(path: str):
# region Solana # region Solana
@cache.file_cache()
coin_price_cache = TTLCache(maxsize=100, ttl=3600)
@cached(coin_price_cache)
def get_coin_price(coin_id): def get_coin_price(coin_id):
try: try:
@ -165,37 +167,33 @@ def get_coin_price(coin_id):
except: except:
return 0 return 0
token_price_cache = TTLCache(maxsize=100, ttl=3600) @cache.file_cache()
@cached(token_price_cache) def get_token_price(token_address: str):
def get_token_price(token_address:str):
try: 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()] return price[token_address][fiat.lower()]
except: except:
return 0 return 0
@cache.file_cache()
token_supply_str_cache = TTLCache(maxsize=1, ttl=3600)
@cached(token_supply_str_cache)
def getTokenSupplyString() -> str: def getTokenSupplyString() -> str:
supply = solana_client.get_token_supply(stWDBRN_token_mint) supply = solana_client.get_token_supply(stWDBRN_token_mint)
return supply.value.ui_amount_string return supply.value.ui_amount_string
token_supply_cache = TTLCache(maxsize=1, ttl=3600) @cache.file_cache()
@cached(token_supply_cache)
def getTokenSupply() -> int: def getTokenSupply() -> int:
supply = solana_client.get_token_supply(stWDBRN_token_mint) supply = solana_client.get_token_supply(stWDBRN_token_mint)
return supply.value.ui_amount return supply.value.ui_amount
sol_value_cache = TTLCache(maxsize=1, ttl=3600) @cache.file_cache()
@cached(sol_value_cache)
def getSolValue() -> int: 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") SOLPrice = get_coin_price("solana")
return SOLbalance * SOLPrice return SOLbalance * SOLPrice
vault_balance_cache = TTLCache(maxsize=1, ttl=3600) @cache.file_cache()
@cached(vault_balance_cache)
def getVaultBalance() -> int: def getVaultBalance() -> int:
# Get balance of vault # Get balance of vault
vaultBalance = 0 vaultBalance = 0
@ -210,16 +208,15 @@ def getVaultBalance() -> int:
return vaultBalance return vaultBalance
@cache.file_cache()
get_tokens_cache = TTLCache(maxsize=1, ttl=3600)
@cached(get_tokens_cache)
def getTokens(): def getTokens():
programID = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") programID = Pubkey.from_string(
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
tokenAccounts = solana_client.get_token_accounts_by_owner( tokenAccounts = solana_client.get_token_accounts_by_owner(
vault_sol_address, vault_sol_address,
TokenAccountOpts(program_id=programID) TokenAccountOpts(program_id=programID)
) )
tokenAccounts = tokenAccounts.value tokenAccounts = tokenAccounts.value
tokens = [] tokens = []
@ -257,7 +254,8 @@ def getTokenData(tokenMint):
data = json.load(f) data = json.load(f)
return data return data
else: 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: with open(f"tokens/{tokenMint}.json", "w") as f:
json.dump(data, f) json.dump(data, f)
return data return data
@ -278,11 +276,14 @@ def getTokenPrice():
# region Cardano # region Cardano
get_cardano_balance_cache = TTLCache(maxsize=1, ttl=3600) get_cardano_balance_cache = TTLCache(maxsize=1, ttl=3600)
@cached(get_cardano_balance_cache)
@cache.file_cache()
def getCardanoBalance(address: str): def getCardanoBalance(address: str):
# Get balance of cardano address # Get balance of cardano address
try: 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: if response.status_code != 200:
print("Error getting cardano balance") print("Error getting cardano balance")
return 0 return 0
@ -295,6 +296,7 @@ def getCardanoBalance(address: str):
print("Error getting cardano balance") print("Error getting cardano balance")
return 0 return 0
def getCardanoValue(address: str): def getCardanoValue(address: str):
balance = getCardanoBalance(address) balance = getCardanoBalance(address)
price = get_coin_price("cardano") price = get_coin_price("cardano")
@ -304,11 +306,32 @@ def getCardanoValue(address: str):
# region Error Catching # region Error Catching
# 404 catch all # 404 catch all
@app.errorhandler(404) @app.errorhandler(404)
def not_found(e): def not_found(e):
return render_template("404.html"), 404 return render_template("404.html"), 404
# endregion # 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__": if __name__ == "__main__":
threading.Thread(target=update_data).start()
app.run(debug=True, port=5000, host="0.0.0.0") app.run(debug=True, port=5000, host="0.0.0.0")