In cases where there is not a clear scope, or where enforcing a scope
and passing a temp directory to callees creates unnecessary coupling
between components, this will let us tie the lifetime of temporary
directories to the lifetime of the application without using e.g.
atexit or finalizers.
This has the benefit of being easier to test and reason about.
A boolean flag is simpler to reason about than a complex type like
`conflicts_with`.
In all of these situations `conflicts_with` was being assigned a
non-None value and only being checked for truthyness, so a bool is
sufficient to capture the required usages.
InstallRequirement.uninstall doesn't actually use conflicts_with, so
don't log it. Instead we output the requirement name so we still have
something that looks like a heading before indenting the log, and log
what we actually uninstall inside the function.
This will also enable us to get rid of conflicts_with.
This makes InstallRequirement simpler and overall makes it easier to
track how the parts of InstallRequirement are being used for the phases
of package processing.
This will be home to Dowloader, Download, and associated helper
functions. Since this is an abstraction over PipSession, it makes
sense to keep these functions in a separate module.
Also move a helper function here from operations.prepare.
When we factor out tests these will be needed in both sets, and it's
easier to refactor tests later if we avoid creating a dependency between
test files.
This aligns it with wheel installation and reinforces that it is the
very last thing that happens before we return (so we can potentially
refactor it out later).
Previously InstallRequirement.uninstall was using
InstallRequirement.check_if_exists, which is a very overloaded
function with several callers that operate at different phases in
pip processing, not to mention that it mutates InstallRequirement itself.
Now we don't use that function for InstallRequirement.uninstall.
There should be no behavior change here.
This helps in several ways:
1. makes it easier to test the correct behavior of wheel installation via
`install_unpacked_wheel` independent of `InstallRequirement`
2. is easier to understand, since `install_unpacked_wheel` itself should
know which Wheel version(s) it supports
3. reduces the scope of `check_compatibility` and `wheel_version`, which
will make it easier to move `wheel` to `operations.install.wheel`