generated from nathanwoodburn/python-webserver-template
This commit is contained in:
parent
0ead851279
commit
e8d5dc8f9f
49
cache.py
Normal file
49
cache.py
Normal 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
|
1
cache/10118a51009b13b2592c87579e15e61e.json
vendored
Normal file
1
cache/10118a51009b13b2592c87579e15e61e.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"timestamp": 1733363360.6520803, "result": "120"}
|
1
cache/1ccff5c6f117409fea0c861aa44b8e62.json
vendored
Normal file
1
cache/1ccff5c6f117409fea0c861aa44b8e62.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"timestamp": 1733363478.77673, "result": 1.14}
|
1
cache/29409a8a40dd2d547a7a44b8f6758f54.json
vendored
Normal file
1
cache/29409a8a40dd2d547a7a44b8f6758f54.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"timestamp": 1733363478.8822353, "result": 4.16}
|
1
cache/4104ed0427efe63d4ca0dead970a4391.json
vendored
Normal file
1
cache/4104ed0427efe63d4ca0dead970a4391.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"timestamp": 1733363358.2618918, "result": 240.01}
|
1
cache/598f5dbf97fb0d45cbc6e1a5b0a3b575.json
vendored
Normal file
1
cache/598f5dbf97fb0d45cbc6e1a5b0a3b575.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"timestamp": 1733363355.9661899, "result": 226.21}
|
1
cache/6eec370e2713cfc84c84e1080b8a191a.json
vendored
Normal file
1
cache/6eec370e2713cfc84c84e1080b8a191a.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"timestamp": 1733363361.094433, "result": 120.0}
|
1
cache/94ac30c93587c50252ac382a8d02257f.json
vendored
Normal file
1
cache/94ac30c93587c50252ac382a8d02257f.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"timestamp": 1733363355.9663823, "result": 1.47422459502}
|
1
cache/a071d7bdda25c22e42ad7840f17c4b0e.json
vendored
Normal file
1
cache/a071d7bdda25c22e42ad7840f17c4b0e.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"timestamp": 1733363357.525003, "result": 0.999445}
|
1
cache/a0ee60913ba556f39d128e7d7249e788.json
vendored
Normal file
1
cache/a0ee60913ba556f39d128e7d7249e788.json
vendored
Normal 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"}]}
|
1
cache/b81351778df9f812bbd75ee85a7a073e.json
vendored
Normal file
1
cache/b81351778df9f812bbd75ee85a7a073e.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"timestamp": 1733363478.7767982, "result": 115.43342152993999}
|
1
cache/ccf2a009e56f1b05d471a55d9c9ea8ea.json
vendored
Normal file
1
cache/ccf2a009e56f1b05d471a55d9c9ea8ea.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"timestamp": 1733363419.3752136, "result": 82.815227}
|
4
main.py
4
main.py
@ -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
109
server.py
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user