pyinvoke / invoke

Pythonic task management & command execution.
http://pyinvoke.org
BSD 2-Clause "Simplified" License
4.31k stars 365 forks source link

Properly annotate run functions #997

Open rmorshea opened 1 month ago

rmorshea commented 1 month ago

I'd like to run a type checker against the code in tasks.py however the fact that Context.run returns Result | None instead of just a Result makes it rather annoying to deal with. I constantly have to assert result is not None in order to access attributes like Result.stdout.

Given the underlying implementation, the solution is to describe run() with overloads. Further, since there a number of public interfaces that look like Context.run, in this PR I've devised an annotate_run_function decorator that makes it easy to declare a function as having a run-like interface. Beyond just properly describing the return type of run-like methods, this PR also annotates all the valid options that can be passed to run as well. I also find this quite useful since I always have to dive into the docs for Runner.run to figure out what parameters are available instead of just being able to refer to Context.run.

In order to make all this happen I've had to do a couple things...

First, I had to upgrade MyPy for it to understand the most recent type tooling (e.g. Unpack). This allowed me to remove a couple type: ignore comments. However, the latest version of MyPy only supports Python>=3.8 so it looks like there will need to be a change to orb/invocations so that it runs under Python 3.8 by default. Dropping support for Python<3.8 seems fairly reasonable - for example, Numpy's policy is that it's newest releases will only support the last three Python versions (3.10+ as of writing).

Second, I needed to use typing_extensions in order to get access to back-ported implementations of newer typing features. Since invoke doesn't seem to have dependencies I've defined most of the type annotations in if TYPE_CHECKING to avoid any issues at runtime.