From 14a5d44b1a914b524d0c65ed60869b23489f7e37 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Mon, 9 Sep 2024 16:20:03 -0700 Subject: [PATCH] Make widgets templates optional (#93) --- README.md | 13 +++++++++++- cookiecutter.json | 4 +++- hooks/post_gen_project.py | 19 ++++++++++++++++++ tests/test_bake_project.py | 20 +++++++++++++++++++ {{ cookiecutter.namespace }}/NEXTSTEPS.md | 11 ++++++---- .../requirements-dev.txt | 2 +- .../__init__.py | 6 ------ 7 files changed, 62 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e47c439..40beecc 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This repo provides a template for creating Neurodata Extensions (NDX) for the [Neurodata Without Borders](https://nwb.org/) data standard. -We currently support creating Neurodata Extensions only using Python 3.8+. +This template currently supports creating Neurodata Extensions only using Python 3.8+. MATLAB support is in development. ## Getting started @@ -59,6 +59,17 @@ By default, to aid with debugging, the project is configured NOT to run code cov https://github.com/nwb-extensions/ndx-template/blob/11ae225b3fd3934fa3c56e6e7b563081793b3b43/%7B%7B%20cookiecutter.namespace%20%7D%7D/pyproject.toml#L82-L83 +## Integrating with NWB Widgets + +When answering the cookiecutter prompts, you will be asked whether you would like to create templates for integration with [NWB Widgets](https://github.com/NeurodataWithoutBorders/nwbwidgets), a library of plotting widgets for interactive visualization of NWB neurodata types within a Jupyter notebook. If you answer "yes", then an example widget and example notebook will be created for you. If you answer "no", but would like to add a widget later on, follow the instructions below: + +1. Create a directory named `widgets` in `src/pynwb/{your_python_package_name}/`. +2. Copy [`__init__.py`](https://github.com/nwb-extensions/ndx-template/blob/main/%7B%7B%20cookiecutter.namespace%20%7D%7D/src/pynwb/%7B%7B%20cookiecutter.py_pkg_name%20%7D%7D/widgets/__init__.py) to that directory and adapt the contents to your extension. +3. Copy [`tetrode_series_widget.py`](https://github.com/nwb-extensions/ndx-template/blob/main/%7B%7B%20cookiecutter.namespace%20%7D%7D/src/pynwb/%7B%7B%20cookiecutter.py_pkg_name%20%7D%7D/widgets/tetrode_series_widget.py) to that directory and adapt the contents to your extension. +4. Create a directory named `notebooks` in the root of the repository. +5. Copy [example.ipynb](https://github.com/nwb-extensions/ndx-template/blob/main/%7B%7B%20cookiecutter.namespace%20%7D%7D/notebooks/example.ipynb) to that directory and adapt the contents to your extension. +6. Add `nwbwidgets` to `requirements-dev.txt`. + ## Maintainers - [@rly](https://github.com/rly) - [@oruebel](https://github.com/oruebel) diff --git a/cookiecutter.json b/cookiecutter.json index e3e6679..994a9dd 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -10,6 +10,7 @@ "license": ["BSD-3", "MIT", "Apache Software License 2.0", "Other"], "py_pkg_name": "{{ cookiecutter.namespace|replace('-', '_') }}", "initialize_git": true, + "widgets": false, "_extensions": ["local_extensions.ZipExtension"], "__prompts__": { "namespace": "Select a name for your extension. It must start with 'ndx-'", @@ -22,6 +23,7 @@ "release": "Select an initial release level", "license": "Select a license", "py_pkg_name": "Select a name for the Python package", - "initialize_git": "Initialize a git repository?" + "initialize_git": "Initialize a git repository?", + "widgets": "Create templates for integration with NWB Widgets (interactive visualization)?" } } diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 5c12530..6a3d8c3 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -1,4 +1,5 @@ from hdmf_docutils.init_sphinx_extension_doc import main as init_sphinx_extension_doc +import shutil from subprocess import check_call import sys @@ -43,11 +44,29 @@ def _initialize_git(): check_call(["git", "branch", "-M", "main"]) +def _remove_widget_files(): + # The template contains example files for NWB Widgets integration. Many extension creators + # do not plan to add widgets, so these files add clutter and potential confusion. If the + # user specifies that they do not want to add widgets, remove these files from the template. + # This is easier than adding the files only if they want to add widgets. + dirs_to_remove = { + "./notebooks", # currently contains only widget demo -- be more specific if others exist + "src/pynwb/{{ cookiecutter.py_pkg_name }}/widgets" + } + for path in dirs_to_remove: + print(f"Deleting directory {path}") + shutil.rmtree(path) + + def main(): """Run the post gen project hook main entry point.""" + if "{{ cookiecutter.widgets }}" == "True": + _remove_widget_files() + _generate_doc() _create_extension_spec() + if "{{ cookiecutter.initialize_git }}" == "True": _initialize_git() diff --git a/tests/test_bake_project.py b/tests/test_bake_project.py index 303c19c..49c1510 100644 --- a/tests/test_bake_project.py +++ b/tests/test_bake_project.py @@ -30,6 +30,26 @@ def test_bake_project_extra(cookies): _check_gen_files(result.project_path, "ndx-test") +def test_bake_project_widgets(cookies): + """Test evaluating the template with widgets.""" + result = cookies.bake(extra_context={"widgets": "yes"}) + + assert result.exit_code == 0 + assert result.exception is None + + for expected_file in [ + "notebooks/example.ipynb", + "src/pynwb/ndx_my_namespace/widgets/__init__.py", + "src/pynwb/ndx_my_namespace/widgets/tetrode_series_widget.py", + "src/pynwb/ndx_my_namespace/widgets/README.md", + ]: + expected_file = os.path.join(result.project_path, expected_file) + assert os.path.exists(expected_file), f"Missing file: {expected_file}" + + with open(expected_file, "r") as fp: + assert fp.read().strip() != "", f"Empty file: {expected_file}" + + def _check_gen_files(project_dir: str, namespace: str): """Test that the correct files are generated after the template is evaluated.""" for expected_file in [ diff --git a/{{ cookiecutter.namespace }}/NEXTSTEPS.md b/{{ cookiecutter.namespace }}/NEXTSTEPS.md index b1c4b40..e896633 100644 --- a/{{ cookiecutter.namespace }}/NEXTSTEPS.md +++ b/{{ cookiecutter.namespace }}/NEXTSTEPS.md @@ -19,7 +19,8 @@ and any other packages required to develop, document, and run your extension. 6. Define API classes for your new extension data types. - - As a starting point, `src/pynwb/__init__.py` includes an example for how to use + - As a starting point, `src/pynwb/{{ cookiecutter.py_pkg_name }}/__init__.py` includes an + example for how to use the `pynwb.get_class` to generate a basic Python class for your new extension data type. This class contains a constructor and properties for the new data type. - Instead of using `pynwb.get_class`, you can define your own custom class for the @@ -28,7 +29,8 @@ and any other packages required to develop, document, and run your extension. [Extending NWB tutorial](https://pynwb.readthedocs.io/en/stable/tutorials/general/extensions.html) for more details. -7. Define tests for your new extension data types in `src/pynwb/tests` or `src/matnwb/tests`. +7. Define tests for your new extension data types in +`src/pynwb/{{ cookiecutter.py_pkg_name }}/tests` or `src/matnwb/tests`. A test for the example `TetrodeSeries` data type is provided as a reference and should be replaced or removed. @@ -49,9 +51,10 @@ replaced or removed. ) 7. (Optional) Define custom visualization widgets for your new extension data types in -`src/pynwb/widgets` so that the visualizations can be displayed with +`src/pynwb/{{ cookiecutter.py_pkg_name }}/widgets` so that the visualizations can be displayed with [nwbwidgets](https://github.com/NeurodataWithoutBorders/nwbwidgets). -You will also need to update the `vis_spec` dictionary in `__init__.py` so that +You will also need to update the `vis_spec` dictionary in +`src/pynwb/{{ cookiecutter.py_pkg_name }}/widgets/__init__.py` so that nwbwidgets can find your custom visualizations. 8. You may need to modify `pyproject.toml` and re-run `python -m pip install -e .` if you diff --git a/{{ cookiecutter.namespace }}/requirements-dev.txt b/{{ cookiecutter.namespace }}/requirements-dev.txt index 864e182..60d17c6 100644 --- a/{{ cookiecutter.namespace }}/requirements-dev.txt +++ b/{{ cookiecutter.namespace }}/requirements-dev.txt @@ -5,7 +5,7 @@ codespell==2.3.0 coverage==7.5.4 hdmf==3.14.1 hdmf-docutils==0.4.7 -nwbwidgets==0.11.3 +{%- if cookiecutter.widgets -%}nwbwidgets==0.11.3{% endif %} pre-commit==3.5.0 # latest pre-commit does not support py3.8 pynwb==2.8.0 pytest==8.2.2 diff --git a/{{ cookiecutter.namespace }}/src/pynwb/{{ cookiecutter.py_pkg_name }}/__init__.py b/{{ cookiecutter.namespace }}/src/pynwb/{{ cookiecutter.py_pkg_name }}/__init__.py index 57de416..9a72ad0 100644 --- a/{{ cookiecutter.namespace }}/src/pynwb/{{ cookiecutter.py_pkg_name }}/__init__.py +++ b/{{ cookiecutter.namespace }}/src/pynwb/{{ cookiecutter.py_pkg_name }}/__init__.py @@ -24,11 +24,5 @@ # `@register_class("TetrodeSeries", "{{ cookiecutter.namespace }}")` TetrodeSeries = get_class("TetrodeSeries", "{{ cookiecutter.namespace }}") -# NOTE: `widgets/tetrode_series_widget.py` adds a "widget" -# attribute to the TetrodeSeries class. This attribute is used by NWBWidgets. -# Delete the `widgets` subpackage or the `tetrode_series_widget.py` module -# if you do not want to define a custom widget for your extension neurodata -# type. - # Remove these functions from the package del load_namespaces, get_class