Package: helpdev
Version: 0.7.1-7
Severity: normal
Tags: patch

Dear Maintainer,

(I am the maintainer; filing this so the problem is on record before the fix,
since it affects the version currently in unstable.)

helpdev aborts with an unhandled traceback when pip is not installed:

  $ helpdev --packages-pip
  Traceback (most recent call last):
    ...
    File "/usr/lib/python3/dist-packages/helpdev/__init__.py", line 73, in
      _run_subprocess_split
      output = subprocess.check_output(command, shell=False)
  FileNotFoundError: [Errno 2] No such file or directory: 'pip'

Debian does not install pip by default and helpdev does not depend on it, so
this is the behaviour on a stock system.

Affected invocations, tested on a machine with no pip on PATH:

  helpdev                 completes normally (does not reach the pip path)
  helpdev --all           FileNotFoundError
  helpdev --packages-pip  FileNotFoundError
  helpdev --packages      FileNotFoundError

--all matters most: it is the obvious invocation for a tool whose whole
purpose is reporting environment information, and it is the one documented
first in helpdev(1).

CAUSE

check_python_packages() in helpdev/__init__.py calls the subprocess helper
directly:

      all_packages = _run_subprocess_split(['pip', 'list'])

and _run_subprocess_split() is a bare subprocess.check_output(), so a missing
pip propagates out of main().

The interesting part is that upstream already solved this for the other
external tool it calls. check_conda_packages(), twenty lines further down,
does:

      try:
          all_packages = _run_subprocess_split(['conda', 'list', ...])
      except (subprocess.CalledProcessError, FileNotFoundError2and3):
          info['CONDA PACKAGES']['Status'] = 'Conda not available!'
      else:
          ...

That works correctly - with no conda installed, helpdev reports
"Conda not available!" and carries on. Upstream even defines a
FileNotFoundError2and3 compatibility alias specifically to catch this. The
guard was simply never applied to the pip path.

FIX

The patch attached to this report (0001-report-missing-pip-instead-of-
crashing.patch) applies the existing conda pattern to check_python_packages(),
so a missing pip is reported as "PIP not available!" alongside the existing
"Conda not available!" line, and no invocation crashes.

I have deliberately not added a dependency on python3-pip. pip is
heavyweight, Debian discourages relying on it at runtime, and a tool whose
job is to report what is present in an environment should say when something
is absent rather than refuse to run without it. Suggests: python3-pip would
be reasonable to add alongside this.

The patch is against 0.7.1-7. With it applied:

  * helpdev --all, --packages-pip and --packages-pip-e all complete, printing
    "PIP not available!" under PYTHON PACKAGES.
  * The package builds in a clean unstable chroot and the test suite passes
    (5 passed). python3-pip is a build dependency, so the tests still
    exercise the real pip code path, which is unchanged.
  * lintian reports nothing new.

I will forward it upstream to https://gitlab.com/dpizetta/helpdev as well.

Reproduced and fixed against 0.7.1-7 as built from the packaging git at
31ca302. Testing was done in clean unstable chroots rather than on an
installed system: sbuild for the build and test suite, autopkgtest under
unshare for the runtime check, and the crash itself reproduced by importing
the module directly on a host with no pip on PATH. I have not tried to
reproduce it on a stock installed helpdev, since the code path does not
depend on how the package was installed.
Description: report a missing pip instead of crashing
 check_python_packages() shells out to "pip list" via _run_subprocess_split(),
 but nothing catches the FileNotFoundError raised when pip is absent. Debian
 does not install pip by default, so on a stock system "helpdev --all" and
 "helpdev --packages-pip" abort with an unhandled traceback rather than
 reporting what they could not determine.
 .
 The sibling function check_conda_packages() already handles exactly this
 case: it wraps the same subprocess helper in a try/except for
 CalledProcessError and the upstream FileNotFoundError2and3 compatibility
 alias, and records a Status string instead of propagating. This applies that
 existing pattern to the pip path, so a missing pip reports
 "PIP not available!" just as a missing conda reports "Conda not available!".
 .
 Adding a dependency on python3-pip would be the wrong fix: pip is heavyweight
 and Debian discourages relying on it at runtime. A diagnostics tool should
 report an absent tool, not require it.
Author: Dustin Kost <[email protected]>
Forwarded: no, to be submitted to https://gitlab.com/dpizetta/helpdev
Last-Update: 2026-08-14
---
--- a/helpdev/__init__.py
+++ b/helpdev/__init__.py
@@ -497,27 +497,30 @@ def check_python_packages(edit_mode=Fals
         dict(str): Dictionary filled with respective information.
     """
 
+    info = {'PYTHON PACKAGES': {}}
     all_packages = ''
 
-    if edit_mode:
-        all_packages = _run_subprocess_split(['pip', 'list', '-e'])
+    try:
+        if edit_mode:
+            all_packages = _run_subprocess_split(['pip', 'list', '-e'])
+        else:
+            # list all packages, including in editable mode
+            all_packages = _run_subprocess_split(['pip', 'list'])
+    except (subprocess.CalledProcessError, FileNotFoundError2and3):
+        info['PYTHON PACKAGES']['Status'] = 'PIP not available!'
     else:
-        # list all packages, including in editable mode
-        all_packages = _run_subprocess_split(['pip', 'list'])
-
-    # split lines and remove table name
-    line_packages = all_packages.split("\n")[2:]
-
-    info = {'PYTHON PACKAGES': {}}
-
-    # clean spaces, create a list and insert in the dictionary
-    for line in line_packages:
-        splitted = line.split(' ')
-        cleaned = ' '.join(splitted).split()
-        info['PYTHON PACKAGES'][cleaned[0]] = cleaned[1]
+        # split lines and remove table name
+        line_packages = all_packages.split("\n")[2:]
 
-    if packages:
-        info['PYTHON PACKAGES'] = filter_packages(info['PYTHON PACKAGES'], packages)
+        # clean spaces, create a list and insert in the dictionary
+        for line in line_packages:
+            splitted = line.split(' ')
+            cleaned = ' '.join(splitted).split()
+            info['PYTHON PACKAGES'][cleaned[0]] = cleaned[1]
+
+        if packages:
+            info['PYTHON PACKAGES'] = filter_packages(info['PYTHON PACKAGES'],
+                                                      packages)
 
     return info
 

Reply via email to