How to deal with "incompatible type "Optional[str]"; expected "str""?
Question:
Let’s say I have a code structure like this
from __future__ import annotations
from typing import TypedDict
class ValDict(TypedDict):
something: str
a: A
class A:
def __init__(self, x: str) -> None:
if x and isinstance(x, str):
self.x = x
else:
raise ValueError("x has to be a non-empty string")
class B:
def __init__(self, something: str, a: A) -> None:
self.something = something
if isinstance(a, A):
self.a = a
else:
raise ValueError("a has to be of type A")
@classmethod
def from_dict(cls, stuff: ValDict) -> B:
something = stuff.get('something')
a = stuff.get('a')
return cls(something, a)
and I run mypy
on this, I will receive 2 errors
error: Argument 1 to "B" has incompatible type "Optional[str]"; expected "str"
error: Argument 2 to "B" has incompatible type "Optional[A]"; expected "A"
The errors are quite clear: As .get
can also return None
, I might not pass the correct types to the cls
call in my from_dict
method.
My question is how to avoid it. For the argument something
I could potentially get away with modifying to
something = stuff.get('something', '')
but how would I handle the case for a
? Any ideas?
Answers:
I would simply drop the use of get
entirely and just access the keys directly. Sure, this raises a KeyError
, but the class would fail to initialize properly anyway if you pass in anything other than an instance of A
.
You could have a "default instance" of A to use whenever it isn’t present in stuff
, but judging from your B.__init__
you don’t seem to want default parameters.
So the method would become:
@classmethod
def from_dict(cls, stuff: ValDict) -> B:
something = stuff['something']
a = stuff['a']
return cls(something, a)
This way, three things can happen:
stuff
has the proper keys and values -> B
is initialized
stuff
does not have the proper keys -> KeyError
on B.from_dict
stuff
has the proper keys, but key "a"
‘s value is not of the proper type -> ValueError
on B.__init__
mypy is very distrustful, so some assertions of parameters before they are passed in can help:
arg_1 = "something"
arg_2 = A()
assert isistance(arg_1, str)
assert isistance(arg_2, A)
# and call your code without linting failing with an incompatible type warning.
b = B(arg_1, arg_2)
Edit: I see the assertion are in the class, however mypy would like to be sure of the type BEFORE they are passed to the class.
Let’s say I have a code structure like this
from __future__ import annotations
from typing import TypedDict
class ValDict(TypedDict):
something: str
a: A
class A:
def __init__(self, x: str) -> None:
if x and isinstance(x, str):
self.x = x
else:
raise ValueError("x has to be a non-empty string")
class B:
def __init__(self, something: str, a: A) -> None:
self.something = something
if isinstance(a, A):
self.a = a
else:
raise ValueError("a has to be of type A")
@classmethod
def from_dict(cls, stuff: ValDict) -> B:
something = stuff.get('something')
a = stuff.get('a')
return cls(something, a)
and I run mypy
on this, I will receive 2 errors
error: Argument 1 to "B" has incompatible type "Optional[str]"; expected "str"
error: Argument 2 to "B" has incompatible type "Optional[A]"; expected "A"
The errors are quite clear: As .get
can also return None
, I might not pass the correct types to the cls
call in my from_dict
method.
My question is how to avoid it. For the argument something
I could potentially get away with modifying to
something = stuff.get('something', '')
but how would I handle the case for a
? Any ideas?
I would simply drop the use of get
entirely and just access the keys directly. Sure, this raises a KeyError
, but the class would fail to initialize properly anyway if you pass in anything other than an instance of A
.
You could have a "default instance" of A to use whenever it isn’t present in stuff
, but judging from your B.__init__
you don’t seem to want default parameters.
So the method would become:
@classmethod
def from_dict(cls, stuff: ValDict) -> B:
something = stuff['something']
a = stuff['a']
return cls(something, a)
This way, three things can happen:
stuff
has the proper keys and values ->B
is initializedstuff
does not have the proper keys ->KeyError
onB.from_dict
stuff
has the proper keys, but key"a"
‘s value is not of the proper type ->ValueError
onB.__init__
mypy is very distrustful, so some assertions of parameters before they are passed in can help:
arg_1 = "something"
arg_2 = A()
assert isistance(arg_1, str)
assert isistance(arg_2, A)
# and call your code without linting failing with an incompatible type warning.
b = B(arg_1, arg_2)
Edit: I see the assertion are in the class, however mypy would like to be sure of the type BEFORE they are passed to the class.