Skip to content

Commit

Permalink
Make widgets templates optional (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
rly committed Sep 9, 2024
1 parent 9582d64 commit 14a5d44
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 13 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion cookiecutter.json
Original file line number Diff line number Diff line change
Expand Up @@ -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-'",
Expand All @@ -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)?"
}
}
19 changes: 19 additions & 0 deletions hooks/post_gen_project.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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()

Expand Down
20 changes: 20 additions & 0 deletions tests/test_bake_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 [
Expand Down
11 changes: 7 additions & 4 deletions {{ cookiecutter.namespace }}/NEXTSTEPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion {{ cookiecutter.namespace }}/requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 14a5d44

Please sign in to comment.