0

I'm trying to check my type annotations using mypy, but this error keeps ocurring:

Script.py:201: error: Item "Dict[str, Union[float, int]]" of "Union[Dict[str, Union[float, int]], str, float, int, bool]" has no 
attribute "endswith"

My code looks like this:

from typing import Counter, Dict, Iterable, List, NoReturn, Optional, Set, Tuple, Union

TYPE_NUMBER         = Union[float, int]
TYPE_CONFIGURATION  = Dict[str, Union[Dict[str, TYPE_NUMBER], str, float, int, bool]]

def check_configuration(config: TYPE_CONFIGURATION) -> Union[bool, NoReturn]:
    database = 'database'
    assert isinstance(config[database], str)
    assert config[database].endswith('.prdb')

It runs just fine calling python normally. So I know the result of config[database1] is in fact a string. Is the problem is my type alias:

TYPE_CONFIGURATION = Dict[str, Union[Dict[str, TYPE_NUMBER], str, float, int, bool]]

or is it a bug ?

The config is a dict loaded from a JSON file where the only optional parameter is "start". The JSON file looks like this:

{
    "database" : "bla/bla/bla/file.csv",
    "distance" : 800,
    "t"        : false,
    "output"   : "bla/bla/bla/file-out.csv",

    "start"    : {"1": 1343.786, "2": 1356.523}
}
Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
  • 2
    https://stackoverflow.com/questions/54786574/mypy-error-on-dict-of-dict-value-of-type-object-is-not-indexable will likely answer your question – Marcel Wilson Jul 08 '21 at 17:25

1 Answers1

1

If you know the "start" key in config will always have a value of type Dict[str, Union[int, float]], then one solution could be to use a TypedDict in your type annotation. A TypedDict allows you to specify the expected type of the value associated with each key. In the below example, I've specified total=False, to let mypy know that a dictionary can still be considered a ConfigDict even if it doesn't contain all the fields that were specified in the type definition (you mentioned that "start" was an optional parameter).

from typing import Dict, Union, TypedDict, NoReturn
    
TYPE_NUMBER = Union[float, int]
    
class ConfigDict(TypedDict, total=False):
    database: str
    distance: int
    t: bool
    start: Dict[str, TYPE_NUMBER]
    
def check_configuration(config: ConfigDict) -> Union[bool, NoReturn]:
    assert isinstance(config['database'], str)
    assert config['database'].endswith('.prdb')
    return True

Subclasses of TypedDict aren't actually types, they're just dictionaries with some extra info attached to them for the benefit of type-checkers. So you can instantiate them like this:

config = ConfigDict(
    database="bla/bla/bla/file.csv",
    distance=800,
    t=False,
    output="bla/bla/bla/file-out.csv",
    start={"1": 1343.786, "2": 1356.523}
)

Or like this:

config: ConfigDict = {
    "database" : "bla/bla/bla/file.csv",
    "distance" : 800,
    "t"        : False,
    "output"   : "bla/bla/bla/file-out.csv",

    "start"    : {"1": 1343.786, "2": 1356.523}
}

— the two syntaxes are identical in the results they produce, and for both of them, type(config) will be dict rather than ConfigDict or TypedDict (isinstance does not work).

I agree with @MarcelWilson in the comments that Mypy error on dict of dict: Value of type "object" is not indexable is relevant here as to the cause of your problem.

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
  • 1
    Thanks for the answer! I needed to import from typing_extensions: ```from typing_extensions import TypedDict``` but it worked exactly as you said. – João Vitor Barbosa Jan 06 '22 at 14:12