I think there are probably some more considerations in implementing support for generics where fields are annotated with TypeVars. But here are some examples use cases of where type vars / generics are currently not supported, but perhaps could be.
Use case where a field can be one of, but generics are used for safe typing, not a dynamic union type
from pathlib import Path
from typing import Generic, TypeVar
import serde
import serde.json
@serde.serde
class S3Address:
bucket: str
key: str
F = TypeVar("F", bound=Path | S3Address)
"""TypeVar for fields which correspond to files. Fields annotated with F are intended to either be a remote file or a local file. No subclasses are intended to be supported."""
@serde.serde
class MyFiles(Generic[F]):
example_file: F # Annotation with generic fails to deserialize.
# For reference, the similar non-generic class deserializes successfully.
@serde.serde
class MyFilesDynamic:
example_file: Path | S3Address # Annotation with union deserializes successfully.
# Aliases to demonstrate the intended usage of MyFiles's generic parameter.
MyRemoteFiles = MyFiles[S3Address]
MyLocalFiles = MyFiles[Path]
original_remote_files = MyFiles(S3Address(bucket="my_bucket", key="my_key"))
json_remote_files = serde.json.to_json(original_remote_files)
# Deserialization fails with the following error:
# serde.compat.SerdeError: Method __main__.MyFiles.__init__() parameter example_file={'bucket': 'my_bucket', 'key': 'my_key'} violates type hint ~I, as dict {'bucket': 'my_bucket', 'key': 'my_key'} not <class "__main__.S3Address"> or <class "pathlib.Path">.
recovered_remote_files = serde.json.from_json(MyFiles[S3Address], json_remote_files)
assert recovered_remote_files == original_remote_files
In this example, since the generic F, specifies that a type should be one of two possible types, and not some arbitrary type which has the same bounds, we could probably add support for this case in the same way that the MyFilesDynamic class is supported.
Use case where a field could be a subclass with additional fields
from typing import Generic, TypeVar
import serde
import serde.json
@serde.serde
class MyBaseClass:
value: str
T = TypeVar("T", bound=MyBaseClass)
@serde.serde
class Foo(Generic[T]):
bar: T
@serde.serde
class MySpecificClass(MyBaseClass):
num: int
original = Foo(bar=MySpecificClass(str("bar"), num=42))
json = serde.json.to_json(original)
# Deserialization fails with the following error:
# serde.compat.SerdeError: Method __main__.Foo.__init__() parameter bar={'value': 'bar', 'num': 42} violates type hint ~T, as dict {'value': 'bar', 'num': 42} not instance of <class "__main__.MyBaseClass">.
recovered = serde.json.from_json(Foo[MySpecificClass], json)
print(recovered)
assert recovered == original
To support this case we would need to get the concrete type of the generic parameter from the Foo[MySpecificClass] passed to from_json.
I think there are probably some more considerations in implementing support for generics where fields are annotated with TypeVars. But here are some examples use cases of where type vars / generics are currently not supported, but perhaps could be.
Use case where a field can be one of, but generics are used for safe typing, not a dynamic union type
In this example, since the generic F, specifies that a type should be one of two possible types, and not some arbitrary type which has the same bounds, we could probably add support for this case in the same way that the
MyFilesDynamicclass is supported.Use case where a field could be a subclass with additional fields
To support this case we would need to get the concrete type of the generic parameter from the
Foo[MySpecificClass]passed to from_json.