def _validate_credentials(auth, settings): """ Detects discrepancy in DVC config and cached credentials file. Usually happens when a second remote is added and it is using the same credentials default file. Or when someones decides to change DVC config client id or secret but forgets to remove the cached credentials file. """ if not os.getenv(GDriveTree.GDRIVE_CREDENTIALS_DATA): if ( settings["client_config"]["client_id"] != auth.credentials.client_id or settings["client_config"]["client_secret"] != auth.credentials.client_secret ): logger.warning( "Client ID and secret configured do not match the " "actual ones used\nto access the remote. Do you " "use multiple GDrive remotes and forgot to\nset " "`gdrive_user_credentials_file` for one or more of them? " "Learn more at\n{}.\n".format( format_link("https://man.dvc.org/remote/modify") ) )
def _add_props_arguments(parser): parser.add_argument( "-t", "--template", nargs="?", default=None, help=( "Special JSON or HTML schema file to inject with the data. " "See {}".format( format_link("https://man.dvc.org/plots#plot-templates") ) ), metavar="<path>", ).complete = completion.FILE parser.add_argument( "-x", default=None, help="Field name for X axis.", metavar="<field>" ) parser.add_argument( "-y", default=None, help="Field name for Y axis.", metavar="<field>" ) parser.add_argument( "--no-header", action="store_false", dest="header", default=None, # Use default None to distinguish when it's not used help="Provided CSV or TSV datafile does not have a header.", ) parser.add_argument( "--title", default=None, metavar="<text>", help="Plot title." ) parser.add_argument( "--x-label", default=None, help="X axis label", metavar="<text>" ) parser.add_argument( "--y-label", default=None, help="Y axis label", metavar="<text>" )
def check_no_externals(stage): from urllib.parse import urlparse from dvc.utils import format_link # NOTE: preventing users from accidentally using external outputs. See # https://github.com/iterative/dvc/issues/1545 for more details. def _is_external(out): # NOTE: in case of `remote://` notation, the user clearly knows that # this is an advanced feature and so we shouldn't error-out. if out.is_in_repo or urlparse(out.def_path).scheme == "remote": return False return True outs = [str(out) for out in stage.outs if _is_external(out)] if not outs: return str_outs = ", ".join(outs) link = format_link("https://dvc.org/doc/user-guide/managing-external-data") raise StageExternalOutputsError( f"Output(s) outside of DVC project: {str_outs}. " f"See {link} for more info.")
def add_parser(subparsers, parent_parser): from dvc.command.config import parent_config_parser REMOTE_HELP = "Set up and manage data remotes." remote_parser = subparsers.add_parser( "remote", parents=[parent_parser], description=append_doc_link(REMOTE_HELP, "remote"), help=REMOTE_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) remote_subparsers = remote_parser.add_subparsers( dest="cmd", help="Use `dvc remote CMD --help` for " "command-specific help.", ) fix_subparsers(remote_subparsers) REMOTE_ADD_HELP = "Add a new data remote." remote_add_parser = remote_subparsers.add_parser( "add", parents=[parent_config_parser, parent_parser], description=append_doc_link(REMOTE_ADD_HELP, "remote/add"), help=REMOTE_ADD_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) remote_add_parser.add_argument("name", help="Name of the remote") remote_add_parser.add_argument( "url", help="URL. See full list of supported urls at {}".format( format_link("https://man.dvc.org/remote")), ) remote_add_parser.add_argument( "-d", "--default", action="store_true", default=False, help="Set as default remote.", ) remote_add_parser.add_argument( "-f", "--force", action="store_true", default=False, help="Force overwriting existing configs", ) remote_add_parser.set_defaults(func=CmdRemoteAdd) REMOTE_DEFAULT_HELP = "Set/unset the default data remote." remote_default_parser = remote_subparsers.add_parser( "default", parents=[parent_config_parser, parent_parser], description=append_doc_link(REMOTE_DEFAULT_HELP, "remote/default"), help=REMOTE_DEFAULT_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) remote_default_parser.add_argument("name", nargs="?", help="Name of the remote.") remote_default_parser.add_argument( "-u", "--unset", action="store_true", default=False, help="Unset default remote.", ) remote_default_parser.set_defaults(func=CmdRemoteDefault) REMOTE_MODIFY_HELP = "Modify the configuration of a data remote." remote_modify_parser = remote_subparsers.add_parser( "modify", parents=[parent_config_parser, parent_parser], description=append_doc_link(REMOTE_MODIFY_HELP, "remote/modify"), help=REMOTE_MODIFY_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) remote_modify_parser.add_argument("name", help="Name of the remote.") remote_modify_parser.add_argument("option", help="Name of the option to modify.") remote_modify_parser.add_argument("value", nargs="?", help="(optional) Value of the option.") remote_modify_parser.add_argument( "-u", "--unset", default=False, action="store_true", help="Unset option.", ) remote_modify_parser.set_defaults(func=CmdRemoteModify) REMOTE_LIST_HELP = "List all available data remotes." remote_list_parser = remote_subparsers.add_parser( "list", parents=[parent_config_parser, parent_parser], description=append_doc_link(REMOTE_LIST_HELP, "remote/list"), help=REMOTE_LIST_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) remote_list_parser.set_defaults(func=CmdRemoteList) REMOTE_REMOVE_HELP = "Remove a data remote." remote_remove_parser = remote_subparsers.add_parser( "remove", parents=[parent_config_parser, parent_parser], description=append_doc_link(REMOTE_REMOVE_HELP, "remote/remove"), help=REMOTE_REMOVE_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) remote_remove_parser.add_argument("name", help="Name of the remote to remove.") remote_remove_parser.set_defaults(func=CmdRemoteRemove) REMOTE_RENAME_HELP = "Rename a DVC remote" remote_rename_parser = remote_subparsers.add_parser( "rename", parents=[parent_config_parser, parent_parser], description=append_doc_link(REMOTE_RENAME_HELP, "remote/rename"), help=REMOTE_RENAME_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) remote_rename_parser.add_argument("name", help="Remote to be renamed") remote_rename_parser.add_argument("new", help="New name of the remote") remote_rename_parser.set_defaults(func=CmdRemoteRename)
import zc.lockfile from funcy import retry from dvc.exceptions import DvcException from dvc.progress import Tqdm from dvc.utils import format_link DEFAULT_TIMEOUT = 3 FAILED_TO_LOCK_MESSAGE = ( "Unable to acquire lock. Most likely another DVC process is running or " "was terminated abruptly. Check the page {} for other possible reasons " "and to learn how to resolve this." ).format( format_link("https://dvc.org/doc/user-guide/troubleshooting#lock-issue") ) class LockError(DvcException): """Thrown when unable to acquire the lock for DVC repo.""" class Lock: """Class for DVC repo lock. Uses zc.lockfile as backend. """ def __init__(self, lockfile, friendly=False, **kwargs): self._friendly = friendly
class CmdDataStatus(CmdDataBase): STATUS_LEN = 20 STATUS_INDENT = "\t" UP_TO_DATE_MSG = "Data and pipelines are up to date." IN_SYNC_MSG = "Cache and remote '{remote}' are in sync." EMPTY_PROJECT_MSG = ( "There are no data or pipelines tracked in this project yet.\n" "See {link} to get started!").format( link=format_link("https://dvc.org/doc/start")) def _normalize(self, s): s += ":" assert len(s) < self.STATUS_LEN return s + (self.STATUS_LEN - len(s)) * " " def _show(self, status, indent=0): ind = indent * self.STATUS_INDENT if isinstance(status, str): ui.write(f"{ind}{status}") return if isinstance(status, list): for entry in status: self._show(entry, indent) return assert isinstance(status, dict) for key, value in status.items(): if isinstance(value, str): ui.write(f"{ind}{self._normalize(value)}{key}") elif value: ui.write(f"{ind}{key}:") self._show(value, indent + 1) def run(self): from dvc.repo import lock_repo indent = 1 if self.args.cloud else 0 with lock_repo(self.repo): try: st = self.repo.status( targets=self.args.targets, jobs=self.args.jobs, cloud=self.args.cloud, remote=self.args.remote, all_branches=self.args.all_branches, all_tags=self.args.all_tags, all_commits=self.args.all_commits, with_deps=self.args.with_deps, recursive=self.args.recursive, ) except DvcException: logger.exception("") return 1 if self.args.quiet: return bool(st) if self.args.json: import json ui.write(json.dumps(st)) return 0 if st: self._show(st, indent) return 0 # additional hints for the user if not self.repo.index.stages: ui.write(self.EMPTY_PROJECT_MSG) elif self.args.cloud or self.args.remote: remote = self.args.remote or self.repo.config["core"].get( "remote") ui.write(self.IN_SYNC_MSG.format(remote=remote)) else: ui.write(self.UP_TO_DATE_MSG) return 0
def _prepare_credentials(self, **config): from azure.identity.aio import DefaultAzureCredential # Disable spam from failed cred types for DefaultAzureCredential logging.getLogger("azure.identity.aio").setLevel(logging.ERROR) login_info = {} login_info["connection_string"] = config.get( "connection_string", _az_config().get("storage", "connection_string", None), ) login_info["account_name"] = config.get( "account_name", _az_config().get("storage", "account", None) ) login_info["account_key"] = config.get( "account_key", _az_config().get("storage", "key", None) ) login_info["sas_token"] = config.get( "sas_token", _az_config().get("storage", "sas_token", None) ) login_info["tenant_id"] = config.get("tenant_id") login_info["client_id"] = config.get("client_id") login_info["client_secret"] = config.get("client_secret") if not (login_info["account_name"] or login_info["connection_string"]): raise AzureAuthError( "Authentication to Azure Blob Storage requires either " "account_name or connection_string.\nLearn more about " "configuration settings at " + format_link("https://man.dvc.org/remote/modify") ) any_secondary = any( value for key, value in login_info.items() if key != "account_name" ) if ( login_info["account_name"] and not any_secondary and not config.get("allow_anonymous_login", False) ): with fsspec_loop(): login_info["credential"] = DefaultAzureCredential( exclude_interactive_browser_credential=False, exclude_environment_credential=config.get( "exclude_environment_credential", False ), exclude_visual_studio_code_credential=config.get( "exclude_visual_studio_code_credential", False ), exclude_shared_token_cache_credential=config.get( "exclude_shared_token_cache_credential", False ), exclude_managed_identity_credential=config.get( "exclude_managed_identity_credential", False ), ) for login_method, required_keys in [ # noqa ("connection string", ["connection_string"]), ( "AD service principal", ["tenant_id", "client_id", "client_secret"], ), ("account key", ["account_name", "account_key"]), ("SAS token", ["account_name", "sas_token"]), ( f"default credentials ({_DEFAULT_CREDS_STEPS})", ["account_name", "credential"], ), ("anonymous login", ["account_name"]), ]: if all(login_info.get(key) is not None for key in required_keys): break else: login_method = None self.login_method = login_method return login_info
def add_parser(subparsers, parent_parser): PLOTS_HELP = ("Generating plots for metrics stored in structured files " "(JSON, CSV, TSV).") plots_parser = subparsers.add_parser( "plots", parents=[parent_parser], description=append_doc_link(PLOTS_HELP, "plots"), help=PLOTS_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) plots_subparsers = plots_parser.add_subparsers( dest="cmd", help="Use `dvc plots CMD --help` to display command-specific help.", ) fix_subparsers(plots_subparsers) SHOW_HELP = "Generate a plots image file from a metrics file." plots_show_parser = plots_subparsers.add_parser( "show", parents=[parent_parser], description=append_doc_link(SHOW_HELP, "plots/show"), help=SHOW_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) plots_show_parser.add_argument( "-t", "--template", nargs="?", default=None, help=("Special JSON or HTML schema file to inject with the data. " "See {}".format( format_link("https://man.dvc.org/plots#plot-templates"))), ) plots_show_parser.add_argument( "-o", "--out", default=None, help="Destination path to save plots to.", ) plots_show_parser.add_argument("-x", default=None, help="Field name for x axis.") plots_show_parser.add_argument("-y", default=None, help="Field name for y axis.") plots_show_parser.add_argument( "--no-csv-header", action="store_true", default=False, help="Required when CSV or TSV datafile does not have a header.", ) plots_show_parser.add_argument( "--show-json", action="store_true", default=False, help="Show output in JSON format.", ) plots_show_parser.add_argument("--title", default=None, help="Plot title.") plots_show_parser.add_argument("--xlab", default=None, help="X axis title.") plots_show_parser.add_argument("--ylab", default=None, help="Y axis title.") plots_show_parser.add_argument( "targets", nargs="*", help="Metrics files to visualize. Shows all plots by default.", ) plots_show_parser.set_defaults(func=CmdPlotsShow) PLOTS_DIFF_HELP = ( "Plot differences in metrics between commits in the DVC " "repository, or between the last commit and the workspace.") plots_diff_parser = plots_subparsers.add_parser( "diff", parents=[parent_parser], description=append_doc_link(PLOTS_DIFF_HELP, "plots/diff"), help=PLOTS_DIFF_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) plots_diff_parser.add_argument( "-t", "--template", nargs="?", default=None, help=("Special JSON or HTML schema to inject with the data. " "See {}".format( format_link("https://man.dvc.org/plots#plot-templates"))), ) plots_diff_parser.add_argument( "--targets", nargs="*", help="Metrics file to visualize. Shows all plots by default.", ) plots_diff_parser.add_argument( "-o", "--out", default=None, help="Destination path to save plots to.", ) plots_diff_parser.add_argument("-x", default=None, help="Field name for x axis.") plots_diff_parser.add_argument("-y", default=None, help="Field name for y axis.") plots_diff_parser.add_argument( "--no-csv-header", action="store_true", default=False, help="Provided CSV ot TSV datafile does not have a header.", ) plots_diff_parser.add_argument( "--show-json", action="store_true", default=False, help="Show output in JSON format.", ) plots_diff_parser.add_argument("--title", default=None, help="Plot title.") plots_diff_parser.add_argument("--xlab", default=None, help="X axis title.") plots_diff_parser.add_argument("--ylab", default=None, help="Y axis title.") plots_diff_parser.add_argument( "revisions", nargs="*", default=None, help="Git commits to plot from", ) plots_diff_parser.set_defaults(func=CmdPlotsDiff)
def main(argv=None): """Run dvc CLI command. Args: argv: optional list of arguments to parse. sys.argv is used by default. Returns: int: command's return code. """ args = None disable_other_loggers() outerLogLevel = logger.level try: args = parse_args(argv) if args.quiet: logger.setLevel(logging.CRITICAL) elif args.verbose: logger.setLevel(logging.DEBUG) cmd = args.func(args) ret = cmd.run() except ConfigError: logger.exception("configuration error") ret = 251 except KeyboardInterrupt: logger.exception("interrupted by the user") ret = 252 except NotDvcRepoError: logger.exception("") ret = 253 except DvcParserError: ret = 254 except Exception as exc: # pylint: disable=broad-except if isinstance(exc, OSError) and exc.errno == errno.EMFILE: logger.exception( "too many open files, please visit " "{} to see how to handle this " "problem".format( format_link("https://error.dvc.org/many-files") ), extra={"tb_only": True}, ) else: logger.exception("unexpected error") ret = 255 finally: logger.setLevel(outerLogLevel) # Closing pools by-hand to prevent wierd messages when closing SSH # connections. See https://github.com/iterative/dvc/issues/3248 for # more info. close_pools() # Remove cached repos in the end of the call, these are anonymous # so won't be reused by any other subsequent run anyway. clean_repos() if ret != 0: logger.info(FOOTER) if analytics.is_enabled(): analytics.collect_and_send_report(args, ret) return ret
def __init__(self, hook_name): super().__init__( "Hook '{}' already exists. Please refer to {} for more " "info.".format(hook_name, format_link("https://man.dvc.org/install")))
def add_parser(subparsers, parent_parser): from dvc.command.config import parent_config_parser machine_HELP = "Set up and manage cloud machines." machine_parser = subparsers.add_parser( "machine", parents=[parent_parser], description=append_doc_link(machine_HELP, "machine"), # NOTE: suppress help during development to hide command # help=machine_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) machine_subparsers = machine_parser.add_subparsers( dest="cmd", help="Use `dvc machine CMD --help` for " "command-specific help.", ) fix_subparsers(machine_subparsers) machine_ADD_HELP = "Add a new data machine." machine_add_parser = machine_subparsers.add_parser( "add", parents=[parent_config_parser, parent_parser], description=append_doc_link(machine_ADD_HELP, "machine/add"), help=machine_ADD_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) machine_add_parser.add_argument("name", help="Name of the machine") machine_add_parser.add_argument( "cloud", help="Machine cloud. See full list of supported clouds at {}".format( format_link("https://github.com/iterative/" "terraform-provider-iterative#machine")), ) machine_add_parser.add_argument( "-d", "--default", action="store_true", default=False, help="Set as default machine.", ) machine_add_parser.add_argument( "-f", "--force", action="store_true", default=False, help="Force overwriting existing configs", ) machine_add_parser.set_defaults(func=CmdMachineAdd) machine_DEFAULT_HELP = "Set/unset the default machine." machine_default_parser = machine_subparsers.add_parser( "default", parents=[parent_config_parser, parent_parser], description=append_doc_link(machine_DEFAULT_HELP, "machine/default"), help=machine_DEFAULT_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) machine_default_parser.add_argument("name", nargs="?", help="Name of the machine") machine_default_parser.add_argument( "-u", "--unset", action="store_true", default=False, help="Unset default machine.", ) machine_default_parser.set_defaults(func=CmdMachineDefault) machine_LIST_HELP = "List the configuration of one/all machines." machine_list_parser = machine_subparsers.add_parser( "list", parents=[parent_config_parser, parent_parser], description=append_doc_link(machine_LIST_HELP, "machine/list"), help=machine_LIST_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) machine_list_parser.add_argument( "--show-origin", default=False, action="store_true", help="Show the source file containing each config value.", ) machine_list_parser.add_argument( "name", nargs="?", type=str, help="name of machine to specify", ) machine_list_parser.set_defaults(func=CmdMachineList) machine_MODIFY_HELP = "Modify the configuration of an machine." machine_modify_parser = machine_subparsers.add_parser( "modify", parents=[parent_config_parser, parent_parser], description=append_doc_link(machine_MODIFY_HELP, "machine/modify"), help=machine_MODIFY_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) machine_modify_parser.add_argument("name", help="Name of the machine") machine_modify_parser.add_argument("option", help="Name of the option to modify.") machine_modify_parser.add_argument("value", nargs="?", help="(optional) Value of the option.") machine_modify_parser.add_argument( "-u", "--unset", default=False, action="store_true", help="Unset option.", ) machine_modify_parser.set_defaults(func=CmdMachineModify) machine_RENAME_HELP = "Rename a machine " machine_rename_parser = machine_subparsers.add_parser( "rename", parents=[parent_config_parser, parent_parser], description=append_doc_link(machine_RENAME_HELP, "remote/rename"), help=machine_RENAME_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) machine_rename_parser.add_argument("name", help="Machine to be renamed") machine_rename_parser.add_argument("new", help="New name of the machine") machine_rename_parser.set_defaults(func=CmdMachineRename) machine_REMOVE_HELP = "Remove an machine." machine_remove_parser = machine_subparsers.add_parser( "remove", parents=[parent_config_parser, parent_parser], description=append_doc_link(machine_REMOVE_HELP, "machine/remove"), help=machine_REMOVE_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) machine_remove_parser.add_argument("name", help="Name of the machine to remove.") machine_remove_parser.set_defaults(func=CmdMachineRemove) machine_CREATE_HELP = "Create and start a machine instance." machine_create_parser = machine_subparsers.add_parser( "create", parents=[parent_parser], description=append_doc_link(machine_CREATE_HELP, "machine/create"), help=machine_CREATE_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) machine_create_parser.add_argument("name", help="Name of the machine to create.") machine_create_parser.set_defaults(func=CmdMachineCreate) machine_STATUS_HELP = ( "List the status of running instances for one/all machines.") machine_status_parser = machine_subparsers.add_parser( "status", parents=[parent_parser], description=append_doc_link(machine_STATUS_HELP, "machine/status"), help=machine_STATUS_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) machine_status_parser.add_argument("name", nargs="?", help="(optional) Name of the machine.") machine_status_parser.set_defaults(func=CmdMachineStatus) machine_DESTROY_HELP = "Destroy an machine instance." machine_destroy_parser = machine_subparsers.add_parser( "destroy", parents=[parent_parser], description=append_doc_link(machine_DESTROY_HELP, "machine/destroy"), help=machine_DESTROY_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) machine_destroy_parser.add_argument( "name", help="Name of the machine instance to destroy.") machine_destroy_parser.set_defaults(func=CmdMachineDestroy) machine_SSH_HELP = "Connect to a machine via SSH." machine_ssh_parser = machine_subparsers.add_parser( "ssh", parents=[parent_parser], description=append_doc_link(machine_SSH_HELP, "machine/ssh"), help=machine_SSH_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) machine_ssh_parser.add_argument( "name", help="Name of the machine instance to connect to.") machine_ssh_parser.set_defaults(func=CmdMachineSsh)
class CmdDataStatus(CmdDataBase): STATUS_LEN = 20 STATUS_INDENT = "\t" UP_TO_DATE_MSG = "Data and pipelines are up to date." EMPTY_PROJECT_MSG = ( "There are no data or pipelines tracked in this project yet.\n" "See {link} to get started!").format( link=format_link("https://dvc.org/doc/start")) def _normalize(self, s): s += ":" assert len(s) < self.STATUS_LEN return s + (self.STATUS_LEN - len(s)) * " " def _show(self, status, indent=0): ind = indent * self.STATUS_INDENT if isinstance(status, str): logger.info(f"{ind}{status}") return if isinstance(status, list): for entry in status: self._show(entry, indent) return assert isinstance(status, dict) for key, value in status.items(): if isinstance(value, str): logger.info("{}{}{}".format(ind, self._normalize(value), key)) elif value: logger.info(f"{ind}{key}:") self._show(value, indent + 1) def run(self): indent = 1 if self.args.cloud else 0 try: st = self.repo.status( targets=self.args.targets, jobs=self.args.jobs, cloud=self.args.cloud, remote=self.args.remote, all_branches=self.args.all_branches, all_tags=self.args.all_tags, all_commits=self.args.all_commits, with_deps=self.args.with_deps, recursive=self.args.recursive, ) if self.args.quiet: return bool(st) if self.args.show_json: import json logger.info(json.dumps(st)) elif st: self._show(st, indent) elif not self.repo.stages: logger.info(self.EMPTY_PROJECT_MSG) else: logger.info(self.UP_TO_DATE_MSG) except DvcException: logger.exception("") return 1 return 0
def _log_exceptions(exc: Exception) -> Optional[int]: """Try to log some known exceptions, that are not DVCExceptions.""" from dvc.utils import error_link, format_link if isinstance(exc, OSError): import errno if exc.errno == errno.EMFILE: logger.exception( "too many open files, please visit " "{} to see how to handle this " "problem".format(error_link("many-files")), extra={"tb_only": True}, ) else: _log_unknown_exceptions() return None from dvc.fs import AuthError, ConfigError, RemoteMissingDepsError if isinstance(exc, RemoteMissingDepsError): from dvc.utils.pkg import PKG proto = exc.protocol by_pkg = { "pip": f"pip install 'dvc[{proto}]'", "conda": f"conda install -c conda-forge dvc-{proto}", } cmd = by_pkg.get(PKG) if cmd: link = format_link("https://dvc.org/doc/install") hint = (f"To install dvc with those dependencies, run:\n" "\n" f"\t{cmd}\n" "\n" f"See {link} for more info.") else: link = format_link("https://github.com/iterative/dvc/issues") hint = f"\nPlease report this bug to {link}. Thank you!" logger.exception( f"URL '{exc.url}' is supported but requires these missing " f"dependencies: {exc.missing_deps}. {hint}", extra={"tb_only": True}, ) return None if isinstance(exc, (AuthError, ConfigError)): link = format_link("https://man.dvc.org/remote/modify") logger.exception("configuration error") logger.exception( f"{exc!s}\nLearn more about configuration settings at {link}.", extra={"tb_only": True}, ) return 251 from dvc_data.hashfile.cache import DiskError if isinstance(exc, DiskError): from dvc.utils import relpath directory = relpath(exc.directory) logger.exception( f"Could not open pickled '{exc.type}' cache.\n" f"Remove the '{directory}' directory and then retry this command." f"\nSee {error_link('pickle')} for more information.", extra={"tb_only": True}, ) return None from dvc_data.stage import IgnoreInCollectedDirError if isinstance(exc, IgnoreInCollectedDirError): logger.exception("") return None _log_unknown_exceptions() return None