1"""Exceptions raised by the dvc.""" 2 3from __future__ import unicode_literals 4 5from dvc.utils.compat import str, builtin_str 6 7import os 8import traceback 9 10 11class DvcException(Exception): 12 """Base class for all dvc exceptions. 13 14 Args: 15 msg (unicode): message for this exception. 16 cause (Exception): optional cause exception. 17 """ 18 19 def __init__(self, msg, cause=None): 20 # NOTE: unlike python 3, python 2 doesn't have built-in support 21 # for chained exceptions, so we are using our own implementation. 22 self.cause = cause 23 self.cause_tb = None 24 if cause: 25 try: 26 self.cause_tb = traceback.format_exc() 27 except AttributeError: # pragma: no cover 28 pass 29 super(DvcException, self).__init__(msg) 30 31 32class OutputDuplicationError(DvcException): 33 """Thrown if a file/directory is specified as an output in more than one 34 stage. 35 36 Args: 37 output (unicode): path to the file/directory. 38 stages (list): list of paths to stages. 39 """ 40 41 def __init__(self, output, stages): 42 assert isinstance(output, str) or isinstance(output, builtin_str) 43 assert isinstance(stages, list) 44 assert all( 45 isinstance(stage, str) or isinstance(stage, builtin_str) 46 for stage in stages 47 ) 48 msg = ( 49 "file/directory '{}' is specified as an output in more than one" 50 "stage: {}" 51 ).format(output, "\n ".join(stages)) 52 super(OutputDuplicationError, self).__init__(msg) 53 54 55class OutputNotFoundError(DvcException): 56 """Thrown if a file/directory not found in repository pipelines. 57 58 Args: 59 output (unicode): path to the file/directory. 60 """ 61 62 def __init__(self, output): 63 super(OutputNotFoundError, self).__init__( 64 "unable to find stage file with output '{path}'".format( 65 path=os.path.relpath(output) 66 ) 67 ) 68 69 70class StagePathAsOutputError(DvcException): 71 """Thrown if directory that stage is going to be saved in is specified as 72 an output of another stage. 73 74 Args: 75 cwd (str): path to the directory. 76 fname (str): path to the stage file that has cwd specified as an 77 output. 78 """ 79 80 def __init__(self, wdir, fname): 81 assert isinstance(wdir, str) or isinstance(wdir, builtin_str) 82 assert isinstance(fname, str) or isinstance(fname, builtin_str) 83 msg = ( 84 "current working directory '{cwd}' is specified as an output in" 85 " '{fname}'. Use another CWD to prevent any data removal.".format( 86 cwd=wdir, fname=fname 87 ) 88 ) 89 super(StagePathAsOutputError, self).__init__(msg) 90 91 92class CircularDependencyError(DvcException): 93 """Thrown if a file/directory specified both as an output and as a 94 dependency. 95 96 Args: 97 dependency (str): path to the dependency. 98 """ 99 100 def __init__(self, dependency): 101 assert isinstance(dependency, str) or isinstance( 102 dependency, builtin_str 103 ) 104 105 msg = ( 106 "file/directory '{}' is specified as an output and as a " 107 "dependency." 108 ) 109 super(CircularDependencyError, self).__init__(msg.format(dependency)) 110 111 112class ArgumentDuplicationError(DvcException): 113 """Thrown if a file/directory is specified as a dependency/output more 114 than once. 115 116 Args: 117 path (str): path to the file/directory. 118 """ 119 120 def __init__(self, path): 121 assert isinstance(path, str) or isinstance(path, builtin_str) 122 msg = "file '{}' is specified more than once." 123 super(ArgumentDuplicationError, self).__init__(msg.format(path)) 124 125 126class MoveNotDataSourceError(DvcException): 127 """Thrown if attempted to move a file/directory that is not an output 128 in a data source stage. 129 130 Args: 131 path (str): path to the file/directory. 132 """ 133 134 def __init__(self, path): 135 msg = ( 136 "move is not permitted for stages that are not data sources. " 137 "You need to either move '{path}' to a new location and edit " 138 "it by hand, or remove '{path}' and create a new one at the " 139 "desired location." 140 ) 141 super(MoveNotDataSourceError, self).__init__(msg.format(path=path)) 142 143 144class NotDvcRepoError(DvcException): 145 """Thrown if a directory is not a dvc repo. 146 147 Args: 148 root (str): path to the directory. 149 """ 150 151 def __init__(self, root): 152 msg = "not a dvc repository (checked up to mount point '{}')" 153 super(NotDvcRepoError, self).__init__(msg.format(root)) 154 155 156class DvcParserError(DvcException): 157 """Base class for CLI parser errors.""" 158 159 def __init__(self): 160 super(DvcParserError, self).__init__("parser error") 161 162 163class CyclicGraphError(DvcException): 164 def __init__(self, stages): 165 assert isinstance(stages, list) 166 stages = "\n".join("\t- {}".format(stage) for stage in stages) 167 msg = ( 168 "you've introduced a cycle in your pipeline that involves" 169 " the following stages:" 170 "\n" 171 "{stages}".format(stages=stages) 172 ) 173 super(CyclicGraphError, self).__init__(msg) 174 175 176class ConfirmRemoveError(DvcException): 177 def __init__(self, path): 178 super(ConfirmRemoveError, self).__init__( 179 "unable to remove '{}' without a confirmation from the user. Use " 180 "'-f' to force.".format(path) 181 ) 182 183 184class InitError(DvcException): 185 def __init__(self, msg): 186 super(InitError, self).__init__(msg) 187 188 189class ReproductionError(DvcException): 190 def __init__(self, dvc_file_name, ex): 191 self.path = dvc_file_name 192 msg = "failed to reproduce '{}'".format(dvc_file_name) 193 super(ReproductionError, self).__init__(msg, cause=ex) 194 195 196class BadMetricError(DvcException): 197 def __init__(self, path): 198 super(BadMetricError, self).__init__( 199 "'{}' does not exist, not a metric or is malformed".format( 200 os.path.relpath(path) 201 ) 202 ) 203 204 205class NoMetricsError(DvcException): 206 def __init__(self): 207 super(NoMetricsError, self).__init__( 208 "no metric files in this repository. " 209 "Use 'dvc metrics add' to add a metric file to track." 210 ) 211