-
-
Notifications
You must be signed in to change notification settings - Fork 22
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
Generic Type Problem #50
Comments
Hi @Esatyilmaz0, first of all thanks for opening this issue! I personally knew I'd have to deal with The main points that are worth asking here would probably be something along the lines of:
Notes on the above: I have a feeling that the builtin In the meantime, I would certainly encourage you to check out the Contributing docs if you are interested in adding your own changes. As mentioned, I'm going to dig a bit deeper to understand how |
I believe I'm running into the same issue using a Generic to define a JSONWizard dataclass. I've created a simple case with a User that can take a generic type to used for a user's name. The first two examples work fine when the main class is a generic dataclass. To json and from json both work as expected. The problem seems to happen when I try to use the working dataclass as a type in another dataclass. Serialization works but deserialization doesn't. Works..
The following does not work though:
Full stack trace:
I've also tried:
But this gives the same error. Last, this does work, but I cannot define the Type should be restricted to Name (it accepts a string too)
|
By the way, I've looked at adding support for custom/new types, but I'm unclear where to begin. Is this something that can happen in user code or does it need to live in the library? For example, I've been able to implement on own dump logic using DumpMeta in my user code. Works well!
But I could not follow the logic on how to support additional types, or to say, avoid dumping specific types (just leaving as is) and making do with a |
@tahouse Just noting that it is possible to achieve this through user code, only the process is somewhat convoluted at the moment, and I haven't gotten around to documenting that at present. I have added a section on Type hooks in the docs that explains how to manipulate the current load/dump functions for existing types, but I understand that's not too useful in this case in particular. While there is not ideal support for adding custom/new types, it is possible to achieve this by subclassing from the Mixin classes To illustrate this, here's a simple example I came up with for handling a dataclass field annotated as a from dataclasses import dataclass
from pathlib import Path
from typing import Type
from dataclass_wizard import JSONWizard, LoadMixin
from dataclass_wizard.abstractions import AbstractParser
from dataclass_wizard.models import Extras
from dataclass_wizard.parsers import SingleArgParser
from dataclass_wizard.type_def import T
from dataclass_wizard.utils.typing_compat import eval_forward_ref_if_needed
@dataclass
class MyClass(JSONWizard, LoadMixin):
name: str
some_path: Path
@classmethod
def get_parser_for_annotation(cls, ann_type: Type[T],
base_cls: Type = None,
extras: Extras = None) -> AbstractParser:
# evaluate any forward-declared annotations (i.e. strings) as needed
ann_type = eval_forward_ref_if_needed(ann_type, base_cls)
if issubclass(ann_type, Path):
base_type = ann_type
# alias: base_type(o)
return SingleArgParser(base_cls, extras, base_type, ann_type)
# else, forward to the default `LoadMixin.get_parser_for_annotation()` implementation
return super().get_parser_for_annotation(ann_type, base_cls, extras)
c = MyClass.from_dict({'some_path': 'a/b/c', 'name': 'Jane Doe'})
print(f'object: {c!r}')
assert isinstance(c.name, str)
assert isinstance(c.some_path, Path)
assert c.some_path == Path('a/b/c') |
@rnag Thanks! I will give this method a try when I get a chance. BTW, will be filing a new ticket for mypy static type check support (didn't want to overload this particular issue) |
Also just adding this for awareness, but I'll be on vacation until May 16th. This is mostly due to Pycon, which I'm attending this year - it's actually my first Pycon. However, will be back then to take a closer look at this issue. In the meantime, if anyone is able to look into it and implement a solution - or least a work-in-progress - I'd be more than happy to review, once I get back. |
So I've made a bit of progress, and just wanted to share what I was able to put together so far. I've only tested this on Python 3.10, but hopefully this works on Python 3.7+ at least. FWIW I'm planning on dropping support for 3.6 in a future release (since 3.6 reached end-of-life a while ago), so I feel that maintaining support for 3.7+ is a solid goal moving forward. To preface this, my initial goal here was to resolve the definitions of I'm wondering if there's an easier or more straightforward way - I haven't looked too much into helper functions that the from dataclasses import dataclass
from typing import TypeVar, Generic, Dict, List
T1 = TypeVar('T1')
T2 = TypeVar('T2')
T3 = TypeVar('T3')
KT = TypeVar('KT') # Key type.
VT = TypeVar('VT') # Value type.
@dataclass
class MyGenericClass(Generic[T1, T2]):
results: List[T2]
class MyGenericDict(Dict[KT, VT]):
...
@dataclass
class BaseClass:
my_str: str
@dataclass
class MyTestClass(BaseClass, MyGenericClass[str, int], MyGenericDict[float, bool]):
value: VT
@dataclass
class MyOtherTestClass(MyGenericClass[float, bool], Generic[T2, T3]):
my_field: T2
def is_generic_cls(cls: type) -> bool:
"""TODO: Stub function to check if `cls` is a `Generic` type"""
return hasattr(cls, '__orig_bases__')
def resolve_generics(cls: type):
"""Resolve generic to concrete types for a class `cls` which inherits from typing Generics"""
# this will be the case for a dataclass `MyClass` that inherits from
# `Generic[T]` and is then passed in as `cls` like `MyClass[str]`
cls_origin = getattr(cls, '__origin__', None)
if cls_origin is not None:
# save the passed in args - which will be `(str, )` in above example
cls_args = getattr(cls, '__args__', None)
cls = cls_origin
else:
cls_args = None
if is_generic_cls(cls):
type_var_to_concrete_type = {}
for base_cls in cls.__orig_bases__:
cls_origin = getattr(base_cls, '__origin__', base_cls)
if is_generic_cls(cls_origin):
concrete_args = base_cls.__args__
generic_args = cls_origin.__parameters__
# it is possible that each class, `MyGenericClass` for example can inherit
# from more than one "Generic" class, but for simplicity's sake we just
# assume it inherits from one.
generic_cls = cls_origin.__orig_bases__[0]
print(cls_origin.__qualname__)
print(' Generic base: ', generic_cls)
print(' Generic args: ', generic_args)
print(' Concrete args: ', concrete_args)
print()
type_var_to_concrete_type.update(zip(generic_args, concrete_args))
# again, this is the case for a `cls` argument like `MyClass[str]`
if cls_args:
cls_params = cls.__parameters__
type_var_to_concrete_type.update(zip(cls_params, cls_args))
print('---')
print('TypeVar to Concrete (user) type:')
print(' ', type_var_to_concrete_type)
resolve_generics(MyTestClass)
# resolve_generics(MyOtherTestClass[str, bytes]) Result:
|
I know it's been a while but it's 2024 and lot of changes have been made, and on the roadmap for V1 is to ensure no key transform in dump process. Accordingly, I've added a Mixin class Feel free to follow my announcement on #153 to keep up-to-date on what's expected in V1. Thanks! |
Description
Creating and using a data class to generate Generic classes work for me, dataclass-wizard fromdict not work . What is the best way for this?
What I Did
@DataClass
class User:
username: str
email:str
@DataClass
class Post:
title:str
slug:str
T = TypeVar("T")
@DataClass
class Collection(Generic[T]):
results:List[T]
count: int
@DataClass
class PostCollection(Collection[Post]):
pass
@DataClass
class UserCollection(Collection[User]):
pass
a = fromdict(UserCollection, {"count":35, "results":[{"username":"esat", "email":"[email protected]"}, {"username":"test1", "email":"[email protected]"}]})
results is a dict not User Object List
UserCollection(results=[{'username': 'esat', 'email': '[email protected]'}, {'username': 'test1', 'email': '[email protected]'}], count=35)
The text was updated successfully, but these errors were encountered: