1

How do you check the value of NewType against its basic type str without casting and mixing up type checking?

I declared a new type:

BoardId = NewType("BoardId", str)

that I use as a method parameter either as a single value or as an Iteration other than str.

I use instanceof to check if the type is str and not Tuple then I convert it to a tuple otherwise use it as it is. The problem pyright things BoardId is str because of isinstance which fails the type checking. On the other hand, I cannot use isinstance against NewType since:

TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union

Leaving as it is

    async def get_stickies(
        self, board_id: Union[BoardId, Iterable[BoardId]]
    ) -> List[Sticky]:
        board_ids: Iterable[BoardId] = (
            (board_id,) if isinstance(board_id, str) else board_id
        )

causes this issue:

Diagnostics:                                                                                                                      
1. Expression of type "tuple[BoardId | str] | Iterable[BoardId]" cannot be assigned to declared type "Iterable[BoardId]"
     Type "tuple[BoardId | str] | Iterable[BoardId]" cannot be assigned to type "Iterable[BoardId]"
       TypeVar "_T_co@Iterable" is covariant
         Type "BoardId | str" cannot be assigned to type "BoardId"
           "str" is incompatible with "BoardId"

enter image description here

Lajos
  • 2,549
  • 6
  • 31
  • 38
  • someone else will give you a proper answer, but your problem dealing with `board_id: Union[BoardId, Iterable[BoardId]]` is a sign that it's a bad design... IMHO (with exception of `Optional`) Union types should only be used for types that can "duck type" for each other, rather than dissimilar types like `thing` and `list[thing]`... e.g. force your callers to pre-wrap a single board-id (they probably already know which they have?), then your method can be just `board_ids: Iterable[BoardId]` – Anentropic Jun 30 '22 at 12:50
  • I don't know if there's a cleverer way, but you could `(cast(BoardId, board_id),)` – Anentropic Jun 30 '22 at 12:52

1 Answers1

0

One solution was proposed in this issue:

from typing import Iterable, TypeAlias, NewType

_BoardIdT: TypeAlias = str
BoardId = NewType('BoardId', _BoardIdT)

def get_stickies(board_id: BoardId | Iterable[BoardId]) -> None:
    board_ids: Iterable[BoardId] = (
        (board_id,) if isinstance(board_id, _BoardIdT) else board_id
    )

Playground link

The underlying type is accessible via BoardId.__supertype__, but mypy doesn't like this undocumented attribute.

STerliakov
  • 4,983
  • 3
  • 15
  • 37