Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typing changes in 8.1.4 cause failure of mypy check #2558

Closed
sgillies opened this issue Jul 6, 2023 · 28 comments · Fixed by #2562
Closed

Typing changes in 8.1.4 cause failure of mypy check #2558

sgillies opened this issue Jul 6, 2023 · 28 comments · Fixed by #2562
Milestone

Comments

@sgillies
Copy link

sgillies commented Jul 6, 2023

Click's simple example https:/pallets/click#a-simple-example doesn't pass mypy 1.4.1 since 8.1.4. With click 8.1.3 the example did pass. This affects not only the simple example but also existing projects that use click. I ran mypy on one of my projects and everything passed, ran it 30 minutes later and saw failures out of the blue. Click 8.1.4 was published to PyPI between my checks.

To reproduce:

  • Save the simple example as click.py
  • pip install mypy==1.4.1 click==8.1.3
  • mypy click.py

This is the output:

click.py:3: error: Argument 1 has incompatible type "Callable[[Any, Any], Any]"; expected <nothing>  [arg-type]
click.py:12: error: <nothing> not callable  [misc]

Environment:

  • Python version: 3.11
  • Click version: 8.1.4
@davidism
Copy link
Member

davidism commented Jul 6, 2023

To anyone seeing or voting on this issue: Happy to review a PR.

@sirosen
Copy link
Contributor

sirosen commented Jul 6, 2023

We ran into this as well. Unfortunately I won't be able to work on a fix for several hours at least, but I'm happy to try things out and submit a PR if I can. If someone else gets to it first, I'd also be happy to help review and check the fix against our codebase if that would be useful.

I think the issue stems from the definition of the _Decorator type alias in the decorators. I'm not quite sure how the types should be shaped, but I tried a change which swaps an underlying TypeVar for a TypeAlias.

I put together a reproducer for the expected <nothing> behavior which tries to ignore most of the details, and a possible fix (though I'm not 100% sure that the types I played with are correct):

import typing as t 

from click import Command


T = t.TypeVar("T")
_AnyCallable: t.TypeAlias = t.Callable[..., t.Any]
_Decorator: t.TypeAlias = t.Callable[[T], T]
FC = t.TypeVar("FC", bound=t.Union[_AnyCallable, Command])


def option() -> _Decorator[FC]:
    def decorator(f: FC) -> FC:
        return f 

    return decorator


FC2: t.TypeAlias = t.Union[_AnyCallable, Command]
FC2_Decorator: t.TypeAlias = t.Callable[[FC2], FC2]


def option2() -> FC2_Decorator:
    def decorator(f: FC) -> FC:
        return f 

    return decorator


def foo() -> None:
    pass


reveal_type(option)
reveal_type(option2)
foo = option()(foo)
foo2 = option2()(foo)

gertvdijk added a commit to gertvdijk/purepythonmilter that referenced this issue Jul 6, 2023
amyreese added a commit to Instagram/Fixit that referenced this issue Jul 6, 2023
There is a typing issue with click decorators:
pallets/click#2558

[ghstack-poisoned]
amyreese added a commit to Instagram/Fixit that referenced this issue Jul 6, 2023
There is a typing issue with click decorators:
pallets/click#2558

ghstack-source-id: 26c468f16e6e67dc4aed70ee7d65e1696ea1755a
Pull Request resolved: #339
amyreese added a commit to Instagram/Fixit that referenced this issue Jul 6, 2023
There is a typing issue with click decorators:
pallets/click#2558

[ghstack-poisoned]
amyreese added a commit to Instagram/Fixit that referenced this issue Jul 6, 2023
There is a typing issue with click decorators:
pallets/click#2558

ghstack-source-id: 26c468f16e6e67dc4aed70ee7d65e1696ea1755a
Pull Request resolved: #339
@sirosen
Copy link
Contributor

sirosen commented Jul 6, 2023

A minor note: I'm now fairly certain that my "solution" above is incorrect.

If FC2 is defined as an alias for _AnyCallable | Command, then it won't allow subtypes of _AnyCallable, which is not acceptable. (It would mean that a command whose callback is defined with a return type other than Any gets rejected.)

It might be okay to use Callable (with no parameters) in lieu of _AnyCallable, but I haven't yet learned why _AnyCallable is being used rather than Callable.

@rra
Copy link

rra commented Jul 6, 2023

I'm guessing the right way to type these decorators is to use the facilities described here, which are intended to deal with exactly this problem of decorating functions that may have arbitrary signatures:

https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators

@davidism
Copy link
Member

davidism commented Jul 6, 2023

That was what I used originally. It was apparently lacking, because we continue to get reports from users saying that their type checker doesn't like something, then we add more complexity to the type, then something else isn't correct, etc. Python's and mypy's implementation of typing can be a huge mess sometimes.

Whoever is going to change this, be sure to follow git blame (or GitHub's view of it) back in time until you get to the original addition of types. Make sure to take into account why the various commits were made along the way.

@Tinche
Copy link
Contributor

Tinche commented Jul 6, 2023

Taking a quick look at this.

Would you consider changing the signature of option (and maybe others) to:

def option(
    *param_decls: str, cls: t.Optional[t.Type[Option]] = None, **attrs: t.Any
) -> t.Callable[[FC], FC]:

? This syntax is equivalent to what we have today.

The current implementation with a generic type alias might be a little too much for Mypy at this time (I'm getting note: Revealed type is "<nothing>"). This by itself won't solve this issue but it might be a necessary step.

After that, I think this overload is the issue:

# variant: name omitted, cls _must_ be a keyword argument, @command(cmd=CommandCls, ...)
# The correct way to spell this overload is to use keyword-only argument syntax:
# def command(*, cls: t.Type[CmdType], **attrs: t.Any) -> ...
# However, mypy thinks this doesn't fit the overloaded function. Pyright does
# accept that spelling, and the following work-around makes pyright issue a
# warning that CmdType could be left unsolved, but mypy sees it as fine. *shrug*
@t.overload
def command(
    name: None = None,
    cls: t.Type[CmdType] = ...,
    **attrs: t.Any,
) -> t.Callable[[_AnyCallable], CmdType]:
    ...

If I comment it out, the example type-checks. So we need to figure out how to fix it. Given the comment, it might not be super easy.

@Tinche
Copy link
Contributor

Tinche commented Jul 7, 2023

Like the command comment mentions, the proper way to type that overload is:

@t.overload
def command(
    name: None = None,
    *,
    cls: t.Type[CmdType],
    **attrs: t.Any,
) -> t.Callable[[_AnyCallable], CmdType]:
    ...

The comment also mentions this doesn't actually pass Mypy. Now I haven't really used custom command in click, so I googled and found this example from the official docs: https://click.palletsprojects.com/en/8.1.x/advanced/#command-aliases. It looks like this example might be exercising the problematic overload?

If I try pasting the first part of the example into a file, Mypy seems to work fine with it:

import click


class AliasedGroup(click.Group):
    def get_command(self, ctx, cmd_name):
        rv = click.Group.get_command(self, ctx, cmd_name)
        if rv is not None:
            return rv
        matches = [x for x in self.list_commands(ctx) if x.startswith(cmd_name)]
        if not matches:
            return None
        elif len(matches) == 1:
            return click.Group.get_command(self, ctx, matches[0])
        ctx.fail(f"Too many matches: {', '.join(sorted(matches))}")

    def resolve_command(self, ctx, args):
        # always return the full command name
        _, cmd, args = super().resolve_command(ctx, args)
        return cmd.name if cmd is not None else None, cmd, args


@click.command(cls=AliasedGroup)
def cli():
    pass


reveal_type(cli)  # note: Revealed type is "a02.AliasedGroup"

So maybe Mypy fixed this in the meantime and we can use the proper overload variant?

Tatsh added a commit to Tatsh/instagram-archiver that referenced this issue Jul 7, 2023
@davidism
Copy link
Member

davidism commented Jul 7, 2023

I'm fine with changing signatures. I'm also fine with writing the "right" type for people using Click, even if mypy doesn't like it when checking Click itself; we can just add ignores and comments.

Another wrinkle in this, as shown by that comment, is that people want both mypy and pyright to work, and they have different behaviors regarding decorators.

@Tinche
Copy link
Contributor

Tinche commented Jul 7, 2023

I can put together a PR over the weekend. How about I set up some typing tests too (by stealing them from @hynek in attrs)?

@davidism
Copy link
Member

davidism commented Jul 7, 2023

That would be great!

We added some typing tests to Flask after talking with Hynek at PyCon. The tests/typing directory has files with different examples that should pass (they don't have to be sensible/useful code). MyPy is configured to type check that directory as well, so it runs as part of tox and CI.

@davidism
Copy link
Member

Happy to review a PR.

@layday layday mentioned this issue Jul 13, 2023
6 tasks
sallybg added a commit to seattleflu/id3c that referenced this issue Jul 13, 2023
…ompatible Fiona, temporarily pin click to <8.1.4 to avoid mypy type check failures (pallets/click#2558), and pin pandas to 1.5.3 until pandas 2.0 is more thoroughly tested.
@ofek
Copy link
Contributor

ofek commented Jul 13, 2023

I think this is still broken but maybe I'm doing something incorrectly:

~\Desktop\j ddev{repo: core, org: staging}
❯ pip freeze | rg "click|mypy"
click==8.1.5
mypy==1.4.1
mypy-extensions==1.0.0

~\Desktop\j ddev{repo: core, org: staging}
❯ mypy .
t.py:17: error: <nothing> has no attribute "add_command"  [attr-defined]
Found 1 error in 1 file (checked 1 source file)
import click


@click.group(context_settings={'help_option_names': ['-h', '--help']}, invoke_without_command=True)
@click.version_option(version='0.1.0', prog_name='ddev')
@click.pass_context
def ddev(ctx: click.Context):
    print('ddev')


@click.command(name='ci')
@click.pass_context
def ci(ctx: click.Context):
    print('ci')


ddev.add_command(ci)


def main():
    return ddev(prog_name='ddev', windows_expand_args=False)


if __name__ == '__main__':
    main()

sallybg added a commit to seattleflu/id3c-customizations that referenced this issue Jul 13, 2023
…orarily pin click to <8.1.4 to avoid mypy type check failure (pallets/click#2558), and pin pandas to 1.5.3 until pandas 2.x has been more thoroughly tested.'
@Tinche
Copy link
Contributor

Tinche commented Jul 13, 2023

@ofek And if you try with #2565?

@ofek
Copy link
Contributor

ofek commented Jul 13, 2023

Oh indeed that fixes it:

~\Desktop\j ddev{repo: core, org: staging}
❯ pip install -q -I "click @ git+https:/layday/click.git@typing-fixes"

[notice] A new release of pip is available: 23.1.1 -> 23.1.2
[notice] To update, run: C:\USERS\OFEK\APPDATA\LOCAL\PROGRAMS\PYTHON\PYTHON311\PYTHON.EXE -m pip install --upgrade pip

~\Desktop\j ddev{repo: core, org: staging} took 5s
❯ pip freeze | rg "click|mypy"
click @ git+https:/layday/click.git@dc597ba60ead6dac09c7858e18074b3dae7f1273
mypy==1.4.1
mypy-extensions==1.0.0

~\Desktop\j ddev{repo: core, org: staging}
❯ mypy .
Success: no issues found in 1 source file

kaczmarj added a commit to SBU-BMI/wsinfer-zoo that referenced this issue Jul 14, 2023
kaczmarj added a commit to SBU-BMI/wsinfer-zoo that referenced this issue Jul 14, 2023
…nd add github actions CI (#7)

* use functools.lru_cache to please python 3.8

fixes #6

* ignore mypy huggingface-hub

* add basic tests for ci

* limit click versions to please mypy

related to pallets/click#2558
kaczmarj added a commit to SBU-BMI/wsinfer that referenced this issue Jul 14, 2023
kaczmarj added a commit to SBU-BMI/wsinfer that referenced this issue Jul 16, 2023
* use pytorch 2.0.0 as base image

* install g++

* do not remove gcc

* import torch to please jit compiler

* move custom model impls to custom_models namespace

* refactor to use wsinfer-zoo

* run isort and then black

* rm modeldefs + make modellib and patchlib public (no underscore)

* do not use torch.compile on torchscript models

* Fix/issue 131 (#133)

* use tifffile in lieu of large_image

* run isort

* make outputs float or None

* changes to please mypy

* add newline at end of document

* add openslide-python and tifffile to core deps

* add back roi support and mps device caps

* black formatting

* rm unused file

* add wsinfer-zoo to deps

* predownload registry JSON + install system deps in early layer

* scale step size and print info

Fixes #135

* add patchlib presets to package data and rm modeldefs

* set default step_size to None

* only allow step-size=patch-size

* allow custom step sizes

* update mpp print logs to slide mpp

* add tiff mpp via openslide

* resize patches to prescribed patch size and spacing

* add model config schema

* add schemas to package data

* fix error messages

Replace `--model-name` with `--model`.

* create OpenSlide obj in worker_init func

Fixes #137

The OpenSlide object is no longer created in `__init__`. Previously the
openslide object was shared across workers. Now each worker creates its
own OpenSlide object. I hypothesize that this will allow multi-worker
data loading on Windows.

* handle num_workers=0

* ADD choice of backends (tiffslide or openslide) (#139)

* replace openslide with tiffslide

* patch zarr to avoid decoding tiles in duplicate

This implements the change proposed in
zarr-developers/zarr-python#1454

* rm openslide-python and add tiffslide

* do not stitch because it imposes a performance penalty

* ignore types in vis_params

* add isort and tiffslide to dev deps

* add NoBackendException

* run isort

* use wsinfer.wsi module instead of slide_utils and add tiffslide and openslide backends

* use wsinfer.wsi.WSI as generic entrypoint for whole slides

* replace PathType with "str | Path"

* add logging and backend selection to cli

* add "from __future__ import annotations"

* TST: update tests for dev branch (#143)

* begin to update tests

* do not resize images prior to transform

This introduces subtle differences from the current stable version of
wsinfer.

* fix for issue #125

* do not save slide path in model outputs csv

* add test_cli_run_with_registered_models

* add reference model outputs

These reference outputs were created using a patched version of 0.3.6
wsinfer. The patches involved padding the patches from large-image to be
the expected patch size. Large image does not pad images by default,
whereas openslide and tiffslide pad with black.

* skip jit tests and cli with custom config

* deprecate python 3.7

* install openslide and tiffslide

* remove WSIType object

* remove dense grid creation

fixes #138

* remove timm and custom models

We will focus on using TorchScript models only. In the future, we can
also look into using ONNX as a backend.

fixes #140

* limit click versions to please mypy

related to pallets/click#2558

* satisfy mypy

* fix cli args for wsinfer run

* fail loudly with dev pytorch + fix jit compile tests

* fix test of issue 89

* move wsinfer imports to beginning of file

* add test of mutually exclusive cli args

* use -p shorthand for model-path

* mark that we support typing

* add py.typed to package data

* run test-package on windows, macos, and linux

* fix test of patching

* install openslide differently on different systems

* close the case statement

* fix the way we install openslide on different envs

* fix matrix.os test

* get line length with python for cross-platform

* test "wsinfer run" differently for unix and windows

* fix windows test

* fix path to csv

* skip windows tests for now because tissue segmentation is different

* run "wsinfer run" on windows but do not test file length

* add test of local model with config
@blue42u
Copy link

blue42u commented Jul 16, 2023

I'm still experiencing this issue, the examples in https://click.palletsprojects.com/en/8.1.x/quickstart/#nesting-commands fail to typecheck under MyPy 1.4.1. I imagine whatever type adjustments were done to click.command for 8.1.5 also needs to be done to click.group.

import click

@click.group()
def cli():
    pass

@cli.command()
def initdb():
    click.echo('Initialized the database')

@cli.command()
def dropdb():
    click.echo('Dropped the database')
python3 -m pip list
Package           Version
----------------- -------
click             8.1.5
mypy              1.4.1
mypy-extensions   1.0.0
pip               23.1.2
setuptools        68.0.0
typing_extensions 4.7.1mypy --pretty example.py
example.py:7: error: <nothing> has no attribute "command"  [attr-defined]
    @cli.command()
     ^~~~~~~~~~~
example.py:11: error: <nothing> has no attribute "command"  [attr-defined]
    @cli.command()
     ^~~~~~~~~~~
Found 2 errors in 1 file (checked 1 source file)

@layday
Copy link
Contributor

layday commented Jul 16, 2023

Can you try #2565?

@blue42u
Copy link

blue42u commented Jul 16, 2023

Can confirm #2565 resolves this issue for me. Thanks! Now to wait for 8.1.6...

@pierrejeambrun

This comment was marked as off-topic.

@davidism
Copy link
Member

Locking to avoid "me too" comments.

@pallets pallets locked as resolved and limited conversation to collaborators Jul 16, 2023
nedbat pushed a commit to nedbat/scriv that referenced this issue Jul 18, 2023
These type: pragmas can be removed when click 8.1.6 is out, to fix
pallets/click#2558.
@pallets pallets unlocked this conversation Jul 18, 2023
@davidism
Copy link
Member

Click 8.1.6 is available on PyPI.

@aarnphm
Copy link

aarnphm commented Jul 29, 2023

For anyone that runs mypy with pre-commit, make sure to add click>=8.1.6 to additional_dependencies

JureZmrzlikar added a commit to JureZmrzlikar/RNAnorm that referenced this issue Aug 2, 2023
For more context, see Click issue:
pallets/click#2558
At this point, click 8.1.6 is released and fixes can be reverted.
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 13, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet