GreenMan
GreenMan

Reputation: 317

mypy doesn't undestand raise Exception for None value

I create a function in another function if one of the variables is None to raise ValueError. But mypy doesn't understand it, my code is:

from typing import Mapping, Optional, Union


def make_setting(name: str, age: Optional[int], score: Optional[int]) -> Mapping[str, Union[str, int]]:
    def check(the_first_name,*arg):
        if None in arg:
            raise ValueError('The args of "{}" should not be None.'.format(the_first_name))
    check(name, age, score)
    return make_setting_of_policy(name, age, score)

def make_setting_of_policy(name: str, age:int, score: int) -> Mapping[str, Union[str, int]]:
    return {'name':name, 'age':age, 'score':score}

mypy doesn't understand the input of make_setting_of_policy is not None, and shows the below error:

Argument 2 to "make_setting_of_policy" has incompatible type "Optional[int]"; expected "int"
Argument 3 to "make_setting_of_policy" has incompatible type "Optional[int]"; expected "int"

Upvotes: 3

Views: 2396

Answers (1)

bad_coder
bad_coder

Reputation: 12930

mypy doesn't understand the input of make_setting_of_policy is not None

True, the question is about type narrowing the Optional arguments to remove the potential None. Mypy doesn't take the raising of a run-time exception into account. The simplest solution is to check the arguments directly (this should work for all Python 3.5+ versions supporting PEP 484.)

from typing import Mapping, Optional, Union


def make_setting(name: str, age: Optional[int], score: Optional[int]) -> Mapping[str, Union[str, int]]:

    if age is not None and score is not None:
        return make_setting_of_policy(name, age, score)  # runtime type check
    else:
        raise ValueError('The args of "{}" should not be None.'.format(name))  # runtime exception


def make_setting_of_policy(name: str, age: int, score: int) -> Mapping[str, Union[str, int]]:
    return {'name': name, 'age': age, 'score': score}

The mypy result:

Success: no issues found in 1 source file

If using Python 3.10 you could use TypeGuards minding its specific rules.

PEP 647 - TypeGuard Type

When a conditional statement includes a call to a user-defined type guard function, and that function returns true, the expression passed as the first positional argument to the type guard function should be assumed by a static type checker to take on the type specified in the TypeGuard return type, unless and until it is further narrowed within the conditional code block.

A solution using TypeGuard (using a single argument so we don't repeat the narrowing of the previous example) look like this:

from typing import Mapping, Optional, Union, TypeGuard


def check(args: tuple[str, Optional[int], Optional[int]]) -> TypeGuard[tuple[str, int, int]]:
    if None in args:
        raise ValueError('The args of "{}" should not be None.'.format(args[0]))
    return all(x is not None for x in args)


def make_setting(name: str, age: Optional[int], score: Optional[int]) -> Mapping[str, Union[str, int]]:

    the_args = (name, age, score)
    # reveal_type(the_args)
    if check(the_args):
        # reveal_type(the_args)
        return make_setting_of_policy(*the_args)  # runtime type check
    else:
        raise ValueError('The args of "{}" should not be None.'.format(name))  # runtime exception


def make_setting_of_policy(name: str, age: int, score: int) -> Mapping[str, Union[str, int]]:
    return {'name': name, 'age': age, 'score': score}

The mypy result:

Success: no issues found in 1 source file

Uncommenting the reveal_type() calls has mypy reveal the intended type narrowing:

Q71223933.py:13: note: Revealed type is "Tuple[builtins.str, Union[builtins.int, None], Union[builtins.int, None]]"
Q71223933.py:15: note: Revealed type is "Tuple[builtins.str, builtins.int, builtins.int]"


But there's a subtle hint in your question to having type narrowing done on *args in the def check(the_first_name,*arg) function. With Python 3.11 it can be done using PEP 646 -- Variadic Generics, but since your Optionals where written with Python 3.9 syntax I'm assuming that's beyond the scope of this question, see PEP 604 -- Allow writing union types as X | Y.

Upvotes: 3

Related Questions