8000 Implement support for returning TypedDict for dataclasses.asdict by syastrov · Pull Request #8583 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content

Implement support for returning TypedDict for dataclasses.asdict #8583

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

Open
wants to merge 46 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
bb9f051
Implement support for returning TypedDict for dataclasses.asdict
syastrov Mar 26, 2020
8000
e2f9f06
Remove redundant test. Fix comment typo.
syastrov Mar 26, 2020
2f6ec2d
Test for cases where dataclasses.asdict is called on non-dataclass in…
syastrov Mar 26, 2020
a9779e2
Clean up tests, and test more edge-cases.
syastrov Mar 26, 2020
c5d0a15
Remove no-longer-needed type: ignore on CheckerPluginInterface.module…
syastrov Mar 26, 2020
454431d
Make typeddicts non-total.
syastrov Mar 26, 2020
d809e8b
Address some of review comments (formatting, docs, code nitpicks, rem…
syastrov Mar 30, 2020
4d195cc
Simplify: Remove _transform_type_args and remove unneeded None check …
syastrov Mar 30, 2020
b33798c
Fix unused import
syastrov Mar 30, 2020
fe19bc9
Add fine-grained test for dataclasses.asdict.
syastrov Mar 30, 2020
6328a7c
Oops, add forgotten fine-grained dataclasses test. And remove redunda…
syastrov Mar 30, 2020
4694299
Only import the module containing TypedDict fallback if dataclasses i…
syastrov Apr 1, 2020
9c29081
Only enable TypedDict for Python >= 3.8.
syastrov Apr 2, 2020
d7df77a
Refactor asdict implementation to use TypeTranslator instead of recur…
syastrov Apr 2, 2020
e9a56ba
Made TypedDicts returned by asdict total again.
syastrov Apr 2, 2020
2e5240e
Fixed test after total change.
syastrov Apr 2, 2020
52a1c27
Make code a bit more readable, and a bit more robust.
syastrov Apr 2, 2020
43f174c
Fix typo
syastrov Apr 2, 2020
227ba90
After refactoring to use TypeTranslator, ensure Callable and Type[..]…
syastrov Apr 2, 2020
d12c665
Address second review comments.
syastrov Apr 8, 2020
45e72d7
Fix return type
syastrov Apr 8, 2020
a03f033
Try to address more review comments and fix flake8
syastrov Jun 3, 2020
b4d7e15
Add fine grained deps test to help debug asdict dependencies.
syastrov Jun 3, 2020
d96d977
Fix some asdict tests missing tuple dependency
syastrov Jun 3, 2020
441b665
Revert "Fix some asdict tests missing tuple dependency"
syastrov Jun 4, 2020
344ca6a
Don't need dep on typing_extensions
syastrov Jun 4, 2020
c8858fa
Checker lookup_fully_qualified_or_none: Don't raise KeyError, return …
syastrov Jun 9, 2020
20d7716
Add dependencies for asdict on the referenced dataclasses and its att…
syastrov Jun 9, 2020
5fec41b
Fix fine-grained no-cache test by adding correct dep on dataclass attrs.
syastrov Aug 18, 2020
5862c16
remove unused imports
syastrov Aug 18, 2020
26b7393
Merge branch 'master' into dataclasses-asdict
syastrov Feb 17, 2021
d7e0310
Remove error when passing a "non-dataclass" to asdict to reduce false…
syastrov Feb 17, 2021
080c00c
Fix flake8
syastrov Feb 17, 2021
9e45f8f
Fix asdict tests (require using python version 3.7 minimum).
syastrov Feb 17, 2021
38b466a
Merge branch 'master' into dataclasses-asdict
syastrov Aug 19, 2021
74ebc6f
Fix tests for quoting changes
syastrov Aug 19, 2021
aef274a
Merge branch 'master' into dataclasses-asdict
97littleleaf11 Nov 17, 2021
79f25db
Merge
97littleleaf11 Jan 18, 2022
f54e503
Fix
97littleleaf11 Jan 18, 2022
9820cfc
Add fixture for tests
97littleleaf11 Jan 18, 2022
9f49cac
Add fixture for tests
97littleleaf11 Jan 18, 2022
fcd1ff5
Add fixture for tests
97littleleaf11 Jan 18, 2022
9fa6a6b
Merge from master
97littleleaf11 Jan 18, 2022
6e1585d
Fix
97littleleaf11 Jan 18, 2022
d6f9170
Merge branch 'master' of https://github.com/python/mypy into HEAD
97littleleaf11 Jan 19, 2022
e226562
Test for a workaround
97littleleaf11 Jan 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor asdict implementation to use TypeTranslator instead of recur…
…sive functions.
  • Loading branch information
syastrov committed Aug 18, 2020
commit d7df77a089345ae657fc060159d5162ad99b5302
124 changes: 68 additions & 56 deletions mypy/plugins/dataclasses.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Plugin that provides support for dataclasses."""

from collections import OrderedDict
from typing import Dict, List, Set, Tuple, Optional, FrozenSet, Union
from typing import Dict, List, Set, Tuple, Optional, Union

from typing_extensions import Final

Expand All @@ -18,9 +18,10 @@
deserialize_and_fixup_type
)
from mypy.server.trigger import make_wildcard_trigger
from mypy.type_visitor import TypeTranslator
from mypy.types import (
Instance, NoneType, TypeVarDef, TypeVarType, get_proper_type, Type, TupleType, UnionType,
AnyType, TypeOfAny
AnyType, TypeOfAny, TypeAliasType
)

# The set of decorators that generate dataclasses.
Expand Down Expand Up @@ -401, 8000 61 +402,72 @@ def asdict_callback(ctx: FunctionContext, return_typeddicts: bool = False) -> Ty
return ctx.default_return_type


class AsDictVisitor(TypeTranslator):
def __init__(self, api: CheckerPluginInterface) -> None:
self.api = api
self.seen_dataclasses = set() # type: Set[str]

def visit_type_alias_type(self, t: TypeAliasType) -> Type:
return t.copy_modified(args=[a.accept(self) for a in t.args])

def visit_instance(self, t: Instance) -> Type:
info = t.type
if is_type_dataclass(info):
if info.fullname in self.seen_dataclasses:
# Recursive types not supported, so fall back to Dict[str, Any]
# Note: Would be nicer to fallback to default_return_type, but that is Any
# (due to overloads?)
return self.api.named_generic_type(
'builtins.dict', [self.api.named_generic_type('builtins.str', []),
AnyType(TypeOfAny.implementation_artifact)])
attrs = info.metadata['dataclass']['attributes']
fields = OrderedDict() # type: OrderedDict[str, Type]
self.seen_dataclasses.add(info.fullname)
for data in attrs:
attr = DataclassAttribute.deserialize(info, data, self.api)
sym_node = info.names[attr.name]
attr_type = sym_node.type
assert attr_type is not None
fields[attr.name] = attr_type.accept(self)
self.seen_dataclasses.remove(info.fullname)
return make_anonymous_typeddict(self.api, fields=fields,
required_keys=set())
elif info.has_base('builtins.list'):
supertype_instance = map_instance_to_supertype(t, self.api.named_generic_type(
'builtins.list', [AnyType(TypeOfAny.implementation_artifact)]).type)
return self.api.named_generic_type('builtins.list', [
supertype_instance.args[0].accept(self)
])
elif info.has_base('builtins.dict'):
supertype_instance = map_instance_to_supertype(t, self.api.named_generic_type(
'builtins.dict', [AnyType(TypeOfAny.implementation_artifact),
AnyType(TypeOfAny.implementation_artifact)]).type)
return self.api.named_generic_type('builtins.dict', [
supertype_instance.args[0].accept(self),
supertype_instance.args[1].accept(self)
])
return t

def visit_union_type(self, t: UnionType) -> Type:
return UnionType([item.accept(self) for item in t.items])

def visit_tuple_type(self, t: TupleType) -> Type:
if t.partial_fallback.type.is_named_tuple:
# For namedtuples, return Any. To properly support transforming namedtuples,
# we would have to generate a partial_fallback type for the TupleType and add it
# to the symbol table. It's not currently possibl to do this via the
# CheckerPluginInterface. Ideally it would use the same code as
# NamedTupleAnalyzer.build_namedtuple_typeinfo.
return AnyType(TypeOfAny.implementation_artifact)
# Note: Tuple subclasses not supported, hence overriding the fallback
return t.copy_modified(items=[item.accept(self) for item in t.items],
fallback=self.api.named_generic_type('builtins.tuple', []))


def _asdictify(api: CheckerPluginInterface, typ: Type) -> Type:
"""Convert dataclasses into TypedDicts, recursively looking into built-in containers.

It will look for dataclasses inside of tuples, lists, and dicts and convert them to TypedDicts.
It will look for dataclasses inside of tuples, lists, and dicts and convert them to
TypedDicts.
"""

def _asdictify_inner(typ: Type, seen_dataclasses: FrozenSet[str]) -> Type:
typ = get_proper_type(typ)
if isinstance(typ, UnionType):
return UnionType([_asdictify_inner(item, seen_dataclasses) for item in typ.items])
if isinstance(typ, Instance):
info = typ.type
if is_type_dataclass(info):
if info.fullname in seen_dataclasses:
# Recursive types not supported, so fall back to Dict[str, Any]
# Note: Would be nicer to fallback to default_return_type, but that is Any
# (due to overloads?)
return api.named_generic_type('builtins.dict',
[api.named_generic_type('builtins.str', []),
AnyType(TypeOfAny.implementation_artifact)])
seen_dataclasses |= {info.fullname}
attrs = info.metadata['dataclass']['attributes']
fields = OrderedDict() # type: OrderedDict[str, Type]
for data in attrs:
attr = DataclassAttribute.deserialize(info, data, api)
sym_node = info.names[attr.name]
attr_type = sym_node.type
assert attr_type is not None
fields[attr.name] = _asdictify_inner(attr_type, seen_dataclasses)
return make_anonymous_typeddict(api, fields=fields,
required_keys=set())
elif info.has_base('builtins.list'):
supertype_instance = map_instance_to_supertype(typ, api.named_generic_type(
'builtins.list', [AnyType(TypeOfAny.implementation_artifact)]).type)
return api.named_generic_type('builtins.list', [
_asdictify_inner(supertype_instance.args[0], seen_dataclasses)
])
elif info.has_base('builtins.dict'):
supertype_instance = map_instance_to_supertype(typ, api.named_generic_type(
'builtins.dict', [AnyType(TypeOfAny.implementation_artifact),
AnyType(TypeOfAny.implementation_artifact)]).type)
return api.named_generic_type('builtins.dict', [
_asdictify_inner(supertype_instance.args[0], seen_dataclasses),
_asdictify_inner(supertype_instance.args[1], seen_dataclasses)
])
elif isinstance(typ, TupleType):
if typ.partial_fallback.type.is_named_tuple:
# For namedtuples, return Any. To properly support transforming namedtuples,
# we would have to generate a partial_fallback type for the TupleType and add it
# to the symbol table. It's not currently possibl to do this via the
# CheckerPluginInterface. Ideally it would use the same code as
# NamedTupleAnalyzer.build_namedtuple_typeinfo.
return AnyType(TypeOfAny.implementation_artifact)
return TupleType([_asdictify_inner(item, seen_dataclasses) for item in typ.items],
api.named_generic_type('builtins.tuple', []), implicit=typ.implicit)
return typ

return _asdictify_inner(typ, seen_dataclasses=frozenset())
return typ.accept(AsDictVisitor(api))
0