Use unified OSError and its subclasses

Since Python 3.3, the following classes have merged into OSError. They
remain as aliases for backward compatibility.

- EnvironmentError
- IOError
- WindowsError

https://docs.python.org/3/library/exceptions.html#OSError

Python 3 also has subclasses of OSError to help identify more specific
errors. For example, FileNotFoundError. This allows simplifying some
except blocks.
This commit is contained in:
Jon Dufresne 2020-12-26 05:49:31 -08:00
parent 2fb341aac7
commit d282fb94a3
12 changed files with 48 additions and 68 deletions

View File

@ -29,13 +29,6 @@ per-file-ignores =
noxfile.py: G
# B011: Do not call assert False since python -O removes these calls
tests/*: B011
# TODO: Remove IOError from except (OSError, IOError) blocks in
# these files when Python 2 is removed.
# In Python 3, IOError have been merged into OSError
# https://github.com/PyCQA/flake8-bugbear/issues/110
src/pip/_internal/utils/filesystem.py: B014
src/pip/_internal/network/cache.py: B014
src/pip/_internal/utils/misc.py: B014
[mypy]
follow_imports = silent

View File

@ -435,10 +435,10 @@ class InstallCommand(RequirementCommand):
write_output(
'Successfully installed %s', installed_desc,
)
except EnvironmentError as error:
except OSError as error:
show_traceback = (self.verbosity >= 1)
message = create_env_error_message(
message = create_os_error_message(
error, show_traceback, options.use_user_site,
)
logger.error(message, exc_info=show_traceback) # noqa
@ -697,16 +697,16 @@ def reject_location_related_install_options(requirements, options):
)
def create_env_error_message(error, show_traceback, using_user_site):
# type: (EnvironmentError, bool, bool) -> str
"""Format an error message for an EnvironmentError
def create_os_error_message(error, show_traceback, using_user_site):
# type: (OSError, bool, bool) -> str
"""Format an error message for an OSError
It may occur anytime during the execution of the install command.
"""
parts = []
# Mention the error if we are not going to show a traceback
parts.append("Could not install packages due to an EnvironmentError")
parts.append("Could not install packages due to an OSError")
if not show_traceback:
parts.append(": ")
parts.append(str(error))

View File

@ -29,7 +29,7 @@ def suppressed_cache_errors():
"""
try:
yield
except (OSError, IOError):
except OSError:
pass

View File

@ -553,7 +553,7 @@ def get_file_content(url, session):
try:
with open(url, 'rb') as f:
content = auto_decode(f.read())
except IOError as exc:
except OSError as exc:
raise InstallationError(
f'Could not open requirements file: {exc}'
)

View File

@ -1,5 +1,4 @@
import contextlib
import errno
import hashlib
import logging
import os
@ -103,10 +102,8 @@ class RequirementTracker:
try:
with open(entry_path) as fp:
contents = fp.read()
except IOError as e:
# if the error is anything other than "file does not exist", raise.
if e.errno != errno.ENOENT:
raise
except FileNotFoundError:
pass
else:
message = '{} is already being built: {}'.format(
req.link, contents)

View File

@ -50,7 +50,7 @@ class SelfCheckState:
try:
with open(self.statefile_path) as statefile:
self.state = json.load(statefile)
except (IOError, ValueError, KeyError):
except (OSError, ValueError, KeyError):
# Explicitly suppressing exceptions, since we don't want to
# error out if the cache file is invalid.
pass

View File

@ -1,4 +1,3 @@
import errno
import fnmatch
import os
import os.path
@ -64,7 +63,7 @@ def copy2_fixed(src, dest):
"""
try:
shutil.copy2(src, dest)
except (OSError, IOError):
except OSError:
for f in [src, dest]:
try:
is_socket_file = is_socket(f)
@ -148,27 +147,22 @@ def _test_writable_dir_win(path):
file = os.path.join(path, name)
try:
fd = os.open(file, os.O_RDWR | os.O_CREAT | os.O_EXCL)
# Python 2 doesn't support FileExistsError and PermissionError.
except OSError as e:
# exception FileExistsError
if e.errno == errno.EEXIST:
continue
# exception PermissionError
if e.errno == errno.EPERM or e.errno == errno.EACCES:
# This could be because there's a directory with the same name.
# But it's highly unlikely there's a directory called that,
# so we'll assume it's because the parent dir is not writable.
# This could as well be because the parent dir is not readable,
# due to non-privileged user access.
return False
raise
except FileExistsError:
pass
except PermissionError:
# This could be because there's a directory with the same name.
# But it's highly unlikely there's a directory called that,
# so we'll assume it's because the parent dir is not writable.
# This could as well be because the parent dir is not readable,
# due to non-privileged user access.
return False
else:
os.close(fd)
os.unlink(file)
return True
# This should never be reached
raise EnvironmentError(
raise OSError(
'Unexpected condition testing for writable directory'
)

View File

@ -138,7 +138,7 @@ def rmtree_errorhandler(func, path, exc_info):
read-only attribute, and hopefully continue without problems."""
try:
has_attr_readonly = not (os.stat(path).st_mode & stat.S_IWRITE)
except (IOError, OSError):
except OSError:
# it's equivalent to os.path.exists
return

View File

@ -53,7 +53,7 @@ def _get_pyvenv_cfg_lines():
# writes with UTF-8. (pypa/pip#8717)
with open(pyvenv_cfg_file, encoding='utf-8') as f:
return f.read().splitlines() # avoids trailing newlines
except IOError:
except OSError:
return None

View File

@ -1,6 +1,5 @@
"""Handles all VCS (version control) support"""
import errno
import logging
import os
import shutil
@ -772,16 +771,13 @@ class VersionControl:
extra_environ=extra_environ,
extra_ok_returncodes=extra_ok_returncodes,
log_failed_cmd=log_failed_cmd)
except OSError as e:
except FileNotFoundError:
# errno.ENOENT = no such file or directory
# In other words, the VCS executable isn't available
if e.errno == errno.ENOENT:
raise BadCommand(
'Cannot find command {cls.name!r} - do you have '
'{cls.name!r} installed and in your '
'PATH?'.format(**locals()))
else:
raise # re-raise exception if a different error occurred
raise BadCommand(
'Cannot find command {cls.name!r} - do you have '
'{cls.name!r} installed and in your '
'PATH?'.format(**locals()))
@classmethod
def is_repository_directory(cls, path):

View File

@ -5,7 +5,7 @@ from mock import patch
from pip._vendor.packaging.requirements import Requirement
from pip._internal.commands.install import (
create_env_error_message,
create_os_error_message,
decide_user_install,
reject_location_related_install_options,
)
@ -81,35 +81,35 @@ def test_rejection_for_location_requirement_options():
@pytest.mark.parametrize('error, show_traceback, using_user_site, expected', [
# show_traceback = True, using_user_site = True
(EnvironmentError("Illegal byte sequence"), True, True, 'Could not install'
' packages due to an EnvironmentError.\n'),
(EnvironmentError(errno.EACCES, "No file permission"), True, True, 'Could'
' not install packages due to an EnvironmentError.\nCheck the'
(OSError("Illegal byte sequence"), True, True, 'Could not install'
' packages due to an OSError.\n'),
(OSError(errno.EACCES, "No file permission"), True, True, 'Could'
' not install packages due to an OSError.\nCheck the'
' permissions.\n'),
# show_traceback = True, using_user_site = False
(EnvironmentError("Illegal byte sequence"), True, False, 'Could not'
' install packages due to an EnvironmentError.\n'),
(EnvironmentError(errno.EACCES, "No file permission"), True, False, 'Could'
' not install packages due to an EnvironmentError.\nConsider using the'
(OSError("Illegal byte sequence"), True, False, 'Could not'
' install packages due to an OSError.\n'),
(OSError(errno.EACCES, "No file permission"), True, False, 'Could'
' not install packages due to an OSError.\nConsider using the'
' `--user` option or check the permissions.\n'),
# show_traceback = False, using_user_site = True
(EnvironmentError("Illegal byte sequence"), False, True, 'Could not'
' install packages due to an EnvironmentError: Illegal byte'
(OSError("Illegal byte sequence"), False, True, 'Could not'
' install packages due to an OSError: Illegal byte'
' sequence\n'),
(EnvironmentError(errno.EACCES, "No file permission"), False, True, 'Could'
' not install packages due to an EnvironmentError: [Errno 13] No file'
(OSError(errno.EACCES, "No file permission"), False, True, 'Could'
' not install packages due to an OSError: [Errno 13] No file'
' permission\nCheck the permissions.\n'),
# show_traceback = False, using_user_site = False
(EnvironmentError("Illegal byte sequence"), False, False, 'Could not'
' install packages due to an EnvironmentError: Illegal byte sequence'
(OSError("Illegal byte sequence"), False, False, 'Could not'
' install packages due to an OSError: Illegal byte sequence'
'\n'),
(EnvironmentError(errno.EACCES, "No file permission"), False, False,
'Could not install packages due to an EnvironmentError: [Errno 13] No'
(OSError(errno.EACCES, "No file permission"), False, False,
'Could not install packages due to an OSError: [Errno 13] No'
' file permission\nConsider using the `--user` option or check the'
' permissions.\n'),
])
def test_create_env_error_message(
def test_create_os_error_message(
error, show_traceback, using_user_site, expected
):
msg = create_env_error_message(error, show_traceback, using_user_site)
msg = create_os_error_message(error, show_traceback, using_user_site)
assert msg == expected