Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle Mypy when many possible types but expecting a specific type?

Tags:

python

mypy

Say I have a generic function that can return a number of different types depending on what properties I select:

def json_parser(json_data: Dict[str, Any], property_tree: List[str]
                ) -> Union[Dict[str, Any], List[str], str, None]:
    ....

I then call this generic function with specific properties that I know will return a string.

def get_language(self, metadata_json: Dict[str, Any]) -> str:
    return json_parser(metadata_json, ["volumeInfo", "language"])

I get the following error when running Mypy.

error: Incompatible return value type (got "Union[Dict[str, Any], List[str], str, None]", expected "List[str]")  [return-value]

From my understanding, this is because Mypy doesn't know to expect a string. What is the best way of handling this?

like image 911
Marmstrong Avatar asked Sep 04 '25 01:09

Marmstrong


1 Answers

You've got two options, depending on how careful you want to be.

First, typing.cast is a function that takes a type and a value and... magically makes the value have that type. At runtime it's defined as

def cast(ty, value):
  return value

but type-checkers are instructed to treat it as some magic black box. You could write

from typing import cast

...

return cast(str, json_parser(metadata_json, ["volumeInfo", "language"]))

and your type checker will simply accept the reality that the value is a str. This is not checked at runtime. If you made a mistake and that turned out to be a dictionary or something else, cast will let it pass, and your beautiful statically-typed code will fail with a type error at runtime.

That leads us to our second option: assertions. Mypy and other type checkers understand assertions, if statements, and other constructs, and they also understand isinstance. So if you can convince Mypy that you've done your due diligence, it will accept your type outright.

result = json_parser(metadata_json, ["volumeInfo", "language"])
assert isinstance(result, str)
return result # Accepted as str by mypy

or

result = json_parser(metadata_json, ["volumeInfo", "language"])
if isinstance(result, str):
  return result # Accepted as str by mypy
else:
  raise SomeSpecificError(...)

This is checked at runtime. If someone hands you bad data or you made a mistake, you'll get a runtime error, which is better than nothing.

like image 181
Silvio Mayolo Avatar answered Sep 06 '25 21:09

Silvio Mayolo