Yam Mesicka
Yam Mesicka

Reputation: 6581

Incompatible types in assignment on Union

Mypy prints me the following message:

x.py:74: error: Incompatible types in assignment (expression has type "Union[str, Dict[str, str]]", variable has type "str")

Isn't it strange? str is part of Union[str, Dict[str, str]]

The code follows:

def get_multiple(fields: List[str], config_data) -> Dict[str, str]:
    config_results = {k: v for k, v in config_data.items() if k in fields}
    log_missing_fields(fields, config_results)
    return config_results


def get_single(field: List[str], config_data) -> str:
    result = config_data.get(field)
    if result is None:
        log.warning('The following fields are missing: %s', field)
    return result


def get(fields: Union[str, List[str]]) -> Union[str, Dict[str, str]]:
    log.debug('Retrieving values %s from config', str(fields))
    config_data = read_config()
    get_data = get_multiple if isinstance(fields, list) else get_single
    return get_data(fields, config_data)


def get_ts_url() -> str:
    timeout = get('timeout')  # type: str <-- Line 74 is here
    log.info('Trying to connect the servers.')
    with db_session() as db_handler:
        url = scan_availability(db_handler, int(timeout))

    if url:
        return url

    log.critical("Could not find available servers.")
    raise ConnectionError("Could not find available servers.")

Upvotes: 5

Views: 3759

Answers (1)

chadrik
chadrik

Reputation: 3462

The proper way to avoid this problem is to use the @overload decorator to describe the relationship between the function's arguments and its results.

The function get() will return a str (actually an Optional[str] since it may also return None) if it is passed a str and it will return a dictionary if it is passed a list. Here's how we describe that using type annotations:

from typing import *


def read_config() -> Dict[str, str]:
    return {}


def get_multiple(fields: List[str], config_data: Dict[str, str]) -> Dict[str, str]:
    config_results = {k: v for k, v in config_data.items() if k in fields}
    return config_results


def get_single(field: List[str], config_data: Dict[str, str]) -> Optional[str]:
    result = config_data.get(field)
    return result


@overload
def get(fields: str) -> Optional[str]: ...

@overload
def get(fields: List[str]) -> Dict[str, str]: ...

def get(fields):
    config_data = read_config()
    get_data = get_multiple if isinstance(fields, list) else get_single
    return get_data(fields, config_data)


def get_ts_url() -> None:
    timeout = get('timeout')
    if TYPE_CHECKING:
        reveal_type(timeout)

If you run mypy on this it will print

Revealed type is 'Union[builtins.str, builtins.None]'

In the future I also recommend taking a few minutes before posting to simplify your example so that it is reproducible for others.

Upvotes: 5

Related Questions