Skip to content

Instantly share code, notes, and snippets.

@bennokr
Last active May 6, 2023 06:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bennokr/ca92b04e95ae84fc37cd3139507ca960 to your computer and use it in GitHub Desktop.
Save bennokr/ca92b04e95ae84fc37cd3139507ca960 to your computer and use it in GitHub Desktop.
Minimal Flask app of an ML competition leaderboard for JupyterHub
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>
"""
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