Last active
May 6, 2023 06:57
-
-
Save bennokr/ca92b04e95ae84fc37cd3139507ca960 to your computer and use it in GitHub Desktop.
Minimal Flask app of an ML competition leaderboard for JupyterHub
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from flask import Flask, g, request, render_template_string, redirect, url_for | |
from werkzeug.middleware.proxy_fix import ProxyFix | |
import sqlite3 | |
import pathlib | |
app = Flask(__name__) | |
app.url_map.strict_slashes = False | |
app.wsgi_app = ProxyFix( | |
app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1 | |
) | |
DATABASE = '/srv/scratch/leaderboard.db' | |
INIT=""" | |
CREATE TABLE IF NOT EXISTS board(user VARCHAR, score DOUBLE, time TIMESTAMP) | |
""" | |
GOLD_FILE = '/srv/scratch/solution.csv' | |
def score(pred_file): | |
import pandas as pd | |
gold = pd.read_csv(GOLD_FILE, index_col=[0,1,2,3], usecols=[0,1,2,3,4,6]) | |
pred = pd.read_csv(pred_file, index_col=[0,1,2,3], usecols=[0,1,2,3,4,6]) | |
# accuracy | |
return (pred.sort_index() == gold.sort_index()).prod(axis=1).mean() | |
def setup_leaderboard(): | |
return { | |
"command": ["flask", "--app", "leaderboard", "run", "-p", "{port}"] | |
} | |
def get_db(): | |
db = getattr(g, '_database', None) | |
if db is None: | |
db = g._database = sqlite3.connect(DATABASE) | |
db.row_factory = sqlite3.Row | |
return db | |
def query_db(query, args=(), one=False): | |
cur = get_db().execute(query, args) | |
rv = cur.fetchall() | |
cur.close() | |
return (rv[0] if rv else None) if one else rv | |
@app.teardown_appcontext | |
def close_connection(exception): | |
db = getattr(g, '_database', None) | |
if db is not None: | |
db.close() | |
with app.app_context(): | |
db = get_db() | |
db.cursor().executescript(INIT) | |
db.commit() | |
@app.before_request | |
def clear_trailing(): | |
rp = request.path | |
if rp != '/' and rp.endswith('/'): | |
return redirect(rp[:-1]) | |
@app.route('/submit', methods=['POST']) | |
def index(): | |
tup = (request.form['user'], score(request.form['file']) ) | |
q = 'insert into board(user, score, time) values (?, ?, CURRENT_TIMESTAMP)' | |
db = get_db() | |
db.cursor().execute(q, tup) | |
db.commit() | |
return redirect(request.referrer) | |
@app.route('/') | |
def home(): | |
return redirect(url_for('board')) | |
@app.route('/board') | |
def board(): | |
rows = query_db(""" | |
select user, score, max(datetime(time, 'localtime')) as time | |
from board | |
group by user | |
order by score desc | |
""") | |
files = list(pathlib.Path.home().glob('*.csv')) | |
return render_template_string(PAGE, rows=[dict(r) for r in rows], files=files) | |
PAGE = """ | |
<head> | |
<title>Leaderboard</title> | |
<style> | |
table, th, td { | |
border: 1px solid black; | |
border-collapse: collapse; | |
padding: .5em; | |
} | |
</style> | |
</head> | |
<body> | |
<div style="margin:1em auto; width:60em; max-width:100%"> | |
<h1>Leaderboard</h1> | |
<p>Hello, <script>document.write(window.location.href.split('/')[4])</script>!</p> | |
<p> | |
<table style="width:100%"> | |
<tr><th>Rank</th><th>User</th><th>Score</th><th>Last submission</th></tr> | |
{% for row in rows %} | |
<tr><td>{{loop.index}}</td><td>{{row.user}}</td><td>{{row.score}}</td><td>{{row.time}}</td></tr> | |
{% endfor %} | |
</table> | |
</p> | |
<p> | |
</p> | |
<p> | |
<form action="./submit" name=form method=POST onsubmit="document.form.user.value = window.location.href.split('/')[4]"> | |
New Submission: | |
<select name=file> | |
{% for file in files %} | |
<option value="{{file}}">{{file.name}}</option> | |
{% endfor %} | |
</select> | |
<input type=hidden name=user /> | |
<input type='submit' value="Submit" /> | |
</form> | |
</p> | |
</div> | |
</body> | |
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import setuptools | |
setuptools.setup( | |
name="jupyter-leaderboard-server", | |
# py_modules rather than packages, since we only have 1 file | |
py_modules=["leaderboard"], | |
entry_points={ | |
"jupyter_serverproxy_servers": [ | |
# name = packagename:function_name | |
"leaderboard = leaderboard:setup_leaderboard", | |
] | |
}, | |
install_requires=["jupyter-server-proxy", "flask"], | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment