Skip to content

[IMPROVEMENT] Replacing type aliases with classes that utilize inheritance to avoid MyPy's issues with the current types #1110

@Laurynas-Jagutis

Description

@Laurynas-Jagutis

We want to move to Python's function @overloading (https://typing.python.org/en/latest/spec/overload.html) to increase readability of the code. However, it was found that Mypy raises errors in certain scenarios for our current type structure.

At the moment, we use type aliases:
SingleColumn: TypeAlias = np.ndarray
DenseBatchArray: TypeAlias = np.ndarray

The problem is that because both SingleColumn and DenseBatchArray are aliases for np.ndarray, Mypy concludes that they are the same type. We do not want to merge or remove these aliases, as the distinct names convey important contextual meaning.

Therefore, a potential solution is defining classes that inherit from the underlying data structure rather than relying solely on type aliases:

class SingleArray(np.ndarray):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if len(self.shape) != 1:
            # of course with its own sub-error-type
            raise PowerGridError("Single arrays must be 1-dimensional")
class DenseBatchArray(np.ndarray):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if len(self.shape) != 1:
            # of course with its own sub-error-type
            raise PowerGridError("Dense batch arrays must be 1-dimensional")

Furthermore, for migration purposes it is a good idea to further extend these and all the other classes that will be created by adding another method:

class SingleArray(np.ndarray):
    # init
 
    @staticmethod
    def from(data: np.ndarray) -> "SingleArray":
        if isinstance(data, SingleArray):
            return data
        return SingleArray(data)
 
SingleArrayLike: TypeAlias = SingleArray | np.ndarray

The implementation should contain the Class implementations for:

  • SingleArray,
  • SingleColumn,
  • DenseBatchArray,
  • SingleColumnarData,
  • BatchColumn,
  • DenseBatchColumnarData,
  • IndexPointer

A desired end result is:

  1. Mypy should be able to distinguish between the types
  2. Existing code should be backwards compatible

Example code of overloading where mypy complains:

@overload
def _check_sparse_dense(component_data: SingleArray, err_msg_suffixed: str) -> SingleArray: ...
@overload
def _check_sparse_dense(component_data: DenseBatchArray, err_msg_suffixed: str) -> DenseBatchArray: ...  # type: ignore[overload-cannot-match]
@overload
def _check_sparse_dense(component_data: DenseBatchColumnarData, err_msg_suffixed: str) -> DenseBatchColumnarData: ...  # type: ignore[overload-overlap]
@overload
def _check_sparse_dense(component_data: SparseBatchArray, err_msg_suffixed: str) -> SingleArray: ...  # type: ignore[overload-cannot-match, overload-overlap]
@overload
def _check_sparse_dense(component_data: SingleColumnarData, err_msg_suffixed: str) -> SingleColumnarData: ...  # type: ignore[overload-cannot-match]
@overload
def _check_sparse_dense(component_data: SparseBatchColumnarData, err_msg_suffixed: str) -> SingleColumnarData: ...
def _check_sparse_dense(component_data, err_msg_suffixed):

The block of code above was setup locally and was not merged, therefore it will not be found in the repository. To set up your own version, you will need to copy and paste it into: src\power_grid_model\_core\utils.py. One of the proofs that Mypy does not complain about the types that you have created would be that there is no need for the Mypy ignore comments in the code block above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    good first issueIndicates a good issue for first-time contributorsimprovementImprovement on internal implementation

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions