Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support generic classes with fields annotated with bound TypeVars #624

Open
BaxHugh opened this issue Dec 22, 2024 · 0 comments
Open

Support generic classes with fields annotated with bound TypeVars #624

BaxHugh opened this issue Dec 22, 2024 · 0 comments

Comments

@BaxHugh
Copy link

BaxHugh commented Dec 22, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant