click icon indicating copy to clipboard operation
click copied to clipboard

Make ProgressBar type available in public interface

Open ikokostya opened this issue 1 year ago • 2 comments

mypy considers function click.progressbar() as generic with type parameter V: https://github.com/pallets/click/blob/ca5e1c3d75e95cbc70fa6ed51ef263592e9ac0d0/src/click/termui.py#L287-L303

In most cases type parameter V will be inferred from iterable function parameter:

from typing import reveal_type

import click

with click.progressbar([1, 2, 3]) as bar:
    reveal_type(bar) # Revealed type is "click._termui_impl.ProgressBar[builtins.int]"

However, if iterable parameter is not specified (or if it's empty list), type parameter V cannot be inferred:

import click

with click.progressbar(length=1) as bar:
    pass
$ mypy mypy_click/__init__.py 
mypy_click/__init__.py:3: error: Need type annotation for "bar"  [var-annotated]

This issue can be solved with manually specified type parameter:

import click

with click.progressbar[int](length=1) as bar:
    pass

Unfortunately, this doesn't work for functions:

$ mypy mypy_click/
mypy_click/__init__.py:3: error: Type application is only supported for generic classes  [misc]

Another way to fix this is specify type for variable bar:

from typing import cast

import click
from click._termui_impl import ProgressBar

with cast(ProgressBar[int], click.progressbar(length=1)) as bar:
    pass

# or

with click.progressbar(length=1) as bar: # type: ProgressBar[int]
    pass

But in this case ProgressBar is imported as not a part of public interface.

So, I think ProgressBar type should be a part of the public interface. Maybe it should not be exported directly like class, because users should use click.progressbar() function instead. Public ProgressBar type can be defined as protocol and click.progressbar() just returns its implementation.

Environment:

  • Python version: 3.11.5
  • Click version: 8.1.7
  • Mypy version: 1.6.1

ikokostya avatar Oct 19 '23 23:10 ikokostya

Also possible to use function overloading to solve the problem above with inference of type parameter V:

@overload
def progressbar(iterable: Optional[Iterable[V]], ...) -> ProgressBar[V]: ...

@overload
def progressbar(length: int, ...) -> ProgressBar[int]: ...

Anyway, return type of the function should be public.

ikokostya avatar Oct 20 '23 12:10 ikokostya