Skip to content

Commit d9c479a

Browse files
committed
Merge tag '5.3.2'
5.3.2 (Wednesday 23 October 2024) Bug-fix release in the 5.3.x series. Bug fixes --------- * Restore MRS extension type to Nifti1Extension to maintain backwards compatibility. (pr/1380) (CM)
2 parents e9ed337 + 7b5060e commit d9c479a

File tree

3 files changed

+65
-11
lines changed

3 files changed

+65
-11
lines changed

Changelog

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,29 @@ Eric Larson (EL), Demian Wassermann, Stephan Gerhard and Ross Markello (RM).
2525

2626
References like "pr/298" refer to github pull request numbers.
2727

28+
5.3.2 (Wednesday 23 October 2024)
29+
=================================
30+
31+
Bug-fix release in the 5.3.x series.
32+
33+
Bug fixes
34+
---------
35+
* Restore MRS extension type to Nifti1Extension to maintain backwards compatibility.
36+
(pr/1380) (CM)
37+
38+
39+
5.3.1 (Tuesday 15 October 2024)
40+
===============================
41+
42+
Bug-fix release in the 5.3.x series.
43+
44+
Bug fixes
45+
---------
46+
* Restore access to private attribute ``Nifti1Extension._content`` to unbreak subclasses
47+
that did not use public accessor methods. (pr/1378) (CM, reviewed by Basile Pinsard)
48+
* Remove test order dependency in ``test_api_validators`` (pr/1377) (CM)
49+
50+
2851
5.3.0 (Tuesday 8 October 2024)
2952
==============================
3053

@@ -34,9 +57,9 @@ NiBabel 6.0 will drop support for Numpy 1.x.
3457

3558
New features
3659
------------
37-
* Update NIfTI extension protocol to include ``.content : bytes``, ``.text : str`` and ``.json : dict``
38-
properties for accessing extension contents. Exceptions will be raised on ``.text`` and ``.json`` if
39-
conversion fails. (pr/1336) (CM)
60+
* Update NIfTI extension protocol to include ``.content : bytes``, ``.text : str`` and
61+
``.json() : dict`` properties/methods for accessing extension contents.
62+
Exceptions will be raised on ``.text`` and ``.json()`` if conversion fails. (pr/1336) (CM)
4063

4164
Enhancements
4265
------------

nibabel/nifti1.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ class NiftiExtension(ty.Generic[T]):
326326

327327
code: int
328328
encoding: str | None = None
329-
_content: bytes
329+
_raw: bytes
330330
_object: T | None = None
331331

332332
def __init__(
@@ -351,10 +351,14 @@ def __init__(
351351
self.code = extension_codes.code[code] # type: ignore[assignment]
352352
except KeyError:
353353
self.code = code # type: ignore[assignment]
354-
self._content = content
354+
self._raw = content
355355
if object is not None:
356356
self._object = object
357357

358+
@property
359+
def _content(self):
360+
return self.get_object()
361+
358362
@classmethod
359363
def from_bytes(cls, content: bytes) -> Self:
360364
"""Create an extension from raw bytes.
@@ -394,15 +398,15 @@ def _sync(self) -> None:
394398
and updates the bytes representation accordingly.
395399
"""
396400
if self._object is not None:
397-
self._content = self._mangle(self._object)
401+
self._raw = self._mangle(self._object)
398402

399403
def __repr__(self) -> str:
400404
try:
401405
code = extension_codes.label[self.code]
402406
except KeyError:
403407
# deal with unknown codes
404408
code = self.code
405-
return f'{self.__class__.__name__}({code}, {self._content!r})'
409+
return f'{self.__class__.__name__}({code}, {self._raw!r})'
406410

407411
def __eq__(self, other: object) -> bool:
408412
return (
@@ -425,7 +429,7 @@ def get_code(self):
425429
def content(self) -> bytes:
426430
"""Return the extension content as raw bytes."""
427431
self._sync()
428-
return self._content
432+
return self._raw
429433

430434
@property
431435
def text(self) -> str:
@@ -452,7 +456,7 @@ def get_object(self) -> T:
452456
instead.
453457
"""
454458
if self._object is None:
455-
self._object = self._unmangle(self._content)
459+
self._object = self._unmangle(self._raw)
456460
return self._object
457461

458462
# Backwards compatibility
@@ -488,7 +492,7 @@ def write_to(self, fileobj: ty.BinaryIO, byteswap: bool = False) -> None:
488492
extinfo = extinfo.byteswap()
489493
fileobj.write(extinfo.tobytes())
490494
# followed by the actual extension content, synced above
491-
fileobj.write(self._content)
495+
fileobj.write(self._raw)
492496
# be nice and zero out remaining part of the extension till the
493497
# next 16 byte border
494498
pad = extstart + rawsize - fileobj.tell()
@@ -671,7 +675,7 @@ def _mangle(self, dataset: DicomDataset) -> bytes:
671675
(38, 'eval', NiftiExtension),
672676
(40, 'matlab', NiftiExtension),
673677
(42, 'quantiphyse', NiftiExtension),
674-
(44, 'mrs', NiftiExtension[dict[str, ty.Any]]),
678+
(44, 'mrs', Nifti1Extension),
675679
),
676680
fields=('code', 'label', 'handler'),
677681
)

nibabel/tests/test_nifti1.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1250,6 +1250,33 @@ def test_extension_content_access():
12501250
assert json_ext.json() == {'a': 1}
12511251

12521252

1253+
def test_legacy_underscore_content():
1254+
"""Verify that subclasses that depended on access to ._content continue to work."""
1255+
import io
1256+
import json
1257+
1258+
class MyLegacyExtension(Nifti1Extension):
1259+
def _mangle(self, value):
1260+
return json.dumps(value).encode()
1261+
1262+
def _unmangle(self, value):
1263+
if isinstance(value, bytes):
1264+
value = value.decode()
1265+
return json.loads(value)
1266+
1267+
ext = MyLegacyExtension(0, '{}')
1268+
1269+
assert isinstance(ext._content, dict)
1270+
# Object identity is not broken by multiple accesses
1271+
assert ext._content is ext._content
1272+
1273+
ext._content['val'] = 1
1274+
1275+
fobj = io.BytesIO()
1276+
ext.write_to(fobj)
1277+
assert fobj.getvalue() == b'\x20\x00\x00\x00\x00\x00\x00\x00{"val": 1}' + bytes(14)
1278+
1279+
12531280
def test_extension_codes():
12541281
for k in extension_codes.keys():
12551282
Nifti1Extension(k, 'somevalue')

0 commit comments

Comments
 (0)