Package: python3-pip
Version: 23.0.1+dfsg-1
Severity: normal
X-Debbugs-Cc: [email protected]

As an end user installing a python app on an offline host without any
python knowledge, I was able to work out that a cloud-attached machine
needed to fetch whl files using the download command. After copying
the whl files to the offline target host, an installation can execute
using the very non-intuitive “--find-links” argument.

A command like this simply goes to lunch with no apparent activity:

  $ pipx install --verbose --pip-args='--verbose --find-links wheel_cache' .

So after several attempts letting the above command quite long without
results, pipx was abandoned and this was tried:

  $ pip install --verbose --log-file ~/logs/pip_$(date +%F)_err.log --log-file 
~/logs/pip_$(date +%F).log --find-links wheel_cache .

The output indicates 5 /unexpected/ attempts to reach the cloud for
every whl file. The time waste is so great that it’s generally
unsurmountable particularly when silently non-transparent (without
verbosity).

Sample output:

===✂----------------------------------------
  …
  Processing ./wheel_cache/stanza-1.10.1-py3-none-any.whl
  WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, 
status=None)) after connection broken by 
'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 
0x7f64c075cfd0>: Failed to establish a new connection: [Errno -2] Name or 
service not known')': /simple/emoji/
  WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, 
status=None)) after connection broken by 
'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 
0x7f64c075edd0>: Failed to establish a new connection: [Errno -2] Name or 
service not known')': /simple/emoji/
  WARNING: Retrying (Retry(total=2, connect=None, read=None, redirect=None, 
status=None)) after connection broken by 
'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 
0x7f64c077c390>: Failed to establish a new connection: [Errno -2] Name or 
service not known')': /simple/emoji/
  WARNING: Retrying (Retry(total=1, connect=None, read=None, redirect=None, 
status=None)) after connection broken by 
'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 
0x7f64c077cc10>: Failed to establish a new connection: [Errno -2] Name or 
service not known')': /simple/emoji/
  WARNING: Retrying (Retry(total=0, connect=None, read=None, redirect=None, 
status=None)) after connection broken by 
'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 
0x7f64c077d4d0>: Failed to establish a new connection: [Errno -2] Name or 
service not known')': /simple/emoji/
  Processing ./wheel_cache/emoji-2.15.0-py3-none-any.whl
  WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, 
status=None)) after connection broken by 
'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 
0x7f64c0753590>: Failed to establish a new connection: [Errno -2] Name or 
service not known')': /simple/numpy/
  WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, 
status=None)) after connection broken by 
'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 
0x7f64c077df90>: Failed to establish a new connection: [Errno -2] Name or 
service not known')': /simple/numpy/
  WARNING: Retrying (Retry(total=2, connect=None, read=None, redirect=None, 
status=None)) after connection broken by 
'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 
0x7f64c077cf10>: Failed to establish a new connection: [Errno -2] Name or 
service not known')': /simple/numpy/
  WARNING: Retrying (Retry(total=1, connect=None, read=None, redirect=None, 
status=None)) after connection broken by 
'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 
0x7f64c077cd10>: Failed to establish a new connection: [Errno -2] Name or 
service not known')': /simple/numpy/
  … 
===✂----------------------------------------

The man page has this:
===✂----------------------------------------
  …
  --no-index
    Ignore package index (only looking at --find-links URLs instead).

  -f,--find-links <url>
    If  a  url  or path to an html file, then parse for links to archives. If a 
local path or file:// url that's a directory,
    then look for archives in the directory listing.
  …
===✂----------------------------------------

It’s first worth noting that -f is factually incorrect (out of
alignment with what the code does). That is, “<url>” must be an URL,
not a simple filename or directory, according to the man page. But in
fact it *must* be a simple directory name in some cases. Supplying
“file://./wheel_cache” is rejected:

===✂----------------------------------------
  Looking in links: file://./wheel_cache
  ValueError: non-local file URIs are not supported on this platform: 
'file://./wheel_cache'
===✂----------------------------------------

The syntax must be “wheel_cache” in this case. Apart from that, the
wording assumes the user has some kind of deep knowledge about lower
level internal structures. “Archives” is vague and non-intuitive to
refer to dependency packages. It’s effectively concealed from the user
that -f is needed to specify the location of whl files. After fetching
the whl files, users would expect to find an option somewhere for
referring to them in the install command. We must read every option
and use a process elimination to work out that -f is the best fitting
for that purpose.

The --no-index description is meaningless to users without a deep
understanding of python project structure. There is no mention of
offline ops. It was only after many failed installation attempts that
I was forced to consider that I may need the --no-index option. The
“only looking at --find-links URLs instead” was critical in that
guesswork. Users should not have to guess.

The --no-index option was still sloppy because there were still
unexpected attempts to access the WAN at the start of an
installation. But at least no furter attempts were made for each
dependency, so the process could finish in reasonable time.

-- System Information:
Debian Release: 12.12
  APT prefers oldstable-updates
  APT policy: (990, 'oldstable-updates'), (990, 'oldstable-security'), (990, 
'stable'), (990, 'oldstable'), (500, 'oldoldstable')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 5.10.0-28-amd64 (SMP w/2 CPU threads)
Kernel taint flags: TAINT_OOT_MODULE, TAINT_UNSIGNED_MODULE
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8), LANGUAGE not set
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled

Versions of packages python3-pip depends on:
ii  ca-certificates     20230311+deb12u1
ii  python3             3.11.2-1+b1
ii  python3-distutils   3.11.2-3
ii  python3-setuptools  66.1.1-1+deb12u2
ii  python3-wheel       0.38.4-2

Versions of packages python3-pip recommends:
ii  build-essential  12.9
ii  python3-dev      3.11.2-1+b1

python3-pip suggests no packages.

-- no debconf information

Reply via email to