Nested TypeDict defined as inner class - python

I'm trying to define a "nested" TypedDict as a set of inner classes, where SomeDict dictionary is supposed to have an id field which can be either yet another TypedDict or None:
from typing import TypedDict
class MyClass:
class SomeIdFieldDict(TypedDict):
some_nested_field: str
class SomeDict(TypedDict):
id: SomeIdFieldDict | None # The error is in this line.
The above code gives me the following error: NameError: name 'SomeIdFieldDict' is not defined
I've tried to quote the SomeIdFieldDict, but now Python type checker treats it as a string:
from typing import TypedDict
class MyClass:
class SomeIdFieldDict(TypedDict):
some_nested_field: str
class SomeDict(TypedDict):
id: "SomeIdFieldDict" | None # The error is in this line.
With the above, I'm getting:
TypeError: unsupported operand type(s) for |: 'str' and 'NoneType'
I've also tried referencing to the top-level class, to no avail (getting the same error as above):
from typing import TypedDict
class MyClass:
class SomeIdFieldDict(TypedDict):
some_nested_field: str
class SomeDict(TypedDict):
id: "MyClass.SomeIdFieldDict" | None # The error is in this line.
Yet another approach I've tried taking was to define the id type "inline", like so:
from typing import TypedDict
class MyClass:
class SomeDict(TypedDict):
id: TypedDict("SomeIdFieldDict", {"some_nested_field": str}) | None
... but it seems it isn't parsed correctly and the field is being treated as Any, the type hints for id field show as: id: Any | None
Is there any way to be able to define this sort of "nested" TypeDict as inner classes?

You can either use typing.Optional["SomeIdFieldDict"] instead or, what I would suggest, use __future__.annotations like this:
from __future__ import annotations
from typing import TypedDict
class MyClass:
class SomeIdFieldDict(TypedDict):
some_nested_field: str
class SomeDict(TypedDict):
id: SomeIdFieldDict | None
def f(x: MyClass.SomeDict) -> None:
print(x)
if __name__ == '__main__':
f({"id": {"some_nested_field": "a"}}) # works
f({"foo": "bar"}) # error: Extra key "foo" for TypedDict "SomeDict"
This will pass/fail with mypy as expected. However, I noticed that PyCharm mistakenly still complains about Unresolved reference 'SomeIdFieldDict'. But that is a bug on their end.

Related

Python error "Parameters to generic types must be types. Got <module "

When trying to use my own class as a type hint :
from mycode.ltm import MyClass
def DoSomething(self, values: List[MyClass]) -> None:
I get:
Parameters to generic types must be types. Got <module '...' from
'...'>.
How to fix this?
If you name your class the same as the file name, make sure you import the class, not just the module the file represents. So for example if your MyClass class is defined in MyClass.py the above would need to change to
from mycode.ltm.MyClass import MyClass

Inherited dataclass default value override only works if type hints match?

Having a bizarre issue with dataclasses, where I can't redeclare a default value unless it's type hinted. Am I doing something wrong?
The following example:
from dataclasses import dataclass
#dataclass
class Test:
text:str = "Test"
#dataclass
class Test2(Test):
text = "Overwritten"
a = Test()
b = Test2()
print(a,b)
Outputs this: Test(text='Test') Test2(text='Test')
in Test2, I'm trying to change the default value of 'text' to "overwritten" but this doesn't work.
However, the same code with Test2 typehinted works:
#dataclass
class Test2(Test):
text:str = "Overwritten"
Results in Test(text='Test') Test2(text='Overwritten')
Am I misunderstanding something? Why does it only let me redefine a default value if I type hint?

Why won't mypy understand this object instantiation?

I'm trying to define a class that takes another class as an attribute _model and will instantiate objects of that class.
from abc import ABC
from typing import Generic, TypeVar, Any, ClassVar, Type
Item = TypeVar("Item", bound=Any)
class SomeClass(Generic[Item], ABC):
_model: ClassVar[Type[Item]]
def _compose_item(self, **attrs: Any) -> Item:
return self._model(**attrs)
I think it should be obvious that self._model(**attrs) returns an instance of Item, since _model is explicitly declared as Type[Item] and attrs is declared as Dict[str, Any].
But what I'm getting from mypy 0.910 is:
test.py: note: In member "_compose_item" of class "SomeClass":
test.py:11: error: Returning Any from function declared to return "Item"
return self._model(**attrs)
^
What am I doing wrong?
MyPy can sometimes be a bit funny about the types of classes. You can solve this by specifying _model as Callable[..., Item] (which, after all, isn't a lie) instead of Type[Item]:
from abc import ABC
from typing import Generic, TypeVar, Any, ClassVar, Callable
Item = TypeVar("Item")
class SomeClass(Generic[Item], ABC):
_model: ClassVar[Callable[..., Item]]
def _compose_item(self, **attrs: Any) -> Item:
return self._model(**attrs)

Why is TypeError raised when attribute name matches class name using typing?

I'm unable to initialise a dataclass list attribute where the attribute has the same name as the list elements' class.
The initialisation works fine when the attribute name is changed. I have the same problem with Pydantic classes.
from dataclasses import dataclass, field
from typing import List
#dataclass
class Thing:
name: str
#dataclass
class MyClass:
Thing: List[Thing] = field(default_factory=list)
c = MyClass()
This gives the following error:
TypeError: Parameters to generic types must be types. Got Field(name=None,type=None,default=<dataclasses._MISSING_TYPE object at 0x00000201CA208518>,default_f.
When I change:
Thing: List[Thing] = field(default_factory=list)
to:
thing: List[Thing] = field(default_factory=list)
the TypeError is not raised.
Because then it's overriding Thing.
That's why thing works.

Mypy annotation on a class decorator

I'm using class decorators in Python and cannot figure out which type annotation to give to my class to make mypy happy.
My code is the following:
from typing import Type
from pprint import pformat
def betterrepr(cls:Type[object]):
"""Improve representation of a class"""
class improved_class(cls): # line 12
def __repr__(self) -> str:
return f"Instance of {cls.__name__}, vars = {pformat(vars(self))}"
return improved_class
I'm currently having the 2 following errors:
myprog.py:12: error: Invalid type "cls"
myprog.py:12: error: Invalid base class
What shall I use for the type of cls (and by the way, is it Pythonic to use this keyword for a class used as argument?)?
Thanks
Using function arguments as base classes is currently not supported by mypy. Your only option is to silence the error, either with a type: ignore comment or a dummy alias like base: Any = cls.
Even without annotating cls, mypy will correctly infer the type of a class decorated with betterrepr. To document that your decorator returns a class similar to the decorated class, use a TypeVar.
from typing import Type, TypeVar
from pprint import pformat
T = TypeVar('T')
def betterrepr(cls: Type[T]) -> Type[T]:
"""Improve representation of a class"""
class IClass(cls): # type: ignore
def __repr__(self) -> str:
return f"Instance of {cls.__name__}, vars = {pformat(vars(self))}"
return IClass

Categories

Resources