prefer-nominal-id-types
Python boundaries should distinguish swappable identifier roles with nominal types.
Why
Two identifiers with the same primitive or container carrier can be exchanged without a type-checking error. Erasing an established domain ID has the same problem even when a boundary accepts one identifier. Nominal types make those role mistakes visible while leaving unlike carriers alone.
Fix
Use typing.NewType as the low-runtime-cost default and propagate it from the raw edge through the domain boundary. A nominal value object is also valid when runtime validation or behavior is required.
Examples
from typing import NewType
FileId = NewType("FileId", str)
def load(file_id: str) -> None: ...from typing import NewType
FileId = NewType("FileId", str)
def load(file_id: FileId) -> None: ...def move(file_id: str, parent_folder_id: str) -> None: ...from typing import NewType
FileId = NewType("FileId", str)FolderId = NewType("FolderId", str)
def move(file_id: FileId, parent_folder_id: FolderId) -> None: ...from abc import ABC, abstractmethod
class FilePort(ABC): @abstractmethod def move(self, file_id: str, folder_id: str) -> None: ...from abc import ABC, abstractmethodfrom typing import NewType
FileId = NewType("FileId", str)FolderId = NewType("FolderId", str)
class FilePort(ABC): @abstractmethod def move(self, file_id: FileId, folder_id: FolderId) -> None: ...