Restricting dataclass member types / type alias parametrized by a bounded TypeVar

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?

Yeah, you’re running up against what the static type system can do. You could consider writing a mypy plugin.

1 Like

If I were you, I’d just wrap @dataclass and do a runtime check at class definition time. Unless you’re creating classes dynamically somehow (but then typechecking doesn’t help anyway) the ergonomics are very close - instead of a typechecking error when you save the file, you’ll see the problem when you import the class (so, when the test suite starts).

2 Likes

That’s true. I think I’ll go this route, then. Thank you!

1 Like