Search code examples
pythonmypytyper

Mypy shows error when using a function to return a type


I have written a function string_prompt to return a type annotation which I have to reuse a lot in the context of typer. This is a working example (run with python -m):

from typing import Annotated

import typer

app = typer.Typer()


def string_prompt(prompt: bool | str = True):
    return Annotated[str, typer.Option(prompt=prompt)]


@app.command()
def public(name: string_prompt(), project_number: string_prompt()):
    print(f"You called public with {name}, {project_number}")

if __name__ == "__main__":
    app()

This code works, but mypy shows the error:

Invalid type comment or annotation (valid-type). Suggestion: use string_prompt[...] instead of string_prompt(...)

I've tried using square brackets instead, which obviously returns a syntax error, since I can't use square brackets to call a function.

Now my question is: Is there a different way I should declare this function to make mypy understand what I am trying to achieve? I had a look at the documentation of the mypy error, in which it explains, that functions are not a valid type, but the solution proposed which uses Callable is not applicable in my case, since I want to actually use the return value of the function called.


Solution

  • This is not exactly what you wanted, but you can avoid type annotations getting overly long with type aliases.

    For Python <= 3.11:

    from typing import TypeAlias
    
    string_prompt: TypeAlias = Annotated[str, typer.Option(prompt=True)]
    
    @app.command()
    def public(name: string_prompt, project_number: string_prompt) -> None:
        print(f"You called public with {name}, {project_number}")
    

    For Python 3.12+:

    type string_prompt = Annotated[str, typer.Option(prompt=True)]
    
    @app.command()
    def public(name: string_prompt, project_number: string_prompt) -> None:
        print(f"You called public with {name}, {project_number}")
    

    The main limitation here is that you cannot pass arguments to string_prompt and thus cannot use another value for prompt. You could do that with "arguments" that are types, for example:

    # Python <= 3.11
    from typing import TypeAlias, TypeVar
    T = TypeVar('T')
    prompt: TypeAlias = Annotated[T, typer.Option(prompt=True)]
    
    # Python 3.12+
    type prompt[T] = Annotated[T, typer.Option(prompt=True)]
    

    ... but the second argument to Annotated is a runtime value, ignored by type checkers and thus can't be parameterised by type checkers.