Skip to content

Commit 4b4e5c9

Browse files
committed
maintain positional arg order for imaging plane
1 parent 7da222f commit 4b4e5c9

File tree

2 files changed

+71
-14
lines changed

2 files changed

+71
-14
lines changed

src/pynwb/ophys.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ class ImagingPlane(NWBContainer):
6969
{'name': 'optical_channel', 'type': (list, OpticalChannel), # required
7070
'doc': 'One of possibly many groups storing channel-specific data.'},
7171
{'name': 'description', 'type': str, 'doc': 'Description of this ImagingPlane.', 'default': None},
72-
{'name': 'device', 'type': Device, 'doc': 'the device that was used to record'}, # required
73-
{'name': 'excitation_lambda', 'type': float, 'doc': 'Excitation wavelength in nm.'}, # required
74-
{'name': 'indicator', 'type': str, 'doc': 'Calcium indicator'}, # required
75-
{'name': 'location', 'type': str, 'doc': 'Location of image plane.'}, # required
72+
{'name': 'device', 'type': Device, 'doc': 'the device that was used to record', 'default': None}, # required
73+
{'name': 'excitation_lambda', 'type': float, 'doc': 'Excitation wavelength in nm.', 'default': None}, # required
74+
{'name': 'indicator', 'type': str, 'doc': 'Calcium indicator', 'default': None}, # required
75+
{'name': 'location', 'type': str, 'doc': 'Location of image plane.', 'default': None}, # required
7676
{'name': 'imaging_rate', 'type': float,
7777
'doc': 'Rate images are acquired, in Hz. If the corresponding TimeSeries is present, the rate should be '
7878
'stored there instead.', 'default': None},
@@ -128,6 +128,21 @@ def __init__(self, **kwargs):
128128

129129
if not isinstance(args_to_set['optical_channel'], list):
130130
args_to_set['optical_channel'] = [args_to_set['optical_channel']]
131+
132+
# Note: device, excitation_lambda, indicator, and location are required arguments.
133+
# Description was made to be optional in PyNWB 3.1.0, however to avoid breaking API changes,
134+
# the order of the arguments needs to be maintained even though the optional arguments came before the required ones.
135+
# So in docval these required arguments are displayed as optional when really they are required.
136+
# This section can be removed when positional arguments are no longer allowed.
137+
if args_to_set['device'] is None:
138+
raise ValueError("The 'device' argument is required for ImagingPlane.")
139+
if args_to_set['excitation_lambda'] is None:
140+
raise ValueError("The 'excitation_lambda' argument is required for ImagingPlane.")
141+
if args_to_set['indicator'] is None:
142+
raise ValueError("The 'indicator' argument is required for ImagingPlane.")
143+
if args_to_set['location'] is None:
144+
raise ValueError("The 'location' argument is required for ImagingPlane.")
145+
131146
if args_to_set['manifold'] is not None:
132147
error_msg = "The 'manifold' argument is deprecated in favor of 'origin_coords' and 'grid_spacing'."
133148
self._error_on_new_pass_on_construct(error_msg=error_msg)

tests/unit/test_ophys.py

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -197,25 +197,67 @@ def test_unit_deprecated(self):
197197
obj = ImagingPlane.__new__(ImagingPlane, in_construct_mode=True)
198198
obj.__init__(**kwargs)
199199

200-
def test_init_pos_args(self):
201-
"""Check creation of ImagingPlane with only required dependencies and positional kwargs.
200+
def test_init_description_optional(self):
201+
"""Check creation of ImagingPlane with only required dependencies.
202202
203203
This is to check how creation of an ImagingPlane changes when the "description" argument moves from a
204204
required arg in the middle of the required args section to an optional arg, in alignment with the schema.
205205
"""
206206
oc, device = self.set_up_dependencies()
207207

208+
# description is now optional
208209
ip = ImagingPlane(
209-
'test_imaging_plane',
210-
oc,
211-
device,
212-
600.,
213-
'indicator',
214-
'location',
215-
)
210+
name='test_imaging_plane',
211+
optical_channel=oc,
212+
device=device,
213+
excitation_lambda=600.,
214+
indicator='indicator',
215+
location='location',
216+
)
216217
self.assertIsNone(ip.description)
217-
self.assertIs(ip.device, device)
218218

219+
# description is still required to be provided when using positional arguments
220+
with warnings.catch_warnings(record=True) as w: # catch positional argument deprecation warning
221+
with self.assertRaises(TypeError):
222+
ImagingPlane(
223+
'test_imaging_plane',
224+
oc,
225+
device,
226+
600.,
227+
'indicator',
228+
'location',
229+
)
230+
231+
def test_init_missing_required_args(self):
232+
"""Check that ImagingPlane raises an error if required args are missing."""
233+
oc, device = self.set_up_dependencies()
234+
235+
with self.assertRaisesWith(ValueError, "The 'device' argument is required for ImagingPlane."):
236+
ImagingPlane(
237+
name='test_imaging_plane',
238+
optical_channel=oc,
239+
)
240+
with self.assertRaisesWith(ValueError, "The 'excitation_lambda' argument is required for ImagingPlane."):
241+
ImagingPlane(
242+
name='test_imaging_plane',
243+
optical_channel=oc,
244+
device=device,
245+
)
246+
with self.assertRaisesWith(ValueError, "The 'indicator' argument is required for ImagingPlane."):
247+
ImagingPlane(
248+
name='test_imaging_plane',
249+
optical_channel=oc,
250+
device=device,
251+
excitation_lambda=600.,
252+
)
253+
with self.assertRaisesWith(ValueError, "The 'location' argument is required for ImagingPlane."):
254+
ImagingPlane(
255+
name='test_imaging_plane',
256+
optical_channel=oc,
257+
device=device,
258+
excitation_lambda=600.,
259+
indicator='indicator',
260+
)
219261

220262
class OnePhotonSeriesConstructor(TestCase):
221263

0 commit comments

Comments
 (0)