[BUG]: Incomplete Support for Union & NotRequired
Closed this issue · 6 comments
Issues
There are 2 issues I've stumbled across when combining some semi-complex types:
- Low-impact as can be workaround by Using
Union[...]
orOptional
: You can't useX | Y
syntax < python3.10 withfrom __future__ import annotations
if the types are not simple. In my case, it was when I was using a TypedDict(...) | None. I got crazy syntax error with incomplete terminations. E.g.:
meta: TypedDict("Meta", {"superset": NotRequired[ColumnMetaSuperset]}, total=False) | None = None
SyntaxError: TypedDict('Meta',{'superset':NotRequired['ColumnMetaSuperset']},Union[total=False),None]
Root cause suggestion from community member who helped me debug was:
My guess is that third party library created an override for type and not _TypedDictMeta
(which allows types to be or'ed in its annotations, but only if they do not have a metaclass)
Probably the same issue as https://github.com/rnag/dataclass-wizard/issues/118 , since StrEnum also uses a metaclass IIRC
- High-impact as there is no workaround: It looks like NotRequired can't be used in conjunction with Union/Optional
meta: Union[TypedDict("Meta", {"superset": NotRequired[ColumnMetaSuperset]}, total=False), None] = None
TypeError: issubclass() arg 1 must be a class
Again, suggestion from community member:
"I think it has to due with the Union[..., None]; the Optional parser assumes the first argument does not need a custom parser"
@rnag here's the traceback to help investigation of point 2. I'm trying to understand what fix is needed but this get_parser_for_annotation function is complex.
You can see from the traceback below that I am printing base type (I've inserted print(base_type)
on L341 of loaders.py). It's the specific flow of an optional/union followed by a NotRequired (which is used within TypedDict). You handle NotRequired outside of optional context but not within. I'd be really grateful for a fix/workaround here.
typing.Union
typing_extensions.NotRequired
Traceback (most recent call last):
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/loaders.py", line 534, in fromdict
load = _CLASS_TO_LOAD_FUNC[cls]
KeyError: <class '__main__.Common'>
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/loaders.py", line 273, in get_parser_for_annotation
base_type = get_origin(ann_type, raise_=True)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/utils/typing_compat.py", line 345, in get_origin
return _get_origin(cls, raise_=raise_)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/utils/typing_compat.py", line 177, in _get_origin
return cls.__origin__
AttributeError: type object 'Metric' has no attribute '__origin__'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/loaders.py", line 273, in get_parser_for_annotation
base_type = get_origin(ann_type, raise_=True)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/utils/typing_compat.py", line 345, in get_origin
return _get_origin(cls, raise_=raise_)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/utils/typing_compat.py", line 177, in _get_origin
return cls.__origin__
AttributeError: type object 'TypeParams' has no attribute '__origin__'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/ac/projects/medialabgroup/misc/scripts/mart-linter.py", line 810, in <module>
main()
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/click/core.py", line 1157, in __call__
return self.main(*args, **kwargs)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/click/core.py", line 1078, in main
rv = self.invoke(ctx)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/click/core.py", line 1434, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/click/core.py", line 783, in invoke
return __callback(*args, **kwargs)
File "/home/ac/projects/medialabgroup/misc/scripts/mart-linter.py", line 799, in main
LinterWorkflow(bq_client, bq_dataset_name, prompter=questionary.prompt).run(mart_yaml_file_paths, fix)
File "/home/ac/projects/medialabgroup/misc/scripts/mart-linter.py", line 747, in run
self.common = Common.from_yaml_file(self.COMMON_FILE_PATH)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/wizard_mixins.py", line 147, in from_yaml_file
return cls.from_yaml(in_file, decoder=decoder,
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/wizard_mixins.py", line 136, in from_yaml
return fromdict(cls, o) if isinstance(o, dict) else fromlist(cls, o)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/loaders.py", line 536, in fromdict
load = load_func_for_dataclass(cls)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/loaders.py", line 583, in load_func_for_dataclass
field_to_parser = dataclass_field_to_load_parser(cls_loader, cls, config)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/class_helper.py", line 120, in dataclass_field_to_load_parser
return _setup_load_config_for_cls(cls_loader, cls, config, save)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/class_helper.py", line 189, in _setup_load_config_for_cls
name_to_parser[f.name] = cls_loader.get_parser_for_annotation(
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/loaders.py", line 373, in get_parser_for_annotation
return MappingParser(
File "<string>", line 5, in __init__
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/parsers.py", line 502, in __post_init__
self.val_parser = get_parser(val_type, cls, extras)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/loaders.py", line 287, in get_parser_for_annotation
load_hook = load_func_for_dataclass(
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/loaders.py", line 583, in load_func_for_dataclass
field_to_parser = dataclass_field_to_load_parser(cls_loader, cls, config)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/class_helper.py", line 120, in dataclass_field_to_load_parser
return _setup_load_config_for_cls(cls_loader, cls, config, save)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/class_helper.py", line 189, in _setup_load_config_for_cls
name_to_parser[f.name] = cls_loader.get_parser_for_annotation(
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/loaders.py", line 353, in get_parser_for_annotation
return OptionalParser(
File "<string>", line 4, in __init__
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/parsers.py", line 156, in __post_init__
self.parser: AbstractParser = get_parser(self.base_type, cls, extras)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/loaders.py", line 319, in get_parser_for_annotation
return TypedDictParser(
File "<string>", line 5, in __init__
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/parsers.py", line 545, in __post_init__
self.key_to_parser: FieldToParser = {
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/parsers.py", line 546, in <dictcomp>
k: get_parser(v, cls, extras)
File "/home/ac/projects/medialabgroup/misc/scripts/.venv/lib/python3.9/site-packages/dataclass_wizard/loaders.py", line 364, in get_parser_for_annotation
elif issubclass(base_type, defaultdict):
TypeError: issubclass() arg 1 must be a class
@rnag are you able to take a look at this? is this project still maintained?
@adamcunnington-mlg I need more info on the second part of your ask:
High-impact as there is no workaround: It looks like NotRequired can't be used in conjunction with Union/Optional
Can you provide an example of how you're using either in conjunction with Union/Optional?
The below is invalid. TypedDict just does not seem to like it at all, and the field becomes required by default.
my_int: Optional[NotRequired[int]]
Assuming you mean below, it should be solved thanks to #125, but I'm not 100% sure if that's what you meant.
my_int: NotRequired[Optional[int]]
Thanks - yes I meant that - and I can confirm the merged PR fixes it - thanks for releasing. The former issue remains but it's not important as there is a workaround.
Closing for now, if you still experience the issue feel free to re-create, or if you are able to create a PR with a workaround, that would be great as well. Thanks!