Skip to content

Commit

Permalink
add api response handler (#12)
Browse files Browse the repository at this point in the history
- let frontend figure out what to do with data.
- may want to someday allow dynamically build handler on js side
so it's not a giant switch.
  • Loading branch information
chungg authored Dec 22, 2023
1 parent d4181e3 commit 8ab49a2
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 36 deletions.
19 changes: 19 additions & 0 deletions app/api/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import functools
import json
import uuid

import flask

Expand All @@ -16,3 +17,21 @@ def decorated_function(*args, **kwargs):
return resp
return decorated_function
return decorator


def hx_api(f):
"""convert response into js variable in script for htmx to handle"""
@functools.wraps(f)
def wrapper(*args, **kwargs):
resp = f(*args, **kwargs)
if flask.request.headers.get('Hx-Request'):
if not isinstance(resp, str):
resp = json.dumps(resp)
data_id = str(uuid.uuid4())
resp = flask.Response(
'<script id="%s" type="application/json">%s</script>' % (data_id, resp))
resp.headers['HX-Trigger-After-Swap'] = json.dumps(
{'apiResponse': {'origin': flask.request.headers.get('Hx-Current-Url'),
'path': flask.request.path, 'dataId': data_id}})
return resp
return wrapper
27 changes: 4 additions & 23 deletions app/api/v1/data.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,26 @@
import datetime
import json

import flask
from flask import request
import pyarrow.csv as pa_csv
import pyarrow.compute as pc
import numpy as np

from app.api.v1 import bp
from app.api.utils import hx_api
from app.extensions import reqs


@bp.get('/data/random')
@hx_api
def random_data():
label = f'blah{np.random.randint(0, 10000)}'
data = {'datasets': [{'data': np.random.randint(0, 100, 6).tolist(),
'label': label}]}
if request.headers.get('Hx-Request'):
resp = flask.Response(
'<script id="%s" type="application/json">%s</script>' %
(label, json.dumps(data)))
resp.headers['HX-Trigger-After-Swap'] = json.dumps(
{'drawChart': {'target': 'chartId', 'dataId': label}})
return resp

return data


@bp.get('/data/sales')
@hx_api
def sales_data():
# sample processing via duckdb. performance is much worse as of writing
# df = duckdb.read_csv('app/static/data/Monthly_Transportation_Statistics.csv')
Expand All @@ -46,12 +39,6 @@ def sales_data():
{'label': 'auto',
'data': table['Auto sales'].to_pylist()}],
'labels': pc.strftime(table['Date'], format='%Y-%m-%d').to_pylist()}
if request.headers.get('Hx-Request'):
resp = flask.Response(
'<script id="lineChartData" type="application/json">%s</script>' % (json.dumps(data)))
resp.headers['HX-Trigger-After-Swap'] = json.dumps(
{'drawChart': {'target': 'lineChartId', 'dataId': 'lineChartData'}})
return resp
return data


Expand Down Expand Up @@ -96,6 +83,7 @@ def death_data():


@bp.get('/data/market/prices')
@hx_api
def get_prices():
ticker = request.args['ticker']
interval = request.args.get('interval', '1d')
Expand All @@ -110,11 +98,4 @@ def get_prices():
data = res.json()['chart']['result'][0]
data['timestamp'] = np.datetime_as_string(
np.asarray(data['timestamp'], dtype='datetime64[s]'), unit='D').tolist()
if request.headers.get('Hx-Request'):
resp = flask.Response()
resp = flask.Response(
'<script id="priceData" type="application/json">%s</script>' % (json.dumps(data)))
resp.headers['HX-Trigger-After-Swap'] = json.dumps(
{'displayPrices': {'target': 'priceChart', 'dataId': 'priceData'}})
return resp
return data
32 changes: 23 additions & 9 deletions app/static/js/api.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
function updateChart(evt) {
const chart = Chart.getChart(evt.detail.target);
// https://www.reddit.com/r/htmx/comments/10sdk43/comment/j72m2j7/
const data = JSON.parse(document.getElementById(evt.detail.dataId).textContent);
function updateChart(data, target) {
const chart = Chart.getChart(target);
for (const dataset of data.datasets) {
chart.data.datasets.push(dataset);
}
if (data.labels != null) {
chart.data.labels = data.labels;
}
chart.update();
document.getElementById(evt.detail.dataId).remove();
}

function displayPrices(evt) {
const chart = Chart.getChart(evt.detail.target);
function displayPrices(data) {
const chart = Chart.getChart("priceChart");
const table = Tabulator.findTable("#priceTable")[0];
const data = JSON.parse(document.getElementById(evt.detail.dataId).textContent);
const prices = data.indicators.quote[0].close;
// ideally, should check overlap of labels between datasets
table.updateOrAddData(data.timestamp.map((a, i) => ({ date: a, [data.meta.symbol]: prices[i] })));
Expand All @@ -37,7 +33,25 @@ function displayPrices(evt) {
data: prices.map((x) => ((x - prices[0]) / prices[0]) * 100),
});
chart.update();
}

function handler(evt) {
// https://www.reddit.com/r/htmx/comments/10sdk43/comment/j72m2j7/
const data = JSON.parse(document.getElementById(evt.detail.dataId).textContent);
// TODO: improve this so we don't have a giant switch. also need to handle origin
// in case same endpoint displayed differently
switch (evt.detail.path) {
case "/api/v1/data/sales":
updateChart(data, "lineChartId");
break;
case "/api/v1/data/random":
updateChart(data, "chartId");
break;
case "/api/v1/data/market/prices":
displayPrices(data);
break;
}
document.getElementById(evt.detail.dataId).remove();
}

export { displayPrices, updateChart };
export { handler };
6 changes: 2 additions & 4 deletions app/static/js/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { displayPrices, updateChart } from "../../static/js/api.js";
import { handler } from "../../static/js/api.js";
import setupYahooPage from "../../static/js/page.yahoo.js";

const router = (evt) => {
Expand Down Expand Up @@ -29,6 +29,4 @@ window.addEventListener("initPage", router);
// handle browser back/fwd. location.reload() on popstate works as well but is terrible solution
window.addEventListener("htmx:historyRestore", router);

// TODO: use single event and process based on response/url
window.addEventListener("displayPrices", displayPrices);
window.addEventListener("drawChart", updateChart);
window.addEventListener("apiResponse", handler);

0 comments on commit 8ab49a2

Please sign in to comment.