I’m working on typing an old code base that attempts to provide uniform access to data file formats, including versions of the same format. Generally, the data format is a straightforward binary block, and the difference is in the header, so we have a structure File(Header, Data)
. It would be nice for type checkers to be able to recognize that FileTypeA().header
has type HeaderTypeA
.
My attempts so far have left me with all file classes having headers that are interpreted as the first concrete type. Below is my approach, boiled down to two versions:
import typing as ty
class Header:
pass
HdrT = ty.TypeVar('HdrT', bound=Header)
class HeaderWithMetaData(Header):
metadata: dict[str, str]
def __init__(self, metadata=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.metadata = {}
if metadata:
self.metadata.update(metadata)
class DataFile(ty.Generic[HdrT]):
header: HdrT
header_class: type[HdrT]
def __init__(self, header=None, data=None):
self.header = header or self.header_class()
class HeaderV1(Header):
magic_number: bytes = b'HDR'
format_version: int = 1
class DataFileV1(DataFile[HeaderV1]):
header_class = HeaderV1
class HeaderV2(HeaderV1, HeaderWithMetaData):
format_version = 2
class DataFileV2(DataFileV1, DataFile[HeaderV2]):
header_class = HeaderV2
file1 = DataFileV1()
file2 = DataFileV2()
print(file2.header.format_version)
if ty.TYPE_CHECKING:
# Shows HeaderV1, but I would like it to be HeaderV2
reveal_type(file2.header)
else:
# Acts like HeaderV2
print(file2.header.metadata)
Is what I’m aiming for possible? I haven’t fully wrapped my head around covariants and contravariants, so it’s possible the answer is in that direction, but my playing around there has not landed on something helpful yet.