Skip to content
This repository has been archived by the owner on Feb 1, 2024. It is now read-only.

Commit

Permalink
Custom API demos (#112)
Browse files Browse the repository at this point in the history
The Swagger automatic API client generation is not working, nor likely to work anytime soon (see #62 ). This patch deprecates the automatic Swagger client generation, and includes custom example code for using the API in Matlab (already written), Python (fixed), and Julia (new).
The Julia code should close #96 when acceptable.
  • Loading branch information
mjaquiery committed Jul 3, 2023
1 parent c785744 commit e4a5569
Show file tree
Hide file tree
Showing 7 changed files with 312 additions and 100 deletions.
20 changes: 11 additions & 9 deletions .github/workflows/side-effects.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,17 @@ jobs:
docker-compose -f docker-compose.docs.yml run app python manage.py spectacular --format openapi-json --file schema.json
mv backend/backend_django/schema.* docs/source/resources/
- name: Create API client
run: |
echo "{\"lang\": \"python\", \"type\": \"CLIENT\", \"codegenVersion\": \"V3\", \"spec\": $(cat docs/schema.json)}" > payload.json
curl -d @payload.json --output docs/source/resources/galv-client-python.zip -H "Content-Type: application/json" https://generator3.swagger.io/api/generate
# Check size
if [ ! -s docs/source/resources/galv-client-python.zip ]; then
echo "Downloaded python client zip file is zero bytes"
exit 1
fi
# - name: Create API client
# run: |
# echo "{\"lang\": \"python\", \"type\": \"CLIENT\", \"codegenVersion\": \"V3\", \"spec\": $(cat docs/schema.json)}" > payload.json
# curl -d @payload.json --output docs/source/resources/galv-client-python.zip -H "Content-Type: application/json" https://generator3.swagger.io/api/generate
# # Check size
# if [ ! -s docs/source/resources/galv-client-python.zip ]; then
# echo "Downloaded python client zip file is zero bytes"
# exit 1
# fi
# # Check we can unzip
# unzip -t docs/source/resources/galv-client-python.zip

# Enable tmate debugging of manually-triggered workflows if the input option was provided
- name: Setup tmate session
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ services:
restart: unless-stopped

postgres:
image: "postgres"
image: "postgres:14"
stop_signal: SIGINT # Fast Shutdown mode
volumes:
- "${GALV_DATA_PATH}:/var/lib/postgresql/data"
Expand Down
105 changes: 59 additions & 46 deletions frontend/src/Datasets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import useStyles from "./UseStyles";
import ActionButtons from "./ActionButtons";
import Stack from "@mui/material/Stack";
import DatasetChart from "./DatasetChart";
import GetDatasetJulia from "./GetDatasetJulia";

export type DatasetFields = {
url: string;
Expand Down Expand Up @@ -76,25 +77,9 @@ export default function Datasets() {
{label: 'Actions', help: 'Inspect / Save dataset'}
]

const [codeOpen, setCodeOpen] = React.useState(false);
const [codeOpen, setCodeOpen] = React.useState<string|null>(null);

const handleCodeOpen = () => {
setCodeOpen(true);
};

const handleCodeClose = () => {
setCodeOpen(false);
};

const [codeMatlabOpen, setMatlabCodeOpen] = React.useState(false);

const handleMatlabCodeOpen = () => {
setMatlabCodeOpen(true);
};

const handleMatlabCodeClose = () => {
setMatlabCodeOpen(false);
};
const closeCode = () => setCodeOpen(null);

const get_cell_items = (dataset: DatasetFields) => {
const cellList = Connection.results.get_contents<CellFields>('cells/')
Expand Down Expand Up @@ -266,42 +251,70 @@ export default function Datasets() {
subrow={
<Stack spacing={1} justifyContent="center" alignItems="center">
<Stack spacing={1} justifyContent="center" alignItems="center" direction="row">
<Button
variant="contained" onClick={handleCodeOpen}
className={classes.button}
>
API Code (Python)
</Button>
<Dialog
fullWidth={true}
maxWidth={'md'}
open={codeOpen}
onClose={handleCodeClose}
>
<DialogTitle>
{`API Code (Python) for dataset "${selected?.name}"`}
</DialogTitle>
<DialogContent>
<GetDatasetPython dataset={selected} />
</DialogContent>
<DialogActions>
<Button onClick={handleCodeClose} color="primary" autoFocus>
Close
</Button>
</DialogActions>
</Dialog>
<React.Fragment>
<Button
variant="contained" onClick={handleMatlabCodeOpen}
variant="contained" onClick={() => setCodeOpen("Python")}
className={classes.button}
>
API Code (Python)
</Button>
<Dialog
fullWidth={true}
maxWidth={'md'}
open={codeOpen === "Python"}
onClose={closeCode}
>
<DialogTitle>
{`API Code (Python) for dataset "${selected?.name}"`}
</DialogTitle>
<DialogContent>
<GetDatasetPython dataset={selected} />
</DialogContent>
<DialogActions>
<Button onClick={closeCode} color="primary" autoFocus>
Close
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
<React.Fragment>
<Button
variant="contained" onClick={() => setCodeOpen("Julia")}
className={classes.button}
>
API Code (Julia)
</Button>
<Dialog
fullWidth={true}
maxWidth={'md'}
open={codeOpen === "Julia"}
onClose={closeCode}
>
<DialogTitle>
{`API Code (Julia) for dataset "${selected?.name}"`}
</DialogTitle>
<DialogContent>
<GetDatasetJulia dataset={selected} />
</DialogContent>
<DialogActions>
<Button onClick={closeCode} color="primary" autoFocus>
Close
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
<React.Fragment>
<Button
variant="contained" onClick={() => setCodeOpen("Matlab")}
className={classes.button}
>
API Code (MATLAB)
</Button>
<Dialog
fullWidth={true}
maxWidth={'md'}
open={codeMatlabOpen}
onClose={handleMatlabCodeClose}
open={codeOpen === "Matlab"}
onClose={() => setCodeOpen(null)}
>
<DialogTitle>
{`API Code (MATLAB) for dataset "${selected?.name}"`}
Expand All @@ -310,7 +323,7 @@ export default function Datasets() {
<GetDatasetMatlab dataset={selected} />
</DialogContent>
<DialogActions>
<Button onClick={handleMatlabCodeClose} color="primary" autoFocus>
<Button onClick={closeCode} color="primary" autoFocus>
Close
</Button>
</DialogActions>
Expand Down
159 changes: 159 additions & 0 deletions frontend/src/GetDatasetJulia.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import React, {useState, useEffect} from "react";
import SyntaxHighlighter from 'react-syntax-highlighter';
import { docco } from 'react-syntax-highlighter/dist/cjs/styles/hljs';
import Typography from '@mui/material/Typography';
import Connection from "./APIConnection";
import {CircularProgress} from "@mui/material";


export default function GetDatasetJulia({dataset}) {
const token = Connection.user?.token;
const [columns, setColumns] = useState("")
const [code, setCode] = useState(<CircularProgress/>)

let domain = window.location.href.split('/')[2];
domain = domain.split(':')[0]

const host = `http://api.${domain}`

useEffect(() => {
Promise.all(dataset.columns.map(column =>
Connection.fetch(column)
.then(r => r.content)
.then(col => ` '${col.name}': ${col.id},`)
))
.then(cols => setColumns(cols.join('\n')))
}, [dataset])

useEffect(() => {
if (!columns)
setCode(<CircularProgress/>)
else
setCode(
<SyntaxHighlighter language="julia" style={docco}>{
`# SPDX-License-Identifier: BSD-2-Clause
# Copyright (c) 2020-2023, The Chancellor, Masters and Scholars of the University
# of Oxford, and the 'Galv' Developers. All rights reserved.
using HTTP
using JSON
host = "${host}"
headers = Dict{String, String}("Authorization" => "Bearer ${token}")
# Configuration
verbose = true
dataset_ids = [${dataset.id}]
api_data = Dict{Int, Dict{String, Any}}()
function vprintln(s)
if verbose
println(s)
end
end
function get_column_values(dataset_id, column)
url = column["values"]
if url == ""
return
end
column_name = column["name"]
dtype = column["data_type"]
vprintln("Downloading values for column $dataset_id:$column_name [$url]")
response = HTTP.request("GET", url, headers)
try
body = String(response.body)
str_values = split(body, '\n')
values = Vector{String}(str_values[begin:end-1])
if dtype == "float"
return map((x -> parse(Float64, x)), values)
elseif dtype == "int"
return map((x -> parse(Int64, x)), values)
else
return convert(String, values)
end
catch
println("Error parsing values $dataset_id:$column_name [$url]")
return
end
end
function get_column(dataset_id, url)
vprintln("Downloading column $url")
response = HTTP.request("GET", url, headers)
column = Dict{String, Any}()
try
column = JSON.parse(String(response.body))
catch
println("Error parsing JSON for column $url")
return
end
# Download column values
values = get_column_values(dataset_id, column)
pop!(column, "values", "")
column["values"] = values
return column
end
function get_dataset(id)
vprintln("Downloading dataset $id")
response = HTTP.request("GET", "$host/datasets/$id", headers)
body = Dict{String, Any}()
try
body = JSON.parse(String(response.body))
catch
println("Error parsing JSON for dataset $id")
return
end
api_data[id] = body
# Download columns
columns = api_data[id]["columns"]
len = length(columns)
vprintln("Downloading $len columns for dataset $id")
for (i, col) in enumerate(columns)
timings = @timed column = get_column(id, col)
api_data[id]["columns"][i] = column
n = column["name"]
s = round(timings.time, digits = 2)
vprintln("Column $n completed in $s seconds")
end
vprintln("Completed.")
end
for id in dataset_ids
timings = @timed get_dataset(id)
s = round(timings.time, digits = 2)
vprintln("Completed dataset $id in $s seconds")
end
vprintln("All datasets complete.")
`
}</SyntaxHighlighter>
)
}, [columns, dataset.id, host, token])

return (
<React.Fragment>
<Typography>
Julia Code
</Typography>

{code}
</React.Fragment>
)
}
2 changes: 1 addition & 1 deletion frontend/src/GetDatasetMatlab.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ options = weboptions('HeaderFields', {'Authorization' ['Bearer ' token]});
% You can add in additional dataset_names or dataset_ids to also
% fetch the contents of those datasets.
dataset_names = [];
dataset_ids = [3, 4]; % add additional dataset ids here if required
dataset_ids = [${dataset.id}]; % add additional dataset ids here if required
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Expand Down
Loading

0 comments on commit e4a5569

Please sign in to comment.