Skip to content

Improve type hints of interface classes that inherit from BaseInterface #3741

@mcsitter

Description

@mcsitter

Goal

Improve static typing (e.g., mypy, IDE support) for Nipype interface classes that inherit from BaseInterface, so that concrete types of inputs and outputs are correctly inferred.

Background

Inputs and outputs are instantiated dynamically from input_spec / output_spec in BaseInterfaces __init__ function, which prevents mypy from inferring their concrete types in subclasses.
Since InputSpec and OutputSpec classes inherit from traits.HasTraits, and recent versions of traits include type stubs, type hinting these classes is now feasible.

Option 1: Explicit annotations in subclasses

Add inputs: FooInputSpec / outputs: FooOutputSpec to each interface class.
Example:

class NewSegment(SPMCommand):
    input_spec = NewSegmentInputSpec
    output_spec = NewSegmentOutputSpec

    inputs: NewSegmentInputSpec
    outputs: NewSegmentOutputSpec

Pros: Simple, minimal change, no base-class refactor needed
Cons: Must be repeated for every interface

Option 2: Generic base / command classes

Make Interface (and intermediate command classes) use Generic types to propagate input/output types.

Example:

from typing import TypeVar, Generic

# Type variables for input/output specs
I = TypeVar("I", bound=BaseInterfaceInputSpec)
O = TypeVar("O", bound=BaseInterfaceOutputSpec)

# Generic base interface
class Interface(Generic[I, O]):
    inputs: I
    outputs: O

# Concrete interface specifying generics
class NewSegment(BaseInterface[NewSegmentInputSpec, NewSegmentOutputSpec]):
    input_spec = NewSegmentInputSpec
    output_spec = NewSegmentOutputSpec

# Usage example
seg = NewSegment()
seg.inputs.warping_regularization  # mypy knows type
seg.outputs.bias_corrected_images  # mypy knows type

Pros: DRY, scalable, propagates types
Cons: Requires broader changes

Next steps

I’d like feedback on whether this is desirable and which approach fits Nipype best. In my local install I tried Option 1, but Option 2 may be better overall, as it would also allow base-class methods to have typed return values.
I’m happy to start with a small, incremental PR for SPM interfaces using either approach, based on your preference.

Open to suggestions and alternatives.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions