Repository: nipreps/nibabies

Issue:
Proposed change to the __repr__ method of the Derivatives class
Hello, 

I was peeking around the code again (debugging some issues on my side), and was trying to inspect  the `Derivatives` instance created during my nibabies run. 

However, the current `__repr__` method only shows attributes (with matching keys in `self.names`) that have non-`None` values. This was confusing, because those attributes are _always_ initially set to `None`, and only get populated once the `populate` method is called.. When everything is `None`,  it appears as if the entire `Derivatives` instance is `None`:

https://github.com/nipreps/nibabies/blob/1829514594a7627a39c53cfccd7106b5173a8a6c/nibabies/utils/bids.py#L69-L70

### Current behavior (toy example):

<details>
  <summary>Code</summary>

```
class DerivativesMain:
    def __init__(self, bids_root):
        self.names = dict(t1w_mask=dict(), t2w_mask=dict(), t1w_aseg=dict(), t2w_aseg=dict())
        for name in self.names:
            setattr(self, name, None)
    def __repr__(self):
         return '\n'.join([name for name in self.names  if getattr(self, name)])
```
</details>

```
foo = DerivativesMain("foo")
foo
```

```
>
```

### Proposed change

Always include information about attributes with matching keys in `self.names`, regardless of their current value, in the __repr__ string:

<details>
  <summary>Code</summary>

```
class DerivativesPR:
    def __init__(self, bids_root):
        self.names = dict(t1w_mask=dict(), t2w_mask=dict(), t1w_aseg=dict(), t2w_aseg=dict())
        for name in self.names:
            setattr(self, name, None)

    def __repr__(self):
        return '\n'.join(f"{attr}: {getattr(self, attr)}" for attr in self.names)
```

</details>

```
bar = DerivativesPR("bar")
bar
```

```
t1w_mask: None
t2w_mask: None
t1w_aseg: None
t2w_aseg: None
```

This representation would make it easier to verify the values for the expected attributes, helping in debugging (and in my case, I would have immediately known that I had done something wrong on my end 🙂 )


If this sounds reasonable to you, I can start a PR.

Discussion:

Patch:
diff --git a/nibabies/utils/bids.py b/nibabies/utils/bids.py
index bfd63e7..010c977 100644
--- a/nibabies/utils/bids.py
+++ b/nibabies/utils/bids.py
@@ -67,7 +67,9 @@ class Derivatives:
             setattr(self, name, None)
 
     def __repr__(self):
-        return '\n'.join([name for name in self.names if getattr(self, name)])
+        output = ["Derivatives |"]
+        output.extend([f"  {attr}: {getattr(self, attr)}" for attr in self.names])
+        return '\n'.join(output)
 
     def __contains__(self, val: str):
         return val in self.names


Test patch:
diff --git a/nibabies/utils/tests/test_bids.py b/nibabies/utils/tests/test_bids.py
index c46fe3e..29ca735 100644
--- a/nibabies/utils/tests/test_bids.py
+++ b/nibabies/utils/tests/test_bids.py
@@ -99,6 +99,7 @@ def test_derivatives(
     assert derivatives.aseg is None
     assert derivatives.t1w_aseg is None
     assert derivatives.t2w_aseg is None
+    assert "t1w_mask" in repr(derivatives)
 
     derivatives.populate(deriv_dir, subject_id='01')
     if mask:
@@ -111,3 +112,5 @@ def test_derivatives(
         assert derivatives.references[aseg]
     else:
         assert derivatives.aseg == None
+    if t1w_mask:
+        assert "sub-01_space-T1w_desc-brain_mask.nii.gz" in repr(derivatives)