Merge pull request #3042 from mroutis/refactor-exceptions

py3: refactor exceptions
This commit is contained in:
Ruslan Kuprieiev
2020-01-03 05:44:52 +02:00
committed by GitHub
20 changed files with 86 additions and 131 deletions
+3 -4
View File
@@ -104,9 +104,8 @@ def summon(name, repo=None, rev=None, summon_file="dvcsummon.yaml", args=None):
info = obj["summon"]
except SummonError as exc:
raise SummonError(
str(exc) + " at '{}' in '{}'".format(summon_file, repo),
cause=exc.cause,
)
str(exc) + " at '{}' in '{}'".format(summon_file, repo)
) from exc
_pull_dependencies(_repo, info.get("deps", []))
@@ -138,7 +137,7 @@ def _get_object_from_summon_file(name, path):
except FileMissingError:
raise SummonError("Summon file not found")
except ruamel.yaml.YAMLError as exc:
raise SummonError("Failed to parse summon file", exc)
raise SummonError("Failed to parse summon file") from exc
except Invalid as exc:
raise SummonError(str(exc))
+6 -11
View File
@@ -17,19 +17,14 @@ logger = logging.getLogger(__name__)
class ConfigError(DvcException):
"""DVC config exception.
"""DVC config exception."""
Args:
msg (str): error message.
cause (Exception): optional exception that has caused this error.
"""
def __init__(self, msg, cause=None):
super().__init__("config file error: {}".format(msg), cause=cause)
def __init__(self, msg):
super().__init__("config file error: {}".format(msg))
class NoRemoteError(ConfigError):
def __init__(self, command, cause=None):
def __init__(self, command):
msg = (
"no remote specified. Setup default remote with\n"
" dvc config core.remote <name>\n"
@@ -37,7 +32,7 @@ class NoRemoteError(ConfigError):
" dvc {} -r <name>\n".format(command)
)
super().__init__(msg, cause=cause)
super().__init__(msg)
def supported_cache_type(types):
@@ -392,7 +387,7 @@ class Config(object): # pylint: disable=too-many-instance-attributes
try:
d = self.COMPILED_SCHEMA(d)
except Invalid as exc:
raise ConfigError(str(exc), cause=exc)
raise ConfigError(str(exc)) from exc
self.config = configobj.ConfigObj(d, write_empty_values=True)
def save(self, config=None):
+11 -37
View File
@@ -1,29 +1,10 @@
"""Exceptions raised by the dvc."""
import traceback
from dvc.utils import relpath
class DvcException(Exception):
"""Base class for all dvc exceptions.
Args:
msg (unicode): message for this exception.
cause (Exception): optional cause exception.
"""
def __init__(self, msg, cause=None):
# NOTE: unlike python 3, python 2 doesn't have built-in support
# for chained exceptions, so we are using our own implementation.
self.cause = cause
self.cause_tb = None
if cause:
try:
self.cause_tb = traceback.format_exc()
except AttributeError: # pragma: no cover
pass
super().__init__(msg)
"""Base class for all dvc exceptions."""
class OutputDuplicationError(DvcException):
@@ -109,8 +90,7 @@ class ArgumentDuplicationError(DvcException):
def __init__(self, path):
assert isinstance(path, str)
msg = "file '{}' is specified more than once."
super().__init__(msg.format(path))
super().__init__("file '{}' is specified more than once.".format(path))
class MoveNotDataSourceError(DvcException):
@@ -175,15 +155,13 @@ class ConfirmRemoveError(DvcException):
class InitError(DvcException):
def __init__(self, msg):
super().__init__(msg)
pass
class ReproductionError(DvcException):
def __init__(self, dvc_file_name, ex):
def __init__(self, dvc_file_name):
self.path = dvc_file_name
msg = "failed to reproduce '{}'".format(dvc_file_name)
super().__init__(msg, cause=ex)
super().__init__("failed to reproduce '{}'".format(dvc_file_name))
class BadMetricError(DvcException):
@@ -205,12 +183,11 @@ class NoMetricsError(DvcException):
class StageFileCorruptedError(DvcException):
def __init__(self, path, cause=None):
def __init__(self, path):
path = relpath(path)
super().__init__(
"unable to read DVC-file: {} "
"YAML file structure is corrupted".format(path),
cause=cause,
"YAML file structure is corrupted".format(path)
)
@@ -236,10 +213,8 @@ class OverlappingOutputPathsError(DvcException):
class CheckoutErrorSuggestGit(DvcException):
def __init__(self, target, cause):
super().__init__(
"Did you mean `git checkout {}`?".format(target), cause=cause
)
def __init__(self, target):
super().__init__("Did you mean `git checkout {}`?".format(target))
class ETagMismatchError(DvcException):
@@ -251,10 +226,9 @@ class ETagMismatchError(DvcException):
class FileMissingError(DvcException):
def __init__(self, path, cause=None):
def __init__(self, path):
super().__init__(
"Can't find '{}' neither locally nor on remote".format(path),
cause=cause,
"Can't find '{}' neither locally nor on remote".format(path)
)
+3 -3
View File
@@ -114,11 +114,11 @@ class ColorFormatter(logging.Formatter):
tb_list = [tb]
# NOTE: parsing chained exceptions. See dvc/exceptions.py for more info
while hasattr(exc, "cause") and exc.cause:
exc_list.append(str(exc.cause))
while hasattr(exc, "__cause__") and exc.__cause__:
exc_list.append(str(exc.__cause__))
if hasattr(exc, "cause_tb") and exc.cause_tb:
tb_list.insert(0, str(exc.cause_tb))
exc = exc.cause
exc = exc.__cause__
return exc_list, tb_list
+3 -4
View File
@@ -63,10 +63,9 @@ class RemoteMissingDepsError(DvcException):
class DirCacheError(DvcException):
def __init__(self, checksum, cause=None):
def __init__(self, checksum):
super().__init__(
"Failed to load dir cache for checksum: '{}'.".format(checksum),
cause=cause,
"Failed to load dir cache for checksum: '{}'.".format(checksum)
)
@@ -264,7 +263,7 @@ class RemoteBASE(object):
with self.cache.open(path_info, "r") as fobj:
d = json.load(fobj)
except (ValueError, FileNotFoundError) as exc:
raise DirCacheError(checksum, cause=exc)
raise DirCacheError(checksum) from exc
if not isinstance(d, list):
msg = "dir cache file format error '{}' [skipping the file]"
+13 -15
View File
@@ -19,18 +19,15 @@ FOLDER_MIME_TYPE = "application/vnd.google-apps.folder"
class GDriveRetriableError(DvcException):
def __init__(self, msg, cause=None):
super().__init__(msg, cause=cause)
pass
class GDriveAccessTokenRefreshError(DvcException):
def __init__(self, msg, cause=None):
super().__init__(msg, cause=cause)
pass
class GDriveMissedCredentialKeyError(DvcException):
def __init__(self, msg, cause=None):
super().__init__(msg, cause=cause)
pass
@decorator
@@ -208,21 +205,22 @@ class RemoteGDrive(RemoteBASE):
try:
gauth.CommandLineAuth()
except RefreshError as e:
except RefreshError as exc:
raise GDriveAccessTokenRefreshError(
"Google Drive's access token refreshment is failed", e
)
except KeyError as e:
"Google Drive's access token refreshment is failed"
) from exc
except KeyError as exc:
raise GDriveMissedCredentialKeyError(
"Google Drive's user credentials file '{}' "
"misses value for key '{}'".format(
self.gdrive_user_credentials_path, str(e)
),
e,
self.gdrive_user_credentials_path, str(exc)
)
)
# Handle pydrive.auth.AuthenticationError and others auth failures
except Exception as e:
raise DvcException("Google Drive authentication failed", e)
except Exception as exc:
raise DvcException(
"Google Drive authentication failed"
) from exc
finally:
if os.getenv(RemoteGDrive.GDRIVE_USER_CREDENTIALS_DATA):
os.remove(self.gdrive_user_credentials_path)
+2 -2
View File
@@ -87,8 +87,8 @@ class RemoteS3(RemoteBASE):
obj = s3.head_object(Bucket=bucket, Key=path, *args, **kwargs)
except Exception as exc:
raise DvcException(
"s3://{}/{} does not exist".format(bucket, path), exc
)
"s3://{}/{} does not exist".format(bucket, path)
) from exc
return obj
@classmethod
+6 -8
View File
@@ -103,14 +103,13 @@ class SSHConnection:
if tail:
try:
self.sftp.mkdir(path)
except IOError as e:
except IOError as exc:
# Since paramiko errors are very vague we need to recheck
# whether it's because path already exists or something else
if e.errno == errno.EACCES or not self.exists(path):
if exc.errno == errno.EACCES or not self.exists(path):
raise DvcException(
"unable to create remote directory '{}'".format(path),
cause=e,
)
"unable to create remote directory '{}'".format(path)
) from exc
def walk(self, directory, topdown=True):
# NOTE: original os.walk() implementation [1] with default options was
@@ -123,9 +122,8 @@ class SSHConnection:
raise DvcException(
"couldn't get the '{}' remote directory files list".format(
directory
),
cause=exc,
)
)
) from exc
dirs = []
nondirs = []
+5 -5
View File
@@ -444,24 +444,24 @@ class Repo(object):
cause = None
try:
out, = self.find_outs_by_path(path)
except OutputNotFoundError as e:
except OutputNotFoundError as exc:
out = None
cause = e
cause = exc
if out and out.use_cache:
try:
with self._open_cached(out, remote, mode, encoding) as fd:
yield fd
return
except FileNotFoundError as e:
raise FileMissingError(relpath(path, self.root_dir), cause=e)
except FileNotFoundError as exc:
raise FileMissingError(relpath(path, self.root_dir)) from exc
if self.tree.exists(path):
with self.tree.open(path, mode, encoding) as fd:
yield fd
return
raise FileMissingError(relpath(path, self.root_dir), cause=cause)
raise FileMissingError(relpath(path, self.root_dir)) from cause
def _open_cached(self, out, remote=None, mode="r", encoding=None):
if out.isdir():
+1 -1
View File
@@ -49,7 +49,7 @@ def _checkout(
except (StageFileDoesNotExistError, StageFileBadNameError) as exc:
if not target:
raise
raise CheckoutErrorSuggestGit(target, exc)
raise CheckoutErrorSuggestGit(target) from exc
total = get_all_files_numbers(pairs)
if total == 0:
+2 -2
View File
@@ -162,6 +162,6 @@ def _reproduce_stages(
kwargs["force"] = True
result.extend(ret)
except Exception as ex:
raise ReproductionError(st.relpath, ex)
except Exception as exc:
raise ReproductionError(st.relpath) from exc
return result
+6 -7
View File
@@ -17,18 +17,17 @@ SCHEMA = Schema({"write": {str: INFO_SCHEMA}, "read": {str: [INFO_SCHEMA]}})
class RWLockFileCorruptedError(DvcException):
def __init__(self, path, cause):
def __init__(self, path):
super().__init__(
"Unable to read RWLock-file '{}'. JSON structure is "
"corrupted".format(relpath(path)),
cause=cause,
"corrupted".format(relpath(path))
)
class RWLockFileFormatError(DvcException):
def __init__(self, path, cause):
def __init__(self, path):
super().__init__(
"RWLock-file '{}' format error.".format(relpath(path)), cause=cause
"RWLock-file '{}' format error.".format(relpath(path))
)
@@ -42,9 +41,9 @@ def _edit_rwlock(lock_dir):
except FileNotFoundError:
lock = SCHEMA({})
except json.JSONDecodeError as exc:
raise RWLockFileCorruptedError(path, cause=exc)
raise RWLockFileCorruptedError(path) from exc
except Invalid as exc:
raise RWLockFileFormatError(path, cause=exc)
raise RWLockFileFormatError(path) from exc
lock = defaultdict(dict, lock)
lock["read"] = defaultdict(list, lock["read"])
lock["write"] = defaultdict(dict, lock["write"])
+4 -7
View File
@@ -22,17 +22,14 @@ class FileNotInCommitError(SCMError):
class CloneError(SCMError):
def __init__(self, url, path, cause):
super().__init__(
"Failed to clone repo '{}' to '{}'".format(url, path), cause=cause
)
def __init__(self, url, path):
super().__init__("Failed to clone repo '{}' to '{}'".format(url, path))
class RevError(SCMError):
def __init__(self, url, rev, cause):
def __init__(self, url, rev):
super().__init__(
"Failed to access revision '{}' for repo '{}'".format(rev, url),
cause=cause,
"Failed to access revision '{}' for repo '{}'".format(rev, url)
)
+4 -4
View File
@@ -85,7 +85,7 @@ class Git(Base):
)
tmp_repo.close()
except git.exc.GitCommandError as exc:
raise CloneError(url, to_path, exc)
raise CloneError(url, to_path) from exc
# NOTE: using our wrapper to make sure that env is fixed in __init__
repo = Git(to_path)
@@ -94,7 +94,7 @@ class Git(Base):
try:
repo.checkout(rev)
except git.exc.GitCommandError as exc:
raise RevError(url, rev, exc)
raise RevError(url, rev) from exc
return repo
@@ -331,8 +331,8 @@ class Git(Base):
commits.append(b_commit)
trees[DIFF_A_TREE] = self.get_tree(commits[0])
trees[DIFF_B_TREE] = self.get_tree(commits[1])
except (BadName, BadObject) as e:
raise SCMError("git problem", cause=e)
except (BadName, BadObject) as exc:
raise SCMError("git problem") from exc
return trees, commits
def get_diff_trees(self, a_ref, b_ref=None):
+2 -3
View File
@@ -104,9 +104,8 @@ class GitTree(BaseTree):
raise DvcException(
"revision '{}' not found in git '{}'".format(
self.rev, os.path.relpath(self.git.working_dir)
),
cause=exc,
)
)
) from exc
if not path or path == ".":
return tree
+1 -2
View File
@@ -71,8 +71,7 @@ class StageFileIsNotDvcFileError(DvcException):
class StageFileBadNameError(DvcException):
def __init__(self, msg):
super().__init__(msg)
pass
class StagePathOutsideError(DvcException):
+3 -3
View File
@@ -31,7 +31,7 @@ class System(object):
os.link(source, link_name)
return
except Exception as exc:
raise DvcException("link", cause=exc)
raise DvcException("link") from exc
CreateHardLink = ctypes.windll.kernel32.CreateHardLinkW
CreateHardLink.argtypes = [
@@ -43,7 +43,7 @@ class System(object):
res = CreateHardLink(link_name, source, None)
if res == 0:
raise DvcException("CreateHardLinkW", cause=ctypes.WinError())
raise DvcException("CreateHardLinkW") from ctypes.WinError()
@staticmethod
def symlink(source, link_name):
@@ -69,7 +69,7 @@ class System(object):
func.restype = ctypes.c_ubyte
if func(link_name, source, flags) == 0:
raise DvcException("CreateSymbolicLinkW", cause=ctypes.WinError())
raise DvcException("CreateSymbolicLinkW") from ctypes.WinError()
@staticmethod
def _reflink_darwin(src, dst):
+2 -2
View File
@@ -19,7 +19,7 @@ def parse_stage(text, path):
try:
return yaml.load(text, Loader=SafeLoader) or {}
except yaml.error.YAMLError as exc:
raise StageFileCorruptedError(path, cause=exc)
raise StageFileCorruptedError(path) from exc
def parse_stage_for_update(text, path):
@@ -35,7 +35,7 @@ def parse_stage_for_update(text, path):
yaml = YAML()
return yaml.load(text) or {}
except YAMLError as exc:
raise StageFileCorruptedError(path, cause=exc)
raise StageFileCorruptedError(path) from exc
def dump_stage_file(path, data):
+4 -4
View File
@@ -392,15 +392,15 @@ class TestCheckoutSuggestGit(TestRepro):
self.dvc.checkout(targets=["gitbranch"])
except DvcException as exc:
self.assertIsInstance(exc, CheckoutErrorSuggestGit)
self.assertIsInstance(exc.cause, StageFileDoesNotExistError)
self.assertIsNone(exc.cause.cause)
self.assertIsInstance(exc.__cause__, StageFileDoesNotExistError)
self.assertIsNone(exc.__cause__.__cause__)
try:
self.dvc.checkout(targets=[self.FOO])
except DvcException as exc:
self.assertIsInstance(exc, CheckoutErrorSuggestGit)
self.assertIsInstance(exc.cause, StageFileBadNameError)
self.assertIsNone(exc.cause.cause)
self.assertIsInstance(exc.__cause__, StageFileBadNameError)
self.assertIsNone(exc.__cause__.__cause__)
class TestCheckoutTargetRecursiveShouldNotRemoveOtherUsedFiles(TestDvc):
+5 -7
View File
@@ -107,11 +107,10 @@ class TestColorFormatter:
try:
raise Exception("first")
except Exception as exc:
first_traceback = traceback.format_exc()
try:
raise DvcException("second", cause=exc)
raise DvcException("second") from exc
except DvcException:
second_traceback = traceback.format_exc()
stack_trace = traceback.format_exc()
logger.exception("message")
expected = (
@@ -119,13 +118,12 @@ class TestColorFormatter:
"{red}{line}{nc}\n"
"{stack_trace}"
"{red}{line}{nc}\n".format(
line="-" * 60,
stack_trace="\n".join([first_traceback, second_traceback]),
**colors
line="-" * 60, stack_trace=stack_trace, **colors
)
)
assert expected == formatter.format(caplog.records[0])
assert "Exception: first" in stack_trace
assert "dvc.exceptions.DvcException: second" in stack_trace
def test_progress_awareness(self, mocker, capsys, caplog):
from dvc.progress import Tqdm