I have two classes: Top and Nested, and to create both of them I need to provide TopDefinition and NestedDefinition objects, which are of type NamedTuple (definitions are required for type annotations). And Class Top contains attribute, which is a list of Nested instances objects.
There is a nested dict which is used to create instance of named tuple.
The input dict item
looks like below:
type =<class 'dict'>
value={'t1': 'qwe', 't2': 'QWE', 't3': [{'n1': 'aaa', 'n2': 1}, {'n1': 'bb', 'n2': 3}]}
Then it is unpacked to create instance of class TopDefinition with the code
q = Top(top=TopDefinition(**item))
to be used as input to create instance of the class Top. and this works well, I can later see in the q class type and value of input param:
type=<class '__main__.TopDefinition'>
value=TopDefinition(t1='qwe', t2='QWE', t3=[{'n1': 'aaa', 'n2': 1}, {'n1': 'bb', 'n2': 3}])
that TopDefinition instance is properly created as named tuple with fields: t1, t2, t3.
The question is: what is t3 type of?
Is it a list of dicts or is the list of named tuples (implicitly converted, because it is defined in TopDefinition as List[NestedTuple]?
The output suggest that this is a list of dicts, because when I iterate over t3, displaying type and value, I see:
type=<class 'dict'>,
value={'n1': 'aaa', 'n2': 1}
Is named_tuple=False
Then I unpack {'n1': 'aaa', 'n2': 1}
with ** to create NestedDefinition instance which works OK, so it should be a dict.
On the other hand mypy (with options --ignore-missing-imports --strict) says error: Argument after ** must be a mapping
which means to me that it is not a dict.
Full code, to run is below:
"""Replicate the problem."""
from typing import Any, List, NamedTuple
class NestedDefinition(NamedTuple):
"""Nested object metadata for mypy type annotation."""
n1: str
n2: int
class TopDefinition(NamedTuple):
"""Top object metadata for mypy type annotation."""
t1: str
t2: str
t3: List[NestedDefinition]
def isnamedtupleinstance(x: Any) -> bool:
"""Check if object is named tuple."""
t = type(x)
b = t.__bases__
print("-------{}".format(b))
if len(b) != 1 or b[0] != tuple:
return False
f = getattr(t, '_fields', None)
if not isinstance(f, tuple):
return False
return all(type(n) == str for n in f)
class Nested:
"""Nested object."""
n1: str
n2: int
def __init__(self, nested: NestedDefinition) -> None:
print("{cName} got:\n\ttype={y}\n\tvalue={v}\n\tIS named_tuple: {b}".format(
cName=type(self).__name__, y=type(nested), v=nested, b=isnamedtupleinstance(nested)))
self.n1 = nested.n1
self.n2 = nested.n2
class Top:
"""Top object."""
t1: str
t2: str
t3: List[Nested]
def __init__(self, top: TopDefinition) -> None:
print("{cName} got:\n\ttype={y}\n\tvalue={v}".format(cName=type(self).__name__,
y=type(top), v=top))
self.t1 = top.t1
self.t2 = top.t2
self.t3 = []
if top.t3:
for sub_item in top.t3:
print("Nested passing:\n\ttype={t},\n\tvalue={v}\n\tIs named_tuple={b}".format(
t=type(sub_item), v=sub_item, b=isnamedtupleinstance(sub_item)))
nested = Nested(nested=NestedDefinition(**sub_item))
self.addNestedObj(nested)
def addNestedObj(self, nested: Nested) -> None:
"""Append nested object to array in top object."""
self.t3.append(nested)
def build_data_structure(someDict: List) -> None:
"""Replicate problem."""
for item in someDict:
print("Top passing:\n\ttype ={type}\n\tvalue={value}".format(
type=type(item), value=item))
w = Top(top=TopDefinition(**item))
x = [
{
't1': 'qwe',
't2': 'QWE',
't3': [
{'n1': 'aaa', 'n2': 1},
{'n1': 'bb', 'n2': 3}
]
},
{
't1': 'asd',
't2': 'ASD',
't3': [
{'n1': 'cc', 'n2': 7},
{'n1': 'dd', 'n2': 9}
]
}
]
build_data_structure(someDict=x)