mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-09 15:28:26 +01:00
Merge pull request #3042 from mroutis/refactor-exceptions
py3: refactor exceptions
This commit is contained in:
+3
-4
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -71,8 +71,7 @@ class StageFileIsNotDvcFileError(DvcException):
|
||||
|
||||
|
||||
class StageFileBadNameError(DvcException):
|
||||
def __init__(self, msg):
|
||||
super().__init__(msg)
|
||||
pass
|
||||
|
||||
|
||||
class StagePathOutsideError(DvcException):
|
||||
|
||||
+3
-3
@@ -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
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user