From f48f48340c71a50624f553385dd8a0d3950ecfed Mon Sep 17 00:00:00 2001 From: rly <310197+rly@users.noreply.github.com> Date: Tue, 23 Jun 2026 01:28:01 -0700 Subject: [PATCH 1/7] Prepare PyNWB 4.0.0 release: update copyright year to 2026 Update the copyright year from 2017-2025 to 2017-2026 in Legal.txt, license.txt, README.rst, and docs/source/conf.py. Co-Authored-By: Claude Opus 4.8 (1M context) --- Legal.txt | 2 +- README.rst | 4 ++-- docs/source/conf.py | 2 +- license.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Legal.txt b/Legal.txt index c16773cbb..fdcfe2d57 100644 --- a/Legal.txt +++ b/Legal.txt @@ -1,4 +1,4 @@ -“pynwb” Copyright (c) 2017-2025, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. +“pynwb” Copyright (c) 2017-2026, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. If you have questions about your rights to use or distribute this software, please contact Berkeley Lab's Innovation & Partnerships Office at IPO@lbl.gov. diff --git a/README.rst b/README.rst index d23d0a08b..cd3f304b3 100644 --- a/README.rst +++ b/README.rst @@ -99,7 +99,7 @@ Citing NWB LICENSE ======= -"pynwb" Copyright (c) 2017-2025, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. +"pynwb" Copyright (c) 2017-2026, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. @@ -115,7 +115,7 @@ You are under no obligation whatsoever to provide any bug fixes, patches, or upg COPYRIGHT ========= -"pynwb" Copyright (c) 2017-2025, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. +"pynwb" Copyright (c) 2017-2026, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. If you have questions about your rights to use or distribute this software, please contact Berkeley Lab's Innovation & Partnerships Office at IPO@lbl.gov. NOTICE. This Software was developed under funding from the U.S. Department of Energy and the U.S. Government consequently retains certain rights. As such, the U.S. Government has been granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, worldwide license in the Software to reproduce, distribute copies to the public, prepare derivative works, and perform publicly and display publicly, and to permit other to do so. diff --git a/docs/source/conf.py b/docs/source/conf.py index 71d963d7d..6339efc24 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -197,7 +197,7 @@ def __call__(self, filename): # General information about the project. project = u'PyNWB' -copyright = u'2017-2025, Neurodata Without Borders' +copyright = u'2017-2026, Neurodata Without Borders' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/license.txt b/license.txt index c6d771edb..2d3b5b239 100644 --- a/license.txt +++ b/license.txt @@ -1,4 +1,4 @@ -“pynwb” Copyright (c) 2017-2025, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. +“pynwb” Copyright (c) 2017-2026, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: From 54bd730625919658ba83240908ad1c2bb143c9ab Mon Sep 17 00:00:00 2001 From: rly <310197+rly@users.noreply.github.com> Date: Tue, 23 Jun 2026 01:36:50 -0700 Subject: [PATCH 2/7] Prepare PyNWB 4.0.0 release: bump pinned dependencies Bump pinned dependency versions to the latest compatible releases via `pur` on requirements.txt, requirements-dev.txt, and requirements-opt.txt (requirements.txt and requirements-dev.txt were already current). Sync the matching linkml-runtime pin in requirements-doc.txt and manually update environment-ros3.yml, keeping its shared pins consistent with the resolved versions (numpy and pandas stay within hdmf's and pynwb's ceilings) and bumping the ros3-only matplotlib and dandi to latest. Co-Authored-By: Claude Opus 4.8 (1M context) --- environment-ros3.yml | 18 +++++++++--------- requirements-doc.txt | 2 +- requirements-opt.txt | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/environment-ros3.yml b/environment-ros3.yml index c10ad9bd7..348d6d25b 100644 --- a/environment-ros3.yml +++ b/environment-ros3.yml @@ -5,18 +5,18 @@ channels: - defaults dependencies: - python==3.14 - - h5py==3.15.1 + - h5py==3.16.0 - hdmf==6.0.2 - - matplotlib==3.10.8 - - numpy==2.4.2 + - matplotlib==3.11.0 + - numpy==2.4.3 - pandas==2.3.3 - - python-dateutil==2.9.0 + - python-dateutil==2.9.0.post0 - setuptools - - pytest==9.0.2 - - fsspec==2026.1.0 - - requests==2.32.5 - - aiohttp==3.13.3 + - pytest==9.0.3 + - fsspec==2026.6.0 + - requests==2.34.2 + - aiohttp==3.14.1 - pip - pip: - remfile==0.1.13 - - dandi==0.74.3 # NOTE: dandi is not available on conda for osx-arm64 + - dandi==0.76.4 # NOTE: dandi is not available on conda for osx-arm64 diff --git a/requirements-doc.txt b/requirements-doc.txt index 5a0186625..f586f3d8d 100644 --- a/requirements-doc.txt +++ b/requirements-doc.txt @@ -12,7 +12,7 @@ hdf5plugin dandi>=0.46.6 hdmf-zarr>=0.3.0 zarr<3 # limited to zarr<3 until hdmf-zarr resolves issues with zarr 3.0 -linkml-runtime==1.9.3; python_version >= "3.10" +linkml-runtime==1.11.1; python_version >= "3.10" schemasheets==0.4.0; python_version >= "3.10" oaklib==0.6.23; python_version >= "3.10" pooch \ No newline at end of file diff --git a/requirements-opt.txt b/requirements-opt.txt index b9cbfcbb1..c079a1dd3 100644 --- a/requirements-opt.txt +++ b/requirements-opt.txt @@ -1,10 +1,10 @@ -linkml-runtime==1.9.3 +linkml-runtime==1.11.1 schemasheets==0.4.0 oaklib==0.6.23 # for streaming tests -fsspec==2025.5.1 -requests==2.33.0 +fsspec==2026.6.0 +requests==2.34.2 aiohttp==3.14.1 # For read_nwb tests From 4748d5b51acf21ec299b313274bf0e5c7f382cca Mon Sep 17 00:00:00 2001 From: rly <310197+rly@users.noreply.github.com> Date: Mon, 29 Jun 2026 10:16:20 -0700 Subject: [PATCH 3/7] Pass aws_region to ros3 driver in tests and streaming tutorial HDF5 2.1.0 (h5py 3.16.0) requires the AWS region to be specified when opening an S3 URL with the ros3 driver, which broke the ROS3 streaming tests and the streaming gallery example. Pass aws_region="us-east-2" (the DANDI Archive S3 bucket region) at each ros3 open site, and add an aws_region argument to validate() so it can be forwarded when validating a file opened with the ros3 driver. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 1 + docs/gallery/advanced_io/streaming.py | 9 +++++---- src/pynwb/validation.py | 21 ++++++++++++++------- tests/integration/ros3/test_ros3.py | 8 ++++---- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b233f4bc..c52c32505 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Added pandas 3.0 compatibility. `NWBFile.add_scratch` now accepts a `pandas.Series` or a pandas `ExtensionArray` (e.g., `StringArray` and `ArrowStringArray`) as `data`, which is normalized to numpy when constructing `ScratchData`. This relies on the coercion added in [hdmf-dev/hdmf#1469](https://github.com/hdmf-dev/hdmf/pull/1469) (HDMF 6.1.0), which also makes the inherited PyArrow-backed string columns from pandas 3 DataFrames work across `TimeSeries` subclasses, `add_unit`, `add_electrode`, and `DynamicTable.from_dataframe`. The `pandas<3` cap has been lifted. @rly [#2208](https://github.com/NeurodataWithoutBorders/pynwb/pull/2208) ### Fixed +- Fixed ROS3 streaming and added an `aws_region` argument to `validate` so the AWS region can be passed through when validating a file opened with the `ros3` driver. HDF5 2.1.0 (h5py 3.16.0) requires the AWS region to be specified when opening an S3 URL with the `ros3` driver, so the ROS3 tests and the streaming tutorial now pass `aws_region="us-east-2"` (the region of the DANDI Archive S3 bucket). @rly [#2201](https://github.com/NeurodataWithoutBorders/pynwb/pull/2201) - Fixed reading legacy files where `Device.model` is a string containing `/` or `:` (e.g., `"MFC_200/250-0.66_40mm"`), which previously raised a `ValueError`. The string is now remapped to a read-only `DeviceModel` that preserves the original name, with a warning explaining that the file cannot be written or exported until a `DeviceModel` with a valid name is created. Writing or exporting such a `DeviceModel` raises a clear error instead of silently corrupting the file. @rly [#2186](https://github.com/NeurodataWithoutBorders/pynwb/pull/2186) - Fixed invalid CSS properties in documentation assistant toggle that prevented proper positioning on displays ≥1400px wide. @rly [#2151](https://github.com/NeurodataWithoutBorders/pynwb/pull/2151) diff --git a/docs/gallery/advanced_io/streaming.py b/docs/gallery/advanced_io/streaming.py index 428ebe300..47d1dc940 100644 --- a/docs/gallery/advanced_io/streaming.py +++ b/docs/gallery/advanced_io/streaming.py @@ -173,19 +173,20 @@ # ------------------------ # ROS3 stands for "read only S3" and is a driver created by the HDF5 Group that allows HDF5 to read HDF5 files stored # remotely in s3 buckets. Using this method requires that your HDF5 library is installed with the ROS3 driver enabled. -# With ROS3 support enabled in h5py, we can instantiate a :py:class:`~pynwb.NWBHDF5IO` object with the S3 URL and -# specify the driver as "ros3". Like the other methods, you can use a context manager to open the file and close it, +# With ROS3 support enabled in h5py, we can instantiate a :py:class:`~pynwb.NWBHDF5IO` object with the S3 URL, +# specify the driver as "ros3", and pass the AWS region of the S3 bucket as ``aws_region`` (DANDI data are stored +# in ``us-east-2``). Like the other methods, you can use a context manager to open the file and close it, # or open the file and close it manually. from pynwb import NWBHDF5IO # open with context manager -with NWBHDF5IO(s3_url, mode='r', driver='ros3') as io: +with NWBHDF5IO(s3_url, mode='r', driver='ros3', aws_region="us-east-2") as io: nwbfile = io.read() streamed_data = nwbfile.acquisition['lick_times'].time_series['lick_left_times'].data[:] # open and close manually -io = NWBHDF5IO(s3_url, mode='r', driver='ros3') +io = NWBHDF5IO(s3_url, mode='r', driver='ros3', aws_region="us-east-2") nwbfile = io.read() streamed_data = nwbfile.acquisition['lick_times'].time_series['lick_left_times'].data[:] io.close() diff --git a/src/pynwb/validation.py b/src/pynwb/validation.py index 4a25c0fc5..a62dae07e 100644 --- a/src/pynwb/validation.py +++ b/src/pynwb/validation.py @@ -132,7 +132,13 @@ def get_cached_namespaces_to_validate(path: Optional[str] = None, "type": str, "doc": "Driver for h5py to use when opening the HDF5 file.", "default": None, - }, + }, + { + "name": "aws_region", + "type": str, + "doc": "AWS region to use when opening the HDF5 file with the ros3 driver.", + "default": None, + }, returns="Validation errors in the file.", rtype=list, is_method=False, @@ -169,19 +175,20 @@ def validate(**kwargs): def _validate_single_file(**kwargs): - io, path, use_cached_namespaces, namespace, verbose, driver = getargs( - "io", "path", "use_cached_namespaces", "namespace", "verbose", "driver", kwargs + io, path, use_cached_namespaces, namespace, verbose, driver, aws_region = getargs( + "io", "path", "use_cached_namespaces", "namespace", "verbose", "driver", "aws_region", kwargs ) assert io != path, "Both 'io' and 'path' were specified! Please choose only one." path = str(path) if isinstance(path, Path) else path # get namespaces to validate namespace_message = "PyNWB namespace information" - io_kwargs = dict(path=path, mode="r", driver=driver) - + io_kwargs = dict(path=path, mode="r", driver=driver, aws_region=aws_region) + if use_cached_namespaces: - cached_namespaces, manager, namespace_dependencies = get_cached_namespaces_to_validate(path=path, - driver=driver, + cached_namespaces, manager, namespace_dependencies = get_cached_namespaces_to_validate(path=path, + driver=driver, + aws_region=aws_region, io=io) io_kwargs.update(manager=manager) diff --git a/tests/integration/ros3/test_ros3.py b/tests/integration/ros3/test_ros3.py index 666647ddf..7703be02c 100644 --- a/tests/integration/ros3/test_ros3.py +++ b/tests/integration/ros3/test_ros3.py @@ -30,14 +30,14 @@ def setUp(self): def test_read(self): s3_path = 'https://dandiarchive.s3.amazonaws.com/ros3test.nwb' with warnings.catch_warnings(): - with NWBHDF5IO(s3_path, mode='r', driver='ros3') as io: + with NWBHDF5IO(s3_path, mode='r', driver='ros3', aws_region="us-east-2") as io: nwbfile = io.read() test_data = nwbfile.acquisition['ts_name'].data[:] self.assertEqual(len(test_data), 3) def test_dandi_read(self): with warnings.catch_warnings(): - with NWBHDF5IO(path=self.s3_test_path, mode='r', driver='ros3') as io: + with NWBHDF5IO(path=self.s3_test_path, mode='r', driver='ros3', aws_region="us-east-2") as io: nwbfile = io.read() test_data = nwbfile.acquisition['TestData'].data[:] self.assertEqual(len(test_data), 3) @@ -76,13 +76,13 @@ def test_dandi_get_cached_namespaces(self): } } found_namespaces, _, found_namespace_dependencies = get_cached_namespaces_to_validate( - path=self.s3_test_path, driver="ros3" + path=self.s3_test_path, driver="ros3", aws_region="us-east-2" ) self.assertCountEqual(first=found_namespaces, second=expected_namespaces) self.assertDictEqual(d1=found_namespace_dependencies, d2=expected_namespace_dependencies) def test_dandi_validate(self): - result = validate(path=self.s3_test_path, driver="ros3") + result = validate(path=self.s3_test_path, driver="ros3", aws_region="us-east-2") assert result == [] From c737d9d160ac2ec8b780248e460e2e473e3a03fc Mon Sep 17 00:00:00 2001 From: rly <310197+rly@users.noreply.github.com> Date: Mon, 29 Jun 2026 11:40:05 -0700 Subject: [PATCH 4/7] Clarify get_external_resources docs and streaming dependencies Refine the get_external_resources docstring and HERD tutorial wording to describe the linked vs stored HERD behavior, and add tqdm to the streaming example's install instructions. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/gallery/general/plot_external_resources.py | 6 +++--- docs/gallery/general/resources_streaming.py | 4 ++-- src/pynwb/file.py | 10 ++++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/gallery/general/plot_external_resources.py b/docs/gallery/general/plot_external_resources.py index b1a659f6d..819c8369c 100644 --- a/docs/gallery/general/plot_external_resources.py +++ b/docs/gallery/general/plot_external_resources.py @@ -51,9 +51,9 @@ # ------------------- # Use :py:meth:`~pynwb.file.NWBFile.get_external_resources` to get the file's # :py:class:`~pynwb.resources.HERD`. A file has at most one HERD, so this returns the existing HERD if -# the file already has one (for example, when the file was read from disk) and otherwise, it creates -# and attaches a new empty HERD. The :py:attr:`~pynwb.file.NWBFile.external_resources` attribute returns the -# HERD without creating one, returning ``None`` when the file has no external resources. +# the file already has one (for example, when the file was read from disk) and creates and attaches a +# new empty HERD otherwise. The :py:attr:`~pynwb.file.NWBFile.external_resources` attribute returns +# the HERD without creating one, returning ``None`` when the file has no external resources. herd = nwbfile.get_external_resources() diff --git a/docs/gallery/general/resources_streaming.py b/docs/gallery/general/resources_streaming.py index 17ba33b07..76262932f 100644 --- a/docs/gallery/general/resources_streaming.py +++ b/docs/gallery/general/resources_streaming.py @@ -22,11 +22,11 @@ .. note:: This example reads data over the network and is not run when the documentation is built. To run - it yourself, install the ``dandi`` and ``fsspec`` packages: + it yourself, install the streaming dependencies: .. code-block:: bash - pip install dandi fsspec aiohttp requests + pip install dandi fsspec tqdm aiohttp requests """ # sphinx_gallery_thumbnail_path = 'figures/gallery_thumbnails_streaming_external_resources.png' diff --git a/src/pynwb/file.py b/src/pynwb/file.py index 6b82537df..f7cf3c8f1 100644 --- a/src/pynwb/file.py +++ b/src/pynwb/file.py @@ -1137,10 +1137,12 @@ def get_scratch(self, **kwargs): def get_external_resources(self, **kwargs): """Get the HERD external resources for this NWBFile. - Create an empty HERD and attach it to the file if the file does not have one yet. There is at - most one HERD per file, so this returns the existing HERD when the file already has one, for - example when the file was read from disk. The NWBFile.external_resources attribute returns the - HERD without creating one, returning None when the file has no external resources. + With the default ``linked=False``, return the HERD stored on this file, first creating and + attaching a new empty HERD if the file does not have one yet (there is at most one HERD per + file, so an existing HERD is returned as-is, e.g. when the file was read from disk). With + ``linked=True``, return the linked HERD set via link_resources, or None if none is set, + without creating one. The NWBFile.external_resources attribute returns the stored HERD without + creating one, returning None when the file has no external resources. """ linked = getargs('linked', kwargs) if linked: From 2617da7ac9495e05aa51371d4bd463a98c782ef9 Mon Sep 17 00:00:00 2001 From: Ryan Ly <310197+rly@users.noreply.github.com> Date: Mon, 29 Jun 2026 12:52:22 -0700 Subject: [PATCH 5/7] Reorganize 4.0.0 changelog entry --- CHANGELOG.md | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c15bde57..a781a43a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,16 @@ # PyNWB Changelog -## PyNWB 4.0.0 (Upcoming) +## PyNWB 4.0.0 (June 29, 2026) -### Documentation and tutorial enhancements -- Added a tutorial on using HERD to annotate an NWB file with external resources and store it at `/general/external_resources`, plus a companion example showing how to annotate multiple NWB files streamed from a DANDI dandiset with a single HERD. @rly, @mavaylon1 [#2200](https://github.com/NeurodataWithoutBorders/pynwb/pull/2200) -- Added `pandas.ExtensionArray` to `nitpick_ignore` so the Sphinx build does not fail on the unresolved cross-reference that HDMF's `array_data` docval macro renders for every type that accepts array data. @rly [#2209](https://github.com/NeurodataWithoutBorders/pynwb/pull/2209) -- Added `app.readthedocs.org/projects/pynwb/*` to `linkcheck_ignore` to stop the Sphinx linkcheck CI job from intermittently failing when GitHub Actions runners get throttled by readthedocs. @h-mayorquin [#2191](https://github.com/NeurodataWithoutBorders/pynwb/pull/2191) -- Added documentation for `ExternalImage` to the images tutorial. @h-mayorquin [#2159](https://github.com/NeurodataWithoutBorders/pynwb/pull/2159) -- Fixed broken and redirecting links in documentation. @bendichter [#2165](https://github.com/NeurodataWithoutBorders/pynwb/pull/2165) -- Added `EventsTable` examples to the NWB file basics and behavior tutorials. @rly [#2156](https://github.com/NeurodataWithoutBorders/pynwb/pull/2156) -- Added example of setting `Units.resolution` in the ecephys tutorial. @h-mayorquin [#2174](https://github.com/NeurodataWithoutBorders/pynwb/pull/2174) +## Changed +- Consolidated dependency declarations into `pyproject.toml` and removed the `requirements.txt`, `requirements-dev.txt`, `requirements-opt.txt`, `requirements-doc.txt`, and `requirements-min.txt` files. Added user-installable `zarr` and `termset` optional-dependency extras (e.g. `pip install pynwb[zarr]`), and declared development dependencies as PEP 735 `[dependency-groups]` (`test`, `stream`, `docs`). `tox` now installs dependencies via extras and dependency groups, with minimum-version testing using `uv pip install --resolution lowest-direct`. Install development dependencies with `pip install --group test --group docs -e ".[zarr,termset]"`. @rly [#2205](https://github.com/NeurodataWithoutBorders/pynwb/pull/2205) +- Deprecated `NWBGroupSpec.add_group` and `NWBGroupSpec.add_dataset`. Use `NWBGroupSpec.set_group`, `NWBGroupSpec.set_dataset`, or pass the group or dataset to the `NWBGroupSpec` constructor. @rly [#2138](https://github.com/NeurodataWithoutBorders/pynwb/issues/2138) +- Fixed `TimeSeries.get_timestamps()` to handle numpy array timestamps when they are set. @pauladkisson [#2181](https://github.com/NeurodataWithoutBorders/pynwb/pull/2181) +- Fixed `Units.waveform_rate` and `Units.waveform_unit` to also map to the `sampling_rate` and `unit` attributes of the `waveforms` column on write and read, so waveform sampling metadata round-trips for `Units` tables that contain only `waveforms` (without `waveform_mean` or `waveform_sd`). @ehennestad [#2183](https://github.com/NeurodataWithoutBorders/pynwb/pull/2183) +- Added Python 3.14 support. @bendichter, @rly [#2168](https://github.com/NeurodataWithoutBorders/pynwb/pull/2168) +- Bumped the minimum HDMF dependency to >=6.1.0 for pandas 3.0 compatibility. See the [HDMF changelog](https://hdmf.readthedocs.io/en/stable/CHANGELOG.html) for the full list of changes in HDMF 6.1.0. @rly [#2171](https://github.com/NeurodataWithoutBorders/pynwb/issues/2171), [#2208](https://github.com/NeurodataWithoutBorders/pynwb/pull/2208) +- Deprecated Python 3.9 support. (EOL was Oct 31, 2025) @bendichter [#2141](https://github.com/NeurodataWithoutBorders/pynwb/pull/2141) +- Deprecated `BehavioralEvents` and `AnnotationSeries` in favor of using an `EventsTable` in `NWBFile.events`. Creating a new instance of either type now emits a `UserWarning`; reading existing files containing these types continues to work without warnings. @rly [#2156](https://github.com/NeurodataWithoutBorders/pynwb/pull/2156) ### Added - Added optional `source_description` attribute to `EventsTable` for a short free-text label of where events originated (e.g., `"Acquisition system"`, `"Manual video review"`). Added `NWBFile.merge_events_tables()` to merge a list of `EventsTable` objects into a single DataFrame sorted by timestamp with a `source_events_table` column. Added `NWBFile.get_all_events()` to merge all tables in `NWBFile.events`. @rly [#2192](https://github.com/NeurodataWithoutBorders/pynwb/pull/2192) @@ -26,15 +27,15 @@ - Fixed reading legacy files where `Device.model` is a string containing `/` or `:` (e.g., `"MFC_200/250-0.66_40mm"`), which previously raised a `ValueError`. The string is now remapped to a read-only `DeviceModel` that preserves the original name, with a warning explaining that the file cannot be written or exported until a `DeviceModel` with a valid name is created. Writing or exporting such a `DeviceModel` raises a clear error instead of silently corrupting the file. @rly [#2186](https://github.com/NeurodataWithoutBorders/pynwb/pull/2186) - Fixed invalid CSS properties in documentation assistant toggle that prevented proper positioning on displays ≥1400px wide. @rly [#2151](https://github.com/NeurodataWithoutBorders/pynwb/pull/2151) -## Changed -- Consolidated dependency declarations into `pyproject.toml` and removed the `requirements.txt`, `requirements-dev.txt`, `requirements-opt.txt`, `requirements-doc.txt`, and `requirements-min.txt` files. Added user-installable `zarr` and `termset` optional-dependency extras (e.g. `pip install pynwb[zarr]`), and declared development dependencies as PEP 735 `[dependency-groups]` (`test`, `stream`, `docs`). `tox` now installs dependencies via extras and dependency groups, with minimum-version testing using `uv pip install --resolution lowest-direct`. Install development dependencies with `pip install --group test --group docs -e ".[zarr,termset]"`. @rly [#2205](https://github.com/NeurodataWithoutBorders/pynwb/pull/2205) -- Deprecated `NWBGroupSpec.add_group` and `NWBGroupSpec.add_dataset`. Use `NWBGroupSpec.set_group`, `NWBGroupSpec.set_dataset`, or pass the group or dataset to the `NWBGroupSpec` constructor. @rly [#2138](https://github.com/NeurodataWithoutBorders/pynwb/issues/2138) -- Fixed `TimeSeries.get_timestamps()` to handle numpy array timestamps when they are set. @pauladkisson [#2181](https://github.com/NeurodataWithoutBorders/pynwb/pull/2181) -- Fixed `Units.waveform_rate` and `Units.waveform_unit` to also map to the `sampling_rate` and `unit` attributes of the `waveforms` column on write and read, so waveform sampling metadata round-trips for `Units` tables that contain only `waveforms` (without `waveform_mean` or `waveform_sd`). @ehennestad [#2183](https://github.com/NeurodataWithoutBorders/pynwb/pull/2183) -- Added Python 3.14 support. @bendichter, @rly [#2168](https://github.com/NeurodataWithoutBorders/pynwb/pull/2168) -- Bumped the minimum HDMF dependency to >=6.1.0 for pandas 3.0 compatibility. See the [HDMF changelog](https://hdmf.readthedocs.io/en/stable/CHANGELOG.html) for the full list of changes in HDMF 6.1.0. @rly [#2171](https://github.com/NeurodataWithoutBorders/pynwb/issues/2171), [#2208](https://github.com/NeurodataWithoutBorders/pynwb/pull/2208) -- Deprecated Python 3.9 support. (EOL was Oct 31, 2025) @bendichter [#2141](https://github.com/NeurodataWithoutBorders/pynwb/pull/2141) -- Deprecated `BehavioralEvents` and `AnnotationSeries` in favor of using an `EventsTable` in `NWBFile.events`. Creating a new instance of either type now emits a `UserWarning`; reading existing files containing these types continues to work without warnings. @rly [#2156](https://github.com/NeurodataWithoutBorders/pynwb/pull/2156) +### Documentation and tutorial enhancements +- Added a tutorial on using HERD to annotate an NWB file with external resources and store it at `/general/external_resources`, plus a companion example showing how to annotate multiple NWB files streamed from a DANDI dandiset with a single HERD. @rly, @mavaylon1 [#2200](https://github.com/NeurodataWithoutBorders/pynwb/pull/2200) +- Added `pandas.ExtensionArray` to `nitpick_ignore` so the Sphinx build does not fail on the unresolved cross-reference that HDMF's `array_data` docval macro renders for every type that accepts array data. @rly [#2209](https://github.com/NeurodataWithoutBorders/pynwb/pull/2209) +- Added `app.readthedocs.org/projects/pynwb/*` to `linkcheck_ignore` to stop the Sphinx linkcheck CI job from intermittently failing when GitHub Actions runners get throttled by readthedocs. @h-mayorquin [#2191](https://github.com/NeurodataWithoutBorders/pynwb/pull/2191) +- Added documentation for `ExternalImage` to the images tutorial. @h-mayorquin [#2159](https://github.com/NeurodataWithoutBorders/pynwb/pull/2159) +- Fixed broken and redirecting links in documentation. @bendichter [#2165](https://github.com/NeurodataWithoutBorders/pynwb/pull/2165) +- Added `EventsTable` examples to the NWB file basics and behavior tutorials. @rly [#2156](https://github.com/NeurodataWithoutBorders/pynwb/pull/2156) +- Added example of setting `Units.resolution` in the ecephys tutorial. @h-mayorquin [#2174](https://github.com/NeurodataWithoutBorders/pynwb/pull/2174) + ## PyNWB 3.1.3 (December 9, 2025) From 60659c08ff2a50c27d477cf094b755d910d50562 Mon Sep 17 00:00:00 2001 From: rly <310197+rly@users.noreply.github.com> Date: Mon, 29 Jun 2026 15:42:35 -0700 Subject: [PATCH 6/7] Fix validate() getargs unpacking to include aws_region The merge left validate() requesting "aws_region" from getargs without binding it to a variable, so getargs returned 7 values into 6 targets. This raised "too many values to unpack" on every validate() call and left aws_region undefined where it is forwarded to the io kwargs and get_cached_namespaces_to_validate(). Co-Authored-By: Claude Opus 4.8 --- src/pynwb/validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pynwb/validation.py b/src/pynwb/validation.py index 1835783c0..4c177623a 100644 --- a/src/pynwb/validation.py +++ b/src/pynwb/validation.py @@ -145,7 +145,7 @@ def validate(**kwargs): compliance with the schema and compliance of data with NWB best practices. """ - io, path, use_cached_namespaces, namespace, verbose, driver = getargs( + io, path, use_cached_namespaces, namespace, verbose, driver, aws_region = getargs( "io", "path", "use_cached_namespaces", "namespace", "verbose", "driver", "aws_region", kwargs ) assert io != path, "Both 'io' and 'path' were specified! Please choose only one." From d1cc66a8c609fe0d98f21d57f3f53ae30a52cd90 Mon Sep 17 00:00:00 2001 From: Oliver Ruebel Date: Mon, 29 Jun 2026 16:03:39 -0700 Subject: [PATCH 7/7] Apply suggestion from @oruebel --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed952c3b3..5aa25d66f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ - The `paths` argument of `pynwb.validate`. Use `path` and call `validate` once per file instead. - Made `NWBFile.icephys_filtering` read-only. Use `IntracellularElectrode.filtering` instead. The legacy `/general/intracellular_ephys/filtering` value is still read from older files. @rly [#2210](https://github.com/NeurodataWithoutBorders/pynwb/issues/2210) -## Changed +### Changed - Consolidated dependency declarations into `pyproject.toml` and removed the `requirements.txt`, `requirements-dev.txt`, `requirements-opt.txt`, `requirements-doc.txt`, and `requirements-min.txt` files. Added user-installable `zarr` and `termset` optional-dependency extras (e.g. `pip install pynwb[zarr]`), and declared development dependencies as PEP 735 `[dependency-groups]` (`test`, `stream`, `docs`). `tox` now installs dependencies via extras and dependency groups, with minimum-version testing using `uv pip install --resolution lowest-direct`. Install development dependencies with `pip install --group test --group docs -e ".[zarr,termset]"`. @rly [#2205](https://github.com/NeurodataWithoutBorders/pynwb/pull/2205) - Deprecated `NWBGroupSpec.add_group` and `NWBGroupSpec.add_dataset`. Use `NWBGroupSpec.set_group`, `NWBGroupSpec.set_dataset`, or pass the group or dataset to the `NWBGroupSpec` constructor. @rly [#2138](https://github.com/NeurodataWithoutBorders/pynwb/issues/2138) - Fixed `TimeSeries.get_timestamps()` to handle numpy array timestamps when they are set. @pauladkisson [#2181](https://github.com/NeurodataWithoutBorders/pynwb/pull/2181)