Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

~~[Refactor Flask III] Break up top-level~~ #796

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added src/__init__.py
Empty file.
4 changes: 2 additions & 2 deletions src/electron/renderer/src/server/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const activateServer = () => {
export const serverGlobals = {
species: new Promise((res, rej) => {
onServerOpen(() => {
fetch(new URL("get-recommended-species", baseUrl))
fetch(new URL("dandi/get-recommended-species", baseUrl))
.then((res) => res.json())
.then((species) => {
res(species)
Expand All @@ -67,7 +67,7 @@ export const serverGlobals = {
}),
cpus: new Promise((res, rej) => {
onServerOpen(() => {
fetch(new URL("cpus", baseUrl))
fetch(new URL("system/cpus", baseUrl))
.then((res) => res.json())
.then((cpus) => {
res(cpus)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ export class PreviewPage extends Page {

updatePath = async (path) => {
if (path) {
const result = await fetch(`${baseUrl}/files/${path}`, { method: "POST" }).then((res) => res.text());
// Enable access to the explicit file path
const result = await fetch(`${baseUrl}/files/${path}`, {
method: "POST",
}).then((res) => res.text());

// Set Neurosift to access the returned URL
if (result) this.neurosift.url = result;
} else this.neurosift.url = undefined;
};
Expand Down
32 changes: 22 additions & 10 deletions src/electron/renderer/src/stories/preview/NWBFilePreview.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { LitElement, css, html } from "lit";
import { InspectorList } from "./inspector/InspectorList";
import { Neurosift, getURLFromFilePath } from "./Neurosift";
import { Neurosift } from "./Neurosift";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { run } from "../pages/guided-mode/options/utils";
import { until } from "lit/directives/until.js";
import { InstanceManager } from "../InstanceManager";
import { path } from "../../electron";
import { FullScreenToggle } from "../FullScreenToggle";
import { baseUrl } from "../../server/globals";

export function getSharedPath(array) {
array = array.map((str) => str.replace(/\\/g, "/")); // Convert to Mac-style path
Expand Down Expand Up @@ -55,15 +56,26 @@ class NWBPreviewInstance extends LitElement {
render() {
const isOnline = navigator.onLine;

return isOnline
? new Neurosift({ url: getURLFromFilePath(this.file, this.project), fullscreen: false })
: until(
(async () => {
const htmlRep = await run("html", { nwbfile_path: this.file }, { swal: false });
return unsafeHTML(htmlRep);
})(),
html`<small>Loading HTML representation...</small>`
);
if (!isOnline)
return until(
(async () => {
const htmlRep = await run("html", { nwbfile_path: this.file }, { swal: false });
return unsafeHTML(htmlRep);
})(),
html`<small>Loading HTML representation...</small>`
);

const neurosift = new Neurosift({ fullscreen: false });

// Enable access to the explicit file path
fetch(`${baseUrl}/files/${this.file}`, { method: "POST" })
.then((res) => res.text())
.then((result) => {
// Set Neurosift to access the returned URL
if (result) neurosift.url = result;
});

return neurosift;
}
}

Expand Down
10 changes: 3 additions & 7 deletions src/electron/renderer/src/stories/preview/Neurosift.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@ import { LitElement, css, html } from "lit";

import { Loader } from "../Loader";
import { FullScreenToggle } from "../FullScreenToggle";
import { baseUrl } from "../../server/globals";

export function getURLFromFilePath(file, projectName) {
const regexp = new RegExp(`.+(${projectName}.+)`);
return `${baseUrl}/preview/${file.match(regexp)[1]}`;
}

export class Neurosift extends LitElement {
static get styles() {
Expand Down Expand Up @@ -80,7 +74,9 @@ export class Neurosift extends LitElement {
render() {
return this.url
? html` <div class="loader-container">
${new Loader({ message: `Loading Neurosift view...<br/><small>${this.url}</small>` })}
${new Loader({
message: `Loading Neurosift view...<br/><small>${this.url}</small>`,
})}
garrettmflynn marked this conversation as resolved.
Show resolved Hide resolved
</div>
${this.fullscreen ? new FullScreenToggle({ target: this }) : ""}
<iframe
Expand Down
Empty file added src/pyflask/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions src/pyflask/apis/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
from .dandi import dandi_api
from .data import data_api
from .neuroconv import neuroconv_api
from .neurosift import neurosift_api
from .startup import startup_api
from .system import system_api

__all__ = [
"neurosift_api",
"dandi_api",
"system_api",
"data_api",
"neuroconv_api",
"startup_api",
]
26 changes: 26 additions & 0 deletions src/pyflask/apis/dandi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""An API for handling requests to the DANDI Python API."""

from typing import List, Tuple, Union

import flask_restx

from .utils import catch_exception_and_abort, server_error_responses

dandi_api = flask_restx.Namespace(
name="dandi", description="Request various static listings from the DANDI Python API."
)


@dandi_api.route(rule="/get-recommended-species")
class SupportedSpecies(flask_restx.Resource):

@dandi_api.doc(
description="Request the list of currently supported species (by Latin Binomial name) for DANDI. Note that any "
"explicit NCBI taxonomy link is also supported.",
responses=server_error_responses(codes=[200, 500]),
)
@catch_exception_and_abort(api=dandi_api, code=500)
def get(self) -> Union[List[Tuple[List[str], str, str, str]], None]:
from dandi.metadata.util import species_map

return species_map
19 changes: 10 additions & 9 deletions src/pyflask/apis/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@

import traceback

from apis.utils import catch_exception_and_abort
from flask_restx import Namespace, Resource, reqparse
from manageNeuroconv import generate_dataset, generate_test_data

data_api = Namespace("data", description="API route for dataset generation in the NWB GUIDE.")
data_api = Namespace(name="data", description="API route for dataset generation in the NWB GUIDE.")


@data_api.errorhandler(Exception)
def exception_handler(error):
exceptiondata = traceback.format_exception(type(error), error, error.__traceback__)
return {"message": exceptiondata[-1], "traceback": "".join(exceptiondata)}
return {"message": exceptiondata[-1], "traceback": "".join(exceptiondata)}, 500
CodyCBakerPhD marked this conversation as resolved.
Show resolved Hide resolved


generate_test_data_parser = reqparse.RequestParser()
Expand All @@ -22,13 +21,17 @@ def exception_handler(error):
@data_api.route("/generate")
@data_api.expect(generate_test_data_parser)
class GeneratetestData(Resource):
@data_api.doc(responses={200: "Success", 400: "Bad Request", 500: "Internal server error"})

# @catch_exception_and_abort(api=data_api, code=500)
@data_api.doc(
description="Generate example ecephys data using SpikeInterface.",
)
def post(self):
arguments = generate_test_data_parser.parse_args()

@data_api.doc(responses={200: "Success", 400: "Bad Request", 500: "Internal server error"})
def post(self):
arguments = generate_test_data_parser.parse_args()

raise NotImplementedError("test")
generate_test_data(output_path=arguments["output_path"])


Expand All @@ -40,10 +43,8 @@ def post(self):
@data_api.route("/generate/dataset")
@data_api.expect(generate_test_data_parser)
class GenerateDataset(Resource):
@data_api.doc(responses={200: "Success", 400: "Bad Request", 500: "Internal server error"})
@catch_exception_and_abort(api=data_api, code=500)

def post(self):
arguments = generate_test_dataset_parser.parse_args()

raise NotImplementedError("test2")
return generate_dataset(input_path=arguments["input_path"], output_path=arguments["output_path"])
13 changes: 8 additions & 5 deletions src/pyflask/apis/neuroconv.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""API endpoint definitions for interacting with NeuroConv."""

import traceback
from typing import Dict

from flask import Response, request
from flask_restx import Namespace, Resource, reqparse
Expand All @@ -24,16 +25,18 @@
)
from manageNeuroconv.info import announcer

neuroconv_api = Namespace("neuroconv", description="Neuroconv neuroconv_api for the NWB GUIDE.")
neuroconv_api = Namespace(name="neuroconv", description="Neuroconv neuroconv_api for the NWB GUIDE.")

parser = reqparse.RequestParser()
parser.add_argument("interfaces", type=str, action="split", help="Interfaces cannot be converted")
parser.add_argument("interfaces", type=str, action="split", help="Interfaces cannot be converted.")


@neuroconv_api.errorhandler(Exception)
def exception_handler(error):
exceptiondata = traceback.format_exception(type(error), error, error.__traceback__)
return {"message": exceptiondata[-1], "traceback": "".join(exceptiondata)}
def exception_handler(error: Exception) -> Dict[str, str]:
full_traceback = traceback.format_exception(type(error), error, error.__traceback__)
message = full_traceback[-1]
remaining_traceback = "".join(full_traceback[:-1])
return {"message": message, "traceback": remaining_traceback}


@neuroconv_api.route("/")
Expand Down
68 changes: 68 additions & 0 deletions src/pyflask/apis/neurosift.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""An API for handling file system communication with the standalone Neurosift preview page."""

import collections
from typing import Union

import flask
import flask_restx

from .utils import (
abort_if_not_nwb_file,
catch_exception_and_abort,
server_error_responses,
)

neurosift_api = flask_restx.Namespace(
name="neurosift", description="Handle file system communication with the " "standalone Neurosift preview page."
)

# A global in-memory registry of NWB files - much faster than asking if file is in a global list
# Keys are NWB files; values are booleans indicating if the base URL has been exposed for that file
neurosift_file_registry = collections.defaultdict(bool)


@neurosift_api.route(rule="/files/<path:file_path>")
@neurosift_api.doc(
description="Handle adding and fetching NWB files from the global file registry.",
)
class NeurosiftFileManager(flask_restx.Resource):

@neurosift_api.doc(
description="If the file path has been added to the registry (and therefore sent its base "
"URL), return the absolute file path. This is implicitly called by Neurosift.",
responses=server_error_responses(codes=[200, 400, 500]),
)
@catch_exception_and_abort(api=neurosift_api, code=500)
def get(self, file_path: str) -> Union[flask.Response, None]:
abort_if_not_nwb_file(file_path=file_path, api=neurosift_api)
if neurosift_file_registry[file_path]:
code = 404
base_message = server_error_responses(codes=[code])[code]
message = f"{base_message}: The base URL has not been exposed for this NWB file."
api.abort(code=code, message=message)

return

# Decode any URL encoding applied to the file path
parsed_file_path = unquote(file_path)

# Check if the file path is relative
is_file_relative = not isabs(parsed_file_path)
if is_file_relative:
parsed_file_path = f"/{parsed_file_path}"

return flask.send_file(path_or_file=parsed_file_path)

@neurosift_api.doc(
description="Add the file to a global in-memory registry (refreshes on App restart) and return "
"the base URL of the newly "
"added file",
responses=server_error_responses(codes=[200, 400, 500]),
)
@catch_exception_and_abort(api=neurosift_api, code=500)
def post(self, file_path: str) -> Union[str, None]:
abort_if_not_nwb_file(file_path=file_path, api=neurosift_api)

neurosift_file_registry[file_path] = True

return request.base_url
2 changes: 1 addition & 1 deletion src/pyflask/apis/startup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from flask_restx import Namespace, Resource

startup_api = Namespace("startup", description="API for startup commands related to the NWB GUIDE.")
startup_api = Namespace(name="startup", description="API for startup commands related to the NWB GUIDE.")

parser = startup_api.parser()
parser.add_argument(
Expand Down
26 changes: 26 additions & 0 deletions src/pyflask/apis/system.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""An API for handling general system information."""

from typing import Dict, Union

import flask_restx

from .utils import catch_exception_and_abort, server_error_responses

system_api = flask_restx.Namespace(name="system", description="Request various system specific information.")


@system_api.route("/cpus")
class SupportedSpecies(flask_restx.Resource):

@system_api.doc(
description="Request the number of physical and logical cores on the system.",
responses=server_error_responses(codes=[200, 500]),
)
@catch_exception_and_abort(api=system_api, code=500)
def get(self) -> Union[Dict[str, int], None]:
from psutil import cpu_count

physical = cpu_count(logical=False)
logical = cpu_count()

return dict(physical=physical, logical=logical)
Loading
Loading