I’m trying to restrict what types you can use when defining dataclass fields, or __annotations__ in general. In my particular case, I’m using dataclasses to represent rows in a DynamoDB table, but DynamoDB doesn’t support every type that Python does, only these:
DynamoType = (
bytes
| bytearray
| str
| int
| Decimal
| bool
| set[int]
| set[Decimal]
| set[str]
| set[bytes]
| set[bytearray]
| Sequence["DynamoType"]
| Mapping[str, "DynamoType"]
| None
)
I’d like for the type checker to catch invalid usages in a dataclass definition. For example, assuming there were some DynamoModel that implemented this functionality, I could do the following:
@dataclass
class SomeModel(DynamoModel):
good_field: bool
bad_field: float # should error
But this seems impossible. My next idea was to try to make some parametrized type that would work like Sequence:
T = TypeVar("T", bound=DynamoType)
DynamoSequence = Sequence[T]
@dataclass
class SomeModel:
good_field: DynamoSequence[bool]
bad_field: DynamoSequence[float] # type checker error
But I still want type checkers to treat it as the underlying type when I construct an instance or reference a member field, so I came up with the following hack, abusing Annotated:
_DynamoField = TypeVar("_DynamoField", bound=DynamoType)
DynamoField = Annotated[_DynamoField, _DynamoField]
Now, as long as I wrap every field in DynamoField[...], mypy and pyright both recursively verify the types and catch errors, as desired.
Evidently, this problem is easy to solve at runtime by inspecting __annotations__. Is there a principled way to do this statically, or have I just run into a limitation of the type system?