diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d3f9b3e7f..5cbf5fcc8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 23.9.1 hooks: - id: black exclude: ^docs/ diff --git a/docs/format_support.rst b/docs/format_support.rst new file mode 100644 index 000000000..d2f35083e --- /dev/null +++ b/docs/format_support.rst @@ -0,0 +1,7 @@ +Ecosystem Format Support +======================================= +The following is a live record of all the supported formats in the NWB GUIDE and underlying ecosystem. + +.. raw:: html + + diff --git a/docs/index.rst b/docs/index.rst index 57fa8202f..432034626 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,3 +19,4 @@ The resulting files are fully compliant with the best practices expected of the :maxdepth: 2 developer_guide + format_support diff --git a/environments/environment-Windows.yml b/environments/environment-Windows.yml index 44ef46d0c..eb846dee2 100644 --- a/environments/environment-Windows.yml +++ b/environments/environment-Windows.yml @@ -21,28 +21,3 @@ dependencies: - pytest == 7.2.2 - pytest-cov == 4.1.0 - email-validator == 2.0.0 - -# name: nwb-guide -# channels: -# - conda-forge -# - defaults -# dependencies: -# - python = 3.9.18 -# - PyInstaller = 5.13.0 -# - nodejs = 18.16.1 -# - numcodecs = 0.11.0 # not sure if this is needed -# - pywin32 = 303 # not sure if this is needed -# # install these from conda-forge so that dependent packages get included in the distributable -# - jsonschema = 4.18.0 # installs jsonschema-specifications -# - pydantic[email] = 2.0.2 # installs email-validator -# - pip -# - pip: -# - chardet == 5.1.0 -# - configparser == 6.0.0 -# - flask == 2.3.2 -# - flask-cors == 4.0.0 -# - flask_restx == 1.1.0 -# - neuroconv @ git+https://github.com/catalystneuro/neuroconv.git@main#neuroconv[full] -# - hdmf >= 3.7.0 -# - pytest == 7.4.0 -# - pytest-cov == 4.1.0 diff --git a/pyflask/apis/startup.py b/pyflask/apis/startup.py index 3824a9465..d72ae3cdc 100644 --- a/pyflask/apis/startup.py +++ b/pyflask/apis/startup.py @@ -1,6 +1,8 @@ """API endpoint definitions for startup operations.""" from flask_restx import Namespace, Resource +from errorHandlers import notBadRequestException + startup_api = Namespace("startup", description="API for startup commands related to the NWB GUIDE.") parser = startup_api.parser() @@ -19,3 +21,25 @@ class Echo(Resource): def get(self): args = parser.parse_args() return args["arg"] + + +@startup_api.route("/preload-imports") +class PreloadImports(Resource): + """ + Preload various imports on startup instead of waiting for them later on. + + Python caches all modules that have been imported at least once in the same kernel, + even if their namespace is not always exposed to a given scope. This means that later imports + simply expose the cached namespaces to their scope instead of retriggering the entire import. + """ + + @startup_api.doc(responses={200: "Success", 400: "Bad Request", 500: "Internal server error"}) + def get(self): + try: + import neuroconv + + return True + except Exception as exception: + if notBadRequestException(exception=exception): + startup_api.abort(500, str(exception)) + raise exception diff --git a/pyflask/tests/test_neuroconv.py b/pyflask/tests/test_neuroconv.py index 8c363c004..4f0e793f5 100644 --- a/pyflask/tests/test_neuroconv.py +++ b/pyflask/tests/test_neuroconv.py @@ -2,9 +2,8 @@ from utils import get, post, get_converter_output_schema -# --------------------- Tests --------------------- -# Accesses the dictionary of all interfaces and their metadata def test_get_all_interfaces(client): + """Accesses the dictionary of all interfaces and their metadata.""" validate( get("neuroconv", client), schema={ @@ -23,14 +22,14 @@ def test_get_all_interfaces(client): ) -# Test single interface schema request def test_single_schema_request(client): + """Test single interface schema request.""" interfaces = {"myname": "SpikeGLXRecordingInterface"} validate(post("neuroconv/schema", interfaces, client), schema=get_converter_output_schema(interfaces)) -# Uses the NWBConverter Class to combine multiple interfaces def test_multiple_schema_request(client): + """Uses the NWBConverter Class to combine multiple interfaces.""" interfaces = {"myname": "SpikeGLXRecordingInterface", "myphyinterface": "PhySortingInterface"} data = post("/neuroconv/schema", interfaces, client) validate(data, schema=get_converter_output_schema(interfaces)) diff --git a/pyflask/tests/test_startup.py b/pyflask/tests/test_startup.py new file mode 100644 index 000000000..42cead8c7 --- /dev/null +++ b/pyflask/tests/test_startup.py @@ -0,0 +1,7 @@ +from utils import get, post, get_converter_output_schema + + +def test_preload_imports(client): + """Verify that the preload import endpoint returned good status.""" + result = get("startup/preload-imports", client) + assert result == True diff --git a/src/renderer/src/index.ts b/src/renderer/src/index.ts index efbc4b093..c13b6f258 100644 --- a/src/renderer/src/index.ts +++ b/src/renderer/src/index.ts @@ -94,18 +94,22 @@ async function checkInternetConnection() { return hasInternet }; -// Check if the Pysoda server is live +// Check if the Flask server is live const serverIsLiveStartup = async () => { const echoResponse = await fetch(`${baseUrl}/startup/echo?arg=server ready`).then(res => res.json()).catch(e => e) return echoResponse === "server ready" ? true : false; }; +// Preload Flask imports for faster on-demand operations +const preloadFlaskImports = async () => await fetch(`${baseUrl}/startup/preload-imports`).then(res => res.json()).catch(e => e) + let openPythonStatusNotyf: undefined | any; async function pythonServerOpened() { // Confirm requests are actually received by the server const isLive = await serverIsLiveStartup() + if (isLive) await preloadFlaskImports() // initiate preload of Flask imports if (!isLive) return pythonServerClosed() // Update server status and throw a notification diff --git a/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js b/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js index 93938a8ca..b4c24266d 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedStructure.js @@ -75,9 +75,9 @@ export class GuidedStructurePage extends Page { }; async updated() { - const selected = this.info.globalState.interfaces; + const { interfaces = {} } = this.info.globalState; - if (Object.keys(selected).length > 0) this.list.emptyMessage = "Loading valid interfaces..."; + if (Object.keys(interfaces).length > 0) this.list.emptyMessage = "Loading valid interfaces..."; this.search.options = await fetch(`${baseUrl}/neuroconv`) .then((res) => res.json()) @@ -93,7 +93,7 @@ export class GuidedStructurePage extends Page { ) .catch((e) => console.error(e)); - for (const [key, name] of Object.entries(selected || {})) { + for (const [key, name] of Object.entries(interfaces)) { let found = this.search.options?.find((o) => o.value === name); // If not found, spoof based on the key and names provided previously